Skip to content

Commit

Permalink
add eip191 signmode support
Browse files Browse the repository at this point in the history
  • Loading branch information
beer-1 committed Dec 8, 2023
1 parent ebb56e6 commit 7bba6b0
Show file tree
Hide file tree
Showing 10 changed files with 142 additions and 11 deletions.
2 changes: 2 additions & 0 deletions client/tx/tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
13 changes: 13 additions & 0 deletions crypto/keys/secp256k1/secp256k1_cgo.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand All @@ -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)
}
34 changes: 34 additions & 0 deletions crypto/keys/secp256k1/secp256k1_nocgo.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand Down Expand Up @@ -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 {
Expand All @@ -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)
}
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
8 changes: 4 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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=
Expand Down Expand Up @@ -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=
Expand Down
5 changes: 3 additions & 2 deletions runtime/services/reflection.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"compress/gzip"
"context"
"io"
"strings"

reflectionv1 "cosmossdk.io/api/cosmos/reflection/v1"
"github.com/cosmos/gogoproto/proto"
Expand Down Expand Up @@ -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
Expand Down
15 changes: 14 additions & 1 deletion x/auth/signing/verify.go
Original file line number Diff line number Diff line change
@@ -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"
Expand All @@ -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:
Expand Down
64 changes: 64 additions & 0 deletions x/auth/tx/eip191.go
Original file line number Diff line number Diff line change
@@ -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
}
3 changes: 3 additions & 0 deletions x/auth/tx/mode_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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))
}
Expand Down
5 changes: 3 additions & 2 deletions x/gov/module.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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()
Expand Down

0 comments on commit 7bba6b0

Please sign in to comment.