From be59020f29b4f413883564208d67214ad328d9d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?colin=20axn=C3=A9r?= <25233464+colin-axner@users.noreply.github.com> Date: Mon, 28 Sep 2020 13:00:33 +0200 Subject: [PATCH] Add multisig support + tests to solo machine (#7383) * add multisig support to testing pkg Adds single and multisig public key support to the testing package. Fix build associated with changes. * update clientstate tests to use singlesig and multsig * add singlesig and multisig tests for consensus state, header, and misbehaviour * add singlesig and multisig for solomachine handlers * add spec * fix lgtm * fixes from self review * fix lint? * fix build * increase code cov * Update x/ibc/light-clients/solomachine/types/client_state.go Co-authored-by: Federico Kunze <31522760+fedekunze@users.noreply.github.com> * apply @fedekunze review suggestions Add comment to VerifyMultisignature explaining why it uses the nested function. Switch panic to require. Add comment for secp256k1 key usage. Ref: https://github.com/cosmos/cosmos-sdk/pull/7383#pullrequestreview-496591518 Co-authored-by: Federico Kunze <31522760+fedekunze@users.noreply.github.com> Co-authored-by: Christopher Goes Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> --- x/ibc/02-client/types/client_test.go | 2 +- x/ibc/02-client/types/codec_test.go | 8 +- x/ibc/02-client/types/msgs_test.go | 18 +- x/ibc/02-client/types/proposal_test.go | 2 +- .../solomachine/spec/01_concepts.md | 66 + .../light-clients/solomachine/spec/README.md | 18 + .../solomachine/types/client_state.go | 105 +- .../solomachine/types/client_state_test.go | 1360 +++++++++-------- .../light-clients/solomachine/types/codec.go | 15 +- .../solomachine/types/consensus_state_test.go | 95 +- .../solomachine/types/header_test.go | 161 +- .../solomachine/types/misbehaviour_handle.go | 14 +- .../types/misbehaviour_handle_test.go | 333 ++-- .../solomachine/types/misbehaviour_test.go | 163 +- .../light-clients/solomachine/types/proof.go | 33 +- .../solomachine/types/proposal_handle_test.go | 118 +- .../solomachine/types/solomachine_test.go | 13 +- .../light-clients/solomachine/types/update.go | 7 +- .../solomachine/types/update_test.go | 285 ++-- x/ibc/testing/chain.go | 2 +- x/ibc/testing/solomachine.go | 116 +- 21 files changed, 1614 insertions(+), 1320 deletions(-) create mode 100644 x/ibc/light-clients/solomachine/spec/01_concepts.md create mode 100644 x/ibc/light-clients/solomachine/spec/README.md diff --git a/x/ibc/02-client/types/client_test.go b/x/ibc/02-client/types/client_test.go index b641553bfa..1652568fbd 100644 --- a/x/ibc/02-client/types/client_test.go +++ b/x/ibc/02-client/types/client_test.go @@ -16,7 +16,7 @@ func (suite *TypesTestSuite) TestMarshalConsensusStateWithHeight() { }{ { "solo machine client", func() { - soloMachine := ibctesting.NewSolomachine(suite.T(), suite.chainA.Codec, "solomachine", "") + soloMachine := ibctesting.NewSolomachine(suite.T(), suite.chainA.Codec, "solomachine", "", 1) cswh = types.NewConsensusStateWithHeight(types.NewHeight(0, soloMachine.Sequence), soloMachine.ConsensusState()) }, }, diff --git a/x/ibc/02-client/types/codec_test.go b/x/ibc/02-client/types/codec_test.go index f52c87119c..98ad6e4763 100644 --- a/x/ibc/02-client/types/codec_test.go +++ b/x/ibc/02-client/types/codec_test.go @@ -25,7 +25,7 @@ func (suite *TypesTestSuite) TestPackClientState() { }{ { "solo machine client", - ibctesting.NewSolomachine(suite.T(), suite.chainA.Codec, "solomachine", "").ClientState(), + ibctesting.NewSolomachine(suite.T(), suite.chainA.Codec, "solomachine", "", 2).ClientState(), true, }, { @@ -77,7 +77,7 @@ func (suite *TypesTestSuite) TestPackConsensusState() { }{ { "solo machine consensus", - ibctesting.NewSolomachine(suite.T(), suite.chainA.Codec, "solomachine", "").ConsensusState(), + ibctesting.NewSolomachine(suite.T(), suite.chainA.Codec, "solomachine", "", 2).ConsensusState(), true, }, { @@ -123,7 +123,7 @@ func (suite *TypesTestSuite) TestPackHeader() { }{ { "solo machine header", - ibctesting.NewSolomachine(suite.T(), suite.chainA.Codec, "solomachine", "").CreateHeader(), + ibctesting.NewSolomachine(suite.T(), suite.chainA.Codec, "solomachine", "", 2).CreateHeader(), true, }, { @@ -170,7 +170,7 @@ func (suite *TypesTestSuite) TestPackMisbehaviour() { }{ { "solo machine misbehaviour", - ibctesting.NewSolomachine(suite.T(), suite.chainA.Codec, "solomachine", "").CreateMisbehaviour(), + ibctesting.NewSolomachine(suite.T(), suite.chainA.Codec, "solomachine", "", 2).CreateMisbehaviour(), true, }, { diff --git a/x/ibc/02-client/types/msgs_test.go b/x/ibc/02-client/types/msgs_test.go index 6aec4ea6d5..3f29b69f2c 100644 --- a/x/ibc/02-client/types/msgs_test.go +++ b/x/ibc/02-client/types/msgs_test.go @@ -49,7 +49,7 @@ func (suite *TypesTestSuite) TestMarshalMsgCreateClient() { }{ { "solo machine client", func() { - soloMachine := ibctesting.NewSolomachine(suite.T(), suite.chainA.Codec, "solomachine", "") + soloMachine := ibctesting.NewSolomachine(suite.T(), suite.chainA.Codec, "solomachine", "", 2) msg, err = types.NewMsgCreateClient(soloMachine.ClientID, soloMachine.ClientState(), soloMachine.ConsensusState(), suite.chainA.SenderAccount.GetAddress()) suite.Require().NoError(err) }, @@ -149,7 +149,7 @@ func (suite *TypesTestSuite) TestMsgCreateClient_ValidateBasic() { { "valid - solomachine client", func() { - soloMachine := ibctesting.NewSolomachine(suite.T(), suite.chainA.Codec, "solomachine", "") + soloMachine := ibctesting.NewSolomachine(suite.T(), suite.chainA.Codec, "solomachine", "", 2) msg, err = types.NewMsgCreateClient(soloMachine.ClientID, soloMachine.ClientState(), soloMachine.ConsensusState(), suite.chainA.SenderAccount.GetAddress()) suite.Require().NoError(err) }, @@ -158,7 +158,7 @@ func (suite *TypesTestSuite) TestMsgCreateClient_ValidateBasic() { { "invalid solomachine client", func() { - soloMachine := ibctesting.NewSolomachine(suite.T(), suite.chainA.Codec, "solomachine", "") + soloMachine := ibctesting.NewSolomachine(suite.T(), suite.chainA.Codec, "solomachine", "", 2) msg, err = types.NewMsgCreateClient(soloMachine.ClientID, &solomachinetypes.ClientState{}, soloMachine.ConsensusState(), suite.chainA.SenderAccount.GetAddress()) suite.Require().NoError(err) }, @@ -167,7 +167,7 @@ func (suite *TypesTestSuite) TestMsgCreateClient_ValidateBasic() { { "invalid solomachine consensus state", func() { - soloMachine := ibctesting.NewSolomachine(suite.T(), suite.chainA.Codec, "solomachine", "") + soloMachine := ibctesting.NewSolomachine(suite.T(), suite.chainA.Codec, "solomachine", "", 2) msg, err = types.NewMsgCreateClient(soloMachine.ClientID, soloMachine.ClientState(), &solomachinetypes.ConsensusState{}, suite.chainA.SenderAccount.GetAddress()) suite.Require().NoError(err) }, @@ -209,7 +209,7 @@ func (suite *TypesTestSuite) TestMarshalMsgUpdateClient() { }{ { "solo machine client", func() { - soloMachine := ibctesting.NewSolomachine(suite.T(), suite.chainA.Codec, "solomachine", "") + soloMachine := ibctesting.NewSolomachine(suite.T(), suite.chainA.Codec, "solomachine", "", 2) msg, err = types.NewMsgUpdateClient(soloMachine.ClientID, soloMachine.CreateHeader(), suite.chainA.SenderAccount.GetAddress()) suite.Require().NoError(err) }, @@ -298,7 +298,7 @@ func (suite *TypesTestSuite) TestMsgUpdateClient_ValidateBasic() { { "valid - solomachine header", func() { - soloMachine := ibctesting.NewSolomachine(suite.T(), suite.chainA.Codec, "solomachine", "") + soloMachine := ibctesting.NewSolomachine(suite.T(), suite.chainA.Codec, "solomachine", "", 2) msg, err = types.NewMsgUpdateClient(soloMachine.ClientID, soloMachine.CreateHeader(), suite.chainA.SenderAccount.GetAddress()) suite.Require().NoError(err) }, @@ -347,7 +347,7 @@ func (suite *TypesTestSuite) TestMarshalMsgSubmitMisbehaviour() { }{ { "solo machine client", func() { - soloMachine := ibctesting.NewSolomachine(suite.T(), suite.chainA.Codec, "solomachine", "") + soloMachine := ibctesting.NewSolomachine(suite.T(), suite.chainA.Codec, "solomachine", "", 2) msg, err = types.NewMsgSubmitMisbehaviour(soloMachine.ClientID, soloMachine.CreateMisbehaviour(), suite.chainA.SenderAccount.GetAddress()) suite.Require().NoError(err) }, @@ -448,7 +448,7 @@ func (suite *TypesTestSuite) TestMsgSubmitMisbehaviour_ValidateBasic() { { "valid - solomachine misbehaviour", func() { - soloMachine := ibctesting.NewSolomachine(suite.T(), suite.chainA.Codec, "solomachine", "") + soloMachine := ibctesting.NewSolomachine(suite.T(), suite.chainA.Codec, "solomachine", "", 2) msg, err = types.NewMsgSubmitMisbehaviour(soloMachine.ClientID, soloMachine.CreateMisbehaviour(), suite.chainA.SenderAccount.GetAddress()) suite.Require().NoError(err) }, @@ -465,7 +465,7 @@ func (suite *TypesTestSuite) TestMsgSubmitMisbehaviour_ValidateBasic() { { "client-id mismatch", func() { - soloMachineMisbehaviour := ibctesting.NewSolomachine(suite.T(), suite.chainA.Codec, "solomachine", "").CreateMisbehaviour() + soloMachineMisbehaviour := ibctesting.NewSolomachine(suite.T(), suite.chainA.Codec, "solomachine", "", 2).CreateMisbehaviour() msg, err = types.NewMsgSubmitMisbehaviour("external", soloMachineMisbehaviour, suite.chainA.SenderAccount.GetAddress()) suite.Require().NoError(err) }, diff --git a/x/ibc/02-client/types/proposal_test.go b/x/ibc/02-client/types/proposal_test.go index bb8e52b7c4..a0b88a8116 100644 --- a/x/ibc/02-client/types/proposal_test.go +++ b/x/ibc/02-client/types/proposal_test.go @@ -19,7 +19,7 @@ func (suite *TypesTestSuite) TestNewUpdateClientProposal() { func (suite *TypesTestSuite) TestValidateBasic() { // use solo machine header for testing - solomachine := ibctesting.NewSolomachine(suite.T(), suite.chainA.Codec, clientID, "") + solomachine := ibctesting.NewSolomachine(suite.T(), suite.chainA.Codec, clientID, "", 2) smHeader := solomachine.CreateHeader() header, err := types.PackHeader(smHeader) suite.Require().NoError(err) diff --git a/x/ibc/light-clients/solomachine/spec/01_concepts.md b/x/ibc/light-clients/solomachine/spec/01_concepts.md new file mode 100644 index 0000000000..3aaa645b6b --- /dev/null +++ b/x/ibc/light-clients/solomachine/spec/01_concepts.md @@ -0,0 +1,66 @@ + + +# Concepts + +## Proofs + +A solo machine proof should verify that the solomachine public key signed +over some specified data. The format for generating marshaled proofs for +the SDK's implementation of solo machine is as follows: + +Construct the data using the associated protobuf definition and marshal it. + +For example: +```go +data := &ClientStateData{ + Path: []byte(path.String()), + ClientState: any, +} + +dataBz, err := cdc.MarshalBinaryBare(data) +``` + +Construct the `SignBytes` and marshal it. + +For example: +```go +signBytes := &SignBytes{ + Sequence: sequence, + Timestamp: timestamp, + Diversifier: diversifier, + Data: dataBz, +} + +signBz, err := cdc.MarshalBinaryBare(signBytes) +``` + +The helper functions in [proofs.go](../types/proofs.go) handle the above actions. + +Sign the sign bytes. Embed the signatures into either `SingleSignatureData` or +`MultiSignatureData`. Convert the `SignatureData` to proto and marshal it. + +For example: +```go +sig, err := key.Sign(signBz) +sigData := &signing.SingleSignatureData{ + Signature: sig, +} + +protoSigData := signing.SignatureDataToProto(sigData) +bz, err := cdc.MarshalBinaryBare(protoSigData) +``` + +Construct a `TimestampedSignature` and marshal it. The marshaled result can be +passed in as the proof parameter to the verification functions. + +For example: +```go +timestampedSignature := &types.TimestampedSignature{ + Signature: sig, + Timestamp: solomachine.Time, +} + +proof, err := cdc.MarshalBinaryBare(timestampedSignature) +``` diff --git a/x/ibc/light-clients/solomachine/spec/README.md b/x/ibc/light-clients/solomachine/spec/README.md new file mode 100644 index 0000000000..85dd17a304 --- /dev/null +++ b/x/ibc/light-clients/solomachine/spec/README.md @@ -0,0 +1,18 @@ + + +# `solomachine` + +## Abstract + +This paper defines the implementation of the ICS06 protocol on the Cosmos SDK. + +For the general specification please refer to the [ICS06 Specification](https://github.com/cosmos/ics/tree/master/spec/ics-006-solo-machine-client). + +## Contents + +1. **[Concepts](01_concepts.md)** diff --git a/x/ibc/light-clients/solomachine/types/client_state.go b/x/ibc/light-clients/solomachine/types/client_state.go index 69c29da744..f18bd3bd81 100644 --- a/x/ibc/light-clients/solomachine/types/client_state.go +++ b/x/ibc/light-clients/solomachine/types/client_state.go @@ -6,6 +6,7 @@ import ( "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + "github.com/cosmos/cosmos-sdk/types/tx/signing" clienttypes "github.com/cosmos/cosmos-sdk/x/ibc/02-client/types" commitmenttypes "github.com/cosmos/cosmos-sdk/x/ibc/23-commitment/types" host "github.com/cosmos/cosmos-sdk/x/ibc/24-host" @@ -79,7 +80,7 @@ func (cs ClientState) VerifyClientState( proof []byte, clientState exported.ClientState, ) error { - signature, sequence, err := produceVerificationArgs(cdc, cs, height, prefix, proof) + sigData, timestamp, sequence, err := produceVerificationArgs(cdc, cs, height, prefix, proof) if err != nil { return err } @@ -90,17 +91,17 @@ func (cs ClientState) VerifyClientState( return err } - signBz, err := ClientStateSignBytes(cdc, sequence, signature.Timestamp, cs.ConsensusState.Diversifier, path, clientState) + signBz, err := ClientStateSignBytes(cdc, sequence, timestamp, cs.ConsensusState.Diversifier, path, clientState) if err != nil { return err } - if err := VerifySignature(cs.ConsensusState.GetPubKey(), signBz, signature.Signature); err != nil { + if err := VerifySignature(cs.ConsensusState.GetPubKey(), signBz, sigData); err != nil { return err } cs.Sequence++ - cs.ConsensusState.Timestamp = signature.Timestamp + cs.ConsensusState.Timestamp = timestamp setClientState(store, cdc, &cs) return nil } @@ -118,7 +119,7 @@ func (cs ClientState) VerifyClientConsensusState( proof []byte, consensusState exported.ConsensusState, ) error { - signature, sequence, err := produceVerificationArgs(cdc, cs, height, prefix, proof) + sigData, timestamp, sequence, err := produceVerificationArgs(cdc, cs, height, prefix, proof) if err != nil { return err } @@ -129,17 +130,17 @@ func (cs ClientState) VerifyClientConsensusState( return err } - signBz, err := ConsensusStateSignBytes(cdc, sequence, signature.Timestamp, cs.ConsensusState.Diversifier, path, consensusState) + signBz, err := ConsensusStateSignBytes(cdc, sequence, timestamp, cs.ConsensusState.Diversifier, path, consensusState) if err != nil { return err } - if err := VerifySignature(cs.ConsensusState.GetPubKey(), signBz, signature.Signature); err != nil { + if err := VerifySignature(cs.ConsensusState.GetPubKey(), signBz, sigData); err != nil { return err } cs.Sequence++ - cs.ConsensusState.Timestamp = signature.Timestamp + cs.ConsensusState.Timestamp = timestamp setClientState(store, cdc, &cs) return nil } @@ -155,7 +156,7 @@ func (cs ClientState) VerifyConnectionState( connectionID string, connectionEnd exported.ConnectionI, ) error { - signature, sequence, err := produceVerificationArgs(cdc, cs, height, prefix, proof) + sigData, timestamp, sequence, err := produceVerificationArgs(cdc, cs, height, prefix, proof) if err != nil { return err } @@ -165,17 +166,17 @@ func (cs ClientState) VerifyConnectionState( return err } - signBz, err := ConnectionStateSignBytes(cdc, sequence, signature.Timestamp, cs.ConsensusState.Diversifier, path, connectionEnd) + signBz, err := ConnectionStateSignBytes(cdc, sequence, timestamp, cs.ConsensusState.Diversifier, path, connectionEnd) if err != nil { return err } - if err := VerifySignature(cs.ConsensusState.GetPubKey(), signBz, signature.Signature); err != nil { + if err := VerifySignature(cs.ConsensusState.GetPubKey(), signBz, sigData); err != nil { return err } cs.Sequence++ - cs.ConsensusState.Timestamp = signature.Timestamp + cs.ConsensusState.Timestamp = timestamp setClientState(store, cdc, &cs) return nil } @@ -192,7 +193,7 @@ func (cs ClientState) VerifyChannelState( channelID string, channel exported.ChannelI, ) error { - signature, sequence, err := produceVerificationArgs(cdc, cs, height, prefix, proof) + sigData, timestamp, sequence, err := produceVerificationArgs(cdc, cs, height, prefix, proof) if err != nil { return err } @@ -202,17 +203,17 @@ func (cs ClientState) VerifyChannelState( return err } - signBz, err := ChannelStateSignBytes(cdc, sequence, signature.Timestamp, cs.ConsensusState.Diversifier, path, channel) + signBz, err := ChannelStateSignBytes(cdc, sequence, timestamp, cs.ConsensusState.Diversifier, path, channel) if err != nil { return err } - if err := VerifySignature(cs.ConsensusState.GetPubKey(), signBz, signature.Signature); err != nil { + if err := VerifySignature(cs.ConsensusState.GetPubKey(), signBz, sigData); err != nil { return err } cs.Sequence++ - cs.ConsensusState.Timestamp = signature.Timestamp + cs.ConsensusState.Timestamp = timestamp setClientState(store, cdc, &cs) return nil } @@ -230,7 +231,7 @@ func (cs ClientState) VerifyPacketCommitment( packetSequence uint64, commitmentBytes []byte, ) error { - signature, sequence, err := produceVerificationArgs(cdc, cs, height, prefix, proof) + sigData, timestamp, sequence, err := produceVerificationArgs(cdc, cs, height, prefix, proof) if err != nil { return err } @@ -240,17 +241,17 @@ func (cs ClientState) VerifyPacketCommitment( return err } - signBz, err := PacketCommitmentSignBytes(cdc, sequence, signature.Timestamp, cs.ConsensusState.Diversifier, path, commitmentBytes) + signBz, err := PacketCommitmentSignBytes(cdc, sequence, timestamp, cs.ConsensusState.Diversifier, path, commitmentBytes) if err != nil { return err } - if err := VerifySignature(cs.ConsensusState.GetPubKey(), signBz, signature.Signature); err != nil { + if err := VerifySignature(cs.ConsensusState.GetPubKey(), signBz, sigData); err != nil { return err } cs.Sequence++ - cs.ConsensusState.Timestamp = signature.Timestamp + cs.ConsensusState.Timestamp = timestamp setClientState(store, cdc, &cs) return nil } @@ -268,7 +269,7 @@ func (cs ClientState) VerifyPacketAcknowledgement( packetSequence uint64, acknowledgement []byte, ) error { - signature, sequence, err := produceVerificationArgs(cdc, cs, height, prefix, proof) + sigData, timestamp, sequence, err := produceVerificationArgs(cdc, cs, height, prefix, proof) if err != nil { return err } @@ -278,17 +279,17 @@ func (cs ClientState) VerifyPacketAcknowledgement( return err } - signBz, err := PacketAcknowledgementSignBytes(cdc, sequence, signature.Timestamp, cs.ConsensusState.Diversifier, path, acknowledgement) + signBz, err := PacketAcknowledgementSignBytes(cdc, sequence, timestamp, cs.ConsensusState.Diversifier, path, acknowledgement) if err != nil { return err } - if err := VerifySignature(cs.ConsensusState.GetPubKey(), signBz, signature.Signature); err != nil { + if err := VerifySignature(cs.ConsensusState.GetPubKey(), signBz, sigData); err != nil { return err } cs.Sequence++ - cs.ConsensusState.Timestamp = signature.Timestamp + cs.ConsensusState.Timestamp = timestamp setClientState(store, cdc, &cs) return nil } @@ -306,7 +307,7 @@ func (cs ClientState) VerifyPacketAcknowledgementAbsence( channelID string, packetSequence uint64, ) error { - signature, sequence, err := produceVerificationArgs(cdc, cs, height, prefix, proof) + sigData, timestamp, sequence, err := produceVerificationArgs(cdc, cs, height, prefix, proof) if err != nil { return err } @@ -316,17 +317,17 @@ func (cs ClientState) VerifyPacketAcknowledgementAbsence( return err } - signBz, err := PacketAcknowledgementAbsenceSignBytes(cdc, sequence, signature.Timestamp, cs.ConsensusState.Diversifier, path) + signBz, err := PacketAcknowledgementAbsenceSignBytes(cdc, sequence, timestamp, cs.ConsensusState.Diversifier, path) if err != nil { return err } - if err := VerifySignature(cs.ConsensusState.GetPubKey(), signBz, signature.Signature); err != nil { + if err := VerifySignature(cs.ConsensusState.GetPubKey(), signBz, sigData); err != nil { return err } cs.Sequence++ - cs.ConsensusState.Timestamp = signature.Timestamp + cs.ConsensusState.Timestamp = timestamp setClientState(store, cdc, &cs) return nil } @@ -343,7 +344,7 @@ func (cs ClientState) VerifyNextSequenceRecv( channelID string, nextSequenceRecv uint64, ) error { - signature, sequence, err := produceVerificationArgs(cdc, cs, height, prefix, proof) + sigData, timestamp, sequence, err := produceVerificationArgs(cdc, cs, height, prefix, proof) if err != nil { return err } @@ -353,17 +354,17 @@ func (cs ClientState) VerifyNextSequenceRecv( return err } - signBz, err := NextSequenceRecvSignBytes(cdc, sequence, signature.Timestamp, cs.ConsensusState.Diversifier, path, nextSequenceRecv) + signBz, err := NextSequenceRecvSignBytes(cdc, sequence, timestamp, cs.ConsensusState.Diversifier, path, nextSequenceRecv) if err != nil { return err } - if err := VerifySignature(cs.ConsensusState.GetPubKey(), signBz, signature.Signature); err != nil { + if err := VerifySignature(cs.ConsensusState.GetPubKey(), signBz, sigData); err != nil { return err } cs.Sequence++ - cs.ConsensusState.Timestamp = signature.Timestamp + cs.ConsensusState.Timestamp = timestamp setClientState(store, cdc, &cs) return nil } @@ -378,50 +379,62 @@ func produceVerificationArgs( height exported.Height, prefix exported.Prefix, proof []byte, -) (signature TimestampedSignature, sequence uint64, err error) { +) (signing.SignatureData, uint64, uint64, error) { if epoch := height.GetEpochNumber(); epoch != 0 { - return TimestampedSignature{}, 0, sdkerrors.Wrapf(sdkerrors.ErrInvalidHeight, "epoch must be 0 for solomachine, got epoch-number: %d", epoch) + return nil, 0, 0, sdkerrors.Wrapf(sdkerrors.ErrInvalidHeight, "epoch must be 0 for solomachine, got epoch-number: %d", epoch) } // sequence is encoded in the epoch height of height struct - sequence = height.GetEpochHeight() + sequence := height.GetEpochHeight() if cs.IsFrozen() { - return TimestampedSignature{}, 0, clienttypes.ErrClientFrozen + return nil, 0, 0, clienttypes.ErrClientFrozen } if prefix == nil { - return TimestampedSignature{}, 0, sdkerrors.Wrap(commitmenttypes.ErrInvalidPrefix, "prefix cannot be empty") + return nil, 0, 0, sdkerrors.Wrap(commitmenttypes.ErrInvalidPrefix, "prefix cannot be empty") } _, ok := prefix.(commitmenttypes.MerklePrefix) if !ok { - return TimestampedSignature{}, 0, sdkerrors.Wrapf(commitmenttypes.ErrInvalidPrefix, "invalid prefix type %T, expected MerklePrefix", prefix) + return nil, 0, 0, sdkerrors.Wrapf(commitmenttypes.ErrInvalidPrefix, "invalid prefix type %T, expected MerklePrefix", prefix) } if proof == nil { - return TimestampedSignature{}, 0, sdkerrors.Wrap(commitmenttypes.ErrInvalidProof, "proof cannot be empty") + return nil, 0, 0, sdkerrors.Wrap(ErrInvalidProof, "proof cannot be empty") } - if err = cdc.UnmarshalBinaryBare(proof, &signature); err != nil { - return TimestampedSignature{}, 0, sdkerrors.Wrapf(ErrInvalidProof, "failed to unmarshal proof into type %T", TimestampedSignature{}) + timestampedSignature := &TimestampedSignature{} + if err := cdc.UnmarshalBinaryBare(proof, timestampedSignature); err != nil { + return nil, 0, 0, sdkerrors.Wrapf(err, "failed to unmarshal proof into type %T", timestampedSignature) + } + + timestamp := timestampedSignature.Timestamp + + if len(timestampedSignature.Signature) == 0 { + return nil, 0, 0, sdkerrors.Wrap(ErrInvalidProof, "signature data cannot be empty") + } + + sigData, err := UnmarshalSignatureData(cdc, timestampedSignature.Signature) + if err != nil { + return nil, 0, 0, err } if cs.ConsensusState == nil { - return TimestampedSignature{}, 0, sdkerrors.Wrap(clienttypes.ErrInvalidConsensus, "consensus state cannot be empty") + return nil, 0, 0, sdkerrors.Wrap(clienttypes.ErrInvalidConsensus, "consensus state cannot be empty") } latestSequence := cs.GetLatestHeight().GetEpochHeight() if latestSequence < sequence { - return TimestampedSignature{}, 0, sdkerrors.Wrapf( + return nil, 0, 0, sdkerrors.Wrapf( sdkerrors.ErrInvalidHeight, "client state sequence < proof sequence (%d < %d)", latestSequence, sequence, ) } - if cs.ConsensusState.GetTimestamp() > signature.Timestamp { - return TimestampedSignature{}, 0, sdkerrors.Wrapf(ErrInvalidProof, "the consensus state timestamp is greater than the signature timestamp (%d >= %d)", cs.ConsensusState.GetTimestamp(), signature.Timestamp) + if cs.ConsensusState.GetTimestamp() > timestamp { + return nil, 0, 0, sdkerrors.Wrapf(ErrInvalidProof, "the consensus state timestamp is greater than the signature timestamp (%d >= %d)", cs.ConsensusState.GetTimestamp(), timestamp) } - return signature, sequence, nil + return sigData, timestamp, sequence, nil } // sets the client state to the store diff --git a/x/ibc/light-clients/solomachine/types/client_state_test.go b/x/ibc/light-clients/solomachine/types/client_state_test.go index 50ef899bfd..56398db29e 100644 --- a/x/ibc/light-clients/solomachine/types/client_state_test.go +++ b/x/ibc/light-clients/solomachine/types/client_state_test.go @@ -24,56 +24,60 @@ var ( ) func (suite *SoloMachineTestSuite) TestClientStateValidateBasic() { - testCases := []struct { - name string - clientState *types.ClientState - expPass bool - }{ - { - "valid client state", - suite.solomachine.ClientState(), - true, - }, - { - "empty ClientState", - &types.ClientState{}, - false, - }, - { - "sequence is zero", - types.NewClientState(0, &types.ConsensusState{suite.solomachine.ConsensusState().PublicKey, suite.solomachine.Diversifier, suite.solomachine.Time}, false), - false, - }, - { - "timestamp is zero", - types.NewClientState(1, &types.ConsensusState{suite.solomachine.ConsensusState().PublicKey, suite.solomachine.Diversifier, 0}, false), - false, - }, - { - "diversifier is blank", - types.NewClientState(1, &types.ConsensusState{suite.solomachine.ConsensusState().PublicKey, " ", 1}, false), - false, - }, - { - "pubkey is empty", - types.NewClientState(1, &types.ConsensusState{nil, suite.solomachine.Diversifier, suite.solomachine.Time}, false), - false, - }, - } + // test singlesig and multisig public keys + for _, solomachine := range []*ibctesting.Solomachine{suite.solomachine, suite.solomachineMulti} { - for _, tc := range testCases { - tc := tc + testCases := []struct { + name string + clientState *types.ClientState + expPass bool + }{ + { + "valid client state", + solomachine.ClientState(), + true, + }, + { + "empty ClientState", + &types.ClientState{}, + false, + }, + { + "sequence is zero", + types.NewClientState(0, &types.ConsensusState{solomachine.ConsensusState().PublicKey, solomachine.Diversifier, solomachine.Time}, false), + false, + }, + { + "timestamp is zero", + types.NewClientState(1, &types.ConsensusState{solomachine.ConsensusState().PublicKey, solomachine.Diversifier, 0}, false), + false, + }, + { + "diversifier is blank", + types.NewClientState(1, &types.ConsensusState{solomachine.ConsensusState().PublicKey, " ", 1}, false), + false, + }, + { + "pubkey is empty", + types.NewClientState(1, &types.ConsensusState{nil, solomachine.Diversifier, solomachine.Time}, false), + false, + }, + } - suite.Run(tc.name, func() { + for _, tc := range testCases { + tc := tc - err := tc.clientState.Validate() + suite.Run(tc.name, func() { - if tc.expPass { - suite.Require().NoError(err) - } else { - suite.Require().Error(err) - } - }) + err := tc.clientState.Validate() + + if tc.expPass { + suite.Require().NoError(err) + } else { + suite.Require().Error(err) + } + }) + } } } @@ -86,120 +90,123 @@ func (suite *SoloMachineTestSuite) TestVerifyClientState() { path, err := commitmenttypes.ApplyPrefix(prefix, clientPrefixedPath) suite.Require().NoError(err) - value, err := types.ClientStateSignBytes(suite.chainA.Codec, suite.solomachine.Sequence, suite.solomachine.Time, suite.solomachine.Diversifier, path, clientState) - suite.Require().NoError(err) + // test singlesig and multisig public keys + for _, solomachine := range []*ibctesting.Solomachine{suite.solomachine, suite.solomachineMulti} { - sig, err := suite.solomachine.PrivateKey.Sign(value) - suite.Require().NoError(err) + value, err := types.ClientStateSignBytes(suite.chainA.Codec, solomachine.Sequence, solomachine.Time, solomachine.Diversifier, path, clientState) + suite.Require().NoError(err) - signatureDoc := &types.TimestampedSignature{ - Signature: sig, - Timestamp: suite.solomachine.Time, - } + sig := solomachine.GenerateSignature(value) - proof, err := suite.chainA.Codec.MarshalBinaryBare(signatureDoc) - suite.Require().NoError(err) + signatureDoc := &types.TimestampedSignature{ + Signature: sig, + Timestamp: solomachine.Time, + } - testCases := []struct { - name string - clientState *types.ClientState - prefix exported.Prefix - proof []byte - expPass bool - }{ - { - "successful verification", - suite.solomachine.ClientState(), - prefix, - proof, - true, - }, - { - "ApplyPrefix failed", - suite.solomachine.ClientState(), - nil, - proof, - false, - }, - { - "client is frozen", - &types.ClientState{ - Sequence: 1, - FrozenSequence: 1, - ConsensusState: suite.solomachine.ConsensusState(), - AllowUpdateAfterProposal: false, + proof, err := suite.chainA.Codec.MarshalBinaryBare(signatureDoc) + suite.Require().NoError(err) + + testCases := []struct { + name string + clientState *types.ClientState + prefix exported.Prefix + proof []byte + expPass bool + }{ + { + "successful verification", + solomachine.ClientState(), + prefix, + proof, + true, + }, + { + "ApplyPrefix failed", + solomachine.ClientState(), + nil, + proof, + false, + }, + { + "client is frozen", + &types.ClientState{ + Sequence: 1, + FrozenSequence: 1, + ConsensusState: solomachine.ConsensusState(), + AllowUpdateAfterProposal: false, + }, + prefix, + proof, + false, + }, + { + "consensus state in client state is nil", + types.NewClientState(1, nil, false), + prefix, + proof, + false, + }, + { + "client state latest height is less than sequence", + types.NewClientState(solomachine.Sequence-1, + &types.ConsensusState{ + Timestamp: solomachine.Time, + PublicKey: solomachine.ConsensusState().PublicKey, + }, false), + prefix, + proof, + false, + }, + { + "consensus state timestamp is greater than signature", + types.NewClientState(solomachine.Sequence, + &types.ConsensusState{ + Timestamp: solomachine.Time + 1, + PublicKey: solomachine.ConsensusState().PublicKey, + }, false), + prefix, + proof, + false, }, - prefix, - proof, - false, - }, - { - "consensus state in client state is nil", - types.NewClientState(1, nil, false), - prefix, - proof, - false, - }, - { - "client state latest height is less than sequence", - types.NewClientState(suite.solomachine.Sequence-1, - &types.ConsensusState{ - Timestamp: suite.solomachine.Time, - PublicKey: suite.solomachine.ConsensusState().PublicKey, - }, false), - prefix, - proof, - false, - }, - { - "consensus state timestamp is greater than signature", - types.NewClientState(suite.solomachine.Sequence, - &types.ConsensusState{ - Timestamp: suite.solomachine.Time + 1, - PublicKey: suite.solomachine.ConsensusState().PublicKey, - }, false), - prefix, - proof, - false, - }, - { - "proof is nil", - suite.solomachine.ClientState(), - prefix, - nil, - false, - }, - { - "proof verification failed", - suite.solomachine.ClientState(), - prefix, - suite.GetInvalidProof(), - false, - }, - } + { + "proof is nil", + solomachine.ClientState(), + prefix, + nil, + false, + }, + { + "proof verification failed", + solomachine.ClientState(), + prefix, + suite.GetInvalidProof(), + false, + }, + } - for _, tc := range testCases { - tc := tc + for _, tc := range testCases { + tc := tc - suite.Run(tc.name, func() { + suite.Run(tc.name, func() { - var expSeq uint64 - if tc.clientState.ConsensusState != nil { - expSeq = tc.clientState.Sequence + 1 - } + var expSeq uint64 + if tc.clientState.ConsensusState != nil { + expSeq = tc.clientState.Sequence + 1 + } - err := tc.clientState.VerifyClientState( - suite.store, suite.chainA.Codec, nil, suite.solomachine.GetHeight(), tc.prefix, counterpartyClientIdentifier, tc.proof, clientState, - ) + err := tc.clientState.VerifyClientState( + suite.store, suite.chainA.Codec, nil, solomachine.GetHeight(), tc.prefix, counterpartyClientIdentifier, tc.proof, clientState, + ) - if tc.expPass { - suite.Require().NoError(err) - suite.Require().Equal(expSeq, suite.GetSequenceFromStore(), "sequence not updated in the store (%d) on valid test case %s", suite.GetSequenceFromStore(), tc.name) - } else { - suite.Require().Error(err) - } - }) + if tc.expPass { + suite.Require().NoError(err) + suite.Require().Equal(expSeq, suite.GetSequenceFromStore(), "sequence not updated in the store (%d) on valid test case %s", suite.GetSequenceFromStore(), tc.name) + } else { + suite.Require().Error(err) + } + }) + } } } @@ -214,120 +221,122 @@ func (suite *SoloMachineTestSuite) TestVerifyClientConsensusState() { path, err := commitmenttypes.ApplyPrefix(prefix, clientPrefixedPath) suite.Require().NoError(err) - value, err := types.ConsensusStateSignBytes(suite.chainA.Codec, suite.solomachine.Sequence, suite.solomachine.Time, suite.solomachine.Diversifier, path, consensusState) - suite.Require().NoError(err) + // test singlesig and multisig public keys + for _, solomachine := range []*ibctesting.Solomachine{suite.solomachine, suite.solomachineMulti} { - sig, err := suite.solomachine.PrivateKey.Sign(value) - suite.Require().NoError(err) + value, err := types.ConsensusStateSignBytes(suite.chainA.Codec, solomachine.Sequence, solomachine.Time, solomachine.Diversifier, path, consensusState) + suite.Require().NoError(err) - signatureDoc := &types.TimestampedSignature{ - Signature: sig, - Timestamp: suite.solomachine.Time, - } + sig := solomachine.GenerateSignature(value) + signatureDoc := &types.TimestampedSignature{ + Signature: sig, + Timestamp: solomachine.Time, + } - proof, err := suite.chainA.Codec.MarshalBinaryBare(signatureDoc) - suite.Require().NoError(err) + proof, err := suite.chainA.Codec.MarshalBinaryBare(signatureDoc) + suite.Require().NoError(err) - testCases := []struct { - name string - clientState *types.ClientState - prefix exported.Prefix - proof []byte - expPass bool - }{ - { - "successful verification", - suite.solomachine.ClientState(), - prefix, - proof, - true, - }, - { - "ApplyPrefix failed", - suite.solomachine.ClientState(), - nil, - proof, - false, - }, - { - "client is frozen", - &types.ClientState{ - Sequence: 1, - FrozenSequence: 1, - ConsensusState: suite.solomachine.ConsensusState(), - AllowUpdateAfterProposal: false, + testCases := []struct { + name string + clientState *types.ClientState + prefix exported.Prefix + proof []byte + expPass bool + }{ + { + "successful verification", + solomachine.ClientState(), + prefix, + proof, + true, + }, + { + "ApplyPrefix failed", + solomachine.ClientState(), + nil, + proof, + false, + }, + { + "client is frozen", + &types.ClientState{ + Sequence: 1, + FrozenSequence: 1, + ConsensusState: solomachine.ConsensusState(), + AllowUpdateAfterProposal: false, + }, + prefix, + proof, + false, + }, + { + "consensus state in client state is nil", + types.NewClientState(1, nil, false), + prefix, + proof, + false, + }, + { + "client state latest height is less than sequence", + types.NewClientState(solomachine.Sequence-1, + &types.ConsensusState{ + Timestamp: solomachine.Time, + PublicKey: solomachine.ConsensusState().PublicKey, + }, false), + prefix, + proof, + false, + }, + { + "consensus state timestamp is greater than signature", + types.NewClientState(solomachine.Sequence, + &types.ConsensusState{ + Timestamp: solomachine.Time + 1, + PublicKey: solomachine.ConsensusState().PublicKey, + }, false), + prefix, + proof, + false, }, - prefix, - proof, - false, - }, - { - "consensus state in client state is nil", - types.NewClientState(1, nil, false), - prefix, - proof, - false, - }, - { - "client state latest height is less than sequence", - types.NewClientState(suite.solomachine.Sequence-1, - &types.ConsensusState{ - Timestamp: suite.solomachine.Time, - PublicKey: suite.solomachine.ConsensusState().PublicKey, - }, false), - prefix, - proof, - false, - }, - { - "consensus state timestamp is greater than signature", - types.NewClientState(suite.solomachine.Sequence, - &types.ConsensusState{ - Timestamp: suite.solomachine.Time + 1, - PublicKey: suite.solomachine.ConsensusState().PublicKey, - }, false), - prefix, - proof, - false, - }, - { - "proof is nil", - suite.solomachine.ClientState(), - prefix, - nil, - false, - }, - { - "proof verification failed", - suite.solomachine.ClientState(), - prefix, - suite.GetInvalidProof(), - false, - }, - } + { + "proof is nil", + solomachine.ClientState(), + prefix, + nil, + false, + }, + { + "proof verification failed", + solomachine.ClientState(), + prefix, + suite.GetInvalidProof(), + false, + }, + } - for _, tc := range testCases { - tc := tc + for _, tc := range testCases { + tc := tc - suite.Run(tc.name, func() { + suite.Run(tc.name, func() { - var expSeq uint64 - if tc.clientState.ConsensusState != nil { - expSeq = tc.clientState.Sequence + 1 - } + var expSeq uint64 + if tc.clientState.ConsensusState != nil { + expSeq = tc.clientState.Sequence + 1 + } - err := tc.clientState.VerifyClientConsensusState( - suite.store, suite.chainA.Codec, nil, suite.solomachine.GetHeight(), counterpartyClientIdentifier, consensusHeight, tc.prefix, tc.proof, consensusState, - ) + err := tc.clientState.VerifyClientConsensusState( + suite.store, suite.chainA.Codec, nil, solomachine.GetHeight(), counterpartyClientIdentifier, consensusHeight, tc.prefix, tc.proof, consensusState, + ) - if tc.expPass { - suite.Require().NoError(err) - suite.Require().Equal(expSeq, suite.GetSequenceFromStore(), "sequence not updated in the store (%d) on valid test case %s", suite.GetSequenceFromStore(), tc.name) - } else { - suite.Require().Error(err) - } - }) + if tc.expPass { + suite.Require().NoError(err) + suite.Require().Equal(expSeq, suite.GetSequenceFromStore(), "sequence not updated in the store (%d) on valid test case %s", suite.GetSequenceFromStore(), tc.name) + } else { + suite.Require().Error(err) + } + }) + } } } @@ -338,83 +347,85 @@ func (suite *SoloMachineTestSuite) TestVerifyConnectionState() { path, err := commitmenttypes.ApplyPrefix(prefix, host.ConnectionPath(testConnectionID)) suite.Require().NoError(err) - value, err := types.ConnectionStateSignBytes(suite.chainA.Codec, suite.solomachine.Sequence, suite.solomachine.Time, suite.solomachine.Diversifier, path, conn) - suite.Require().NoError(err) + // test singlesig and multisig public keys + for _, solomachine := range []*ibctesting.Solomachine{suite.solomachine, suite.solomachineMulti} { - sig, err := suite.solomachine.PrivateKey.Sign(value) - suite.Require().NoError(err) + value, err := types.ConnectionStateSignBytes(suite.chainA.Codec, solomachine.Sequence, solomachine.Time, solomachine.Diversifier, path, conn) + suite.Require().NoError(err) - signatureDoc := &types.TimestampedSignature{ - Signature: sig, - Timestamp: suite.solomachine.Time, - } + sig := solomachine.GenerateSignature(value) + signatureDoc := &types.TimestampedSignature{ + Signature: sig, + Timestamp: solomachine.Time, + } - proof, err := suite.chainA.Codec.MarshalBinaryBare(signatureDoc) - suite.Require().NoError(err) + proof, err := suite.chainA.Codec.MarshalBinaryBare(signatureDoc) + suite.Require().NoError(err) - testCases := []struct { - name string - clientState *types.ClientState - prefix exported.Prefix - proof []byte - expPass bool - }{ - { - "successful verification", - suite.solomachine.ClientState(), - prefix, - proof, - true, - }, - { - "ApplyPrefix failed", - suite.solomachine.ClientState(), - commitmenttypes.NewMerklePrefix([]byte{}), - proof, - false, - }, - { - "client is frozen", - &types.ClientState{ - Sequence: 1, - FrozenSequence: 1, - ConsensusState: suite.solomachine.ConsensusState(), - AllowUpdateAfterProposal: false, + testCases := []struct { + name string + clientState *types.ClientState + prefix exported.Prefix + proof []byte + expPass bool + }{ + { + "successful verification", + solomachine.ClientState(), + prefix, + proof, + true, }, - prefix, - proof, - false, - }, - { - "proof is nil", - suite.solomachine.ClientState(), - prefix, - nil, - false, - }, - { - "proof verification failed", - suite.solomachine.ClientState(), - prefix, - suite.GetInvalidProof(), - false, - }, - } + { + "ApplyPrefix failed", + solomachine.ClientState(), + commitmenttypes.NewMerklePrefix([]byte{}), + proof, + false, + }, + { + "client is frozen", + &types.ClientState{ + Sequence: 1, + FrozenSequence: 1, + ConsensusState: solomachine.ConsensusState(), + AllowUpdateAfterProposal: false, + }, + prefix, + proof, + false, + }, + { + "proof is nil", + solomachine.ClientState(), + prefix, + nil, + false, + }, + { + "proof verification failed", + solomachine.ClientState(), + prefix, + suite.GetInvalidProof(), + false, + }, + } - for i, tc := range testCases { - tc := tc + for i, tc := range testCases { + tc := tc - expSeq := tc.clientState.Sequence + 1 + expSeq := tc.clientState.Sequence + 1 - err := tc.clientState.VerifyConnectionState( - suite.store, suite.chainA.Codec, suite.solomachine.GetHeight(), tc.prefix, tc.proof, testConnectionID, conn, - ) + err := tc.clientState.VerifyConnectionState( + suite.store, suite.chainA.Codec, solomachine.GetHeight(), tc.prefix, tc.proof, testConnectionID, conn, + ) - if tc.expPass { - suite.Require().NoError(err, "valid test case %d failed: %s", i, tc.name) - suite.Require().Equal(expSeq, suite.GetSequenceFromStore(), "sequence not updated in the store (%d) on valid test case %d: %s", suite.GetSequenceFromStore(), i, tc.name) - } else { - suite.Require().Error(err, "invalid test case %d passed: %s", i, tc.name) + if tc.expPass { + suite.Require().NoError(err, "valid test case %d failed: %s", i, tc.name) + suite.Require().Equal(expSeq, suite.GetSequenceFromStore(), "sequence not updated in the store (%d) on valid test case %d: %s", suite.GetSequenceFromStore(), i, tc.name) + } else { + suite.Require().Error(err, "invalid test case %d passed: %s", i, tc.name) + } } } } @@ -426,426 +437,437 @@ func (suite *SoloMachineTestSuite) TestVerifyChannelState() { path, err := commitmenttypes.ApplyPrefix(prefix, host.ChannelPath(testPortID, testChannelID)) suite.Require().NoError(err) - value, err := types.ChannelStateSignBytes(suite.chainA.Codec, suite.solomachine.Sequence, suite.solomachine.Time, suite.solomachine.Diversifier, path, ch) - suite.Require().NoError(err) + // test singlesig and multisig public keys + for _, solomachine := range []*ibctesting.Solomachine{suite.solomachine, suite.solomachineMulti} { - sig, err := suite.solomachine.PrivateKey.Sign(value) - suite.Require().NoError(err) + value, err := types.ChannelStateSignBytes(suite.chainA.Codec, solomachine.Sequence, solomachine.Time, solomachine.Diversifier, path, ch) + suite.Require().NoError(err) - signatureDoc := &types.TimestampedSignature{ - Signature: sig, - Timestamp: suite.solomachine.Time, - } + sig := solomachine.GenerateSignature(value) + signatureDoc := &types.TimestampedSignature{ + Signature: sig, + Timestamp: solomachine.Time, + } - proof, err := suite.chainA.Codec.MarshalBinaryBare(signatureDoc) - suite.Require().NoError(err) + proof, err := suite.chainA.Codec.MarshalBinaryBare(signatureDoc) + suite.Require().NoError(err) - testCases := []struct { - name string - clientState *types.ClientState - prefix exported.Prefix - proof []byte - expPass bool - }{ - { - "successful verification", - suite.solomachine.ClientState(), - prefix, - proof, - true, - }, - { - "ApplyPrefix failed", - suite.solomachine.ClientState(), - nil, - proof, - false, - }, - { - "client is frozen", - &types.ClientState{ - Sequence: 1, - FrozenSequence: 1, - ConsensusState: suite.solomachine.ConsensusState(), - AllowUpdateAfterProposal: false, + testCases := []struct { + name string + clientState *types.ClientState + prefix exported.Prefix + proof []byte + expPass bool + }{ + { + "successful verification", + solomachine.ClientState(), + prefix, + proof, + true, }, - prefix, - proof, - false, - }, - { - "proof is nil", - suite.solomachine.ClientState(), - prefix, - nil, - false, - }, - { - "proof verification failed", - suite.solomachine.ClientState(), - prefix, - suite.GetInvalidProof(), - false, - }, - } + { + "ApplyPrefix failed", + solomachine.ClientState(), + nil, + proof, + false, + }, + { + "client is frozen", + &types.ClientState{ + Sequence: 1, + FrozenSequence: 1, + ConsensusState: solomachine.ConsensusState(), + AllowUpdateAfterProposal: false, + }, + prefix, + proof, + false, + }, + { + "proof is nil", + solomachine.ClientState(), + prefix, + nil, + false, + }, + { + "proof verification failed", + solomachine.ClientState(), + prefix, + suite.GetInvalidProof(), + false, + }, + } - for i, tc := range testCases { - tc := tc + for i, tc := range testCases { + tc := tc - expSeq := tc.clientState.Sequence + 1 + expSeq := tc.clientState.Sequence + 1 - err := tc.clientState.VerifyChannelState( - suite.store, suite.chainA.Codec, suite.solomachine.GetHeight(), tc.prefix, tc.proof, testPortID, testChannelID, ch, - ) + err := tc.clientState.VerifyChannelState( + suite.store, suite.chainA.Codec, solomachine.GetHeight(), tc.prefix, tc.proof, testPortID, testChannelID, ch, + ) - if tc.expPass { - suite.Require().NoError(err, "valid test case %d failed: %s", i, tc.name) - suite.Require().Equal(expSeq, suite.GetSequenceFromStore(), "sequence not updated in the store (%d) on valid test case %d: %s", suite.GetSequenceFromStore(), i, tc.name) - } else { - suite.Require().Error(err, "invalid test case %d passed: %s", i, tc.name) + if tc.expPass { + suite.Require().NoError(err, "valid test case %d failed: %s", i, tc.name) + suite.Require().Equal(expSeq, suite.GetSequenceFromStore(), "sequence not updated in the store (%d) on valid test case %d: %s", suite.GetSequenceFromStore(), i, tc.name) + } else { + suite.Require().Error(err, "invalid test case %d passed: %s", i, tc.name) + } } } } func (suite *SoloMachineTestSuite) TestVerifyPacketCommitment() { commitmentBytes := []byte("COMMITMENT BYTES") - path, err := commitmenttypes.ApplyPrefix(prefix, host.PacketCommitmentPath(testPortID, testChannelID, suite.solomachine.Sequence)) - suite.Require().NoError(err) - value, err := types.PacketCommitmentSignBytes(suite.chainA.Codec, suite.solomachine.Sequence, suite.solomachine.Time, suite.solomachine.Diversifier, path, commitmentBytes) - suite.Require().NoError(err) + // test singlesig and multisig public keys + for _, solomachine := range []*ibctesting.Solomachine{suite.solomachine, suite.solomachineMulti} { - sig, err := suite.solomachine.PrivateKey.Sign(value) - suite.Require().NoError(err) + path, err := commitmenttypes.ApplyPrefix(prefix, host.PacketCommitmentPath(testPortID, testChannelID, solomachine.Sequence)) + suite.Require().NoError(err) - signatureDoc := &types.TimestampedSignature{ - Signature: sig, - Timestamp: suite.solomachine.Time, - } + value, err := types.PacketCommitmentSignBytes(suite.chainA.Codec, solomachine.Sequence, solomachine.Time, solomachine.Diversifier, path, commitmentBytes) + suite.Require().NoError(err) - proof, err := suite.chainA.Codec.MarshalBinaryBare(signatureDoc) - suite.Require().NoError(err) + sig := solomachine.GenerateSignature(value) + signatureDoc := &types.TimestampedSignature{ + Signature: sig, + Timestamp: solomachine.Time, + } - testCases := []struct { - name string - clientState *types.ClientState - prefix exported.Prefix - proof []byte - expPass bool - }{ - { - "successful verification", - suite.solomachine.ClientState(), - prefix, - proof, - true, - }, - { - "ApplyPrefix failed", - suite.solomachine.ClientState(), - commitmenttypes.NewMerklePrefix([]byte{}), - proof, - false, - }, - { - "client is frozen", - &types.ClientState{ - Sequence: 1, - FrozenSequence: 1, - ConsensusState: suite.solomachine.ConsensusState(), - AllowUpdateAfterProposal: false, + proof, err := suite.chainA.Codec.MarshalBinaryBare(signatureDoc) + suite.Require().NoError(err) + + testCases := []struct { + name string + clientState *types.ClientState + prefix exported.Prefix + proof []byte + expPass bool + }{ + { + "successful verification", + solomachine.ClientState(), + prefix, + proof, + true, }, - prefix, - proof, - false, - }, - { - "proof is nil", - suite.solomachine.ClientState(), - prefix, - nil, - false, - }, - { - "proof verification failed", - suite.solomachine.ClientState(), - prefix, - suite.GetInvalidProof(), - false, - }, - } + { + "ApplyPrefix failed", + solomachine.ClientState(), + commitmenttypes.NewMerklePrefix([]byte{}), + proof, + false, + }, + { + "client is frozen", + &types.ClientState{ + Sequence: 1, + FrozenSequence: 1, + ConsensusState: solomachine.ConsensusState(), + AllowUpdateAfterProposal: false, + }, + prefix, + proof, + false, + }, + { + "proof is nil", + solomachine.ClientState(), + prefix, + nil, + false, + }, + { + "proof verification failed", + solomachine.ClientState(), + prefix, + suite.GetInvalidProof(), + false, + }, + } - for i, tc := range testCases { - tc := tc + for i, tc := range testCases { + tc := tc - expSeq := tc.clientState.Sequence + 1 + expSeq := tc.clientState.Sequence + 1 - err := tc.clientState.VerifyPacketCommitment( - suite.store, suite.chainA.Codec, suite.solomachine.GetHeight(), tc.prefix, tc.proof, testPortID, testChannelID, suite.solomachine.Sequence, commitmentBytes, - ) + err := tc.clientState.VerifyPacketCommitment( + suite.store, suite.chainA.Codec, solomachine.GetHeight(), tc.prefix, tc.proof, testPortID, testChannelID, solomachine.Sequence, commitmentBytes, + ) - if tc.expPass { - suite.Require().NoError(err, "valid test case %d failed: %s", i, tc.name) - suite.Require().Equal(expSeq, suite.GetSequenceFromStore(), "sequence not updated in the store (%d) on valid test case %d: %s", suite.GetSequenceFromStore(), i, tc.name) - } else { - suite.Require().Error(err, "invalid test case %d passed: %s", i, tc.name) + if tc.expPass { + suite.Require().NoError(err, "valid test case %d failed: %s", i, tc.name) + suite.Require().Equal(expSeq, suite.GetSequenceFromStore(), "sequence not updated in the store (%d) on valid test case %d: %s", suite.GetSequenceFromStore(), i, tc.name) + } else { + suite.Require().Error(err, "invalid test case %d passed: %s", i, tc.name) + } } } } func (suite *SoloMachineTestSuite) TestVerifyPacketAcknowledgement() { ack := []byte("ACK") - path, err := commitmenttypes.ApplyPrefix(prefix, host.PacketAcknowledgementPath(testPortID, testChannelID, suite.solomachine.Sequence)) - suite.Require().NoError(err) + // test singlesig and multisig public keys + for _, solomachine := range []*ibctesting.Solomachine{suite.solomachine, suite.solomachineMulti} { - value, err := types.PacketAcknowledgementSignBytes(suite.chainA.Codec, suite.solomachine.Sequence, suite.solomachine.Time, suite.solomachine.Diversifier, path, ack) - suite.Require().NoError(err) + path, err := commitmenttypes.ApplyPrefix(prefix, host.PacketAcknowledgementPath(testPortID, testChannelID, solomachine.Sequence)) + suite.Require().NoError(err) - sig, err := suite.solomachine.PrivateKey.Sign(value) - suite.Require().NoError(err) + value, err := types.PacketAcknowledgementSignBytes(suite.chainA.Codec, solomachine.Sequence, solomachine.Time, solomachine.Diversifier, path, ack) + suite.Require().NoError(err) - signatureDoc := &types.TimestampedSignature{ - Signature: sig, - Timestamp: suite.solomachine.Time, - } + sig := solomachine.GenerateSignature(value) + signatureDoc := &types.TimestampedSignature{ + Signature: sig, + Timestamp: solomachine.Time, + } - proof, err := suite.chainA.Codec.MarshalBinaryBare(signatureDoc) - suite.Require().NoError(err) + proof, err := suite.chainA.Codec.MarshalBinaryBare(signatureDoc) + suite.Require().NoError(err) - testCases := []struct { - name string - clientState *types.ClientState - prefix exported.Prefix - proof []byte - expPass bool - }{ - { - "successful verification", - suite.solomachine.ClientState(), - prefix, - proof, - true, - }, - { - "ApplyPrefix failed", - suite.solomachine.ClientState(), - commitmenttypes.NewMerklePrefix([]byte{}), - proof, - false, - }, - { - "client is frozen", - &types.ClientState{ - Sequence: 1, - FrozenSequence: 1, - ConsensusState: suite.solomachine.ConsensusState(), - AllowUpdateAfterProposal: false, + testCases := []struct { + name string + clientState *types.ClientState + prefix exported.Prefix + proof []byte + expPass bool + }{ + { + "successful verification", + solomachine.ClientState(), + prefix, + proof, + true, }, - prefix, - proof, - false, - }, - { - "proof is nil", - suite.solomachine.ClientState(), - prefix, - nil, - false, - }, - { - "proof verification failed", - suite.solomachine.ClientState(), - prefix, - suite.GetInvalidProof(), - false, - }, - } + { + "ApplyPrefix failed", + solomachine.ClientState(), + commitmenttypes.NewMerklePrefix([]byte{}), + proof, + false, + }, + { + "client is frozen", + &types.ClientState{ + Sequence: 1, + FrozenSequence: 1, + ConsensusState: solomachine.ConsensusState(), + AllowUpdateAfterProposal: false, + }, + prefix, + proof, + false, + }, + { + "proof is nil", + solomachine.ClientState(), + prefix, + nil, + false, + }, + { + "proof verification failed", + solomachine.ClientState(), + prefix, + suite.GetInvalidProof(), + false, + }, + } - for i, tc := range testCases { - tc := tc + for i, tc := range testCases { + tc := tc - expSeq := tc.clientState.Sequence + 1 + expSeq := tc.clientState.Sequence + 1 - err := tc.clientState.VerifyPacketAcknowledgement( - suite.store, suite.chainA.Codec, suite.solomachine.GetHeight(), tc.prefix, tc.proof, testPortID, testChannelID, suite.solomachine.Sequence, ack, - ) + err := tc.clientState.VerifyPacketAcknowledgement( + suite.store, suite.chainA.Codec, solomachine.GetHeight(), tc.prefix, tc.proof, testPortID, testChannelID, solomachine.Sequence, ack, + ) - if tc.expPass { - suite.Require().NoError(err, "valid test case %d failed: %s", i, tc.name) - suite.Require().Equal(expSeq, suite.GetSequenceFromStore(), "sequence not updated in the store (%d) on valid test case %d: %s", suite.GetSequenceFromStore(), i, tc.name) - } else { - suite.Require().Error(err, "invalid test case %d passed: %s", i, tc.name) + if tc.expPass { + suite.Require().NoError(err, "valid test case %d failed: %s", i, tc.name) + suite.Require().Equal(expSeq, suite.GetSequenceFromStore(), "sequence not updated in the store (%d) on valid test case %d: %s", suite.GetSequenceFromStore(), i, tc.name) + } else { + suite.Require().Error(err, "invalid test case %d passed: %s", i, tc.name) + } } } } func (suite *SoloMachineTestSuite) TestVerifyPacketAcknowledgementAbsence() { - path, err := commitmenttypes.ApplyPrefix(prefix, host.PacketAcknowledgementPath(testPortID, testChannelID, suite.solomachine.Sequence)) - suite.Require().NoError(err) + // test singlesig and multisig public keys + for _, solomachine := range []*ibctesting.Solomachine{suite.solomachine, suite.solomachineMulti} { - value, err := types.PacketAcknowledgementAbsenceSignBytes(suite.chainA.Codec, suite.solomachine.Sequence, suite.solomachine.Time, suite.solomachine.Diversifier, path) - suite.Require().NoError(err) + path, err := commitmenttypes.ApplyPrefix(prefix, host.PacketAcknowledgementPath(testPortID, testChannelID, solomachine.Sequence)) + suite.Require().NoError(err) - sig, err := suite.solomachine.PrivateKey.Sign(value) - suite.Require().NoError(err) + value, err := types.PacketAcknowledgementAbsenceSignBytes(suite.chainA.Codec, solomachine.Sequence, solomachine.Time, solomachine.Diversifier, path) + suite.Require().NoError(err) - signatureDoc := &types.TimestampedSignature{ - Signature: sig, - Timestamp: suite.solomachine.Time, - } + sig := solomachine.GenerateSignature(value) + signatureDoc := &types.TimestampedSignature{ + Signature: sig, + Timestamp: solomachine.Time, + } - proof, err := suite.chainA.Codec.MarshalBinaryBare(signatureDoc) - suite.Require().NoError(err) + proof, err := suite.chainA.Codec.MarshalBinaryBare(signatureDoc) + suite.Require().NoError(err) - testCases := []struct { - name string - clientState *types.ClientState - prefix exported.Prefix - proof []byte - expPass bool - }{ - { - "successful verification", - suite.solomachine.ClientState(), - prefix, - proof, - true, - }, - { - "ApplyPrefix failed", - suite.solomachine.ClientState(), - commitmenttypes.NewMerklePrefix([]byte{}), - proof, - false, - }, - { - "client is frozen", - &types.ClientState{ - Sequence: 1, - FrozenSequence: 1, - ConsensusState: suite.solomachine.ConsensusState(), - AllowUpdateAfterProposal: false, + testCases := []struct { + name string + clientState *types.ClientState + prefix exported.Prefix + proof []byte + expPass bool + }{ + { + "successful verification", + solomachine.ClientState(), + prefix, + proof, + true, }, - prefix, - proof, - false, - }, - { - "proof is nil", - suite.solomachine.ClientState(), - prefix, - nil, - false, - }, - { - "proof verification failed", - suite.solomachine.ClientState(), - prefix, - suite.GetInvalidProof(), - false, - }, - } + { + "ApplyPrefix failed", + solomachine.ClientState(), + commitmenttypes.NewMerklePrefix([]byte{}), + proof, + false, + }, + { + "client is frozen", + &types.ClientState{ + Sequence: 1, + FrozenSequence: 1, + ConsensusState: solomachine.ConsensusState(), + AllowUpdateAfterProposal: false, + }, + prefix, + proof, + false, + }, + { + "proof is nil", + solomachine.ClientState(), + prefix, + nil, + false, + }, + { + "proof verification failed", + solomachine.ClientState(), + prefix, + suite.GetInvalidProof(), + false, + }, + } - for i, tc := range testCases { - tc := tc + for i, tc := range testCases { + tc := tc - expSeq := tc.clientState.Sequence + 1 + expSeq := tc.clientState.Sequence + 1 - err := tc.clientState.VerifyPacketAcknowledgementAbsence( - suite.store, suite.chainA.Codec, suite.solomachine.GetHeight(), tc.prefix, tc.proof, testPortID, testChannelID, suite.solomachine.Sequence, - ) + err := tc.clientState.VerifyPacketAcknowledgementAbsence( + suite.store, suite.chainA.Codec, solomachine.GetHeight(), tc.prefix, tc.proof, testPortID, testChannelID, solomachine.Sequence, + ) - if tc.expPass { - suite.Require().NoError(err, "valid test case %d failed: %s", i, tc.name) - suite.Require().Equal(expSeq, suite.GetSequenceFromStore(), "sequence not updated in the store (%d) on valid test case %d: %s", suite.GetSequenceFromStore(), i, tc.name) - } else { - suite.Require().Error(err, "invalid test case %d passed: %s", i, tc.name) + if tc.expPass { + suite.Require().NoError(err, "valid test case %d failed: %s", i, tc.name) + suite.Require().Equal(expSeq, suite.GetSequenceFromStore(), "sequence not updated in the store (%d) on valid test case %d: %s", suite.GetSequenceFromStore(), i, tc.name) + } else { + suite.Require().Error(err, "invalid test case %d passed: %s", i, tc.name) + } } } } func (suite *SoloMachineTestSuite) TestVerifyNextSeqRecv() { - nextSeqRecv := suite.solomachine.Sequence + 1 - path, err := commitmenttypes.ApplyPrefix(prefix, host.NextSequenceRecvPath(testPortID, testChannelID)) - suite.Require().NoError(err) + // test singlesig and multisig public keys + for _, solomachine := range []*ibctesting.Solomachine{suite.solomachine, suite.solomachineMulti} { - value, err := types.NextSequenceRecvSignBytes(suite.chainA.Codec, suite.solomachine.Sequence, suite.solomachine.Time, suite.solomachine.Diversifier, path, nextSeqRecv) - suite.Require().NoError(err) + nextSeqRecv := solomachine.Sequence + 1 + path, err := commitmenttypes.ApplyPrefix(prefix, host.NextSequenceRecvPath(testPortID, testChannelID)) + suite.Require().NoError(err) - sig, err := suite.solomachine.PrivateKey.Sign(value) - suite.Require().NoError(err) + value, err := types.NextSequenceRecvSignBytes(suite.chainA.Codec, solomachine.Sequence, solomachine.Time, solomachine.Diversifier, path, nextSeqRecv) + suite.Require().NoError(err) - signatureDoc := &types.TimestampedSignature{ - Signature: sig, - Timestamp: suite.solomachine.Time, - } + sig := solomachine.GenerateSignature(value) + signatureDoc := &types.TimestampedSignature{ + Signature: sig, + Timestamp: solomachine.Time, + } - proof, err := suite.chainA.Codec.MarshalBinaryBare(signatureDoc) - suite.Require().NoError(err) + proof, err := suite.chainA.Codec.MarshalBinaryBare(signatureDoc) + suite.Require().NoError(err) - testCases := []struct { - name string - clientState *types.ClientState - prefix exported.Prefix - proof []byte - expPass bool - }{ - { - "successful verification", - suite.solomachine.ClientState(), - prefix, - proof, - true, - }, - { - "ApplyPrefix failed", - suite.solomachine.ClientState(), - commitmenttypes.NewMerklePrefix([]byte{}), - proof, - false, - }, - { - "client is frozen", - &types.ClientState{ - Sequence: 1, - FrozenSequence: 1, - ConsensusState: suite.solomachine.ConsensusState(), - AllowUpdateAfterProposal: false, + testCases := []struct { + name string + clientState *types.ClientState + prefix exported.Prefix + proof []byte + expPass bool + }{ + { + "successful verification", + solomachine.ClientState(), + prefix, + proof, + true, }, - prefix, - proof, - false, - }, - { - "proof is nil", - suite.solomachine.ClientState(), - prefix, - nil, - false, - }, - { - "proof verification failed", - suite.solomachine.ClientState(), - prefix, - suite.GetInvalidProof(), - false, - }, - } + { + "ApplyPrefix failed", + solomachine.ClientState(), + commitmenttypes.NewMerklePrefix([]byte{}), + proof, + false, + }, + { + "client is frozen", + &types.ClientState{ + Sequence: 1, + FrozenSequence: 1, + ConsensusState: solomachine.ConsensusState(), + AllowUpdateAfterProposal: false, + }, + prefix, + proof, + false, + }, + { + "proof is nil", + solomachine.ClientState(), + prefix, + nil, + false, + }, + { + "proof verification failed", + solomachine.ClientState(), + prefix, + suite.GetInvalidProof(), + false, + }, + } - for i, tc := range testCases { - tc := tc + for i, tc := range testCases { + tc := tc - expSeq := tc.clientState.Sequence + 1 + expSeq := tc.clientState.Sequence + 1 - err := tc.clientState.VerifyNextSequenceRecv( - suite.store, suite.chainA.Codec, suite.solomachine.GetHeight(), tc.prefix, tc.proof, testPortID, testChannelID, nextSeqRecv, - ) + err := tc.clientState.VerifyNextSequenceRecv( + suite.store, suite.chainA.Codec, solomachine.GetHeight(), tc.prefix, tc.proof, testPortID, testChannelID, nextSeqRecv, + ) - if tc.expPass { - suite.Require().NoError(err, "valid test case %d failed: %s", i, tc.name) - suite.Require().Equal(expSeq, suite.GetSequenceFromStore(), "sequence not updated in the store (%d) on valid test case %d: %s", suite.GetSequenceFromStore(), i, tc.name) - } else { - suite.Require().Error(err, "invalid test case %d passed: %s", i, tc.name) + if tc.expPass { + suite.Require().NoError(err, "valid test case %d failed: %s", i, tc.name) + suite.Require().Equal(expSeq, suite.GetSequenceFromStore(), "sequence not updated in the store (%d) on valid test case %d: %s", suite.GetSequenceFromStore(), i, tc.name) + } else { + suite.Require().Error(err, "invalid test case %d passed: %s", i, tc.name) + } } } } diff --git a/x/ibc/light-clients/solomachine/types/codec.go b/x/ibc/light-clients/solomachine/types/codec.go index 49401bd55e..71a66954e2 100644 --- a/x/ibc/light-clients/solomachine/types/codec.go +++ b/x/ibc/light-clients/solomachine/types/codec.go @@ -3,6 +3,8 @@ package types import ( "github.com/cosmos/cosmos-sdk/codec" codectypes "github.com/cosmos/cosmos-sdk/codec/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + "github.com/cosmos/cosmos-sdk/types/tx/signing" "github.com/cosmos/cosmos-sdk/x/ibc/exported" ) @@ -29,9 +31,20 @@ func RegisterInterfaces(registry codectypes.InterfaceRegistry) { var ( // SubModuleCdc references the global x/ibc/light-clients/solomachine module codec. Note, the codec - // should ONLY be used in certain instances of tests and for JSON encoding.. + // should ONLY be used in certain instances of tests and for JSON encoding. // // The actual codec used for serialization should be provided to x/ibc/light-clients/solomachine and // defined at the application level. SubModuleCdc = codec.NewProtoCodec(codectypes.NewInterfaceRegistry()) ) + +func UnmarshalSignatureData(cdc codec.BinaryMarshaler, data []byte) (signing.SignatureData, error) { + protoSigData := &signing.SignatureDescriptor_Data{} + if err := cdc.UnmarshalBinaryBare(data, protoSigData); err != nil { + return nil, sdkerrors.Wrapf(err, "failed to unmarshal proof into type %T", protoSigData) + } + + sigData := signing.SignatureDataFromProto(protoSigData) + + return sigData, nil +} diff --git a/x/ibc/light-clients/solomachine/types/consensus_state_test.go b/x/ibc/light-clients/solomachine/types/consensus_state_test.go index 8b091f29a0..3742ad9ba5 100644 --- a/x/ibc/light-clients/solomachine/types/consensus_state_test.go +++ b/x/ibc/light-clients/solomachine/types/consensus_state_test.go @@ -2,6 +2,7 @@ package types_test import ( "github.com/cosmos/cosmos-sdk/x/ibc/light-clients/solomachine/types" + ibctesting "github.com/cosmos/cosmos-sdk/x/ibc/testing" ) func (suite *SoloMachineTestSuite) TestConsensusState() { @@ -13,57 +14,61 @@ func (suite *SoloMachineTestSuite) TestConsensusState() { } func (suite *SoloMachineTestSuite) TestConsensusStateValidateBasic() { - testCases := []struct { - name string - consensusState *types.ConsensusState - expPass bool - }{ - { - "valid consensus state", - suite.solomachine.ConsensusState(), - true, - }, - { - "timestamp is zero", - &types.ConsensusState{ - PublicKey: suite.solomachine.ConsensusState().PublicKey, - Timestamp: 0, - Diversifier: suite.solomachine.Diversifier, + // test singlesig and multisig public keys + for _, solomachine := range []*ibctesting.Solomachine{suite.solomachine, suite.solomachineMulti} { + + testCases := []struct { + name string + consensusState *types.ConsensusState + expPass bool + }{ + { + "valid consensus state", + solomachine.ConsensusState(), + true, }, - false, - }, - { - "diversifier is blank", - &types.ConsensusState{ - PublicKey: suite.solomachine.ConsensusState().PublicKey, - Timestamp: suite.solomachine.Time, - Diversifier: " ", + { + "timestamp is zero", + &types.ConsensusState{ + PublicKey: solomachine.ConsensusState().PublicKey, + Timestamp: 0, + Diversifier: solomachine.Diversifier, + }, + false, }, - false, - }, - { - "pubkey is nil", - &types.ConsensusState{ - Timestamp: suite.solomachine.Time, - Diversifier: suite.solomachine.Diversifier, - PublicKey: nil, + { + "diversifier is blank", + &types.ConsensusState{ + PublicKey: solomachine.ConsensusState().PublicKey, + Timestamp: solomachine.Time, + Diversifier: " ", + }, + false, }, - false, - }, - } + { + "pubkey is nil", + &types.ConsensusState{ + Timestamp: solomachine.Time, + Diversifier: solomachine.Diversifier, + PublicKey: nil, + }, + false, + }, + } - for _, tc := range testCases { - tc := tc + for _, tc := range testCases { + tc := tc - suite.Run(tc.name, func() { + suite.Run(tc.name, func() { - err := tc.consensusState.ValidateBasic() + err := tc.consensusState.ValidateBasic() - if tc.expPass { - suite.Require().NoError(err) - } else { - suite.Require().Error(err) - } - }) + if tc.expPass { + suite.Require().NoError(err) + } else { + suite.Require().Error(err) + } + }) + } } } diff --git a/x/ibc/light-clients/solomachine/types/header_test.go b/x/ibc/light-clients/solomachine/types/header_test.go index 6659dfe36f..aebf8267c2 100644 --- a/x/ibc/light-clients/solomachine/types/header_test.go +++ b/x/ibc/light-clients/solomachine/types/header_test.go @@ -2,91 +2,96 @@ package types_test import ( "github.com/cosmos/cosmos-sdk/x/ibc/light-clients/solomachine/types" + ibctesting "github.com/cosmos/cosmos-sdk/x/ibc/testing" ) func (suite *SoloMachineTestSuite) TestHeaderValidateBasic() { - header := suite.solomachine.CreateHeader() + // test singlesig and multisig public keys + for _, solomachine := range []*ibctesting.Solomachine{suite.solomachine, suite.solomachineMulti} { - cases := []struct { - name string - header *types.Header - expPass bool - }{ - { - "valid header", - header, - true, - }, - { - "sequence is zero", - &types.Header{ - Sequence: 0, - Timestamp: header.Timestamp, - Signature: header.Signature, - NewPublicKey: header.NewPublicKey, - NewDiversifier: header.NewDiversifier, - }, - false, - }, - { - "timestamp is zero", - &types.Header{ - Sequence: header.Sequence, - Timestamp: 0, - Signature: header.Signature, - NewPublicKey: header.NewPublicKey, - NewDiversifier: header.NewDiversifier, - }, - false, - }, - { - "signature is empty", - &types.Header{ - Sequence: header.Sequence, - Timestamp: header.Timestamp, - Signature: []byte{}, - NewPublicKey: header.NewPublicKey, - NewDiversifier: header.NewDiversifier, - }, - false, - }, - { - "diversifier contains only spaces", - &types.Header{ - Sequence: header.Sequence, - Timestamp: header.Timestamp, - Signature: header.Signature, - NewPublicKey: header.NewPublicKey, - NewDiversifier: " ", - }, - false, - }, - { - "public key is nil", - &types.Header{ - Sequence: header.Sequence, - Timestamp: header.Timestamp, - Signature: header.Signature, - NewPublicKey: nil, - NewDiversifier: header.NewDiversifier, - }, - false, - }, - } + header := solomachine.CreateHeader() - suite.Require().Equal(types.SoloMachine, header.ClientType()) + cases := []struct { + name string + header *types.Header + expPass bool + }{ + { + "valid header", + header, + true, + }, + { + "sequence is zero", + &types.Header{ + Sequence: 0, + Timestamp: header.Timestamp, + Signature: header.Signature, + NewPublicKey: header.NewPublicKey, + NewDiversifier: header.NewDiversifier, + }, + false, + }, + { + "timestamp is zero", + &types.Header{ + Sequence: header.Sequence, + Timestamp: 0, + Signature: header.Signature, + NewPublicKey: header.NewPublicKey, + NewDiversifier: header.NewDiversifier, + }, + false, + }, + { + "signature is empty", + &types.Header{ + Sequence: header.Sequence, + Timestamp: header.Timestamp, + Signature: []byte{}, + NewPublicKey: header.NewPublicKey, + NewDiversifier: header.NewDiversifier, + }, + false, + }, + { + "diversifier contains only spaces", + &types.Header{ + Sequence: header.Sequence, + Timestamp: header.Timestamp, + Signature: header.Signature, + NewPublicKey: header.NewPublicKey, + NewDiversifier: " ", + }, + false, + }, + { + "public key is nil", + &types.Header{ + Sequence: header.Sequence, + Timestamp: header.Timestamp, + Signature: header.Signature, + NewPublicKey: nil, + NewDiversifier: header.NewDiversifier, + }, + false, + }, + } - for _, tc := range cases { - tc := tc + suite.Require().Equal(types.SoloMachine, header.ClientType()) - suite.Run(tc.name, func() { - err := tc.header.ValidateBasic() + for _, tc := range cases { + tc := tc - if tc.expPass { - suite.Require().NoError(err) - } else { - suite.Require().Error(err) - } - }) + suite.Run(tc.name, func() { + err := tc.header.ValidateBasic() + + if tc.expPass { + suite.Require().NoError(err) + } else { + suite.Require().Error(err) + } + }) + } } } diff --git a/x/ibc/light-clients/solomachine/types/misbehaviour_handle.go b/x/ibc/light-clients/solomachine/types/misbehaviour_handle.go index b34af7ff29..d6ae193c74 100644 --- a/x/ibc/light-clients/solomachine/types/misbehaviour_handle.go +++ b/x/ibc/light-clients/solomachine/types/misbehaviour_handle.go @@ -56,8 +56,13 @@ func checkMisbehaviour(cdc codec.BinaryMarshaler, clientState ClientState, soloM return err } + sigData, err := UnmarshalSignatureData(cdc, soloMisbehaviour.SignatureOne.Signature) + if err != nil { + return err + } + // check first signature - if err := VerifySignature(pubKey, data, soloMisbehaviour.SignatureOne.Signature); err != nil { + if err := VerifySignature(pubKey, data, sigData); err != nil { return sdkerrors.Wrap(err, "misbehaviour signature one failed to be verified") } @@ -71,8 +76,13 @@ func checkMisbehaviour(cdc codec.BinaryMarshaler, clientState ClientState, soloM return err } + sigData, err = UnmarshalSignatureData(cdc, soloMisbehaviour.SignatureTwo.Signature) + if err != nil { + return err + } + // check second signature - if err := VerifySignature(pubKey, data, soloMisbehaviour.SignatureTwo.Signature); err != nil { + if err := VerifySignature(pubKey, data, sigData); err != nil { return sdkerrors.Wrap(err, "misbehaviour signature two failed to be verified") } diff --git a/x/ibc/light-clients/solomachine/types/misbehaviour_handle_test.go b/x/ibc/light-clients/solomachine/types/misbehaviour_handle_test.go index 80bd6d115a..686dea5319 100644 --- a/x/ibc/light-clients/solomachine/types/misbehaviour_handle_test.go +++ b/x/ibc/light-clients/solomachine/types/misbehaviour_handle_test.go @@ -4,6 +4,7 @@ import ( ibctmtypes "github.com/cosmos/cosmos-sdk/x/ibc/07-tendermint/types" "github.com/cosmos/cosmos-sdk/x/ibc/exported" "github.com/cosmos/cosmos-sdk/x/ibc/light-clients/solomachine/types" + ibctesting "github.com/cosmos/cosmos-sdk/x/ibc/testing" ) func (suite *SoloMachineTestSuite) TestCheckMisbehaviourAndUpdateState() { @@ -12,162 +13,186 @@ func (suite *SoloMachineTestSuite) TestCheckMisbehaviourAndUpdateState() { misbehaviour exported.Misbehaviour ) - testCases := []struct { - name string - setup func() - expPass bool - }{ - { - "valid misbehaviour", - func() { - clientState = suite.solomachine.ClientState() - misbehaviour = suite.solomachine.CreateMisbehaviour() - }, - true, - }, - { - "client is frozen", - func() { - cs := suite.solomachine.ClientState() - cs.FrozenSequence = 1 - clientState = cs - misbehaviour = suite.solomachine.CreateMisbehaviour() - }, - false, - }, - { - "wrong client state type", - func() { - clientState = &ibctmtypes.ClientState{} - misbehaviour = suite.solomachine.CreateMisbehaviour() - }, - false, - }, - { - "invalid misbehaviour type", - func() { - clientState = suite.solomachine.ClientState() - misbehaviour = ibctmtypes.Misbehaviour{} - }, - false, - }, - { - "invalid first signature", - func() { - clientState = suite.solomachine.ClientState() + // test singlesig and multisig public keys + for _, solomachine := range []*ibctesting.Solomachine{suite.solomachine, suite.solomachineMulti} { - // store in temp before assigning to interface type - m := suite.solomachine.CreateMisbehaviour() + testCases := []struct { + name string + setup func() + expPass bool + }{ + { + "valid misbehaviour", + func() { + clientState = solomachine.ClientState() + misbehaviour = solomachine.CreateMisbehaviour() + }, + true, + }, + { + "client is frozen", + func() { + cs := solomachine.ClientState() + cs.FrozenSequence = 1 + clientState = cs + misbehaviour = solomachine.CreateMisbehaviour() + }, + false, + }, + { + "wrong client state type", + func() { + clientState = &ibctmtypes.ClientState{} + misbehaviour = solomachine.CreateMisbehaviour() + }, + false, + }, + { + "invalid misbehaviour type", + func() { + clientState = solomachine.ClientState() + misbehaviour = ibctmtypes.Misbehaviour{} + }, + false, + }, + { + "invalid SignatureOne signature", + func() { + clientState = solomachine.ClientState() + m := solomachine.CreateMisbehaviour() - msg := []byte("DATA ONE") - signBytes := &types.SignBytes{ - Sequence: suite.solomachine.Sequence + 1, - Data: msg, + m.SignatureOne.Signature = suite.GetInvalidProof() + misbehaviour = m + }, false, + }, + { + "invalid SignatureTwo signature", + func() { + clientState = solomachine.ClientState() + m := solomachine.CreateMisbehaviour() + + m.SignatureTwo.Signature = suite.GetInvalidProof() + misbehaviour = m + }, false, + }, + { + "invalid first signature", + func() { + clientState = solomachine.ClientState() + + // store in temp before assigning to interface type + m := solomachine.CreateMisbehaviour() + + msg := []byte("DATA ONE") + signBytes := &types.SignBytes{ + Sequence: solomachine.Sequence + 1, + Timestamp: solomachine.Time, + Diversifier: solomachine.Diversifier, + Data: msg, + } + + data, err := suite.chainA.Codec.MarshalBinaryBare(signBytes) + suite.Require().NoError(err) + + sig := solomachine.GenerateSignature(data) + + m.SignatureOne.Signature = sig + m.SignatureOne.Data = msg + misbehaviour = m + }, + false, + }, + { + "invalid second signature", + func() { + clientState = solomachine.ClientState() + + // store in temp before assigning to interface type + m := solomachine.CreateMisbehaviour() + + msg := []byte("DATA TWO") + signBytes := &types.SignBytes{ + Sequence: solomachine.Sequence + 1, + Timestamp: solomachine.Time, + Diversifier: solomachine.Diversifier, + Data: msg, + } + + data, err := suite.chainA.Codec.MarshalBinaryBare(signBytes) + suite.Require().NoError(err) + + sig := solomachine.GenerateSignature(data) + + m.SignatureTwo.Signature = sig + m.SignatureTwo.Data = msg + misbehaviour = m + }, + false, + }, + { + "signatures sign over different sequence", + func() { + clientState = solomachine.ClientState() + + // store in temp before assigning to interface type + m := solomachine.CreateMisbehaviour() + + // Signature One + msg := []byte("DATA ONE") + // sequence used is plus 1 + signBytes := &types.SignBytes{ + Sequence: solomachine.Sequence + 1, + Data: msg, + } + + data, err := suite.chainA.Codec.MarshalBinaryBare(signBytes) + suite.Require().NoError(err) + + sig := solomachine.GenerateSignature(data) + + m.SignatureOne.Signature = sig + m.SignatureOne.Data = msg + + // Signature Two + msg = []byte("DATA TWO") + // sequence used is minus 1 + + signBytes = &types.SignBytes{ + Sequence: solomachine.Sequence - 1, + Data: msg, + } + data, err = suite.chainA.Codec.MarshalBinaryBare(signBytes) + suite.Require().NoError(err) + + sig = solomachine.GenerateSignature(data) + + m.SignatureTwo.Signature = sig + m.SignatureTwo.Data = msg + + misbehaviour = m + + }, + false, + }, + } + + for _, tc := range testCases { + tc := tc + + suite.Run(tc.name, func() { + // setup test + tc.setup() + + clientState, err := clientState.CheckMisbehaviourAndUpdateState(suite.chainA.GetContext(), suite.chainA.App.AppCodec(), suite.store, misbehaviour) + + if tc.expPass { + suite.Require().NoError(err) + suite.Require().True(clientState.IsFrozen(), "client not frozen") + } else { + suite.Require().Error(err) + suite.Require().Nil(clientState) } - - data, err := suite.chainA.Codec.MarshalBinaryBare(signBytes) - suite.Require().NoError(err) - - sig, err := suite.solomachine.PrivateKey.Sign(data) - suite.Require().NoError(err) - - m.SignatureOne.Signature = sig - m.SignatureOne.Data = msg - misbehaviour = m - }, - false, - }, - { - "invalid second signature", - func() { - clientState = suite.solomachine.ClientState() - - // store in temp before assigning to interface type - m := suite.solomachine.CreateMisbehaviour() - - msg := []byte("DATA TWO") - signBytes := &types.SignBytes{ - Sequence: suite.solomachine.Sequence + 1, - Data: msg, - } - - data, err := suite.chainA.Codec.MarshalBinaryBare(signBytes) - suite.Require().NoError(err) - - sig, err := suite.solomachine.PrivateKey.Sign(data) - suite.Require().NoError(err) - - m.SignatureTwo.Signature = sig - m.SignatureTwo.Data = msg - misbehaviour = m - }, - false, - }, - { - "signatures sign over different sequence", - func() { - clientState = suite.solomachine.ClientState() - - // store in temp before assigning to interface type - m := suite.solomachine.CreateMisbehaviour() - - // Signature One - msg := []byte("DATA ONE") - // sequence used is plus 1 - signBytes := &types.SignBytes{ - Sequence: suite.solomachine.Sequence + 1, - Data: msg, - } - - data, err := suite.chainA.Codec.MarshalBinaryBare(signBytes) - suite.Require().NoError(err) - - sig, err := suite.solomachine.PrivateKey.Sign(data) - suite.Require().NoError(err) - - m.SignatureOne.Signature = sig - m.SignatureOne.Data = msg - - // Signature Two - msg = []byte("DATA TWO") - // sequence used is minus 1 - - signBytes = &types.SignBytes{ - Sequence: suite.solomachine.Sequence - 1, - Data: msg, - } - data, err = suite.chainA.Codec.MarshalBinaryBare(signBytes) - suite.Require().NoError(err) - - sig, err = suite.solomachine.PrivateKey.Sign(data) - suite.Require().NoError(err) - - m.SignatureTwo.Signature = sig - m.SignatureTwo.Data = msg - - misbehaviour = m - - }, - false, - }, - } - - for _, tc := range testCases { - tc := tc - - suite.Run(tc.name, func() { - // setup test - tc.setup() - - clientState, err := clientState.CheckMisbehaviourAndUpdateState(suite.chainA.GetContext(), suite.chainA.App.AppCodec(), suite.store, misbehaviour) - - if tc.expPass { - suite.Require().NoError(err) - suite.Require().True(clientState.IsFrozen(), "client not frozen") - } else { - suite.Require().Error(err) - suite.Require().Nil(clientState) - } - }) + }) + } } } diff --git a/x/ibc/light-clients/solomachine/types/misbehaviour_test.go b/x/ibc/light-clients/solomachine/types/misbehaviour_test.go index 4d1be66ad6..c2af23fd7e 100644 --- a/x/ibc/light-clients/solomachine/types/misbehaviour_test.go +++ b/x/ibc/light-clients/solomachine/types/misbehaviour_test.go @@ -2,6 +2,7 @@ package types_test import ( "github.com/cosmos/cosmos-sdk/x/ibc/light-clients/solomachine/types" + ibctesting "github.com/cosmos/cosmos-sdk/x/ibc/testing" ) func (suite *SoloMachineTestSuite) TestMisbehaviour() { @@ -14,89 +15,93 @@ func (suite *SoloMachineTestSuite) TestMisbehaviour() { } func (suite *SoloMachineTestSuite) TestMisbehaviourValidateBasic() { - testCases := []struct { - name string - malleateMisbehaviour func(misbehaviour *types.Misbehaviour) - expPass bool - }{ - { - "valid misbehaviour", - func(*types.Misbehaviour) {}, - true, - }, - { - "invalid client ID", - func(misbehaviour *types.Misbehaviour) { - misbehaviour.ClientId = "(badclientid)" - }, - false, - }, - { - "sequence is zero", - func(misbehaviour *types.Misbehaviour) { - misbehaviour.Sequence = 0 - }, - false, - }, - { - "signature one sig is empty", - func(misbehaviour *types.Misbehaviour) { - misbehaviour.SignatureOne.Signature = []byte{} - }, - false, - }, - { - "signature two sig is empty", - func(misbehaviour *types.Misbehaviour) { - misbehaviour.SignatureTwo.Signature = []byte{} - }, - false, - }, - { - "signature one data is empty", - func(misbehaviour *types.Misbehaviour) { - misbehaviour.SignatureOne.Data = nil - }, - false, - }, - { - "signature two data is empty", - func(misbehaviour *types.Misbehaviour) { - misbehaviour.SignatureTwo.Data = []byte{} - }, - false, - }, - { - "signatures are identical", - func(misbehaviour *types.Misbehaviour) { - misbehaviour.SignatureTwo.Signature = misbehaviour.SignatureOne.Signature - }, - false, - }, - { - "data signed is identical", - func(misbehaviour *types.Misbehaviour) { - misbehaviour.SignatureTwo.Data = misbehaviour.SignatureOne.Data - }, - false, - }, - } + // test singlesig and multisig public keys + for _, solomachine := range []*ibctesting.Solomachine{suite.solomachine, suite.solomachineMulti} { - for _, tc := range testCases { - tc := tc + testCases := []struct { + name string + malleateMisbehaviour func(misbehaviour *types.Misbehaviour) + expPass bool + }{ + { + "valid misbehaviour", + func(*types.Misbehaviour) {}, + true, + }, + { + "invalid client ID", + func(misbehaviour *types.Misbehaviour) { + misbehaviour.ClientId = "(badclientid)" + }, + false, + }, + { + "sequence is zero", + func(misbehaviour *types.Misbehaviour) { + misbehaviour.Sequence = 0 + }, + false, + }, + { + "signature one sig is empty", + func(misbehaviour *types.Misbehaviour) { + misbehaviour.SignatureOne.Signature = []byte{} + }, + false, + }, + { + "signature two sig is empty", + func(misbehaviour *types.Misbehaviour) { + misbehaviour.SignatureTwo.Signature = []byte{} + }, + false, + }, + { + "signature one data is empty", + func(misbehaviour *types.Misbehaviour) { + misbehaviour.SignatureOne.Data = nil + }, + false, + }, + { + "signature two data is empty", + func(misbehaviour *types.Misbehaviour) { + misbehaviour.SignatureTwo.Data = []byte{} + }, + false, + }, + { + "signatures are identical", + func(misbehaviour *types.Misbehaviour) { + misbehaviour.SignatureTwo.Signature = misbehaviour.SignatureOne.Signature + }, + false, + }, + { + "data signed is identical", + func(misbehaviour *types.Misbehaviour) { + misbehaviour.SignatureTwo.Data = misbehaviour.SignatureOne.Data + }, + false, + }, + } - suite.Run(tc.name, func() { + for _, tc := range testCases { + tc := tc - misbehaviour := suite.solomachine.CreateMisbehaviour() - tc.malleateMisbehaviour(misbehaviour) + suite.Run(tc.name, func() { - err := misbehaviour.ValidateBasic() + misbehaviour := solomachine.CreateMisbehaviour() + tc.malleateMisbehaviour(misbehaviour) - if tc.expPass { - suite.Require().NoError(err) - } else { - suite.Require().Error(err) - } - }) + err := misbehaviour.ValidateBasic() + + if tc.expPass { + suite.Require().NoError(err) + } else { + suite.Require().Error(err) + } + }) + } } } diff --git a/x/ibc/light-clients/solomachine/types/proof.go b/x/ibc/light-clients/solomachine/types/proof.go index d13dfc0a8c..ea9303ef92 100644 --- a/x/ibc/light-clients/solomachine/types/proof.go +++ b/x/ibc/light-clients/solomachine/types/proof.go @@ -4,7 +4,9 @@ import ( "github.com/tendermint/tendermint/crypto" "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/crypto/types/multisig" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + "github.com/cosmos/cosmos-sdk/types/tx/signing" clienttypes "github.com/cosmos/cosmos-sdk/x/ibc/02-client/types" connectiontypes "github.com/cosmos/cosmos-sdk/x/ibc/03-connection/types" channeltypes "github.com/cosmos/cosmos-sdk/x/ibc/04-channel/types" @@ -13,10 +15,33 @@ import ( ) // VerifySignature verifies if the the provided public key generated the signature -// over the given data. -func VerifySignature(pubKey crypto.PubKey, data, signature []byte) error { - if !pubKey.VerifySignature(data, signature) { - return ErrSignatureVerificationFailed +// over the given data. Single and Multi signature public keys are supported. +// The type of the signature data determines how the public key is used to +// verify the signature. An error is returned if signature verification fails +// or an invalid SignatureData type is provided. +func VerifySignature(pubKey crypto.PubKey, signBytes []byte, sigData signing.SignatureData) error { + switch data := sigData.(type) { + case *signing.SingleSignatureData: + if !pubKey.VerifySignature(signBytes, data.Signature) { + return ErrSignatureVerificationFailed + } + + case *signing.MultiSignatureData: + multiPK, ok := pubKey.(multisig.PubKey) + if !ok { + return sdkerrors.Wrapf(ErrSignatureVerificationFailed, "invalid pubkey type: expected %T, got %T", (multisig.PubKey)(nil), pubKey) + } + + // The function supplied fulfills the VerifyMultisignature interface. No special + // adjustments need to be made to the sign bytes based on the sign mode. + if err := multiPK.VerifyMultisignature(func(signing.SignMode) ([]byte, error) { + return signBytes, nil + }, data); err != nil { + return err + } + + default: + return sdkerrors.Wrapf(ErrSignatureVerificationFailed, "unsupported signature data type %T", data) } return nil diff --git a/x/ibc/light-clients/solomachine/types/proposal_handle_test.go b/x/ibc/light-clients/solomachine/types/proposal_handle_test.go index b7d02be058..ee65eba97d 100644 --- a/x/ibc/light-clients/solomachine/types/proposal_handle_test.go +++ b/x/ibc/light-clients/solomachine/types/proposal_handle_test.go @@ -4,77 +4,81 @@ import ( ibctmtypes "github.com/cosmos/cosmos-sdk/x/ibc/07-tendermint/types" "github.com/cosmos/cosmos-sdk/x/ibc/exported" "github.com/cosmos/cosmos-sdk/x/ibc/light-clients/solomachine/types" + ibctesting "github.com/cosmos/cosmos-sdk/x/ibc/testing" ) func (suite *SoloMachineTestSuite) TestCheckProposedHeaderAndUpdateState() { var header exported.Header - testCases := []struct { - name string - malleate func() - expPass bool - }{ - { - "valid header", func() { - header = suite.solomachine.CreateHeader() - }, true, - }, - { - "nil header", func() { - header = &ibctmtypes.Header{} - }, false, - }, - { - "header does not update public key", func() { - header = &types.Header{ - Sequence: 1, - NewPublicKey: suite.solomachine.ConsensusState().PublicKey, - } - }, false, - }, - } + // test singlesig and multisig public keys + for _, solomachine := range []*ibctesting.Solomachine{suite.solomachine, suite.solomachineMulti} { - for _, tc := range testCases { - tc := tc + testCases := []struct { + name string + malleate func() + expPass bool + }{ + { + "valid header", func() { + header = solomachine.CreateHeader() + }, true, + }, + { + "nil header", func() { + header = &ibctmtypes.Header{} + }, false, + }, + { + "header does not update public key", func() { + header = &types.Header{ + Sequence: 1, + NewPublicKey: solomachine.ConsensusState().PublicKey, + } + }, false, + }, + } - suite.Run(tc.name, func() { - suite.SetupTest() + for _, tc := range testCases { + tc := tc - clientState := suite.solomachine.ClientState() + suite.Run(tc.name, func() { + suite.SetupTest() - tc.malleate() + clientState := solomachine.ClientState() - clientStore := suite.chainA.App.IBCKeeper.ClientKeeper.ClientStore(suite.chainA.GetContext(), suite.solomachine.ClientID) + tc.malleate() - // all cases should always fail if the client has 'AllowUpdateAfterProposal' set to false - clientState.AllowUpdateAfterProposal = false - cs, consState, err := clientState.CheckProposedHeaderAndUpdateState(suite.chainA.GetContext(), suite.chainA.App.AppCodec(), clientStore, header) - suite.Require().Error(err) - suite.Require().Nil(cs) - suite.Require().Nil(consState) + clientStore := suite.chainA.App.IBCKeeper.ClientKeeper.ClientStore(suite.chainA.GetContext(), solomachine.ClientID) - clientState.AllowUpdateAfterProposal = true - cs, consState, err = clientState.CheckProposedHeaderAndUpdateState(suite.chainA.GetContext(), suite.chainA.App.AppCodec(), clientStore, header) - - if tc.expPass { - suite.Require().NoError(err) - - smConsState, ok := consState.(*types.ConsensusState) - suite.Require().True(ok) - smHeader, ok := header.(*types.Header) - suite.Require().True(ok) - - suite.Require().Equal(cs.(*types.ClientState).ConsensusState, consState) - suite.Require().Equal(smHeader.GetPubKey(), smConsState.GetPubKey()) - suite.Require().Equal(smHeader.NewDiversifier, smConsState.Diversifier) - suite.Require().Equal(smHeader.Timestamp, smConsState.Timestamp) - suite.Require().Equal(smHeader.GetHeight().GetEpochHeight(), cs.(*types.ClientState).Sequence) - } else { + // all cases should always fail if the client has 'AllowUpdateAfterProposal' set to false + clientState.AllowUpdateAfterProposal = false + cs, consState, err := clientState.CheckProposedHeaderAndUpdateState(suite.chainA.GetContext(), suite.chainA.App.AppCodec(), clientStore, header) suite.Require().Error(err) suite.Require().Nil(cs) suite.Require().Nil(consState) - } - }) - } + clientState.AllowUpdateAfterProposal = true + cs, consState, err = clientState.CheckProposedHeaderAndUpdateState(suite.chainA.GetContext(), suite.chainA.App.AppCodec(), clientStore, header) + + if tc.expPass { + suite.Require().NoError(err) + + smConsState, ok := consState.(*types.ConsensusState) + suite.Require().True(ok) + smHeader, ok := header.(*types.Header) + suite.Require().True(ok) + + suite.Require().Equal(cs.(*types.ClientState).ConsensusState, consState) + suite.Require().Equal(smHeader.GetPubKey(), smConsState.GetPubKey()) + suite.Require().Equal(smHeader.NewDiversifier, smConsState.Diversifier) + suite.Require().Equal(smHeader.Timestamp, smConsState.Timestamp) + suite.Require().Equal(smHeader.GetHeight().GetEpochHeight(), cs.(*types.ClientState).Sequence) + } else { + suite.Require().Error(err) + suite.Require().Nil(cs) + suite.Require().Nil(consState) + } + }) + } + } } diff --git a/x/ibc/light-clients/solomachine/types/solomachine_test.go b/x/ibc/light-clients/solomachine/types/solomachine_test.go index 331ed2e71e..a2708cf91b 100644 --- a/x/ibc/light-clients/solomachine/types/solomachine_test.go +++ b/x/ibc/light-clients/solomachine/types/solomachine_test.go @@ -22,8 +22,9 @@ import ( type SoloMachineTestSuite struct { suite.Suite - solomachine *ibctesting.Solomachine - coordinator *ibctesting.Coordinator + solomachine *ibctesting.Solomachine // singlesig public key + solomachineMulti *ibctesting.Solomachine // multisig public key + coordinator *ibctesting.Coordinator // testing chain used for convenience and readability chainA *ibctesting.TestChain @@ -37,12 +38,10 @@ func (suite *SoloMachineTestSuite) SetupTest() { suite.chainA = suite.coordinator.GetChain(ibctesting.GetChainID(0)) suite.chainB = suite.coordinator.GetChain(ibctesting.GetChainID(1)) - suite.solomachine = ibctesting.NewSolomachine(suite.T(), suite.chainA.Codec, "testingsolomachine", "testing") - suite.store = suite.chainA.App.IBCKeeper.ClientKeeper.ClientStore(suite.chainA.GetContext(), types.SoloMachine) + suite.solomachine = ibctesting.NewSolomachine(suite.T(), suite.chainA.Codec, "solomachinesingle", "testing", 1) + suite.solomachineMulti = ibctesting.NewSolomachine(suite.T(), suite.chainA.Codec, "solomachinemulti", "testing", 4) - bz, err := codec.MarshalAny(suite.chainA.Codec, suite.solomachine.ClientState()) - suite.Require().NoError(err) - suite.store.Set(host.KeyClientState(), bz) + suite.store = suite.chainA.App.IBCKeeper.ClientKeeper.ClientStore(suite.chainA.GetContext(), types.SoloMachine) } func TestSoloMachineTestSuite(t *testing.T) { diff --git a/x/ibc/light-clients/solomachine/types/update.go b/x/ibc/light-clients/solomachine/types/update.go index 066b2baab9..e6fe938be0 100644 --- a/x/ibc/light-clients/solomachine/types/update.go +++ b/x/ibc/light-clients/solomachine/types/update.go @@ -57,7 +57,12 @@ func checkHeader(cdc codec.BinaryMarshaler, clientState *ClientState, header *He return err } - if err := VerifySignature(clientState.ConsensusState.GetPubKey(), data, header.Signature); err != nil { + sigData, err := UnmarshalSignatureData(cdc, header.Signature) + if err != nil { + return err + } + + if err := VerifySignature(clientState.ConsensusState.GetPubKey(), data, sigData); err != nil { return sdkerrors.Wrap(ErrInvalidHeader, err.Error()) } diff --git a/x/ibc/light-clients/solomachine/types/update_test.go b/x/ibc/light-clients/solomachine/types/update_test.go index f07ad69f51..a7cdc066da 100644 --- a/x/ibc/light-clients/solomachine/types/update_test.go +++ b/x/ibc/light-clients/solomachine/types/update_test.go @@ -6,6 +6,7 @@ import ( ibctmtypes "github.com/cosmos/cosmos-sdk/x/ibc/07-tendermint/types" "github.com/cosmos/cosmos-sdk/x/ibc/exported" "github.com/cosmos/cosmos-sdk/x/ibc/light-clients/solomachine/types" + ibctesting "github.com/cosmos/cosmos-sdk/x/ibc/testing" ) func (suite *SoloMachineTestSuite) TestCheckHeaderAndUpdateState() { @@ -14,144 +15,156 @@ func (suite *SoloMachineTestSuite) TestCheckHeaderAndUpdateState() { header exported.Header ) - testCases := []struct { - name string - setup func() - expPass bool - }{ - { - "successful update", - func() { - clientState = suite.solomachine.ClientState() - header = suite.solomachine.CreateHeader() - }, - true, - }, - { - "wrong client state type", - func() { - clientState = &ibctmtypes.ClientState{} - header = suite.solomachine.CreateHeader() - }, - false, - }, - { - "invalid header type", - func() { - clientState = suite.solomachine.ClientState() - header = &ibctmtypes.Header{} - }, - false, - }, - { - "wrong sequence in header", - func() { - clientState = suite.solomachine.ClientState() - // store in temp before assigning to interface type - h := suite.solomachine.CreateHeader() - h.Sequence++ - header = h - }, - false, - }, - { - "invalid timestamp in header", - func() { - clientState = suite.solomachine.ClientState() - h := suite.solomachine.CreateHeader() - h.Timestamp-- - header = h - }, false, - }, - { - "signature uses wrong sequence", - func() { - clientState = suite.solomachine.ClientState() - suite.solomachine.Sequence++ - header = suite.solomachine.CreateHeader() - }, - false, - }, - { - "signature uses new pubkey to sign", - func() { - // store in temp before assinging to interface type - cs := suite.solomachine.ClientState() - h := suite.solomachine.CreateHeader() + // test singlesig and multisig public keys + for _, solomachine := range []*ibctesting.Solomachine{suite.solomachine, suite.solomachineMulti} { - publicKey, err := tx.PubKeyToAny(suite.solomachine.PublicKey) - suite.NoError(err) + testCases := []struct { + name string + setup func() + expPass bool + }{ + { + "successful update", + func() { + clientState = solomachine.ClientState() + header = solomachine.CreateHeader() + }, + true, + }, + { + "wrong client state type", + func() { + clientState = &ibctmtypes.ClientState{} + header = solomachine.CreateHeader() + }, + false, + }, + { + "invalid header type", + func() { + clientState = solomachine.ClientState() + header = &ibctmtypes.Header{} + }, + false, + }, + { + "wrong sequence in header", + func() { + clientState = solomachine.ClientState() + // store in temp before assigning to interface type + h := solomachine.CreateHeader() + h.Sequence++ + header = h + }, + false, + }, + { + "invalid header Signature", + func() { + clientState = solomachine.ClientState() + h := solomachine.CreateHeader() + h.Signature = suite.GetInvalidProof() + header = h + }, false, + }, + { + "invalid timestamp in header", + func() { + clientState = solomachine.ClientState() + h := solomachine.CreateHeader() + h.Timestamp-- + header = h + }, false, + }, + { + "signature uses wrong sequence", + func() { + clientState = solomachine.ClientState() + solomachine.Sequence++ + header = solomachine.CreateHeader() + }, + false, + }, + { + "signature uses new pubkey to sign", + func() { + // store in temp before assinging to interface type + cs := solomachine.ClientState() + h := solomachine.CreateHeader() - data := &types.HeaderData{ - NewPubKey: publicKey, - NewDiversifier: h.NewDiversifier, + publicKey, err := tx.PubKeyToAny(solomachine.PublicKey) + suite.NoError(err) + + data := &types.HeaderData{ + NewPubKey: publicKey, + NewDiversifier: h.NewDiversifier, + } + + dataBz, err := suite.chainA.Codec.MarshalBinaryBare(data) + suite.Require().NoError(err) + + // generate invalid signature + signBytes := &types.SignBytes{ + Sequence: cs.Sequence, + Timestamp: solomachine.Time, + Diversifier: solomachine.Diversifier, + Data: dataBz, + } + + signBz, err := suite.chainA.Codec.MarshalBinaryBare(signBytes) + suite.Require().NoError(err) + + sig := solomachine.GenerateSignature(signBz) + suite.Require().NoError(err) + h.Signature = sig + + clientState = cs + header = h + + }, + false, + }, + { + "signature signs over old pubkey", + func() { + // store in temp before assinging to interface type + cs := solomachine.ClientState() + oldPubKey := solomachine.PublicKey + h := solomachine.CreateHeader() + + // generate invalid signature + data := append(sdk.Uint64ToBigEndian(cs.Sequence), oldPubKey.Bytes()...) + sig := solomachine.GenerateSignature(data) + h.Signature = sig + + clientState = cs + header = h + }, + false, + }, + } + + for _, tc := range testCases { + tc := tc + + suite.Run(tc.name, func() { + // setup test + tc.setup() + + clientState, consensusState, err := clientState.CheckHeaderAndUpdateState(suite.chainA.GetContext(), suite.chainA.Codec, suite.store, header) + + if tc.expPass { + suite.Require().NoError(err) + suite.Require().Equal(header.(*types.Header).NewPublicKey, clientState.(*types.ClientState).ConsensusState.PublicKey) + suite.Require().Equal(uint64(0), clientState.(*types.ClientState).FrozenSequence) + suite.Require().Equal(header.(*types.Header).Sequence+1, clientState.(*types.ClientState).Sequence) + suite.Require().Equal(consensusState, clientState.(*types.ClientState).ConsensusState) + } else { + suite.Require().Error(err) + suite.Require().Nil(clientState) + suite.Require().Nil(consensusState) } - - dataBz, err := suite.chainA.Codec.MarshalBinaryBare(data) - suite.Require().NoError(err) - - // generate invalid signature - signBytes := &types.SignBytes{ - Sequence: cs.Sequence, - Timestamp: suite.solomachine.Time, - Diversifier: suite.solomachine.Diversifier, - Data: dataBz, - } - - signBz, err := suite.chainA.Codec.MarshalBinaryBare(signBytes) - suite.Require().NoError(err) - - sig, err := suite.solomachine.PrivateKey.Sign(signBz) - suite.Require().NoError(err) - h.Signature = sig - - clientState = cs - header = h - - }, - false, - }, - { - "signature signs over old pubkey", - func() { - // store in temp before assinging to interface type - cs := suite.solomachine.ClientState() - oldPrivKey := suite.solomachine.PrivateKey - h := suite.solomachine.CreateHeader() - - // generate invalid signature - data := append(sdk.Uint64ToBigEndian(cs.Sequence), oldPrivKey.PubKey().Bytes()...) - sig, err := oldPrivKey.Sign(data) - suite.Require().NoError(err) - h.Signature = sig - - clientState = cs - header = h - }, - false, - }, - } - - for _, tc := range testCases { - tc := tc - - suite.Run(tc.name, func() { - // setup test - tc.setup() - - clientState, consensusState, err := clientState.CheckHeaderAndUpdateState(suite.chainA.GetContext(), suite.chainA.Codec, suite.store, header) - - if tc.expPass { - suite.Require().NoError(err) - suite.Require().Equal(header.(*types.Header).NewPublicKey, clientState.(*types.ClientState).ConsensusState.PublicKey) - suite.Require().Equal(uint64(0), clientState.(*types.ClientState).FrozenSequence) - suite.Require().Equal(header.(*types.Header).Sequence+1, clientState.(*types.ClientState).Sequence) - suite.Require().Equal(consensusState, clientState.(*types.ClientState).ConsensusState) - } else { - suite.Require().Error(err) - suite.Require().Nil(clientState) - suite.Require().Nil(consensusState) - } - }) + }) + } } } diff --git a/x/ibc/testing/chain.go b/x/ibc/testing/chain.go index fc83372602..e7e80d04c9 100644 --- a/x/ibc/testing/chain.go +++ b/x/ibc/testing/chain.go @@ -396,7 +396,7 @@ func (chain *TestChain) ConstructMsgCreateClient(counterparty *TestChain, client ) consensusState = counterparty.LastHeader.ConsensusState() case SoloMachine: - solo := NewSolomachine(chain.t, chain.Codec, clientID, "") + solo := NewSolomachine(chain.t, chain.Codec, clientID, "", 1) clientState = solo.ClientState() consensusState = solo.ConsensusState() default: diff --git a/x/ibc/testing/solomachine.go b/x/ibc/testing/solomachine.go index 547ab5a91a..ed8cbec5c1 100644 --- a/x/ibc/testing/solomachine.go +++ b/x/ibc/testing/solomachine.go @@ -7,7 +7,10 @@ import ( "github.com/tendermint/tendermint/crypto" "github.com/cosmos/cosmos-sdk/codec" + kmultisig "github.com/cosmos/cosmos-sdk/crypto/keys/multisig" "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" + "github.com/cosmos/cosmos-sdk/crypto/types/multisig" + "github.com/cosmos/cosmos-sdk/types/tx/signing" "github.com/cosmos/cosmos-sdk/x/auth/tx" clienttypes "github.com/cosmos/cosmos-sdk/x/ibc/02-client/types" "github.com/cosmos/cosmos-sdk/x/ibc/exported" @@ -21,30 +24,62 @@ type Solomachine struct { cdc codec.BinaryMarshaler ClientID string - PrivateKey crypto.PrivKey - PublicKey crypto.PubKey + PrivateKeys []crypto.PrivKey // keys used for signing + PublicKeys []crypto.PubKey // keys used for generating solo machine pub key + PublicKey crypto.PubKey // key used for verification Sequence uint64 Time uint64 Diversifier string } -// NewSolomachine returns a new solomachine instance with a generated private/public -// key pair and a sequence starting at 1. -func NewSolomachine(t *testing.T, cdc codec.BinaryMarshaler, clientID, diversifier string) *Solomachine { - privKey := secp256k1.GenPrivKey() +// NewSolomachine returns a new solomachine instance with an `nKeys` amount of +// generated private/public key pairs and a sequence starting at 1. If nKeys +// is greater than 1 then a multisig public key is used. +func NewSolomachine(t *testing.T, cdc codec.BinaryMarshaler, clientID, diversifier string, nKeys uint64) *Solomachine { + privKeys, pubKeys, pk := GenerateKeys(t, nKeys) return &Solomachine{ t: t, cdc: cdc, ClientID: clientID, - PrivateKey: privKey, - PublicKey: privKey.PubKey(), + PrivateKeys: privKeys, + PublicKeys: pubKeys, + PublicKey: pk, Sequence: 1, Time: 10, Diversifier: diversifier, } } +// GenerateKeys generates a new set of secp256k1 private keys and public keys. +// If the number of keys is greater than one then the public key returned represents +// a multisig public key. The private keys are used for signing, the public +// keys are used for generating the public key and the public key is used for +// solo machine verification. The usage of secp256k1 is entirely arbitrary. +// The key type can be swapped for any key type supported by the PublicKey +// interface, if needed. The same is true for the amino based Multisignature +// public key. +func GenerateKeys(t *testing.T, n uint64) ([]crypto.PrivKey, []crypto.PubKey, crypto.PubKey) { + require.NotEqual(t, uint64(0), n, "generation of zero keys is not allowed") + + privKeys := make([]crypto.PrivKey, n) + pubKeys := make([]crypto.PubKey, n) + for i := uint64(0); i < n; i++ { + privKeys[i] = secp256k1.GenPrivKey() + pubKeys[i] = privKeys[i].PubKey() + } + + var pk crypto.PubKey + if len(privKeys) > 1 { + // generate multi sig pk + pk = kmultisig.NewLegacyAminoPubKey(int(n), pubKeys) + } else { + pk = privKeys[0].PubKey() + } + + return privKeys, pubKeys, pk +} + // ClientState returns a new solo machine ClientState instance. Default usage does not allow update // after governance proposal func (solo *Solomachine) ClientState() *solomachinetypes.ClientState { @@ -71,10 +106,10 @@ func (solo *Solomachine) GetHeight() exported.Height { // CreateHeader generates a new private/public key pair and creates the // necessary signature to construct a valid solo machine header. func (solo *Solomachine) CreateHeader() *solomachinetypes.Header { - // generate new private key and signature for header - newPrivKey := secp256k1.GenPrivKey() + // generate new private keys and signature for header + newPrivKeys, newPubKeys, newPubKey := GenerateKeys(solo.t, uint64(len(solo.PrivateKeys))) - publicKey, err := tx.PubKeyToAny(newPrivKey.PubKey()) + publicKey, err := tx.PubKeyToAny(newPubKey) require.NoError(solo.t, err) data := &solomachinetypes.HeaderData{ @@ -92,24 +127,24 @@ func (solo *Solomachine) CreateHeader() *solomachinetypes.Header { Data: dataBz, } - signBz, err := solo.cdc.MarshalBinaryBare(signBytes) + bz, err := solo.cdc.MarshalBinaryBare(signBytes) require.NoError(solo.t, err) - signature, err := solo.PrivateKey.Sign(signBz) - require.NoError(solo.t, err) + sig := solo.GenerateSignature(bz) header := &solomachinetypes.Header{ Sequence: solo.Sequence, Timestamp: solo.Time, - Signature: signature, + Signature: sig, NewPublicKey: publicKey, NewDiversifier: solo.Diversifier, } // assumes successful header update solo.Sequence++ - solo.PrivateKey = newPrivKey - solo.PublicKey = newPrivKey.PubKey() + solo.PrivateKeys = newPrivKeys + solo.PublicKeys = newPubKeys + solo.PublicKey = newPubKey return header } @@ -127,12 +162,10 @@ func (solo *Solomachine) CreateMisbehaviour() *solomachinetypes.Misbehaviour { Data: dataOne, } - signBz, err := solo.cdc.MarshalBinaryBare(signBytes) - require.NoError(solo.t, err) - - sig, err := solo.PrivateKey.Sign(signBz) + bz, err := solo.cdc.MarshalBinaryBare(signBytes) require.NoError(solo.t, err) + sig := solo.GenerateSignature(bz) signatureOne := solomachinetypes.SignatureAndData{ Signature: sig, Data: dataOne, @@ -145,12 +178,10 @@ func (solo *Solomachine) CreateMisbehaviour() *solomachinetypes.Misbehaviour { Data: dataTwo, } - signBz, err = solo.cdc.MarshalBinaryBare(signBytes) - require.NoError(solo.t, err) - - sig, err = solo.PrivateKey.Sign(signBz) + bz, err = solo.cdc.MarshalBinaryBare(signBytes) require.NoError(solo.t, err) + sig = solo.GenerateSignature(bz) signatureTwo := solomachinetypes.SignatureAndData{ Signature: sig, Data: dataTwo, @@ -163,3 +194,38 @@ func (solo *Solomachine) CreateMisbehaviour() *solomachinetypes.Misbehaviour { SignatureTwo: &signatureTwo, } } + +// GenerateSignature uses the stored private keys to generate a signature +// over the sign bytes with each key. If the amount of keys is greater than +// 1 then a multisig data type is returned. +func (solo *Solomachine) GenerateSignature(signBytes []byte) []byte { + sigs := make([]signing.SignatureData, len(solo.PrivateKeys)) + for i, key := range solo.PrivateKeys { + sig, err := key.Sign(signBytes) + require.NoError(solo.t, err) + + sigs[i] = &signing.SingleSignatureData{ + Signature: sig, + } + } + + var sigData signing.SignatureData + if len(sigs) == 1 { + // single public key + sigData = sigs[0] + } else { + // generate multi signature data + multiSigData := multisig.NewMultisig(len(sigs)) + for i, sig := range sigs { + multisig.AddSignature(multiSigData, sig, i) + } + + sigData = multiSigData + } + + protoSigData := signing.SignatureDataToProto(sigData) + bz, err := solo.cdc.MarshalBinaryBare(protoSigData) + require.NoError(solo.t, err) + + return bz +}