From 13d908d90ed98e649308e123f87e5353485a72d3 Mon Sep 17 00:00:00 2001 From: Austin Chandra Date: Tue, 30 Aug 2022 15:39:10 -0700 Subject: [PATCH] Add SECP256k1 Ledger customization --- client/cmd.go | 2 +- client/context.go | 8 ++++ crypto/keyring/keyring.go | 25 +++++++++++++ crypto/ledger/ledger_mock.go | 4 +- crypto/ledger/ledger_notavail.go | 4 +- crypto/ledger/ledger_real.go | 4 +- crypto/ledger/ledger_secp256k1.go | 61 +++++++++++++++++++++++++++---- 7 files changed, 96 insertions(+), 12 deletions(-) diff --git a/client/cmd.go b/client/cmd.go index 77b404bcb575..6d5435d2c3c0 100644 --- a/client/cmd.go +++ b/client/cmd.go @@ -258,7 +258,7 @@ func readTxCommandFlags(clientCtx Context, flagSet *pflag.FlagSet) (Context, err // If the `from` signer account is a ledger key, we need to use // SIGN_MODE_AMINO_JSON, because ledger doesn't support proto yet. // ref: https://github.com/cosmos/cosmos-sdk/issues/8109 - if keyType == keyring.TypeLedger && clientCtx.SignModeStr != flags.SignModeLegacyAminoJSON { + if keyType == keyring.TypeLedger && clientCtx.SignModeStr != flags.SignModeLegacyAminoJSON && !clientCtx.LedgerHasProtobuf { fmt.Println("Default sign-mode 'direct' not supported by Ledger, using sign-mode 'amino-json'.") clientCtx = clientCtx.WithSignModeStr(flags.SignModeLegacyAminoJSON) } diff --git a/client/context.go b/client/context.go index 59aab6dde30b..03dfbd73c75d 100644 --- a/client/context.go +++ b/client/context.go @@ -58,6 +58,7 @@ type Context struct { FeePayer sdk.AccAddress FeeGranter sdk.AccAddress Viper *viper.Viper + LedgerHasProtobuf bool // IsAux is true when the signer is an auxiliary signer (e.g. the tipper). IsAux bool @@ -277,6 +278,13 @@ func (ctx Context) WithPreprocessTxHook(preprocessFn PreprocessTxFn) Context { return ctx } +// WithLedgerHasProto returns the context with the provided boolean value, indicating +// whether the target Ledger application can support Protobuf payloads. +func (ctx Context) WithLedgerHasProtobuf(val bool) Context { + ctx.LedgerHasProtobuf = val + return ctx +} + // PrintString prints the raw string to ctx.Output if it's defined, otherwise to os.Stdout func (ctx Context) PrintString(str string) error { return ctx.PrintBytes([]byte(str)) diff --git a/crypto/keyring/keyring.go b/crypto/keyring/keyring.go index d34bfe3c0369..65a6becd0323 100644 --- a/crypto/keyring/keyring.go +++ b/crypto/keyring/keyring.go @@ -144,6 +144,15 @@ type Options struct { SupportedAlgos SigningAlgoList // supported signing algorithms for Ledger SupportedAlgosLedger SigningAlgoList + // define Ledger Derivation function + LedgerDerivation func() (ledger.SECP256K1, error) + // define Ledger key generation function + LedgerCreateKey func([]byte) types.PubKey + // define Ledger app name + LedgerAppName string + // indicate whether Ledger should skip DER Conversion on signature, + // depending on which format (DER or BER) the Ledger app returns signatures + LedgerSigSkipDERConv bool } // NewInMemory creates a transient keyring useful for testing @@ -213,6 +222,22 @@ func newKeystore(kr keyring.Keyring, cdc codec.Codec, backend string, opts ...Op optionFn(&options) } + if options.LedgerDerivation != nil { + ledger.SetDiscoverLedger(options.LedgerDerivation) + } + + if options.LedgerCreateKey != nil { + ledger.SetCreatePubkey(options.LedgerCreateKey) + } + + if options.LedgerAppName != "" { + ledger.SetAppName(options.LedgerAppName) + } + + if options.LedgerSigSkipDERConv { + ledger.SetSkipDERConversion() + } + return keystore{ db: kr, cdc: cdc, diff --git a/crypto/ledger/ledger_mock.go b/crypto/ledger/ledger_mock.go index 740a773d12ce..7a618f40301b 100644 --- a/crypto/ledger/ledger_mock.go +++ b/crypto/ledger/ledger_mock.go @@ -23,9 +23,11 @@ import ( // set the discoverLedger function which is responsible for loading the Ledger // device at runtime or returning an error. func init() { - discoverLedger = func() (SECP256K1, error) { + options.discoverLedger = func() (SECP256K1, error) { return LedgerSECP256K1Mock{}, nil } + + initOptionsDefault() } type LedgerSECP256K1Mock struct{} diff --git a/crypto/ledger/ledger_notavail.go b/crypto/ledger/ledger_notavail.go index 578c33d4369c..a183166faac3 100644 --- a/crypto/ledger/ledger_notavail.go +++ b/crypto/ledger/ledger_notavail.go @@ -13,7 +13,9 @@ import ( // set the discoverLedger function which is responsible for loading the Ledger // device at runtime or returning an error. func init() { - discoverLedger = func() (SECP256K1, error) { + options.discoverLedger = func() (SECP256K1, error) { return nil, errors.New("support for ledger devices is not available in this executable") } + + initOptionsDefault() } diff --git a/crypto/ledger/ledger_real.go b/crypto/ledger/ledger_real.go index 48c87aff7683..b6ba6690218c 100644 --- a/crypto/ledger/ledger_real.go +++ b/crypto/ledger/ledger_real.go @@ -9,7 +9,7 @@ import ledger "github.com/cosmos/ledger-cosmos-go" // set the discoverLedger function which is responsible for loading the Ledger // device at runtime or returning an error. func init() { - discoverLedger = func() (SECP256K1, error) { + options.discoverLedger = func() (SECP256K1, error) { device, err := ledger.FindLedgerCosmosUserApp() if err != nil { return nil, err @@ -17,4 +17,6 @@ func init() { return device, nil } + + initOptionsDefault() } diff --git a/crypto/ledger/ledger_secp256k1.go b/crypto/ledger/ledger_secp256k1.go index dffded571ab5..ed6d80a4ad4a 100644 --- a/crypto/ledger/ledger_secp256k1.go +++ b/crypto/ledger/ledger_secp256k1.go @@ -14,9 +14,8 @@ import ( "github.com/cosmos/cosmos-sdk/crypto/types" ) -// discoverLedger defines a function to be invoked at runtime for discovering -// a connected Ledger device. -var discoverLedger discoverLedgerFn +// options stores the Ledger Options that can be used to customize Ledger usage +var options Options type ( // discoverLedgerFn defines a Ledger discovery function that returns a @@ -24,6 +23,10 @@ type ( // dependencies when Ledger support is potentially not enabled. discoverLedgerFn func() (SECP256K1, error) + // createPubkeyFn supports returning different public key types that implement + // types.PubKey + createPubkeyFn func([]byte) types.PubKey + // SECP256K1 reflects an interface a Ledger API must implement for SECP256K1 SECP256K1 interface { Close() error @@ -35,6 +38,15 @@ type ( SignSECP256K1([]uint32, []byte) ([]byte, error) } + // Options hosts customization options to account for differences in Ledger + // signing and usage across chains. + Options struct { + discoverLedger discoverLedgerFn + createPubkey createPubkeyFn + appName string + skipDERConversion bool + } + // PrivKeyLedgerSecp256k1 implements PrivKey, calling the ledger nano we // cache the PubKey from the first call to use it later. PrivKeyLedgerSecp256k1 struct { @@ -46,6 +58,35 @@ type ( } ) +// Initialize the default options values for the Cosmos Ledger +func initOptionsDefault() { + options.createPubkey = func(key []byte) types.PubKey { + return &secp256k1.PubKey{Key: key} + } + options.appName = "Cosmos" + options.skipDERConversion = false +} + +// Set the discoverLedger function to use a different Ledger derivation +func SetDiscoverLedger(fn discoverLedgerFn) { + options.discoverLedger = fn +} + +// Set the createPubkey function to use a different public key +func SetCreatePubkey(fn createPubkeyFn) { + options.createPubkey = fn +} + +// Set the Ledger app name to use a different app name +func SetAppName(appName string) { + options.appName = appName +} + +// Set the DER Conversion requirement to true (false by default) +func SetSkipDERConversion() { + options.skipDERConversion = true +} + // NewPrivKeySecp256k1Unsafe will generate a new key and store the public key for later use. // // This function is marked as unsafe as it will retrieve a pubkey without user verification. @@ -178,11 +219,11 @@ func convertDERtoBER(signatureDER []byte) ([]byte, error) { } func getDevice() (SECP256K1, error) { - if discoverLedger == nil { + if options.discoverLedger == nil { return nil, errors.New("no Ledger discovery function defined") } - device, err := discoverLedger() + device, err := options.discoverLedger() if err != nil { return nil, errors.Wrap(err, "ledger nano S") } @@ -220,6 +261,10 @@ func sign(device SECP256K1, pkl PrivKeyLedgerSecp256k1, msg []byte) ([]byte, err return nil, err } + if options.skipDERConversion { + return sig, nil + } + return convertDERtoBER(sig) } @@ -234,7 +279,7 @@ func sign(device SECP256K1, pkl PrivKeyLedgerSecp256k1, msg []byte) ([]byte, err func getPubKeyUnsafe(device SECP256K1, path hd.BIP44Params) (types.PubKey, error) { publicKey, err := device.GetPublicKeySECP256K1(path.DerivationPath()) if err != nil { - return nil, fmt.Errorf("please open Cosmos app on the Ledger device - error: %v", err) + return nil, fmt.Errorf("please open the %v app on the Ledger device - error: %v", options.appName, err) } // re-serialize in the 33-byte compressed format @@ -246,7 +291,7 @@ func getPubKeyUnsafe(device SECP256K1, path hd.BIP44Params) (types.PubKey, error compressedPublicKey := make([]byte, secp256k1.PubKeySize) copy(compressedPublicKey, cmp.SerializeCompressed()) - return &secp256k1.PubKey{Key: compressedPublicKey}, nil + return options.createPubkey(compressedPublicKey), nil } // getPubKeyAddr reads the pubkey and the address from a ledger device. @@ -270,5 +315,5 @@ func getPubKeyAddrSafe(device SECP256K1, path hd.BIP44Params, hrp string) (types compressedPublicKey := make([]byte, secp256k1.PubKeySize) copy(compressedPublicKey, cmp.SerializeCompressed()) - return &secp256k1.PubKey{Key: compressedPublicKey}, addr, nil + return options.createPubkey(compressedPublicKey), addr, nil }