diff --git a/sei-cosmos/types/tx/types.go b/sei-cosmos/types/tx/types.go index 50ef5e941f..e90428de0f 100644 --- a/sei-cosmos/types/tx/types.go +++ b/sei-cosmos/types/tx/types.go @@ -112,6 +112,14 @@ func (t *Tx) ValidateBasic() error { return sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "fee grants are not enabled") } + // SignerInfos and Signatures are 1:1 (see SetSignatures). + if len(authInfo.SignerInfos) != len(sigs) { + return sdkerrors.Wrapf( + sdkerrors.ErrUnauthorized, + "wrong number of SignerInfos; expected %d, got %d", len(sigs), len(authInfo.SignerInfos), + ) + } + return nil } diff --git a/sei-cosmos/x/auth/tx/builder_test.go b/sei-cosmos/x/auth/tx/builder_test.go index 3ca6d482f0..523af7f618 100644 --- a/sei-cosmos/x/auth/tx/builder_test.go +++ b/sei-cosmos/x/auth/tx/builder_test.go @@ -199,6 +199,17 @@ func TestBuilderValidateBasic(t *testing.T) { err = txBuilder.ValidateBasic() require.NoError(t, err) + // SignerInfos must match Signatures + origInfos := txBuilder.tx.AuthInfo.SignerInfos + txBuilder.tx.AuthInfo.SignerInfos = append(origInfos, origInfos[0]) + err = txBuilder.ValidateBasic() + require.Error(t, err) + _, code, _ = sdkerrors.ABCIInfo(err, false) + require.Equal(t, sdkerrors.ErrUnauthorized.ABCICode(), code) + txBuilder.tx.AuthInfo.SignerInfos = origInfos + err = txBuilder.ValidateBasic() + require.NoError(t, err) + // gas limit too high txBuilder.SetGasLimit(txtypes.MaxGasWanted + 1) err = txBuilder.ValidateBasic() diff --git a/sei-cosmos/x/auth/tx/sigs.go b/sei-cosmos/x/auth/tx/sigs.go index 8c133b5ba1..e15c9ddc43 100644 --- a/sei-cosmos/x/auth/tx/sigs.go +++ b/sei-cosmos/x/auth/tx/sigs.go @@ -6,6 +6,7 @@ import ( "github.com/sei-protocol/sei-chain/sei-cosmos/codec" codectypes "github.com/sei-protocol/sei-chain/sei-cosmos/codec/types" cryptotypes "github.com/sei-protocol/sei-chain/sei-cosmos/crypto/types" + sdkerrors "github.com/sei-protocol/sei-chain/sei-cosmos/types/errors" "github.com/sei-protocol/sei-chain/sei-cosmos/types/tx" "github.com/sei-protocol/sei-chain/sei-cosmos/types/tx/signing" ) @@ -70,6 +71,11 @@ func ModeInfoAndSigToSignatureData(modeInfo *tx.ModeInfo, sig []byte) (signing.S if err != nil { return nil, err } + // ModeInfos and nested signatures are 1:1 (see SignatureDataToModeInfoAndSig). + if len(multi.ModeInfos) != len(sigs) { + return nil, sdkerrors.Wrapf(sdkerrors.ErrTxDecode, + "invalid multisig: %d mode infos, %d signatures", len(multi.ModeInfos), len(sigs)) + } sigv2s := make([]signing.SignatureData, len(sigs)) for i, mi := range multi.ModeInfos { @@ -85,7 +91,7 @@ func ModeInfoAndSigToSignatureData(modeInfo *tx.ModeInfo, sig []byte) (signing.S }, nil default: - panic(fmt.Errorf("unexpected ModeInfo data type %T", modeInfo)) + return nil, sdkerrors.Wrapf(sdkerrors.ErrTxDecode, "unexpected ModeInfo data type %T", modeInfo) } } diff --git a/sei-cosmos/x/auth/tx/sigs_test.go b/sei-cosmos/x/auth/tx/sigs_test.go index 619f727572..5dd1e03362 100644 --- a/sei-cosmos/x/auth/tx/sigs_test.go +++ b/sei-cosmos/x/auth/tx/sigs_test.go @@ -5,9 +5,12 @@ import ( "github.com/stretchr/testify/require" - "github.com/sei-protocol/sei-chain/sei-cosmos/crypto/types" - + cryptotypes "github.com/sei-protocol/sei-chain/sei-cosmos/crypto/types" + "github.com/sei-protocol/sei-chain/sei-cosmos/crypto/types/multisig" "github.com/sei-protocol/sei-chain/sei-cosmos/testutil/testdata" + sdkerrors "github.com/sei-protocol/sei-chain/sei-cosmos/types/errors" + txtypes "github.com/sei-protocol/sei-chain/sei-cosmos/types/tx" + "github.com/sei-protocol/sei-chain/sei-cosmos/types/tx/signing" ) func TestDecodeMultisignatures(t *testing.T) { @@ -27,7 +30,7 @@ func TestDecodeMultisignatures(t *testing.T) { _, err = decodeMultisignatures(bz) require.Error(t, err) - goodMultisig := types.MultiSignature{ + goodMultisig := cryptotypes.MultiSignature{ Signatures: testSigs, } bz, err = goodMultisig.Marshal() @@ -38,3 +41,64 @@ func TestDecodeMultisignatures(t *testing.T) { require.Equal(t, testSigs, decodedSigs) } + +func TestModeInfoAndSigToSignatureData(t *testing.T) { + single := &signing.SingleSignatureData{ + SignMode: signing.SignMode_SIGN_MODE_DIRECT, + Signature: []byte("a"), + } + + // Nested Multi ModeInfo exercises the recursive decode path. + inner := multisig.NewMultisig(2) + multisig.AddSignature(inner, single, 0) + outer := multisig.NewMultisig(2) + multisig.AddSignature(outer, &signing.SingleSignatureData{ + SignMode: signing.SignMode_SIGN_MODE_DIRECT, + Signature: []byte("b"), + }, 0) + multisig.AddSignature(outer, inner, 1) + modeInfo, raw := SignatureDataToModeInfoAndSig(outer) + got, err := ModeInfoAndSigToSignatureData(modeInfo, raw) + require.NoError(t, err) + require.Equal(t, outer, got) + + mi := &txtypes.ModeInfo{Sum: &txtypes.ModeInfo_Single_{ + Single: &txtypes.ModeInfo_Single{Mode: signing.SignMode_SIGN_MODE_DIRECT}, + }} + bad := &txtypes.ModeInfo{Sum: &txtypes.ModeInfo_Multi_{ + Multi: &txtypes.ModeInfo_Multi{ + Bitarray: cryptotypes.NewCompactBitArray(2), + ModeInfos: []*txtypes.ModeInfo{mi, mi}, + }, + }} + + // fewer nested sigs than ModeInfos must error + rawShort, err := (&cryptotypes.MultiSignature{Signatures: [][]byte{[]byte("a")}}).Marshal() + require.NoError(t, err) + _, err = ModeInfoAndSigToSignatureData(bad, rawShort) + require.ErrorIs(t, err, sdkerrors.ErrTxDecode) + + // more nested sigs than ModeInfos must error + rawLong, err := (&cryptotypes.MultiSignature{Signatures: [][]byte{[]byte("a"), []byte("b"), []byte("c")}}).Marshal() + require.NoError(t, err) + _, err = ModeInfoAndSigToSignatureData(bad, rawLong) + require.ErrorIs(t, err, sdkerrors.ErrTxDecode) + + // mismatch inside nested Multi ModeInfo must error on the recursive call + innerShort, err := (&cryptotypes.MultiSignature{Signatures: [][]byte{[]byte("a")}}).Marshal() + require.NoError(t, err) + rawNested, err := (&cryptotypes.MultiSignature{Signatures: [][]byte{[]byte("b"), innerShort}}).Marshal() + require.NoError(t, err) + nestedBad := &txtypes.ModeInfo{Sum: &txtypes.ModeInfo_Multi_{ + Multi: &txtypes.ModeInfo_Multi{ + Bitarray: cryptotypes.NewCompactBitArray(2), + ModeInfos: []*txtypes.ModeInfo{mi, bad}, + }, + }} + _, err = ModeInfoAndSigToSignatureData(nestedBad, rawNested) + require.ErrorIs(t, err, sdkerrors.ErrTxDecode) + + // non-nil ModeInfo with unset Sum oneof must error (not panic) + _, err = ModeInfoAndSigToSignatureData(&txtypes.ModeInfo{}, []byte("a")) + require.ErrorIs(t, err, sdkerrors.ErrTxDecode) +}