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 <cwgoes@pluranimity.org>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
This commit is contained in:
colin axnér 2020-09-28 13:00:33 +02:00 committed by GitHub
parent 8601dcdbb7
commit be59020f29
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 1614 additions and 1320 deletions

View File

@ -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())
},
},

View File

@ -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,
},
{

View File

@ -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)
},

View File

@ -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)

View File

@ -0,0 +1,66 @@
<!--
order: 1
-->
# 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)
```

View File

@ -0,0 +1,18 @@
<!--
order: 0
title: Solo Machine Client
parent:
title: "solomachine"
-->
# `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)**

View File

@ -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

File diff suppressed because it is too large Load Diff

View File

@ -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
}

View File

@ -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)
}
})
}
}
}

View File

@ -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)
}
})
}
}
}

View File

@ -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")
}

View File

@ -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)
}
})
})
}
}
}

View File

@ -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)
}
})
}
}
}

View File

@ -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

View File

@ -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)
}
})
}
}
}

View File

@ -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) {

View File

@ -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())
}

View File

@ -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)
}
})
})
}
}
}

View File

@ -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:

View File

@ -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
}