feat: add customized Ledger support (#12935)
This commit is contained in:
parent
518003ec29
commit
4a07259ff4
@ -46,6 +46,7 @@ Ref: https://keepachangelog.com/en/1.0.0/
|
||||
* (x/authz) [#13047](https://github.com/cosmos/cosmos-sdk/pull/13047) Add a GetAuthorization function to the keeper.
|
||||
* (cli) [#13064](https://github.com/cosmos/cosmos-sdk/pull/13064) Add `debug prefixes` to list supported HRP prefixes via .
|
||||
* (cli) [#12742](https://github.com/cosmos/cosmos-sdk/pull/12742) Add the `prune` CLI cmd to manually prune app store history versions based on the pruning options.
|
||||
* (ledger) [#12935](https://github.com/cosmos/cosmos-sdk/pull/12935) Generalize Ledger integration to allow for different apps or keytypes that use SECP256k1.
|
||||
|
||||
### Improvements
|
||||
|
||||
|
||||
@ -40,6 +40,12 @@ By default, the new `MinInitialDepositRatio` parameter is set to zero during mig
|
||||
feature is disabled. If chains wish to utilize the minimum proposal deposits at time of submission, the migration logic needs to be
|
||||
modified to set the new parameter to the desired value.
|
||||
|
||||
### Ledger
|
||||
|
||||
Ledger support has been generalized to enable use of different apps and keytypes that use `secp256k1`. The Ledger interface remains the same, but it can now be provided through the Keyring `Options`, allowing higher-level chains to connect to different Ledger apps or use custom implementations. In addition, higher-level chains can provide custom key implementations around the Ledger public key, to enable greater flexibility with address generation and signing.
|
||||
|
||||
This is not a breaking change, as all values will default to use the standard Cosmos app implementation unless specified otherwise.
|
||||
|
||||
## [v0.46.x](https://github.com/cosmos/cosmos-sdk/releases/tag/v0.46.0)
|
||||
|
||||
### Go API Changes
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -53,6 +53,7 @@ type Context struct {
|
||||
FeePayer sdk.AccAddress
|
||||
FeeGranter sdk.AccAddress
|
||||
Viper *viper.Viper
|
||||
LedgerHasProtobuf bool
|
||||
PreprocessTxHook PreprocessTxFn
|
||||
|
||||
// IsAux is true when the signer is an auxiliary signer (e.g. the tipper).
|
||||
@ -266,6 +267,13 @@ func (ctx Context) WithAux(isAux bool) 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
|
||||
}
|
||||
|
||||
// WithPreprocessTxHook returns the context with the provided preprocessing hook, which
|
||||
// enables chains to preprocess the transaction using the builder.
|
||||
func (ctx Context) WithPreprocessTxHook(preprocessFn PreprocessTxFn) Context {
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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{}
|
||||
|
||||
@ -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()
|
||||
}
|
||||
|
||||
@ -3,13 +3,17 @@
|
||||
|
||||
package ledger
|
||||
|
||||
import ledger "github.com/cosmos/ledger-cosmos-go"
|
||||
import (
|
||||
"github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1"
|
||||
"github.com/cosmos/cosmos-sdk/crypto/types"
|
||||
ledger "github.com/cosmos/ledger-cosmos-go"
|
||||
)
|
||||
|
||||
// If ledger support (build tag) has been enabled, which implies a CGO dependency,
|
||||
// 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 +21,6 @@ func init() {
|
||||
|
||||
return device, nil
|
||||
}
|
||||
|
||||
initOptionsDefault()
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user