laconicd-deprecated/ethereum/eip712/eip712_test.go
2024-01-10 11:20:29 +05:30

478 lines
12 KiB
Go

package eip712_test
import (
"testing"
"cosmossdk.io/math"
registrytypes "github.com/cerc-io/laconicd/x/registry/types"
"github.com/cosmos/cosmos-sdk/client"
cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
"github.com/cosmos/cosmos-sdk/simapp/params"
sdk "github.com/cosmos/cosmos-sdk/types"
txtypes "github.com/cosmos/cosmos-sdk/types/tx"
"github.com/cosmos/cosmos-sdk/types/tx/signing"
authsigning "github.com/cosmos/cosmos-sdk/x/auth/signing"
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
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/ethereum/go-ethereum/crypto"
"github.com/stretchr/testify/suite"
"github.com/cerc-io/laconicd/app"
"github.com/cerc-io/laconicd/crypto/ethsecp256k1"
"github.com/cerc-io/laconicd/encoding"
"github.com/cerc-io/laconicd/ethereum/eip712"
)
// Unit tests for single-signer EIP-712 signature verification. Multi-signer verification tests are included
// in ante_test.go.
type EIP712TestSuite struct {
suite.Suite
config params.EncodingConfig
clientCtx client.Context
}
func TestEIP712TestSuite(t *testing.T) {
suite.Run(t, &EIP712TestSuite{})
}
// Set up test env to replicate prod. environment
func (suite *EIP712TestSuite) SetupTest() {
suite.config = encoding.MakeConfig(app.ModuleBasics)
suite.clientCtx = client.Context{}.WithTxConfig(suite.config.TxConfig)
sdk.GetConfig().SetBech32PrefixForAccount("ethm", "")
eip712.SetEncodingConfig(suite.config)
}
// Helper to create random test addresses for messages
func (suite *EIP712TestSuite) createTestAddress() sdk.AccAddress {
privkey, _ := ethsecp256k1.GenerateKey()
key, err := privkey.ToECDSA()
suite.Require().NoError(err)
addr := crypto.PubkeyToAddress(key.PublicKey)
return addr.Bytes()
}
// Helper to create random keypair for signing + verification
func (suite *EIP712TestSuite) createTestKeyPair() (*ethsecp256k1.PrivKey, *ethsecp256k1.PubKey) {
privKey, err := ethsecp256k1.GenerateKey()
suite.Require().NoError(err)
pubKey := &ethsecp256k1.PubKey{
Key: privKey.PubKey().Bytes(),
}
suite.Require().Implements((*cryptotypes.PubKey)(nil), pubKey)
return privKey, pubKey
}
// Helper to create instance of sdk.Coins[] with single coin
func (suite *EIP712TestSuite) makeCoins(denom string, amount math.Int) sdk.Coins {
return sdk.NewCoins(
sdk.NewCoin(
denom,
amount,
),
)
}
func (suite *EIP712TestSuite) TestEIP712SignatureVerification() {
suite.SetupTest()
signModes := []signing.SignMode{
signing.SignMode_SIGN_MODE_DIRECT,
signing.SignMode_SIGN_MODE_LEGACY_AMINO_JSON,
}
// Fixed test address
testAddress := suite.createTestAddress()
testCases := []struct {
title string
chainId string
fee txtypes.Fee
memo string
msgs []sdk.Msg
accountNumber uint64
sequence uint64
timeoutHeight uint64
expectSuccess bool
}{
{
title: "Succeeds - Standard MsgSend",
fee: txtypes.Fee{
Amount: suite.makeCoins("aphoton", math.NewInt(2000)),
GasLimit: 20000,
},
memo: "",
msgs: []sdk.Msg{
banktypes.NewMsgSend(
suite.createTestAddress(),
suite.createTestAddress(),
suite.makeCoins("photon", math.NewInt(1)),
),
},
accountNumber: 8,
sequence: 5,
expectSuccess: true,
},
{
title: "Succeeds - Standard MsgVote",
fee: txtypes.Fee{
Amount: suite.makeCoins("aphoton", math.NewInt(2000)),
GasLimit: 20000,
},
memo: "",
msgs: []sdk.Msg{
govtypes.NewMsgVote(
suite.createTestAddress(),
5,
govtypes.OptionNo,
),
},
accountNumber: 25,
sequence: 78,
expectSuccess: true,
},
{
title: "Succeeds - Standard MsgDelegate",
fee: txtypes.Fee{
Amount: suite.makeCoins("aphoton", math.NewInt(2000)),
GasLimit: 20000,
},
memo: "",
msgs: []sdk.Msg{
stakingtypes.NewMsgDelegate(
suite.createTestAddress(),
sdk.ValAddress(suite.createTestAddress()),
suite.makeCoins("photon", math.NewInt(1))[0],
),
},
accountNumber: 25,
sequence: 78,
expectSuccess: true,
},
{
title: "Succeeds - Standard MsgWithdrawDelegationReward",
fee: txtypes.Fee{
Amount: suite.makeCoins("aphoton", math.NewInt(2000)),
GasLimit: 20000,
},
memo: "",
msgs: []sdk.Msg{
distributiontypes.NewMsgWithdrawDelegatorReward(
suite.createTestAddress(),
sdk.ValAddress(suite.createTestAddress()),
),
},
accountNumber: 25,
sequence: 78,
expectSuccess: true,
},
{
title: "Succeeds - Two Single-Signer MsgDelegate",
fee: txtypes.Fee{
Amount: suite.makeCoins("aphoton", math.NewInt(2000)),
GasLimit: 20000,
},
memo: "",
msgs: []sdk.Msg{
stakingtypes.NewMsgDelegate(
testAddress,
sdk.ValAddress(suite.createTestAddress()),
suite.makeCoins("photon", math.NewInt(1))[0],
),
stakingtypes.NewMsgDelegate(
testAddress,
sdk.ValAddress(suite.createTestAddress()),
suite.makeCoins("photon", math.NewInt(5))[0],
),
},
accountNumber: 25,
sequence: 78,
expectSuccess: true,
},
{
title: "Fails - Two MsgVotes with Different Signers",
fee: txtypes.Fee{
Amount: suite.makeCoins("aphoton", math.NewInt(2000)),
GasLimit: 20000,
},
memo: "",
msgs: []sdk.Msg{
govtypes.NewMsgVote(
suite.createTestAddress(),
5,
govtypes.OptionNo,
),
govtypes.NewMsgVote(
suite.createTestAddress(),
25,
govtypes.OptionAbstain,
),
},
accountNumber: 25,
sequence: 78,
expectSuccess: false,
},
{
title: "Fails - Empty transaction",
fee: txtypes.Fee{
Amount: suite.makeCoins("aphoton", math.NewInt(2000)),
GasLimit: 20000,
},
memo: "",
msgs: []sdk.Msg{},
accountNumber: 25,
sequence: 78,
expectSuccess: false,
},
{
title: "Fails - Single-Signer MsgSend + MsgVote",
fee: txtypes.Fee{
Amount: suite.makeCoins("aphoton", math.NewInt(2000)),
GasLimit: 20000,
},
memo: "",
msgs: []sdk.Msg{
govtypes.NewMsgVote(
testAddress,
5,
govtypes.OptionNo,
),
banktypes.NewMsgSend(
testAddress,
suite.createTestAddress(),
suite.makeCoins("photon", math.NewInt(50)),
),
},
accountNumber: 25,
sequence: 78,
expectSuccess: false,
},
{
title: "Fails - Invalid ChainID",
chainId: "invalidchainid",
fee: txtypes.Fee{
Amount: suite.makeCoins("aphoton", math.NewInt(2000)),
GasLimit: 20000,
},
memo: "",
msgs: []sdk.Msg{
govtypes.NewMsgVote(
suite.createTestAddress(),
5,
govtypes.OptionNo,
),
},
accountNumber: 25,
sequence: 78,
expectSuccess: false,
},
{
title: "Fails - Includes TimeoutHeight",
fee: txtypes.Fee{
Amount: suite.makeCoins("aphoton", math.NewInt(2000)),
GasLimit: 20000,
},
memo: "",
msgs: []sdk.Msg{
govtypes.NewMsgVote(
suite.createTestAddress(),
5,
govtypes.OptionNo,
),
},
accountNumber: 25,
sequence: 78,
timeoutHeight: 1000,
expectSuccess: false,
},
{
title: "Fails - Single Message / Multi-Signer",
fee: txtypes.Fee{
Amount: suite.makeCoins("aphoton", math.NewInt(2000)),
GasLimit: 20000,
},
memo: "",
msgs: []sdk.Msg{
banktypes.NewMsgMultiSend(
[]banktypes.Input{
banktypes.NewInput(
suite.createTestAddress(),
suite.makeCoins("photon", math.NewInt(50)),
),
banktypes.NewInput(
suite.createTestAddress(),
suite.makeCoins("photon", math.NewInt(50)),
),
},
[]banktypes.Output{
banktypes.NewOutput(
suite.createTestAddress(),
suite.makeCoins("photon", math.NewInt(50)),
),
banktypes.NewOutput(
suite.createTestAddress(),
suite.makeCoins("photon", math.NewInt(50)),
),
},
),
},
accountNumber: 25,
sequence: 78,
expectSuccess: false,
},
// test laconic registry messages
{
title: "Succeeds - Standard MsgSetName",
fee: txtypes.Fee{
Amount: suite.makeCoins("aphoton", math.NewInt(2000)),
GasLimit: 100000,
},
memo: "",
msgs: []sdk.Msg{
registrytypes.NewMsgSetName(
"testcrn",
"testcid",
suite.createTestAddress(),
),
},
accountNumber: 25,
sequence: 78,
expectSuccess: true,
},
{
title: "Succeeds - Standard MsgSetRecord",
fee: txtypes.Fee{
Amount: suite.makeCoins("aphoton", math.NewInt(2000)),
GasLimit: 100000,
},
memo: "",
msgs: []sdk.Msg{
registrytypes.NewMsgSetRecord(
registrytypes.Payload{
Record: &registrytypes.Record{
Attributes: []byte("test attributes"),
},
Signatures: []registrytypes.Signature{
{
Sig: "fake sig",
PubKey: "fake pubkey",
},
},
},
"testbondid",
suite.createTestAddress(),
),
},
accountNumber: 25,
sequence: 78,
expectSuccess: true,
},
}
for _, tc := range testCases {
for _, signMode := range signModes {
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)
chainId := "ethermint_9000-1"
if tc.chainId != "" {
chainId = tc.chainId
}
if tc.timeoutHeight != 0 {
txBuilder.SetTimeoutHeight(tc.timeoutHeight)
}
// Declare signerData
signerData := authsigning.SignerData{
ChainID: chainId,
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 (suite *EIP712TestSuite) verifyEIP712SignatureVerification(expectedSuccess bool, privKey ethsecp256k1.PrivKey, pubKey ethsecp256k1.PubKey, signBytes []byte) {
// Convert to EIP712 bytes and sign
eip712Bytes, err := eip712.GetEIP712BytesForMsg(signBytes)
if !expectedSuccess {
// Expect failure generating EIP-712 bytes
suite.Require().Error(err)
return
}
suite.Require().NoError(err)
sig, err := privKey.Sign(eip712Bytes)
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)
suite.Require().True(res)
// Verify against the signed EIP-712 bytes. This should pass, since it is the message signed.
res = pubKey.VerifySignature(eip712Bytes, sig)
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)
suite.Require().False(res)
}