Skip to content
This repository has been archived by the owner on Apr 4, 2024. It is now read-only.

Refactor EIP-712 signature verification #1397

Merged
merged 40 commits into from
Nov 7, 2022
Merged
Changes from 1 commit
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
7611743
[WIP] EIP-712 Signature Refactor
austinchandra Oct 19, 2022
aeda1cf
Merge branch 'main' of https://github.com/evmos/ethermint into austin…
austinchandra Oct 20, 2022
e4e8650
Debug and add ante tests
austinchandra Oct 20, 2022
afc37f6
Add tests for failure cases
austinchandra Oct 21, 2022
57fe441
Add changelog entry
austinchandra Oct 21, 2022
998987a
Code cleanup
austinchandra Oct 24, 2022
578185c
Add tests for MsgDelegate and MsgWithdrawDelegationReward
austinchandra Oct 25, 2022
e941c8e
Update ethereum/eip712/encoding.go
austinchandra Oct 25, 2022
14c4cd7
Update ethereum/eip712/encoding.go
austinchandra Oct 25, 2022
84f061a
Update ethereum/eip712/encoding.go
austinchandra Oct 25, 2022
e93c95e
Update ethereum/eip712/encoding.go
austinchandra Oct 25, 2022
e711f77
Update ethereum/eip712/encoding.go
austinchandra Oct 25, 2022
e97298f
Update ethereum/eip712/encoding.go
austinchandra Oct 25, 2022
8f6f63b
Code cleanup
austinchandra Oct 25, 2022
876876b
Update ethereum/eip712/encoding.go
austinchandra Oct 26, 2022
6b0481f
Minor codefix
austinchandra Oct 26, 2022
b8cb5a4
Merge branch 'main' into austin/ENG-891
austinchandra Oct 26, 2022
e08c6af
Update ethereum/eip712/encoding.go
fedekunze Oct 26, 2022
74548b7
Minor code revision updates
austinchandra Oct 28, 2022
36ea28f
Refactor EIP712 unit tests to use test suite
austinchandra Oct 28, 2022
6a7cd65
Merge branch 'main' into austin/ENG-891
facs95 Oct 31, 2022
d91ea3a
update branch
facs95 Oct 31, 2022
13e91de
Address import cycle and implement minor refactors
austinchandra Oct 31, 2022
386a019
Fix lint issues
austinchandra Oct 31, 2022
3ef98c8
Add EIP712 unit suite test function
austinchandra Oct 31, 2022
5003d85
Update ethereum/eip712/encoding.go
austinchandra Nov 1, 2022
13a0f4d
Update ethereum/eip712/encoding.go
austinchandra Nov 1, 2022
cdba1ee
Update ethereum/eip712/encoding.go
austinchandra Nov 1, 2022
f16265a
Add minor refactors; increase test coverage
austinchandra Nov 1, 2022
3a5a525
Correct ante_test for change in payload
austinchandra Nov 1, 2022
e93b3db
Add single-signer util and tests
austinchandra Nov 1, 2022
47e224c
Update ethereum/eip712/encoding.go
fedekunze Nov 2, 2022
1c075c8
Update ethereum/eip712/encoding.go
fedekunze Nov 2, 2022
f795487
Merge branch 'main' into austin/ENG-891
fedekunze Nov 2, 2022
2b7486e
fix build
facs95 Nov 2, 2022
e4795a2
Merge branch 'main' into austin/ENG-891
facs95 Nov 2, 2022
c265a66
Merge branch 'main' into austin/ENG-891
facs95 Nov 3, 2022
a61c40f
Remove reflect import
austinchandra Nov 3, 2022
658ed26
Merge branch 'austin/ENG-891' of https://github.com/evmos/ethermint i…
austinchandra Nov 3, 2022
be34c8c
Merge branch 'main' into austin/ENG-891
fedekunze Nov 7, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Refactor EIP712 unit tests to use test suite
austinchandra committed Oct 28, 2022
commit 36ea28fcc074fd079831bddb2d8bbdb5cbefb6e2
204 changes: 101 additions & 103 deletions ethereum/eip712/eip712_test.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
package eip712_test

import (
"testing"

"cosmossdk.io/math"
"github.com/stretchr/testify/require"

"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/simapp/params"
"github.com/ethereum/go-ethereum/crypto"
"github.com/evmos/ethermint/ethereum/eip712"

@@ -25,54 +23,54 @@ import (
distributiontypes "github.com/cosmos/cosmos-sdk/x/distribution/types"
govtypes "github.com/cosmos/cosmos-sdk/x/gov/types/v1beta1"
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
"github.com/stretchr/testify/suite"
)

// Tests single-signer EIP-712 signature verification. Multi-signer verification tests are included
// in ante/integration test files.
// Unit tests for single-signer EIP-712 signature verification. Multi-signer verification tests are included
// in ante_test.go.

type EIP712TestSuite struct {
suite.Suite

var config = encoding.MakeConfig(app.ModuleBasics)
var clientCtx = client.Context{}.WithTxConfig(config.TxConfig)
config params.EncodingConfig
clientCtx client.Context
}

// Set up test env to replicate prod. environment
func setupTestEnv(t *testing.T) {
t.Helper()
func (suite *EIP712TestSuite) SetupTest() {
suite.config = encoding.MakeConfig(app.ModuleBasics)
suite.clientCtx = client.Context{}.WithTxConfig(suite.config.TxConfig)

sdk.GetConfig().SetBech32PrefixForAccount("ethm", "")
eip712.SetEncodingConfig(config)
eip712.SetEncodingConfig(suite.config)
}

// Helper to create random test addresses for messages
func createTestAddress(t *testing.T) sdk.AccAddress {
t.Helper()

func (suite *EIP712TestSuite) createTestAddress() sdk.AccAddress {
privkey, _ := ethsecp256k1.GenerateKey()
key, err := privkey.ToECDSA()
require.NoError(t, err)
suite.Require().NoError(err)

addr := crypto.PubkeyToAddress(key.PublicKey)

return addr.Bytes()
}

// Helper to create random keypair for signing + verification
func createTestKeyPair(t *testing.T) (*ethsecp256k1.PrivKey, *ethsecp256k1.PubKey) {
t.Helper()

func (suite *EIP712TestSuite) createTestKeyPair() (*ethsecp256k1.PrivKey, *ethsecp256k1.PubKey) {
privKey, err := ethsecp256k1.GenerateKey()
require.NoError(t, err)
suite.Require().NoError(err)

pubKey := &ethsecp256k1.PubKey{
Key: privKey.PubKey().Bytes(),
}
require.Implements(t, (*cryptotypes.PubKey)(nil), pubKey)
suite.Require().Implements((*cryptotypes.PubKey)(nil), pubKey)

return privKey, pubKey
}

// Helper to create instance of sdk.Coins[] with single coin
func makeCoins(t *testing.T, denom string, amount math.Int) sdk.Coins {
t.Helper()

func (suite *EIP712TestSuite) makeCoins(denom string, amount math.Int) sdk.Coins {
return sdk.NewCoins(
sdk.NewCoin(
denom,
@@ -81,8 +79,8 @@ func makeCoins(t *testing.T, denom string, amount math.Int) sdk.Coins {
)
}

func TestEIP712SignatureVerification(t *testing.T) {
setupTestEnv(t)
func (suite *EIP712TestSuite) TestEIP712SignatureVerification() {
suite.SetupTest()

signModes := []signing.SignMode{
signing.SignMode_SIGN_MODE_DIRECT,
@@ -101,15 +99,15 @@ func TestEIP712SignatureVerification(t *testing.T) {
{
title: "Standard MsgSend",
fee: txtypes.Fee{
Amount: makeCoins(t, "aphoton", math.NewInt(2000)),
Amount: suite.makeCoins("aphoton", math.NewInt(2000)),
GasLimit: 20000,
},
memo: "",
msgs: []sdk.Msg{
banktypes.NewMsgSend(
createTestAddress(t),
createTestAddress(t),
makeCoins(t, "photon", math.NewInt(1)),
suite.createTestAddress(),
suite.createTestAddress(),
suite.makeCoins("photon", math.NewInt(1)),
),
},
accountNumber: 8,
@@ -119,13 +117,13 @@ func TestEIP712SignatureVerification(t *testing.T) {
{
title: "Standard MsgVote",
fee: txtypes.Fee{
Amount: makeCoins(t, "aphoton", math.NewInt(2000)),
Amount: suite.makeCoins("aphoton", math.NewInt(2000)),
GasLimit: 20000,
},
memo: "",
msgs: []sdk.Msg{
govtypes.NewMsgVote(
createTestAddress(t),
suite.createTestAddress(),
5,
govtypes.OptionNo,
),
@@ -137,15 +135,15 @@ func TestEIP712SignatureVerification(t *testing.T) {
{
title: "Standard MsgDelegate",
fee: txtypes.Fee{
Amount: makeCoins(t, "aphoton", math.NewInt(2000)),
Amount: suite.makeCoins("aphoton", math.NewInt(2000)),
GasLimit: 20000,
},
memo: "",
msgs: []sdk.Msg{
stakingtypes.NewMsgDelegate(
createTestAddress(t),
sdk.ValAddress(createTestAddress(t)),
makeCoins(t, "photon", math.NewInt(1))[0],
suite.createTestAddress(),
sdk.ValAddress(suite.createTestAddress()),
suite.makeCoins("photon", math.NewInt(1))[0],
),
},
accountNumber: 25,
@@ -155,14 +153,14 @@ func TestEIP712SignatureVerification(t *testing.T) {
{
title: "Standard MsgWithdrawDelegationReward",
fee: txtypes.Fee{
Amount: makeCoins(t, "aphoton", math.NewInt(2000)),
Amount: suite.makeCoins("aphoton", math.NewInt(2000)),
GasLimit: 20000,
},
memo: "",
msgs: []sdk.Msg{
distributiontypes.NewMsgWithdrawDelegatorReward(
createTestAddress(t),
sdk.ValAddress(createTestAddress(t)),
suite.createTestAddress(),
sdk.ValAddress(suite.createTestAddress()),
),
},
accountNumber: 25,
@@ -172,136 +170,136 @@ func TestEIP712SignatureVerification(t *testing.T) {
{
title: "Two MsgVotes",
fee: txtypes.Fee{
Amount: makeCoins(t, "aphoton", math.NewInt(2000)),
Amount: suite.makeCoins("aphoton", math.NewInt(2000)),
GasLimit: 20000,
},
memo: "",
msgs: []sdk.Msg{
govtypes.NewMsgVote(
createTestAddress(t),
suite.createTestAddress(),
5,
govtypes.OptionNo,
),
govtypes.NewMsgVote(
createTestAddress(t),
suite.createTestAddress(),
25,
govtypes.OptionAbstain,
),
},
accountNumber: 25,
sequence: 78,
expectSuccess: false, // Multiple messages (check for multiple signers in AnteHandler)
expectSuccess: false, // Multiple messages are currently not allowed
},
{
title: "MsgSend + MsgVote",
fee: txtypes.Fee{
Amount: makeCoins(t, "aphoton", math.NewInt(2000)),
Amount: suite.makeCoins("aphoton", math.NewInt(2000)),
GasLimit: 20000,
},
memo: "",
msgs: []sdk.Msg{
govtypes.NewMsgVote(
createTestAddress(t),
suite.createTestAddress(),
5,
govtypes.OptionNo,
),
banktypes.NewMsgSend(
createTestAddress(t),
createTestAddress(t),
makeCoins(t, "photon", math.NewInt(50)),
suite.createTestAddress(),
suite.createTestAddress(),
suite.makeCoins("photon", math.NewInt(50)),
),
},
accountNumber: 25,
sequence: 78,
expectSuccess: false, // Multiple messages
expectSuccess: false,
},
}

for _, tc := range testCases {
for _, signMode := range signModes {
privKey, pubKey := createTestKeyPair(t)

// Init tx builder
txBuilder := clientCtx.TxConfig.NewTxBuilder()

// Set gas and fees
txBuilder.SetGasLimit(tc.fee.GasLimit)
txBuilder.SetFeeAmount(tc.fee.Amount)

// Set messages
err := txBuilder.SetMsgs(tc.msgs...)
require.NoError(t, err)

// Set memo
txBuilder.SetMemo(tc.memo)

// Prepare signature field
txSigData := signing.SingleSignatureData{
SignMode: signMode,
Signature: nil,
}
txSig := signing.SignatureV2{
PubKey: pubKey,
Data: &txSigData,
Sequence: tc.sequence,
}

err = txBuilder.SetSignatures([]signing.SignatureV2{txSig}...)
require.NoError(t, err)

// Declare signerData
signerData := authsigning.SignerData{
ChainID: "ethermint_9000-1",
AccountNumber: tc.accountNumber,
Sequence: tc.sequence,
PubKey: pubKey,
Address: sdk.MustBech32ifyAddressBytes("ethm", pubKey.Bytes()),
}

bz, err := clientCtx.TxConfig.SignModeHandler().GetSignBytes(
signMode,
signerData,
txBuilder.GetTx(),
)
require.NoError(t, err)

verifyEIP712SignatureVerification(t, tc.expectSuccess, *privKey, *pubKey, bz)
suite.Run(tc.title, func() {
privKey, pubKey := suite.createTestKeyPair()

// Init tx builder
txBuilder := suite.clientCtx.TxConfig.NewTxBuilder()

// Set gas and fees
txBuilder.SetGasLimit(tc.fee.GasLimit)
txBuilder.SetFeeAmount(tc.fee.Amount)

// Set messages
err := txBuilder.SetMsgs(tc.msgs...)
suite.Require().NoError(err)

// Set memo
txBuilder.SetMemo(tc.memo)

// Prepare signature field
txSigData := signing.SingleSignatureData{
SignMode: signMode,
Signature: nil,
}
txSig := signing.SignatureV2{
PubKey: pubKey,
Data: &txSigData,
Sequence: tc.sequence,
}

err = txBuilder.SetSignatures([]signing.SignatureV2{txSig}...)
suite.Require().NoError(err)

// Declare signerData
signerData := authsigning.SignerData{
ChainID: "ethermint_9000-1",
AccountNumber: tc.accountNumber,
Sequence: tc.sequence,
PubKey: pubKey,
Address: sdk.MustBech32ifyAddressBytes("ethm", pubKey.Bytes()),
}

bz, err := suite.clientCtx.TxConfig.SignModeHandler().GetSignBytes(
signMode,
signerData,
txBuilder.GetTx(),
)
suite.Require().NoError(err)

suite.verifyEIP712SignatureVerification(tc.expectSuccess, *privKey, *pubKey, bz)
})
}
}
}

// Verify that the payload passes signature verification if signed as its EIP-712 representation.
func verifyEIP712SignatureVerification(t *testing.T, expectedSuccess bool, privKey ethsecp256k1.PrivKey, pubKey ethsecp256k1.PubKey, signBytes []byte) {
t.Helper()

func (suite *EIP712TestSuite) verifyEIP712SignatureVerification(expectedSuccess bool, privKey ethsecp256k1.PrivKey, pubKey ethsecp256k1.PubKey, signBytes []byte) {
// Convert to EIP712 hash and sign
eip712Hash, err := eip712.GetEIP712HashForMsg(signBytes)
if !expectedSuccess {
// Expect failure generating EIP-712 hash
require.Error(t, err)
suite.Require().Error(err)
return
}

require.NoError(t, err)
suite.Require().NoError(err)

sigHash := crypto.Keccak256Hash(eip712Hash)
sig, err := privKey.Sign(sigHash.Bytes())
require.NoError(t, err)
suite.Require().NoError(err)

// Verify against original payload bytes. This should pass, even though it is not
// the original message that was signed.
res := pubKey.VerifySignature(signBytes, sig)
require.True(t, res)
suite.Require().True(res)

// Verify against the signed EIP-712 bytes. This should pass, since it is the message signed.
res = pubKey.VerifySignature(eip712Hash, sig)
require.True(t, res)
suite.Require().True(res)

// Verify against random bytes to ensure it does not pass unexpectedly (sanity check).
randBytes := make([]byte, len(signBytes))
copy(randBytes, signBytes)
// Change the first element of signBytes to a different value
randBytes[0] = (signBytes[0] + 10) % 128
res = pubKey.VerifySignature(randBytes, sig)
require.False(t, res)
suite.Require().False(res)
}