Search Apps Documentation Source Content File Folder Download Copy Actions Download

verify.gno

11.59 Kb · 321 lines
  1package tendermint
  2
  3import (
  4	"bytes"
  5	"crypto/ed25519"
  6	"encoding/hex"
  7	"time"
  8
  9	"gno.land/p/aib/ibc/types"
 10	"gno.land/p/aib/ics23"
 11	"gno.land/p/nt/ufmt/v0"
 12)
 13
 14func (tm *TMLightClient) verifyHeader(msg *MsgHeader) error {
 15	// Retrieve trusted consensus states for trusted height
 16	consState, found := tm.GetConsensusState(msg.TrustedHeight)
 17	if !found {
 18		return ufmt.Errorf("could not get trusted consensus state for Header at TrustedHeight: %s", msg.TrustedHeight)
 19	}
 20
 21	// UpdateClient only accepts updates with a header at the same revision
 22	// as the trusted consensus state
 23	if msg.GetHeight().RevisionNumber != msg.TrustedHeight.RevisionNumber {
 24		return ufmt.Errorf(
 25			"header height revision %d does not match trusted header revision %d",
 26			msg.GetHeight().RevisionNumber, msg.TrustedHeight.RevisionNumber,
 27		)
 28	}
 29	if err := checkTrustedHeader(msg, consState); err != nil {
 30		return err
 31	}
 32
 33	var (
 34		now             = time.Now()
 35		untrustedHeader = msg.Header
 36		untrustedValset = msg.ValidatorSet
 37		chainID         = tm.ClientState.ChainID
 38		trustedHeader   = &Header{
 39			ChainID:            chainID,
 40			Height:             msg.TrustedHeight.RevisionHeight,
 41			Time:               consState.Timestamp,
 42			NextValidatorsHash: consState.NextValidatorsHash,
 43		}
 44		trustedValset = msg.TrustedValidators
 45	)
 46
 47	// UpdateClient only accepts updates with a header at the same revision
 48	// as the trusted consensus state
 49	if untrustedHeader.ChainID != trustedHeader.ChainID {
 50		return ufmt.Errorf("header belongs to another chain %q, not %q",
 51			untrustedHeader.ChainID, trustedHeader.ChainID)
 52	}
 53
 54	if !untrustedHeader.Time.After(trustedHeader.Time) {
 55		return ufmt.Errorf(
 56			"expected new header time %v to be after old header time %v",
 57			untrustedHeader.Time, trustedHeader.Time,
 58		)
 59	}
 60
 61	if !untrustedHeader.Time.Before(now.Add(tm.ClientState.MaxClockDrift)) {
 62		return ufmt.Errorf(
 63			"new header has a time from the future %v (now: %v; max clock drift: %v)",
 64			untrustedHeader.Time, now, tm.ClientState.MaxClockDrift,
 65		)
 66	}
 67
 68	if err := trustedHeader.Expired(tm.ClientState.TrustingPeriod); err != nil {
 69		return err
 70	}
 71
 72	if untrustedHeader.Height == trustedHeader.Height+1 {
 73		// Adjacent heights, check the validator hashes are the same
 74		if !bytes.Equal(untrustedHeader.ValidatorsHash, trustedHeader.NextValidatorsHash) {
 75			h1, h2 := hex.EncodeToString(trustedHeader.NextValidatorsHash), hex.EncodeToString(untrustedHeader.ValidatorsHash)
 76			return ufmt.Errorf(
 77				"expected old header next validators (%s) to match those from new header (%s)",
 78				h1, h2,
 79			)
 80		}
 81	} else {
 82		// Heights aren't adjacent, this requires to check if at least trustlevel
 83		// ([1/3, 1]) of trusted validators have signed correctly.
 84		var (
 85			tl                = tm.ClientState.TrustLevel
 86			votingPowerNeeded = trustedValset.TotalVotingPower *
 87				int64(tl.Numerator) / int64(tl.Denominator)
 88		)
 89		if err := verifyCommit(chainID, trustedValset, msg.Commit, votingPowerNeeded, false); err != nil {
 90			return ufmt.Errorf("check trusted validators VP>%d: %v", votingPowerNeeded, err)
 91		}
 92	}
 93	// Ensure that +2/3 of untrustedValset signed correctly.
 94	//
 95	// NOTE: this should always be the last check because untrustedValset can be
 96	// intentionally made very large to DOS the light client.
 97	votingPowerNeeded := untrustedValset.TotalVotingPower * 2 / 3
 98	err := verifyCommit(chainID, untrustedValset, msg.Commit, votingPowerNeeded, true)
 99	if err != nil {
100		return ufmt.Errorf("check untrusted validators VP>%d: %v", votingPowerNeeded, err)
101	}
102	return nil
103}
104
105// verifyMisbehaviour determines whether or not two conflicting headers at the
106// same height would have convinced the light client.
107func (tm TMLightClient) verifyMisbehavior(misbehaviour *Misbehaviour) error {
108	// Regardless of the type of misbehaviour, ensure that both headers are valid
109	// and would have been accepted by light-client
110
111	// Retrieve trusted consensus states for each Header in misbehaviour
112	tmConsensusState1, found := tm.GetConsensusState(misbehaviour.Header1.TrustedHeight)
113	if !found {
114		return ufmt.Errorf(
115			"could not get trusted consensus state from clientStore for Header1 at TrustedHeight: %s",
116			misbehaviour.Header1.TrustedHeight,
117		)
118	}
119
120	tmConsensusState2, found := tm.GetConsensusState(misbehaviour.Header2.TrustedHeight)
121	if !found {
122		return ufmt.Errorf(
123			"could not get trusted consensus state from clientStore for Header2 at TrustedHeight: %s",
124			misbehaviour.Header2.TrustedHeight,
125		)
126	}
127
128	// Check the validity of the two conflicting headers against their respective
129	// trusted consensus states.
130	if err := tm.checkMisbehaviourHeader(tmConsensusState1, misbehaviour.Header1); err != nil {
131		return ufmt.Errorf("verifying Header1 in Misbehaviour failed: %v", err)
132	}
133	if err := tm.checkMisbehaviourHeader(tmConsensusState2, misbehaviour.Header2); err != nil {
134		return ufmt.Errorf("verifying Header2 in Misbehaviour failed: %v", err)
135	}
136
137	return nil
138}
139
140// checkTrustedHeader checks that consensus state matches trusted fields of Header
141func checkTrustedHeader(msg *MsgHeader, consensusState *ConsensusState) error {
142	tvalHash := msg.TrustedValidators.Hash()
143	if !bytes.Equal(consensusState.NextValidatorsHash, tvalHash) {
144		h1, h2 := hex.EncodeToString(consensusState.NextValidatorsHash), hex.EncodeToString(tvalHash)
145		return ufmt.Errorf(
146			"trusted validators do not hash to latest trusted validators. Expected: %q, got: %q",
147			h1, h2,
148		)
149	}
150	return nil
151}
152
153// verifyCommit verifies the signatures included in a commit. Returns an error
154// if votingPowerNeeded is not reached with the verified signatures.
155// CONTRACT: both commit and validator set should have passed validate basic.
156func verifyCommit(
157	chainID string,
158	vals *ValidatorSet,
159	commit *Commit,
160	votingPowerNeeded int64,
161	lookUpByIndex bool,
162) error {
163	var (
164		val                *Validator
165		valIdx             int32
166		seenVals           = make(map[int32]int, len(commit.Signatures))
167		talliedVotingPower int64
168	)
169	for idx, commitSig := range commit.Signatures {
170		if commitSig.BlockIDFlag != BlockIDFlagCommit {
171			continue
172		}
173
174		// If the vals and commit have a 1-to-1 correspondence we can retrieve
175		// them by index else we need to retrieve them by address
176		if lookUpByIndex {
177			val = vals.Validators[idx]
178		} else {
179			valIdx, val = vals.GetByAddress(commitSig.ValidatorAddress)
180
181			// if the signature doesn't belong to anyone in the validator set
182			// then we just skip over it
183			if val == nil {
184				continue
185			}
186
187			// because we are getting validators by address we need to make sure
188			// that the same validator doesn't commit twice
189			if firstIndex, ok := seenVals[valIdx]; ok {
190				secondIndex := idx
191				addr := hex.EncodeToString(val.Address)
192				return ufmt.Errorf("double vote from %s (%d and %d)", addr, firstIndex, secondIndex)
193			}
194			seenVals[valIdx] = idx
195		}
196
197		if len(val.PubKey) == 0 {
198			addr := hex.EncodeToString(val.Address)
199			return ufmt.Errorf("validator %s has a nil PubKey at index %d", addr, idx)
200		}
201
202		voteSignBytes := commit.BytesToSign(chainID, idx)
203
204		// CONTRACT: all validators use ed25519 keys
205		if !ed25519.Verify(val.PubKey, voteSignBytes, commitSig.Signature) {
206			return ufmt.Errorf("verify signature fail (#%d)", idx)
207		}
208
209		talliedVotingPower += val.VotingPower
210
211		// check if we have enough signatures and can thus exit early
212		if talliedVotingPower > votingPowerNeeded {
213			return nil
214		}
215	}
216	return ufmt.Errorf(
217		"not enough voting power, got %v, needed >%v",
218		talliedVotingPower, votingPowerNeeded,
219	)
220}
221
222// verifyChainedMembershipProof takes a list of proofs and specs and verifies
223// each proof sequentially ensuring that the value is committed to by first
224// proof and each subsequent subroot is committed to by the next subroot and
225// checking that the final calculated root is equal to the given roothash.
226// The proofs and specs are passed in from lowest subtree to the highest
227// subtree, but the keys are passed in from highest subtree to lowest.
228// The index specifies what index to start chaining the membership proofs, this
229// is useful since the lowest proof may not be a membership proof, thus we will
230// want to start the membership proof chaining from index 1 with value being
231// the lowest subroot
232func (tm TMLightClient) verifyChainedMembershipProof(root []byte,
233	proofs []ics23.CommitmentProof, keys types.MerklePath, value []byte,
234	index int) error {
235	var (
236		specs   = tm.ClientState.ProofSpecs
237		subroot []byte
238		err     error
239	)
240	// Initialize subroot to value since the proofs list may be empty.
241	// This may happen if this call is verifying intermediate proofs after the
242	// lowest proof has been executed.
243	// In this case, there may be no intermediate proofs to verify and we just
244	// check that lowest proof root equals final root
245	subroot = value
246	for i := index; i < len(proofs); i++ {
247		// verify membership of the proof at this index with appropriate key and
248		// value
249		exist := proofs[i].GetExist()
250		if exist == nil {
251			return ufmt.Errorf("commitment proof must be existence proof for verifying membership")
252		}
253		subroot, err = exist.Calculate()
254		if err != nil {
255			return ufmt.Errorf("could not calculate proof root at index %d, merkle tree may be empty. %v", i, err)
256		}
257
258		// Since keys are passed in from highest to lowest, we must grab their
259		// indices in reverse order from the proofs and specs which are lowest to
260		// highest
261		key := keys.KeyPath[len(keys.KeyPath)-1-i]
262
263		if err := exist.Verify(specs[i], subroot, key, value); err != nil {
264			return ufmt.Errorf("failed to verify membership proof at index %d: %v", i, err)
265		}
266		// Set value to subroot so that we verify next proof in chain commits to
267		// this subroot
268		value = subroot
269	}
270
271	// Check that chained proof root equals passed-in root
272	if !bytes.Equal(root, subroot) {
273		h1, h2 := hex.EncodeToString(root), hex.EncodeToString(subroot)
274		return ufmt.Errorf("proof did not commit to expected root: %s, got: %s. Please ensure proof was submitted with correct proofHeight and to the correct chain.", h1, h2)
275	}
276
277	return nil
278}
279
280// checkMisbehaviourHeader checks that a MsgHeader in Misbehaviour is valid
281// misbehaviour given a trusted ConsensusState.
282func (tm TMLightClient) checkMisbehaviourHeader(consState *ConsensusState, msg *MsgHeader) error {
283	// check the trusted fields for the header against ConsensusState
284	if err := checkTrustedHeader(msg, consState); err != nil {
285		return err
286	}
287
288	// assert that the age of the trusted consensus state is not older than the
289	// trusting period
290	consensusAge := time.Now().Sub(consState.Timestamp)
291	if consensusAge >= tm.ClientState.TrustingPeriod {
292		return ufmt.Errorf(
293			"current timestamp minus the latest consensus state timestamp is greater than or equal to the trusting period (%s >= %s)",
294			consensusAge, tm.ClientState.TrustingPeriod,
295		)
296	}
297
298	chainID := tm.ClientState.ChainID
299	// If chainID is in revision format, then set revision number of chainID with
300	// the revision number of the misbehaviour header.
301	// NOTE: misbehaviour verification is not supported for chains which upgrade
302	// to a new chainID without strictly following the chainID revision format.
303	if types.IsRevisionFormat(chainID) {
304		chainID, _ = types.SetRevisionNumber(chainID, msg.GetHeight().RevisionNumber)
305	}
306
307	// - ValidatorSet must have TrustLevel similarity with trusted
308	// FromValidatorSet
309	// - ValidatorSets on both headers are valid given the last trusted
310	// ValidatorSet
311	var (
312		tl = tm.ClientState.TrustLevel
313		// TODO protect against overflows
314		votingPowerNeeded = msg.TrustedValidators.TotalVotingPower *
315			int64(tl.Numerator) / int64(tl.Denominator)
316	)
317	if err := verifyCommit(chainID, msg.TrustedValidators, msg.Commit, votingPowerNeeded, false); err != nil {
318		return ufmt.Errorf("validator set in header has too much change from trusted validator set: %v", err)
319	}
320	return nil
321}