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}