Skip to content

Commit

Permalink
refactor: solomachine generic VerifyNonMembership (#1720)
Browse files Browse the repository at this point in the history
* adding verification of non-membership with tests

* refactor common code to produceVerificationArgs

* removing unused produce args func

* Update modules/light-clients/06-solomachine/client_state_test.go

Co-authored-by: colin axnér <25233464+colin-axner@users.noreply.github.com>

Co-authored-by: colin axnér <25233464+colin-axner@users.noreply.github.com>
  • Loading branch information
damiannolan and colin-axner authored Jul 27, 2022
1 parent 1c93ee6 commit e9b5801
Show file tree
Hide file tree
Showing 2 changed files with 273 additions and 58 deletions.
96 changes: 38 additions & 58 deletions modules/light-clients/06-solomachine/client_state.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,55 +118,16 @@ func (cs *ClientState) VerifyMembership(
path []byte,
value []byte,
) error {
// TODO: Attempt to refactor code to smaller function
if revision := height.GetRevisionNumber(); revision != 0 {
return sdkerrors.Wrapf(sdkerrors.ErrInvalidHeight, "revision must be 0 for solomachine, got revision-number: %d", revision)
}

// sequence is encoded in the revision height of height struct
sequence := height.GetRevisionHeight()
latestSequence := cs.GetLatestHeight().GetRevisionHeight()
if latestSequence != sequence {
return sdkerrors.Wrapf(
sdkerrors.ErrInvalidHeight,
"client state sequence != proof sequence (%d != %d)", latestSequence, sequence,
)
publicKey, sigData, timestamp, sequence, err := produceVerificationArgs(cdc, cs, height, proof)
if err != nil {
return err
}

var merklePath commitmenttypes.MerklePath
if err := cdc.Unmarshal(path, &merklePath); err != nil {
return sdkerrors.Wrap(commitmenttypes.ErrInvalidProof, "failed to unmarshal path into ICS 23 commitment merkle path")
}

var timestampedSigData TimestampedSignatureData
if err := cdc.Unmarshal(proof, &timestampedSigData); err != nil {
return sdkerrors.Wrapf(err, "failed to unmarshal proof into type %T", timestampedSigData)
}

timestamp := timestampedSigData.Timestamp

if len(timestampedSigData.SignatureData) == 0 {
return sdkerrors.Wrap(ErrInvalidProof, "signature data cannot be empty")
}

sigData, err := UnmarshalSignatureData(cdc, timestampedSigData.SignatureData)
if err != nil {
return err
}

if cs.ConsensusState == nil {
return sdkerrors.Wrap(clienttypes.ErrInvalidConsensus, "consensus state cannot be empty")
}

if cs.ConsensusState.GetTimestamp() > timestamp {
return sdkerrors.Wrapf(ErrInvalidProof, "the consensus state timestamp is greater than the signature timestamp (%d >= %d)", cs.ConsensusState.GetTimestamp(), timestamp)
}

publicKey, err := cs.ConsensusState.GetPubKey()
if err != nil {
return err
}

signBytes := &SignBytesV2{
Sequence: sequence,
Timestamp: timestamp,
Expand Down Expand Up @@ -203,11 +164,40 @@ func (cs *ClientState) VerifyNonMembership(
proof []byte,
path []byte,
) error {
// TODO: Implement 06-solomachine VerifyNonMembership
publicKey, sigData, timestamp, sequence, err := produceVerificationArgs(cdc, cs, height, proof)
if err != nil {
return err
}

var merklePath commitmenttypes.MerklePath
if err := cdc.Unmarshal(path, &merklePath); err != nil {
return sdkerrors.Wrap(commitmenttypes.ErrInvalidProof, "failed to unmarshal path into ICS 23 commitment merkle path")
}

signBytes := &SignBytesV2{
Sequence: sequence,
Timestamp: timestamp,
Diversifier: cs.ConsensusState.Diversifier,
Path: []byte(merklePath.String()),
Data: nil,
}

signBz, err := cdc.Marshal(signBytes)
if err != nil {
return err
}

if err := VerifySignature(publicKey, signBz, sigData); err != nil {
return err
}

cs.Sequence++
cs.ConsensusState.Timestamp = timestamp
setClientState(clientStore, cdc, cs)

return nil
}

// TODO: Remove function as no longer used
// produceVerificationArgs perfoms the basic checks on the arguments that are
// shared between the verification functions and returns the public key of the
// consensus state, the unmarshalled proof representing the signature and timestamp
Expand All @@ -216,34 +206,22 @@ func produceVerificationArgs(
cdc codec.BinaryCodec,
cs *ClientState,
height exported.Height,
prefix exported.Prefix,
proof []byte,
) (cryptotypes.PubKey, signing.SignatureData, uint64, uint64, error) {
if revision := height.GetRevisionNumber(); revision != 0 {
return nil, nil, 0, 0, sdkerrors.Wrapf(sdkerrors.ErrInvalidHeight, "revision must be 0 for solomachine, got revision-number: %d", revision)
}
// sequence is encoded in the revision height of height struct
sequence := height.GetRevisionHeight()
if prefix == nil {
return nil, nil, 0, 0, sdkerrors.Wrap(commitmenttypes.ErrInvalidPrefix, "prefix cannot be empty")
}

_, ok := prefix.(*commitmenttypes.MerklePrefix)
if !ok {
return nil, nil, 0, 0, sdkerrors.Wrapf(commitmenttypes.ErrInvalidPrefix, "invalid prefix type %T, expected MerklePrefix", prefix)
}

if proof == nil {
return nil, nil, 0, 0, sdkerrors.Wrap(ErrInvalidProof, "proof cannot be empty")
}

timestampedSigData := &TimestampedSignatureData{}
if err := cdc.Unmarshal(proof, timestampedSigData); err != nil {
var timestampedSigData TimestampedSignatureData
if err := cdc.Unmarshal(proof, &timestampedSigData); err != nil {
return nil, nil, 0, 0, sdkerrors.Wrapf(err, "failed to unmarshal proof into type %T", timestampedSigData)
}

timestamp := timestampedSigData.Timestamp

if len(timestampedSigData.SignatureData) == 0 {
return nil, nil, 0, 0, sdkerrors.Wrap(ErrInvalidProof, "signature data cannot be empty")
}
Expand All @@ -257,6 +235,8 @@ func produceVerificationArgs(
return nil, nil, 0, 0, sdkerrors.Wrap(clienttypes.ErrInvalidConsensus, "consensus state cannot be empty")
}

// sequence is encoded in the revision height of height struct
sequence := height.GetRevisionHeight()
latestSequence := cs.GetLatestHeight().GetRevisionHeight()
if latestSequence != sequence {
return nil, nil, 0, 0, sdkerrors.Wrapf(
Expand Down
235 changes: 235 additions & 0 deletions modules/light-clients/06-solomachine/client_state_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -536,6 +536,13 @@ func (suite *SoloMachineTestSuite) TestVerifyMembership() {
},
false,
},
{
"proof is nil",
func() {
proof = nil
},
false,
},
{
"proof verification failed",
func() {
Expand Down Expand Up @@ -605,6 +612,234 @@ func (suite *SoloMachineTestSuite) TestVerifyMembership() {
}
}

func (suite *SoloMachineTestSuite) TestVerifyNonMembership() {
// test singlesig and multisig public keys
for _, sm := range []*ibctesting.Solomachine{suite.solomachine, suite.solomachineMulti} {

var (
clientState *solomachine.ClientState
err error
height clienttypes.Height
path []byte
proof []byte
signBytes solomachine.SignBytesV2
)

testCases := []struct {
name string
malleate func()
expPass bool
}{
{
"success",
func() {},
true,
},
{
"success: packet receipt absence verification",
func() {
merklePath := suite.solomachine.GetPacketReceiptPath(ibctesting.MockPort, ibctesting.FirstChannelID)
signBytes = solomachine.SignBytesV2{
Sequence: sm.GetHeight().GetRevisionHeight(),
Timestamp: sm.Time,
Diversifier: sm.Diversifier,
Path: []byte(merklePath.String()),
Data: nil,
}

signBz, err := suite.chainA.Codec.Marshal(&signBytes)
suite.Require().NoError(err)

sig := sm.GenerateSignature(signBz)

signatureDoc := &solomachine.TimestampedSignatureData{
SignatureData: sig,
Timestamp: sm.Time,
}

path, err = suite.chainA.Codec.Marshal(&merklePath)
suite.Require().NoError(err)

proof, err = suite.chainA.Codec.Marshal(signatureDoc)
suite.Require().NoError(err)
},
true,
},
{
"consensus state in client state is nil",
func() {
clientState = solomachine.NewClientState(1, nil, false)
},
false,
},
{
"client state latest height is less than sequence",
func() {
consensusState := &solomachine.ConsensusState{
Timestamp: sm.Time,
PublicKey: sm.ConsensusState().PublicKey,
}

clientState = solomachine.NewClientState(sm.Sequence-1, consensusState, false)
},
false,
},
{
"height revision number is not zero",
func() {
height = clienttypes.NewHeight(1, sm.GetHeight().GetRevisionHeight())
},
false,
},
{
"malformed merkle path fails to unmarshal",
func() {
path = []byte("invalid path")
},
false,
},
{
"malformed proof fails to unmarshal",
func() {
merklePath := suite.solomachine.GetClientStatePath(counterpartyClientIdentifier)
path, err = suite.chainA.Codec.Marshal(&merklePath)
suite.Require().NoError(err)

proof = []byte("invalid proof")
},
false,
},
{
"consensus state timestamp is greater than signature",
func() {
consensusState := &solomachine.ConsensusState{
Timestamp: sm.Time + 1,
PublicKey: sm.ConsensusState().PublicKey,
}

clientState = solomachine.NewClientState(sm.Sequence, consensusState, false)
},
false,
},
{
"signature data is nil",
func() {
signatureDoc := &solomachine.TimestampedSignatureData{
SignatureData: nil,
Timestamp: sm.Time,
}

proof, err = suite.chainA.Codec.Marshal(signatureDoc)
suite.Require().NoError(err)
},
false,
},
{
"consensus state public key is nil",
func() {
clientState.ConsensusState.PublicKey = nil
},
false,
},
{
"malformed signature data fails to unmarshal",
func() {
signatureDoc := &solomachine.TimestampedSignatureData{
SignatureData: []byte("invalid signature data"),
Timestamp: sm.Time,
}

proof, err = suite.chainA.Codec.Marshal(signatureDoc)
suite.Require().NoError(err)
},
false,
},
{
"proof is nil",
func() {
proof = nil
},
false,
},
{
"proof verification failed",
func() {
signBytes.Data = []byte("invalid non-membership data value")

signBz, err := suite.chainA.Codec.Marshal(&signBytes)
suite.Require().NoError(err)

sig := sm.GenerateSignature(signBz)

signatureDoc := &solomachine.TimestampedSignatureData{
SignatureData: sig,
Timestamp: sm.Time,
}

proof, err = suite.chainA.Codec.Marshal(signatureDoc)
suite.Require().NoError(err)
},
false,
},
}

for _, tc := range testCases {
tc := tc

suite.Run(tc.name, func() {
clientState = sm.ClientState()
height = clienttypes.NewHeight(sm.GetHeight().GetRevisionNumber(), sm.GetHeight().GetRevisionHeight())

merklePath := commitmenttypes.NewMerklePath("ibc", "solomachine")
signBytes = solomachine.SignBytesV2{
Sequence: sm.GetHeight().GetRevisionHeight(),
Timestamp: sm.Time,
Diversifier: sm.Diversifier,
Path: []byte(merklePath.String()),
Data: nil,
}

signBz, err := suite.chainA.Codec.Marshal(&signBytes)
suite.Require().NoError(err)

sig := sm.GenerateSignature(signBz)

signatureDoc := &solomachine.TimestampedSignatureData{
SignatureData: sig,
Timestamp: sm.Time,
}

path, err = suite.chainA.Codec.Marshal(&merklePath)
suite.Require().NoError(err)

proof, err = suite.chainA.Codec.Marshal(signatureDoc)
suite.Require().NoError(err)

tc.malleate()

var expSeq uint64
if clientState.ConsensusState != nil {
expSeq = clientState.Sequence + 1
}

err = clientState.VerifyNonMembership(
suite.chainA.GetContext(), suite.store, suite.chainA.Codec,
height, 0, 0, // solomachine does not check delay periods
proof, path,
)

if tc.expPass {
suite.Require().NoError(err)
suite.Require().Equal(expSeq, clientState.Sequence)
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)
}
})
}
}
}

func (suite *SoloMachineTestSuite) TestGetTimestampAtHeight() {
tmPath := ibctesting.NewPath(suite.chainA, suite.chainB)
suite.coordinator.SetupClients(tmPath)
Expand Down

0 comments on commit e9b5801

Please sign in to comment.