diff --git a/client/tx/tx.go b/client/tx/tx.go index 059ce3a1b9a3b..0e6188d868fed 100644 --- a/client/tx/tx.go +++ b/client/tx/tx.go @@ -243,6 +243,8 @@ func Sign(txf Factory, name string, txBuilder client.TxBuilder, overwriteSig boo if signMode == signing.SignMode_SIGN_MODE_UNSPECIFIED { // use the SignModeHandler's default mode if unspecified signMode = txf.txConfig.SignModeHandler().DefaultMode() + } else if signMode == signing.SignMode_SIGN_MODE_EIP_191 { + return fmt.Errorf("EIP191 signing is not supported in cli") } k, err := txf.keybase.Key(name) diff --git a/crypto/keys/secp256k1/internal/secp256k1/dummy.go b/crypto/keys/secp256k1/internal/secp256k1/dummy.go index 4ad93ac48e2c0..dee7581a847f5 100644 --- a/crypto/keys/secp256k1/internal/secp256k1/dummy.go +++ b/crypto/keys/secp256k1/internal/secp256k1/dummy.go @@ -15,7 +15,7 @@ package secp256k1 import ( - _ "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1/internal/secp256k1/libsecp256k1/include" - _ "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1/internal/secp256k1/libsecp256k1/src" - _ "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1/internal/secp256k1/libsecp256k1/src/modules/recovery" + _ "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1/internal/cgo/secp256k1/libsecp256k1/include" + _ "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1/internal/cgo/secp256k1/libsecp256k1/src" + _ "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1/internal/cgo/secp256k1/libsecp256k1/src/modules/recovery" ) diff --git a/crypto/keys/secp256k1/secp256k1_cgo.go b/crypto/keys/secp256k1/secp256k1_cgo.go index 60d7a04c19e87..b24c24393b9e1 100644 --- a/crypto/keys/secp256k1/secp256k1_cgo.go +++ b/crypto/keys/secp256k1/secp256k1_cgo.go @@ -5,8 +5,9 @@ package secp256k1 import ( "github.com/cometbft/cometbft/crypto" + "golang.org/x/crypto/sha3" - "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1/internal/secp256k1" + "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1/internal/cgo/secp256k1" ) // Sign creates an ECDSA signature on curve Secp256k1, using SHA256 on the msg. @@ -25,3 +26,15 @@ func (privKey *PrivKey) Sign(msg []byte) ([]byte, error) { func (pubKey *PubKey) VerifySignature(msg []byte, sigStr []byte) bool { return secp256k1.VerifySignature(pubKey.Bytes(), crypto.Sha256(msg), sigStr) } + +// VerifySignatureEIP191 validates the signature. +// The msg will be hashed prior to signature verification. +func (pubKey *PubKey) VerifySignatureEIP191(msg []byte, sigStr []byte) bool { + return secp256k1.VerifySignature(pubKey.Bytes(), keccak256(msg), sigStr) +} + +func keccak256(bytes []byte) []byte { + hasher := sha3.NewLegacyKeccak256() + hasher.Write(bytes) + return hasher.Sum(nil) +} diff --git a/crypto/keys/secp256k1/secp256k1_nocgo.go b/crypto/keys/secp256k1/secp256k1_nocgo.go index b8165f388f1d5..30aa3b07f803d 100644 --- a/crypto/keys/secp256k1/secp256k1_nocgo.go +++ b/crypto/keys/secp256k1/secp256k1_nocgo.go @@ -6,6 +6,7 @@ package secp256k1 import ( secp256k1 "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2/ecdsa" + "golang.org/x/crypto/sha3" "github.com/cometbft/cometbft/crypto" ) @@ -48,6 +49,33 @@ func (pubKey *PubKey) VerifySignature(msg []byte, sigStr []byte) bool { return signature.Verify(crypto.Sha256(msg), pub) } +// VerifySignatureEIP191 verifies a signature of the form R || S. +// It rejects signatures which are not in lower-S form. +func (pubKey *PubKey) VerifySignatureEIP191(msg []byte, sigStr []byte) bool { + if len(sigStr) != 64 { + return false + } + pub, err := secp256k1.ParsePubKey(pubKey.Key) + if err != nil { + return false + } + // parse the signature: + signature := signatureFromBytes(sigStr) + // Reject malleable signatures. libsecp256k1 does this check but btcec doesn't. + // see: https://github.com/ethereum/go-ethereum/blob/f9401ae011ddf7f8d2d95020b7446c17f8d98dc1/crypto/signature_nocgo.go#L90-L93 + // Serialize() would negate S value if it is over half order. + // Hence, if the signature is different after Serialize() if should be rejected. + modifiedSignature, parseErr := ecdsa.ParseDERSignature(signature.Serialize()) + if parseErr != nil { + return false + } + if !signature.IsEqual(modifiedSignature) { + return false + } + + return signature.Verify(keccak256(msg), pub) +} + // Read Signature struct from R || S. Caller needs to ensure // that len(sigStr) == 64. func signatureFromBytes(sigStr []byte) *ecdsa.Signature { @@ -57,3 +85,9 @@ func signatureFromBytes(sigStr []byte) *ecdsa.Signature { s.SetByteSlice(sigStr[32:64]) return ecdsa.NewSignature(&r, &s) } + +func keccak256(bytes []byte) []byte { + hasher := sha3.NewLegacyKeccak256() + hasher.Write(bytes) + return hasher.Sum(nil) +} diff --git a/go.mod b/go.mod index eda45f98b6fc3..438771af51c0c 100644 --- a/go.mod +++ b/go.mod @@ -25,7 +25,7 @@ require ( github.com/cosmos/cosmos-sdk/db v1.0.0-beta.1.0.20220726092710-f848e4300a8a github.com/cosmos/go-bip39 v1.0.0 github.com/cosmos/gogogateway v1.2.0 - github.com/cosmos/gogoproto v1.4.10 + github.com/cosmos/gogoproto v1.4.11 github.com/cosmos/iavl v0.20.1 github.com/cosmos/ledger-cosmos-go v0.12.4 github.com/golang/mock v1.6.0 @@ -57,7 +57,7 @@ require ( github.com/tendermint/go-amino v0.16.0 github.com/tidwall/btree v1.6.0 golang.org/x/crypto v0.14.0 - golang.org/x/exp v0.0.0-20230711153332-06a737ee72cb + golang.org/x/exp v0.0.0-20230811145659-89c5cff77bcb google.golang.org/genproto/googleapis/api v0.0.0-20231002182017-d307bd883b97 google.golang.org/grpc v1.58.3 google.golang.org/protobuf v1.31.0 diff --git a/go.sum b/go.sum index e25e0e88d6326..fe1204354770f 100644 --- a/go.sum +++ b/go.sum @@ -337,8 +337,8 @@ github.com/cosmos/go-bip39 v1.0.0/go.mod h1:RNJv0H/pOIVgxw6KS7QeX2a0Uo0aKUlfhZ4x github.com/cosmos/gogogateway v1.2.0 h1:Ae/OivNhp8DqBi/sh2A8a1D0y638GpL3tkmLQAiKxTE= github.com/cosmos/gogogateway v1.2.0/go.mod h1:iQpLkGWxYcnCdz5iAdLcRBSw3h7NXeOkZ4GUkT+tbFI= github.com/cosmos/gogoproto v1.4.2/go.mod h1:cLxOsn1ljAHSV527CHOtaIP91kK6cCrZETRBrkzItWU= -github.com/cosmos/gogoproto v1.4.10 h1:QH/yT8X+c0F4ZDacDv3z+xE3WU1P1Z3wQoLMBRJoKuI= -github.com/cosmos/gogoproto v1.4.10/go.mod h1:3aAZzeRWpAwr+SS/LLkICX2/kDFyaYVzckBDzygIxek= +github.com/cosmos/gogoproto v1.4.11 h1:LZcMHrx4FjUgrqQSWeaGC1v/TeuVFqSLa43CC6aWR2g= +github.com/cosmos/gogoproto v1.4.11/go.mod h1:/g39Mh8m17X8Q/GDEs5zYTSNaNnInBSohtaxzQnYq1Y= github.com/cosmos/iavl v0.20.1 h1:rM1kqeG3/HBT85vsZdoSNsehciqUQPWrR4BYmqE2+zg= github.com/cosmos/iavl v0.20.1/go.mod h1:WO7FyvaZJoH65+HFOsDir7xU9FWk2w9cHXNW1XHcl7A= github.com/cosmos/keyring v1.2.0 h1:8C1lBP9xhImmIabyXW4c3vFjjLiBdGCmfLUfeZlV1Yo= @@ -1034,8 +1034,8 @@ golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= golang.org/x/exp v0.0.0-20200331195152-e8c3332aa8e5/go.mod h1:4M0jN8W1tt0AVLNr8HDosyJCDCDuyL9N9+3m7wDWgKw= -golang.org/x/exp v0.0.0-20230711153332-06a737ee72cb h1:xIApU0ow1zwMa2uL1VDNeQlNVFTWMQxZUZCMDy0Q4Us= -golang.org/x/exp v0.0.0-20230711153332-06a737ee72cb/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc= +golang.org/x/exp v0.0.0-20230811145659-89c5cff77bcb h1:mIKbk8weKhSeLH2GmUTrvx8CjkyJmnU1wFmg59CUjFA= +golang.org/x/exp v0.0.0-20230811145659-89c5cff77bcb/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= diff --git a/runtime/services/reflection.go b/runtime/services/reflection.go index b165ea633ed4c..5585c67f831b1 100644 --- a/runtime/services/reflection.go +++ b/runtime/services/reflection.go @@ -5,6 +5,7 @@ import ( "compress/gzip" "context" "io" + "strings" reflectionv1 "cosmossdk.io/api/cosmos/reflection/v1" "github.com/cosmos/gogoproto/proto" @@ -57,8 +58,8 @@ func NewReflectionService() (*ReflectionService, error) { return true }) - slices.SortFunc(fds.File, func(x, y *descriptorpb.FileDescriptorProto) bool { - return *x.Name < *y.Name + slices.SortFunc(fds.File, func(x, y *descriptorpb.FileDescriptorProto) int { + return strings.Compare(*x.Name, *y.Name) }) return &ReflectionService{files: fds}, nil diff --git a/x/auth/signing/verify.go b/x/auth/signing/verify.go index 5a5395de69f35..6945b07969527 100644 --- a/x/auth/signing/verify.go +++ b/x/auth/signing/verify.go @@ -1,8 +1,10 @@ package signing import ( + "encoding/hex" "fmt" + "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" "github.com/cosmos/cosmos-sdk/crypto/types/multisig" sdk "github.com/cosmos/cosmos-sdk/types" @@ -18,9 +20,20 @@ func VerifySignature(pubKey cryptotypes.PubKey, signerData SignerData, sigData s if err != nil { return err } - if !pubKey.VerifySignature(signBytes, data.Signature) { + + if data.SignMode == signing.SignMode_SIGN_MODE_EIP_191 { + secp256k1PubKey, ok := pubKey.(*secp256k1.PubKey) + if !ok { + return fmt.Errorf("eip191 sign mode requires pubkey to be of type cosmos.crypto.secp256k1.PubKey") + } + + if !secp256k1PubKey.VerifySignatureEIP191(signBytes, data.Signature) { + return fmt.Errorf("unable to verify single signer eip191 signature %s for signBytes %s", hex.EncodeToString(data.Signature), hex.EncodeToString(signBytes)) + } + } else if !pubKey.VerifySignature(signBytes, data.Signature) { return fmt.Errorf("unable to verify single signer signature") } + return nil case *signing.MultiSignatureData: diff --git a/x/auth/tx/eip191.go b/x/auth/tx/eip191.go new file mode 100644 index 0000000000000..6f7f26d60ba8c --- /dev/null +++ b/x/auth/tx/eip191.go @@ -0,0 +1,64 @@ +package tx + +import ( + "fmt" + "strconv" + + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + signingtypes "github.com/cosmos/cosmos-sdk/types/tx/signing" + "github.com/cosmos/cosmos-sdk/x/auth/migrations/legacytx" + "github.com/cosmos/cosmos-sdk/x/auth/signing" +) + +const EIP191MessagePrefix = "\x19Ethereum Signed Message:\n" + +const eip191NonCriticalFieldsError = "protobuf transaction contains unknown non-critical fields. This is a transaction malleability issue and SIGN_MODE_EIP_191 cannot be used." + +// SignModeEIP191Handler defines the SIGN_MODE_DIRECT SignModeHandler +type SignModeEIP191Handler struct{} + +var _ signing.SignModeHandler = SignModeEIP191Handler{} + +// DefaultMode implements SignModeHandler.DefaultMode +func (SignModeEIP191Handler) DefaultMode() signingtypes.SignMode { + return signingtypes.SignMode_SIGN_MODE_EIP_191 +} + +// Modes implements SignModeHandler.Modes +func (SignModeEIP191Handler) Modes() []signingtypes.SignMode { + return []signingtypes.SignMode{signingtypes.SignMode_SIGN_MODE_EIP_191} +} + +// GetSignBytes implements SignModeHandler.GetSignBytes +func (SignModeEIP191Handler) GetSignBytes(mode signingtypes.SignMode, data signing.SignerData, tx sdk.Tx) ([]byte, error) { + if mode != signingtypes.SignMode_SIGN_MODE_EIP_191 { + return nil, fmt.Errorf("expected %s, got %s", signingtypes.SignMode_SIGN_MODE_EIP_191, mode) + } + + protoTx, ok := tx.(*wrapper) + if !ok { + return nil, fmt.Errorf("can only handle a protobuf Tx, got %T", tx) + } + + if protoTx.txBodyHasUnknownNonCriticals { + return nil, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, eip191NonCriticalFieldsError) + } + + body := protoTx.tx.Body + + if len(body.ExtensionOptions) != 0 || len(body.NonCriticalExtensionOptions) != 0 { + return nil, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "SignMode_SIGN_MODE_EIP_191 does not support protobuf extension options.") + } + + aminoJSONBz := legacytx.StdSignBytes( + data.ChainID, data.AccountNumber, data.Sequence, protoTx.GetTimeoutHeight(), + legacytx.StdFee{Amount: protoTx.GetFee(), Gas: protoTx.GetGas()}, + tx.GetMsgs(), protoTx.GetMemo(), protoTx.GetTip(), + ) + + return append(append( + []byte(EIP191MessagePrefix), + []byte(strconv.Itoa(len(aminoJSONBz)))..., + ), aminoJSONBz...), nil +} diff --git a/x/auth/tx/mode_handler.go b/x/auth/tx/mode_handler.go index 19e34df49a241..598b0e755cc53 100644 --- a/x/auth/tx/mode_handler.go +++ b/x/auth/tx/mode_handler.go @@ -12,6 +12,7 @@ var DefaultSignModes = []signingtypes.SignMode{ signingtypes.SignMode_SIGN_MODE_DIRECT, signingtypes.SignMode_SIGN_MODE_DIRECT_AUX, signingtypes.SignMode_SIGN_MODE_LEGACY_AMINO_JSON, + signingtypes.SignMode_SIGN_MODE_EIP_191, } // makeSignModeHandler returns the default protobuf SignModeHandler supporting @@ -31,6 +32,8 @@ func makeSignModeHandler(modes []signingtypes.SignMode) signing.SignModeHandler handlers[i] = signModeLegacyAminoJSONHandler{} case signingtypes.SignMode_SIGN_MODE_DIRECT_AUX: handlers[i] = signModeDirectAuxHandler{} + case signingtypes.SignMode_SIGN_MODE_EIP_191: + handlers[i] = SignModeEIP191Handler{} default: panic(fmt.Errorf("unsupported sign mode %+v", mode)) } diff --git a/x/gov/module.go b/x/gov/module.go index 1755638ed665d..4e898f8e8a55c 100644 --- a/x/gov/module.go +++ b/x/gov/module.go @@ -7,6 +7,7 @@ import ( "encoding/json" "fmt" "sort" + "strings" abci "github.com/cometbft/cometbft/abci/types" gwruntime "github.com/grpc-ecosystem/grpc-gateway/runtime" @@ -226,8 +227,8 @@ func InvokeAddRoutes(keeper *keeper.Keeper, routes []v1beta1.HandlerRoute) { // Default route order is a lexical sort by RouteKey. // Explicit ordering can be added to the module config if required. - slices.SortFunc(routes, func(x, y v1beta1.HandlerRoute) bool { - return x.RouteKey < y.RouteKey + slices.SortFunc(routes, func(x, y v1beta1.HandlerRoute) int { + return strings.Compare(x.RouteKey, y.RouteKey) }) router := v1beta1.NewRouter()