From 80a3ce77275b958681e9e693f5c5e6fa6ac69567 Mon Sep 17 00:00:00 2001 From: beer-1 <147697694+beer-1@users.noreply.github.com> Date: Fri, 1 Dec 2023 23:19:07 +0900 Subject: [PATCH] add eip191 signmode support follow https://github.com/cosmos/gogoproto/pull/83 --- client/tx/tx.go | 2 + crypto/keys/secp256k1/secp256k1_cgo.go | 13 +++++++ crypto/keys/secp256k1/secp256k1_nocgo.go | 26 +++++++++++++ x/auth/signing/verify.go | 15 +++++++- x/auth/tx/config.go | 6 +++ x/auth/tx/eip191.go | 48 ++++++++++++++++++++++++ 6 files changed, 109 insertions(+), 1 deletion(-) create mode 100644 x/auth/tx/eip191.go diff --git a/client/tx/tx.go b/client/tx/tx.go index f71111829627..93ac43ef03e4 100644 --- a/client/tx/tx.go +++ b/client/tx/tx.go @@ -261,6 +261,8 @@ func Sign(ctx context.Context, txf Factory, name string, txBuilder client.TxBuil if err != nil { return err } + } 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/secp256k1_cgo.go b/crypto/keys/secp256k1/secp256k1_cgo.go index 093a4de8eccd..edfa9fbe6fc0 100644 --- a/crypto/keys/secp256k1/secp256k1_cgo.go +++ b/crypto/keys/secp256k1/secp256k1_cgo.go @@ -5,6 +5,7 @@ package secp256k1 import ( "github.com/cometbft/cometbft/crypto" + "golang.org/x/crypto/sha3" "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1/internal/secp256k1" ) @@ -25,3 +26,15 @@ func (privKey *PrivKey) Sign(msg []byte) ([]byte, error) { func (pubKey *PubKey) VerifySignature(msg, 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 fff3ba5e0544..baee9f7b16bb 100644 --- a/crypto/keys/secp256k1/secp256k1_nocgo.go +++ b/crypto/keys/secp256k1/secp256k1_nocgo.go @@ -6,6 +6,8 @@ package secp256k1 import ( "errors" + "golang.org/x/crypto/sha3" + "github.com/cometbft/cometbft/crypto" secp256k1 "github.com/decred/dcrd/dcrec/secp256k1/v4" "github.com/decred/dcrd/dcrec/secp256k1/v4/ecdsa" @@ -39,6 +41,24 @@ func (pubKey *PubKey) VerifySignature(msg, 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, will return error if it is not in lower-S form + signature, err := signatureFromBytes(sigStr) + if err != nil { + return false + } + return signature.Verify(keccak256(msg), pub) +} + // Read Signature struct from R || S. Caller needs to ensure // that len(sigStr) == 64. // Rejects malleable signatures (if S value if it is over half order). @@ -53,3 +73,9 @@ func signatureFromBytes(sigStr []byte) (*ecdsa.Signature, error) { return ecdsa.NewSignature(&r, &s), nil } + +func keccak256(bytes []byte) []byte { + hasher := sha3.NewLegacyKeccak256() + hasher.Write(bytes) + return hasher.Sum(nil) +} diff --git a/x/auth/signing/verify.go b/x/auth/signing/verify.go index a025687c1189..2e71c229a33d 100644 --- a/x/auth/signing/verify.go +++ b/x/auth/signing/verify.go @@ -2,10 +2,12 @@ package signing import ( "context" + "encoding/hex" "fmt" signingv1beta1 "cosmossdk.io/api/cosmos/tx/signing/v1beta1" txsigning "cosmossdk.io/x/tx/signing" + "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" "github.com/cosmos/cosmos-sdk/crypto/types/multisig" @@ -77,9 +79,20 @@ func VerifySignature( 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/config.go b/x/auth/tx/config.go index 8aab60c5192f..efaa72be2de6 100644 --- a/x/auth/tx/config.go +++ b/x/auth/tx/config.go @@ -61,6 +61,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, // signingtypes.SignMode_SIGN_MODE_TEXTUAL is not enabled by default, as it requires a x/bank keeper or gRPC connection. } @@ -152,6 +153,11 @@ func NewSigningHandlerMap(configOpts ConfigOptions) (*txsigning.HandlerMap, erro if err != nil { return nil, err } + case signingtypes.SignMode_SIGN_MODE_EIP_191: + handlers[i] = NewSignModeEIP191Handler(aminojson.SignModeHandlerOptions{ + FileResolver: signingOpts.FileResolver, + TypeResolver: signingOpts.TypeResolver, + }) } } for i, m := range configOpts.CustomSignModes { diff --git a/x/auth/tx/eip191.go b/x/auth/tx/eip191.go new file mode 100644 index 000000000000..21527c14375d --- /dev/null +++ b/x/auth/tx/eip191.go @@ -0,0 +1,48 @@ +package tx + +import ( + "context" + "strconv" + + signingv1beta1 "cosmossdk.io/api/cosmos/tx/signing/v1beta1" + "cosmossdk.io/x/tx/signing" + "cosmossdk.io/x/tx/signing/aminojson" +) + +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 { + *aminojson.SignModeHandler +} + +// NewSignModeEIP191Handler returns a new SignModeEIP191Handler. +func NewSignModeEIP191Handler(options aminojson.SignModeHandlerOptions) *SignModeEIP191Handler { + return &SignModeEIP191Handler{ + SignModeHandler: aminojson.NewSignModeHandler(options), + } +} + +var _ signing.SignModeHandler = SignModeEIP191Handler{} + +// Mode implements signing.SignModeHandler.Mode. +func (SignModeEIP191Handler) Mode() signingv1beta1.SignMode { + return signingv1beta1.SignMode_SIGN_MODE_EIP_191 +} + +// GetSignBytes implements SignModeHandler.GetSignBytes +func (h SignModeEIP191Handler) GetSignBytes( + ctx context.Context, data signing.SignerData, txData signing.TxData, +) ([]byte, error) { + aminoJSONBz, err := h.SignModeHandler.GetSignBytes(ctx, data, txData) + if err != nil { + return nil, err + } + + return append(append( + []byte(EIP191MessagePrefix), + []byte(strconv.Itoa(len(aminoJSONBz)))..., + ), aminoJSONBz...), nil +}