Search Apps Documentation Source Content File Folder Download Copy Actions Download

header_test.gno

30.95 Kb · 952 lines
  1package tendermint_test
  2
  3import (
  4	"bytes"
  5	"crypto/ed25519"
  6	"crypto/sha256"
  7	"encoding/base64"
  8	"encoding/hex"
  9	"strings"
 10	"testing"
 11	"time"
 12
 13	"gno.land/p/aib/ibc/lightclient"
 14	"gno.land/p/aib/ibc/lightclient/tendermint"
 15	"gno.land/p/aib/ibc/types"
 16	"gno.land/p/nt/uassert/v0"
 17)
 18
 19func TestMsgHeaderValidateBasic(t *testing.T) {
 20	var msg *tendermint.MsgHeader
 21	testCases := []struct {
 22		name          string
 23		malleate      func()
 24		expectedError string
 25	}{
 26		{
 27			name:          "valid",
 28			malleate:      func() {},
 29			expectedError: "",
 30		},
 31		{
 32			name:          "header is nil",
 33			malleate:      func() { msg.Header = nil },
 34			expectedError: "missing header",
 35		},
 36		{
 37			name:          "header version block invalid",
 38			malleate:      func() { msg.Header.Version.Block-- },
 39			expectedError: "block protocol is incorrect",
 40		},
 41		{
 42			name: "header chainId too long",
 43			malleate: func() {
 44				msg.Header.ChainID = fiftyOneCharChainID
 45			},
 46			expectedError: "chainID is too long",
 47		},
 48		{
 49			name:          "header height is zero",
 50			malleate:      func() { msg.Header.Height = 0 },
 51			expectedError: "zero Height",
 52		},
 53		{
 54			name: "header lastBlockID hash invalid",
 55			malleate: func() {
 56				msg.Header.LastBlockID.Hash = invalidHash
 57			},
 58			expectedError: "wrong blockID Hash",
 59		},
 60		{
 61			name: "header lastBlockID ParSetHeader hash invalid",
 62			malleate: func() {
 63				msg.Header.LastBlockID.PartSetHeader.Hash = invalidHash
 64			},
 65			expectedError: "wrong parSetHeader Hash",
 66		},
 67		{
 68			name: "header lastCommitHash invalid",
 69			malleate: func() {
 70				msg.Header.LastCommitHash = invalidHash
 71			},
 72			expectedError: "wrong LastCommitHash",
 73		},
 74		{
 75			name: "header dataHash invalid",
 76			malleate: func() {
 77				msg.Header.DataHash = invalidHash
 78			},
 79			expectedError: "wrong DataHash",
 80		},
 81		{
 82			name: "header evidenceHash invalid",
 83			malleate: func() {
 84				msg.Header.EvidenceHash = invalidHash
 85			},
 86			expectedError: "wrong EvidenceHash",
 87		},
 88		{
 89			name: "header proposerAddress invalid",
 90			malleate: func() {
 91				msg.Header.ProposerAddress = []byte("adr_too_short")
 92			},
 93			expectedError: "invalid ProposerAddress",
 94		},
 95		{
 96			name: "header validatorHash invalid",
 97			malleate: func() {
 98				msg.Header.ValidatorsHash = invalidHash
 99			},
100			expectedError: "wrong ValidatorsHash",
101		},
102		{
103			name: "header nextValidatorHash invalid",
104			malleate: func() {
105				msg.Header.NextValidatorsHash = invalidHash
106			},
107			expectedError: "wrong NextValidatorsHash",
108		},
109		{
110			name: "header consensusHash invalid",
111			malleate: func() {
112				msg.Header.ConsensusHash = invalidHash
113			},
114			expectedError: "wrong ConsensusHash",
115		},
116		{
117			name: "header lastResultHash invalid",
118			malleate: func() {
119				msg.Header.LastResultsHash = invalidHash
120			},
121			expectedError: "wrong LastResultsHash",
122		},
123		{
124			name:          "commit is nil",
125			malleate:      func() { msg.Commit = nil },
126			expectedError: "missing commit",
127		},
128		{
129			name:          "commit round negative",
130			malleate:      func() { msg.Commit.Round = -1 },
131			expectedError: "negative Round",
132		},
133		{
134			name: "commit blockId is zero",
135			malleate: func() {
136				msg.Commit.BlockID.Hash = nil
137				msg.Commit.BlockID.PartSetHeader.Total = 0
138				msg.Commit.BlockID.PartSetHeader.Hash = nil
139			},
140			expectedError: "commit cannot be for nil block",
141		},
142		{
143			name: "commit signatures are empty",
144			malleate: func() {
145				msg.Commit.Signatures = nil
146			},
147			expectedError: "no signatures in commit",
148		},
149		{
150			name: "commit signature blockIdFlag is invalid",
151			malleate: func() {
152				msg.Commit.Signatures[0].BlockIDFlag = tendermint.BlockIDFlagUnknown
153			},
154			expectedError: "unknown BlockIDFlag",
155		},
156		{
157			name: "commit signature blockIdAbsent validatorAddress is not empty",
158			malleate: func() {
159				msg.Commit.Signatures[0].BlockIDFlag = tendermint.BlockIDFlagAbsent
160			},
161			expectedError: "validator address is present",
162		},
163		{
164			name: "commit signature blockIdAbsent timestamp is not empty",
165			malleate: func() {
166				msg.Commit.Signatures[0].BlockIDFlag = tendermint.BlockIDFlagAbsent
167				msg.Commit.Signatures[0].ValidatorAddress = nil
168			},
169			expectedError: "time is present",
170		},
171		{
172			name: "commit signature blockIdAbsent signature is not empty",
173			malleate: func() {
174				msg.Commit.Signatures[0].BlockIDFlag = tendermint.BlockIDFlagAbsent
175				msg.Commit.Signatures[0].ValidatorAddress = nil
176				msg.Commit.Signatures[0].Timestamp = time.Time{}
177			},
178			expectedError: "signature is present",
179		},
180		{
181			name: "commit signature validatorAddress is invalid",
182			malleate: func() {
183				msg.Commit.Signatures[0].ValidatorAddress = []byte("addr_too_short")
184			},
185			expectedError: "expected ValidatorAddress size to be",
186		},
187		{
188			name: "commit signature is missing",
189			malleate: func() {
190				msg.Commit.Signatures[0].Signature = nil
191			},
192			expectedError: "signature is missing",
193		},
194		{
195			name: "commit signature is invalid",
196			malleate: func() {
197				msg.Commit.Signatures[0].Signature = bytes.Repeat([]byte{1}, tendermint.MaxSignatureSize+1)
198			},
199			expectedError: "signature is too big",
200		},
201		{
202			name:          "commit and header height mismatch",
203			malleate:      func() { msg.Commit.Height-- },
204			expectedError: "header and commit height mismatch: 3 vs 2",
205		},
206		{
207			name: "trusted height is equal to header height",
208			malleate: func() {
209				msg.TrustedHeight = msg.GetHeight()
210			},
211			expectedError: "trustedHeight 0/3 must be less than header height 0/3",
212		},
213		{
214			name: "trusted height is lower to header height",
215			malleate: func() {
216				msg.TrustedHeight = msg.GetHeight()
217				msg.TrustedHeight.RevisionHeight++
218			},
219			expectedError: "trustedHeight 0/4 must be less than header height 0/3",
220		},
221		{
222			name:          "validator set nil",
223			malleate:      func() { msg.ValidatorSet = nil },
224			expectedError: "validator set is nil",
225		},
226		{
227			name:          "trusted validator set nil",
228			malleate:      func() { msg.TrustedValidators = nil },
229			expectedError: "trusted validator set is nil",
230		},
231		{
232			name: "validator set hash mismatch",
233			malleate: func() {
234				msg.ValidatorSet.Validators[0].PubKey = []byte{}
235			},
236			expectedError: "validator set does not match hash",
237		},
238		{
239			name: "commit signatures and validators count are different",
240			malleate: func() {
241				msg.Commit.Signatures = msg.Commit.Signatures[:1]
242			},
243			expectedError: "Invalid commit -- wrong set size: 2 vs 1",
244		},
245		{
246			name: "commit.blockid.hash != header.hash()",
247			malleate: func() {
248				msg.Commit.BlockID.Hash = hash("")
249			},
250			expectedError: `expected BlockID#Hash and Header#Hash to be the same, got "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" != "4f633a8766a596303f9139cd4706eb03484be5f5521b724c57b7021fb6889e1d"`,
251		},
252	}
253
254	if lightclient.Tendermint != msg.ClientType() {
255		t.Fatal("client type is not tendermint")
256	}
257
258	for _, tc := range testCases {
259		t.Run(tc.name, func(t *testing.T) {
260			var (
261				newHeight    = uint64(3)
262				newTimestamp = time.Now()
263				val1         = &tendermint.Validator{
264					Address:     genAddr("val1"),
265					PubKey:      genPubkey("val1_pubkey"),
266					VotingPower: 2,
267				}
268				val2 = &tendermint.Validator{
269					Address:     genAddr("val2"),
270					PubKey:      genPubkey("val2_pubkey"),
271					VotingPower: 3,
272				}
273				valset      = tendermint.NewValset(val1, val2)
274				trustedval1 = &tendermint.Validator{
275					Address:     genAddr("val1"),
276					PubKey:      genPubkey("val1_pubkey"),
277					VotingPower: 2,
278				}
279				trustedval2 = &tendermint.Validator{
280					Address:     genAddr("val2"),
281					PubKey:      genPubkey("val2_pubkey"),
282					VotingPower: 3,
283				}
284				nextValset    = tendermint.NewValset(trustedval1, trustedval2)
285				trustedValset = tendermint.NewValset(trustedval1, trustedval2)
286				trustedHeight = types.NewHeight(0, 1)
287				apphash       = hash("apphash")
288				signatures    = []tendermint.CommitSig{
289					{
290						BlockIDFlag:      tendermint.BlockIDFlagCommit,
291						ValidatorAddress: valset.Validators[0].Address,
292						Timestamp:        toTime("2025-09-25T07:55:57.306746166Z"),
293						Signature:        b64Dec("qtv1z4S2Q6T87vGQo0lrjRZqv9PrHIji4pTyviMnVyGx9td6eySdzwQwCthwmihU48ebNlFiMlFJ0CT891UmDg=="),
294					},
295					{
296						BlockIDFlag:      tendermint.BlockIDFlagCommit,
297						ValidatorAddress: valset.Validators[1].Address,
298						Timestamp:        toTime("2025-09-25T07:55:57.310583641Z"),
299						Signature:        b64Dec("Q5E6Kjma00n/T98rC9qJmoB6JTGFX/IB+mDVs4Wd1h0eJ8fabY/6oI8zdoU6/7W6VR6wjpHyWBsJrpGT6C0LCg=="),
300					},
301				}
302			)
303			msg = newMsgHeader(
304				chainID, newTimestamp, apphash, newHeight, trustedHeight, valset,
305				nextValset, trustedValset, signatures,
306			)
307			tc.malleate()
308
309			err := msg.ValidateBasic()
310
311			if tc.expectedError == "" && err != nil {
312				t.Errorf("expected no error got %v", err)
313				return
314			}
315			if tc.expectedError != "" {
316				if err == nil || !strings.Contains(err.Error(), tc.expectedError) {
317					t.Errorf("expected error %s, got %s", tc.expectedError, err)
318				}
319			}
320		})
321	}
322}
323
324func TestHeaderHash(t *testing.T) {
325	tests := []struct {
326		desc         string
327		header       *tendermint.Header
328		expectedHash string // hex encoded
329	}{
330		{
331			desc: "generates expected hash",
332			header: &tendermint.Header{
333				Version: tendermint.Consensus{Block: 1, App: 2},
334				ChainID: "chainId",
335				Height:  3,
336				Time:    time.Date(2019, 10, 13, 16, 14, 44, 0, time.UTC),
337				LastBlockID: tendermint.BlockID{
338					Hash: make([]byte, sha256.Size),
339					PartSetHeader: tendermint.PartSetHeader{
340						Total: 6,
341						Hash:  make([]byte, sha256.Size),
342					},
343				},
344				LastCommitHash:     hash("last_commit_hash"),
345				DataHash:           hash("data_hash"),
346				ValidatorsHash:     hash("validators_hash"),
347				NextValidatorsHash: hash("next_validators_hash"),
348				ConsensusHash:      hash("consensus_hash"),
349				AppHash:            hash("app_hash"),
350				LastResultsHash:    hash("last_results_hash"),
351				EvidenceHash:       hash("evidence_hash"),
352				ProposerAddress:    genAddr("proposer_address"),
353			},
354			expectedHash: "f740121f553b5418c3efbd343c2dbfe9e007bb67b0d020a0741374bab65242a4",
355		},
356		{
357			desc:         "nil header yields nil",
358			header:       nil,
359			expectedHash: "",
360		},
361		{
362			desc: "nil ValidatorsHash yields nil",
363			header: &tendermint.Header{
364				Version: tendermint.Consensus{Block: 1, App: 2},
365				ChainID: "chainId",
366				Height:  3,
367				Time:    time.Date(2019, 10, 13, 16, 14, 44, 0, time.UTC),
368				LastBlockID: tendermint.BlockID{
369					Hash: make([]byte, sha256.Size),
370					PartSetHeader: tendermint.PartSetHeader{
371						Total: 6,
372						Hash:  make([]byte, sha256.Size),
373					},
374				},
375				LastCommitHash:     hash("last_commit_hash"),
376				DataHash:           hash("data_hash"),
377				ValidatorsHash:     nil,
378				NextValidatorsHash: hash("next_validators_hash"),
379				ConsensusHash:      hash("consensus_hash"),
380				AppHash:            hash("app_hash"),
381				LastResultsHash:    hash("last_results_hash"),
382				EvidenceHash:       hash("evidence_hash"),
383				ProposerAddress:    genAddr("proposer_address"),
384			},
385			expectedHash: "",
386		},
387	}
388	for _, tt := range tests {
389		t.Run(tt.desc, func(t *testing.T) {
390			h := tt.header.Hash()
391
392			uassert.Equal(t, tt.expectedHash, hex.EncodeToString(h))
393		})
394	}
395}
396
397func TestCommitBytesToSign(t *testing.T) {
398	t.Run("update client 1", func(t *testing.T) {
399		var (
400			// ref: https://www.mintscan.io/atomone/tx/E7A075A6B6BC56ED2006A91F7833354C7F721CC9618BF09F276B4F58B119149B?sector=json
401			// (truncated with only the first 2 validators)
402			//	{
403			//  "@type": "/ibc.core.client.v1.MsgUpdateClient",
404			//  "client_id": "07-tendermint-2",
405			//  "client_message": {
406			//    "@type": "/ibc.lightclients.tendermint.v1.Header",
407			//    "signed_header": {
408			//      "header": {
409			//        "version": {
410			//          "block": "11",
411			//          "app": "0"
412			//        },
413			//        "chain_id": "osmosis-1",
414			//        "height": "44873552",
415			//        "time": "2025-09-25T07:55:56.299553312Z",
416			//        "last_block_id": {
417			//          "hash": "rV4iVJhw1gwx1d7nij1tiI0211CsYbqzrlT1bAKB6FM=",
418			//          "part_set_header": {
419			//            "total": 1,
420			//            "hash": "TmTAPEknTC5O3/XV12onhZvcvLiK0X6CdPFND4NMARw="
421			//          }
422			//        },
423			//        "last_commit_hash": "I18uOE4JQq7ZpkGb5oZQqYxLSWO86nFZ8yEICfs+XIU=",
424			//        "data_hash": "Wv50wpNbp0d8P7bnNPiDqKOO8fo2RCvs8H8LI2c5dbo=",
425			//        "validators_hash": "ehQCurs4+UgURefaP7Sw1T/MnviV6jCE8SLFMGB2Mis=",
426			//        "next_validators_hash": "ehQCurs4+UgURefaP7Sw1T/MnviV6jCE8SLFMGB2Mis=",
427			//        "consensus_hash": "JIS7qLmeUx4aP2QiNi59PuuZqDJRGZaKNV8FwJ7t1As=",
428			//        "app_hash": "DAHBO3lYaIFu9XmplThmF7ijXG7YwDZawGSDKKCHh7c=",
429			//        "last_results_hash": "aSOQCy7TAjnW/YXTu8OJjzKmnrLA3loaf0i0y+D9U/Q=",
430			//        "evidence_hash": "47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=",
431			//        "proposer_address": "ZraWZuv3dufry+GXq6RmpxLicHY="
432			//      },
433			//      "commit": {
434			//        "height": "44873552",
435			//        "round": 0,
436			//        "block_id": {
437			//          "hash": "NpiImIJoaSaIucwNs5cqpgMsL/8wxEPYC3P0jA5aQSI=",
438			//          "part_set_header": {
439			//            "total": 1,
440			//            "hash": "QqzwnLzvixIcUz+hPeUQjDV6NaLkFRKXACCxJIrBHzw="
441			//          }
442			//        },
443			//        "signatures": [
444			//          {
445			//            "block_id_flag": "BLOCK_ID_FLAG_COMMIT",
446			//            "validator_address": "y1pjuR6PTujbk1lCy+JXJGNkeeA=",
447			//            "timestamp": "2025-09-25T07:55:57.306746166Z",
448			//            "signature": "qtv1z4S2Q6T87vGQo0lrjRZqv9PrHIji4pTyviMnVyGx9td6eySdzwQwCthwmihU48ebNlFiMlFJ0CT891UmDg=="
449			//          },
450			//          {
451			//            "block_id_flag": "BLOCK_ID_FLAG_COMMIT",
452			//            "validator_address": "ZraWZuv3dufry+GXq6RmpxLicHY=",
453			//            "timestamp": "2025-09-25T07:55:57.310583641Z",
454			//            "signature": "Q5E6Kjma00n/T98rC9qJmoB6JTGFX/IB+mDVs4Wd1h0eJ8fabY/6oI8zdoU6/7W6VR6wjpHyWBsJrpGT6C0LCg=="
455			//          }
456			//        ]
457			//      }
458			//    },
459			//    "validator_set": {
460			//      "validators": [
461			//        {
462			//          "address": "y1pjuR6PTujbk1lCy+JXJGNkeeA=",
463			//          "pub_key": {
464			//            "ed25519": "6Nz09YGHzwWxjczG0IhK4Iv0qY2IcX0P/5KitvRXTUc="
465			//          },
466			//          "voting_power": "19087034",
467			//          "proposer_priority": "0"
468			//        },
469			//        {
470			//          "address": "ZraWZuv3dufry+GXq6RmpxLicHY=",
471			//          "pub_key": {
472			//            "ed25519": "wB25StLxbzmD0uTiFiH6xySZd0H13kyanNUvvlUpa34="
473			//          },
474			//          "voting_power": "11979324",
475			//          "proposer_priority": "0"
476			//        }
477			//      ],
478			//      "proposer": {
479			//        "address": "ZraWZuv3dufry+GXq6RmpxLicHY=",
480			//        "pub_key": {
481			//          "ed25519": "wB25StLxbzmD0uTiFiH6xySZd0H13kyanNUvvlUpa34="
482			//        },
483			//        "voting_power": "11979324",
484			//        "proposer_priority": "0"
485			//      },
486			//      "total_voting_power": "276866533"
487			//    },
488			//    "trusted_height": {
489			//      "revision_number": "1",
490			//      "revision_height": "44873531"
491			//    },
492			//    "trusted_validators": {
493			//      "validators": [
494			//        {
495			//          "address": "y1pjuR6PTujbk1lCy+JXJGNkeeA=",
496			//          "pub_key": {
497			//            "ed25519": "6Nz09YGHzwWxjczG0IhK4Iv0qY2IcX0P/5KitvRXTUc="
498			//          },
499			//          "voting_power": "19087034",
500			//          "proposer_priority": "0"
501			//        },
502			//        {
503			//          "address": "ZraWZuv3dufry+GXq6RmpxLicHY=",
504			//          "pub_key": {
505			//            "ed25519": "wB25StLxbzmD0uTiFiH6xySZd0H13kyanNUvvlUpa34="
506			//          },
507			//          "voting_power": "11979324",
508			//          "proposer_priority": "0"
509			//        }
510			//      ],
511			//      "proposer": {
512			//        "address": "xx7zwESZhHO0zsyumXcZdvGY1KU=",
513			//        "pub_key": {
514			//          "ed25519": "c28EquE3UMuallczX/Bu1UHpV9dM/yTaIVIR+ftY1g4="
515			//        },
516			//        "voting_power": "9284967",
517			//        "proposer_priority": "0"
518			//      },
519			//      "total_voting_power": "276866533"
520			//    }
521			//  },
522			//  "signer": "atone14d0ka5jcuhf9v3n7qpdpmc9qhgsd6uae2g7jxy"
523			//}
524			chainID       = "osmosis-1"
525			timestamp     = toTime("2025-09-25T07:55:56.299553312Z")
526			apphash       = b64Dec("DAHBO3lYaIFu9XmplThmF7ijXG7YwDZawGSDKKCHh7c=")
527			height        = uint64(44873552)
528			blockhash     = b64Dec("NpiImIJoaSaIucwNs5cqpgMsL/8wxEPYC3P0jA5aQSI=")
529			parsethash    = b64Dec("QqzwnLzvixIcUz+hPeUQjDV6NaLkFRKXACCxJIrBHzw=")
530			consensushash = b64Dec("JIS7qLmeUx4aP2QiNi59PuuZqDJRGZaKNV8FwJ7t1As=")
531			val1          = tendermint.NewValidator(
532				"y1pjuR6PTujbk1lCy+JXJGNkeeA=", "6Nz09YGHzwWxjczG0IhK4Iv0qY2IcX0P/5KitvRXTUc=", 2,
533			)
534			val2 = tendermint.NewValidator(
535				"ZraWZuv3dufry+GXq6RmpxLicHY=", "wB25StLxbzmD0uTiFiH6xySZd0H13kyanNUvvlUpa34=", 3,
536			)
537			valset     = tendermint.NewValset(val1, val2)
538			nextValset = tendermint.NewValset(val1, val2)
539			signatures = []tendermint.CommitSig{
540				{
541					BlockIDFlag:      tendermint.BlockIDFlagCommit,
542					ValidatorAddress: valset.Validators[0].Address,
543					Timestamp:        toTime("2025-09-25T07:55:57.306746166Z"),
544					Signature:        b64Dec("qtv1z4S2Q6T87vGQo0lrjRZqv9PrHIji4pTyviMnVyGx9td6eySdzwQwCthwmihU48ebNlFiMlFJ0CT891UmDg=="),
545				},
546				{
547					BlockIDFlag:      tendermint.BlockIDFlagCommit,
548					ValidatorAddress: valset.Validators[1].Address,
549					Timestamp:        toTime("2025-09-25T07:55:57.310583641Z"),
550					Signature:        b64Dec("Q5E6Kjma00n/T98rC9qJmoB6JTGFX/IB+mDVs4Wd1h0eJ8fabY/6oI8zdoU6/7W6VR6wjpHyWBsJrpGT6C0LCg=="),
551				},
552			}
553			header = &tendermint.Header{
554				Version: tendermint.Consensus{
555					Block: tendermint.BlockProtocol,
556					App:   0,
557				},
558				ChainID: chainID,
559				Height:  height,
560				Time:    timestamp,
561				LastBlockID: tendermint.BlockID{
562					Hash: blockhash,
563					PartSetHeader: tendermint.PartSetHeader{
564						Total: 1,
565						Hash:  parsethash,
566					},
567				},
568				LastCommitHash:     hash("last_commit_hash"),
569				DataHash:           hash("data_hash"),
570				ValidatorsHash:     valset.Hash(),
571				NextValidatorsHash: nextValset.Hash(),
572				ConsensusHash:      consensushash,
573				AppHash:            apphash,
574				LastResultsHash:    hash("last_result_hash"),
575				EvidenceHash:       hash("evidence_hash"),
576				ProposerAddress:    valset.Proposer.Address,
577			}
578
579			msg = &tendermint.MsgHeader{
580				Header: header,
581				Commit: &tendermint.Commit{
582					Height: height,
583					Round:  0,
584					BlockID: tendermint.BlockID{
585						Hash: blockhash,
586						PartSetHeader: tendermint.PartSetHeader{
587							Total: 1,
588							Hash:  parsethash,
589						},
590					},
591					Signatures: signatures,
592				},
593				ValidatorSet:      valset,
594				TrustedHeight:     types.Height{},
595				TrustedValidators: valset,
596			}
597
598			// expectedBytesToSign are computed from cometbft/types.Commit.VoteSignBytes() function
599			expectedBytesToSign = []string{
600				"6e08021150b7ac020000000022480a20369888988268692688b9cc0db3972aa6032c2fff30c443d80b73f48c0e5a412212240801122042acf09cbcef8b121c533fa13de5108c357a35a2e41512970020b1248ac11f3c2a0c088debd3c60610b6a6a2920132096f736d6f7369732d31",
601				"6e08021150b7ac020000000022480a20369888988268692688b9cc0db3972aa6032c2fff30c443d80b73f48c0e5a412212240801122042acf09cbcef8b121c533fa13de5108c357a35a2e41512970020b1248ac11f3c2a0c088debd3c60610d9c28c940132096f736d6f7369732d31",
602			}
603		)
604
605		testVotesBytesToSign(t, msg, expectedBytesToSign)
606	})
607
608	t.Run("update client 2", func(t *testing.T) {
609		var (
610			// Ref: https://www.mintscan.io/atomone/tx/DAE6D01666864DBB1F7AFDDD2C20B0C22D34974E9682B15A5049C24574EBEC70?sector=json
611			// (truncated with the 3 first validators)
612			//{
613			//  "@type": "/ibc.core.client.v1.MsgUpdateClient",
614			//  "client_id": "07-tendermint-1",
615			//  "client_message": {
616			//    "@type": "/ibc.lightclients.tendermint.v1.Header",
617			//    "signed_header": {
618			//      "header": {
619			//        "version": {
620			//          "block": "11",
621			//          "app": "0"
622			//        },
623			//        "chain_id": "beezee-1",
624			//        "height": "19282029",
625			//        "time": "2025-09-25T14:00:43.469479971Z",
626			//        "last_block_id": {
627			//          "hash": "cx4wWNgIMOrgnXOPFlXMTV6je4GbYZjRhgcJ7RNs0g4=",
628			//          "part_set_header": {
629			//            "total": 1,
630			//            "hash": "Nv1TMWmm4ze2Y1p3pBLF9VOPJaPOARp4ypZglD4HZmo="
631			//          }
632			//        },
633			//        "last_commit_hash": "zNO6+/eHsl8hih5kYSEDQql/vZUqTyuc6qJD6AnjrcI=",
634			//        "data_hash": "1EbR1y5AQgdELcRJTPf7ql6BJI5J9Ji9mTkdNXElcj4=",
635			//        "validators_hash": "ovLukv8mGP7nzYUiYBB8gUWQMvPZehcxQi86HsRGRW4=",
636			//        "next_validators_hash": "ovLukv8mGP7nzYUiYBB8gUWQMvPZehcxQi86HsRGRW4=",
637			//        "consensus_hash": "BOEs4QnQgrOaBfgH2qUBI3M1r0rPLx++gYSRGuYVAns=",
638			//        "app_hash": "XI9E+rmDbjwfn/EvQVp7CAqygaEI8Fingdh3xR+7aKw=",
639			//        "last_results_hash": "pgDffcPvKUJK3G2+adQmiLdiY5Rh7jOtRx81ytLcq4k=",
640			//        "evidence_hash": "47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=",
641			//        "proposer_address": "ysbGRHeOh4hD9fUbkM1RHP9bVfc="
642			//      },
643			//      "commit": {
644			//        "height": "19282029",
645			//        "round": 0,
646			//        "block_id": {
647			//          "hash": "q82PBaLn4Vg3oLm0v670BOm1Ay+CR69DyBqNA/V//l4=",
648			//          "part_set_header": {
649			//            "total": 1,
650			//            "hash": "PqBqLFCb6YhCQZFDnOeHzp1kHAbKs7fwHqOwnn8ha2s="
651			//          }
652			//        },
653			//        "signatures": [
654			//          {
655			//            "block_id_flag": "BLOCK_ID_FLAG_ABSENT",
656			//            "validator_address": null,
657			//            "timestamp": "0001-01-01T00:00:00Z",
658			//            "signature": null
659			//          },
660			//          {
661			//            "block_id_flag": "BLOCK_ID_FLAG_COMMIT",
662			//            "validator_address": "mYLjIF9AxwR+BBlkD9M3f+xqKNE=",
663			//            "timestamp": "2025-09-25T14:00:49.373544902Z",
664			//            "signature": "rgUFLD3Yk9JdcfWSqcscXeUZVY+Q7E1jQ1Eumkh/LT2fUI4dsk2NBeOhjyKaF5Ey/q6DwoINss6iSxNPj8s2Ag=="
665			//          },
666			//          {
667			//            "block_id_flag": "BLOCK_ID_FLAG_COMMIT",
668			//            "validator_address": "ysbGRHeOh4hD9fUbkM1RHP9bVfc=",
669			//            "timestamp": "2025-09-25T14:00:49.396076476Z",
670			//            "signature": "thHpLNT6555JHoNr10iBpp8NJemRMPKMwnV4QDL6gLpaYChqrQND+8K4hQrC8Ld2IAuIT92ZHFWOmoVH9bIcBw=="
671			//          }
672			//        ]
673			//      }
674			//    },
675			//    "validator_set": {
676			//      "validators": [
677			//        {
678			//          "address": "h9mY0OvyfYUX7rMfkXO+9bpEWdo=",
679			//          "pub_key": {
680			//            "ed25519": "wF3Daj7tqX8nVOJd2WCkMVvqyx/lPzuI5/y3wwSRwbY="
681			//          },
682			//          "voting_power": "7870904",
683			//          "proposer_priority": "0"
684			//        },
685			//        {
686			//          "address": "mYLjIF9AxwR+BBlkD9M3f+xqKNE=",
687			//          "pub_key": {
688			//            "ed25519": "dzCYrLu2sjpWSiEd2MVqsr+Q6ocBDUUUrRKBehGOeLM="
689			//          },
690			//          "voting_power": "7847038",
691			//          "proposer_priority": "0"
692			//        },
693			//		    {
694			// 		      "address": "ysbGRHeOh4hD9fUbkM1RHP9bVfc=",
695			// 		      "pub_key": {
696			// 		        "ed25519": "tYyou2DP3JWDrvKPtbAZpTCEtzqr9h1nsi/srNZwLiY="
697			// 		      },
698			// 		      "voting_power": "7381171",
699			// 		      "proposer_priority": "0"
700			// 		    }
701			//      ],
702			//      "proposer": {
703			//        "address": "ysbGRHeOh4hD9fUbkM1RHP9bVfc=",
704			//        "pub_key": {
705			//          "ed25519": "tYyou2DP3JWDrvKPtbAZpTCEtzqr9h1nsi/srNZwLiY="
706			//        },
707			//        "voting_power": "7381171",
708			//        "proposer_priority": "0"
709			//      },
710			//      "total_voting_power": "169513553"
711			//    },
712			//    "trusted_height": {
713			//      "revision_number": "1",
714			//      "revision_height": "19280755"
715			//    },
716			//    "trusted_validators": {
717			//      "validators": [
718			//        {
719			//          "address": "h9mY0OvyfYUX7rMfkXO+9bpEWdo=",
720			//          "pub_key": {
721			//            "ed25519": "wF3Daj7tqX8nVOJd2WCkMVvqyx/lPzuI5/y3wwSRwbY="
722			//          },
723			//          "voting_power": "7870904",
724			//          "proposer_priority": "0"
725			//        },
726			//        {
727			//          "address": "mYLjIF9AxwR+BBlkD9M3f+xqKNE=",
728			//          "pub_key": {
729			//            "ed25519": "dzCYrLu2sjpWSiEd2MVqsr+Q6ocBDUUUrRKBehGOeLM="
730			//          },
731			//          "voting_power": "7847038",
732			//          "proposer_priority": "0"
733			//        },
734			//		    {
735			// 		      "address": "ysbGRHeOh4hD9fUbkM1RHP9bVfc=",
736			// 		      "pub_key": {
737			// 		        "ed25519": "tYyou2DP3JWDrvKPtbAZpTCEtzqr9h1nsi/srNZwLiY="
738			// 		      },
739			// 		      "voting_power": "7381171",
740			// 		      "proposer_priority": "0"
741			// 		    }
742			//      ],
743			//      "proposer": {
744			//        "address": "IhfskqTnO6eehukPpsQYBCnrfEE=",
745			//        "pub_key": {
746			//          "ed25519": "4Payp8jGB+/HF2opX32Ox+rwJ4O/WlCRz4sKwyuQfMA="
747			//        },
748			//        "voting_power": "6849070",
749			//        "proposer_priority": "0"
750			//      },
751			//      "total_voting_power": "169513421"
752			//    }
753			//  },
754			//  "signer": "atone1z7p3628vmw8psjwwzlcak0xqvgyssmwpa3guqm"
755			//}
756			chainID       = "beezee-1"
757			timestamp     = toTime("2025-09-25T14:00:43.469479971Z")
758			apphash       = b64Dec("XI9E+rmDbjwfn/EvQVp7CAqygaEI8Fingdh3xR+7aKw=")
759			height        = uint64(19282029)
760			blockhash     = b64Dec("q82PBaLn4Vg3oLm0v670BOm1Ay+CR69DyBqNA/V//l4=")
761			parsethash    = b64Dec("PqBqLFCb6YhCQZFDnOeHzp1kHAbKs7fwHqOwnn8ha2s=")
762			consensushash = b64Dec("BOEs4QnQgrOaBfgH2qUBI3M1r0rPLx++gYSRGuYVAns=")
763			val1          = tendermint.NewValidator(
764				"h9mY0OvyfYUX7rMfkXO+9bpEWdo=", "wF3Daj7tqX8nVOJd2WCkMVvqyx/lPzuI5/y3wwSRwbY=", 2,
765			)
766			val2 = tendermint.NewValidator(
767				"mYLjIF9AxwR+BBlkD9M3f+xqKNE=", "dzCYrLu2sjpWSiEd2MVqsr+Q6ocBDUUUrRKBehGOeLM=", 3,
768			)
769			val3 = tendermint.NewValidator(
770				"ysbGRHeOh4hD9fUbkM1RHP9bVfc=", "tYyou2DP3JWDrvKPtbAZpTCEtzqr9h1nsi/srNZwLiY=", 4,
771			)
772			valset     = tendermint.NewValset(val1, val2, val3)
773			nextValset = tendermint.NewValset(val1, val2, val3)
774			signatures = []tendermint.CommitSig{
775				{
776					BlockIDFlag:      tendermint.BlockIDFlagAbsent,
777					ValidatorAddress: nil,
778					Timestamp:        time.Time{},
779					Signature:        nil,
780				},
781				{
782					BlockIDFlag:      tendermint.BlockIDFlagCommit,
783					ValidatorAddress: valset.Validators[1].Address,
784					Timestamp:        toTime("2025-09-25T14:00:49.373544902Z"),
785					Signature:        b64Dec("rgUFLD3Yk9JdcfWSqcscXeUZVY+Q7E1jQ1Eumkh/LT2fUI4dsk2NBeOhjyKaF5Ey/q6DwoINss6iSxNPj8s2Ag=="),
786				},
787				{
788					BlockIDFlag:      tendermint.BlockIDFlagCommit,
789					ValidatorAddress: valset.Validators[2].Address,
790					Timestamp:        toTime("2025-09-25T14:00:49.396076476Z"),
791					Signature:        b64Dec("thHpLNT6555JHoNr10iBpp8NJemRMPKMwnV4QDL6gLpaYChqrQND+8K4hQrC8Ld2IAuIT92ZHFWOmoVH9bIcBw=="),
792				},
793			}
794			header = &tendermint.Header{
795				Version: tendermint.Consensus{
796					Block: tendermint.BlockProtocol,
797					App:   0,
798				},
799				ChainID: chainID,
800				Height:  height,
801				Time:    timestamp,
802				LastBlockID: tendermint.BlockID{
803					Hash: blockhash,
804					PartSetHeader: tendermint.PartSetHeader{
805						Total: 1,
806						Hash:  parsethash,
807					},
808				},
809				LastCommitHash:     hash("last_commit_hash"),
810				DataHash:           hash("data_hash"),
811				ValidatorsHash:     valset.Hash(),
812				NextValidatorsHash: nextValset.Hash(),
813				ConsensusHash:      consensushash,
814				AppHash:            apphash,
815				LastResultsHash:    hash("last_result_hash"),
816				EvidenceHash:       hash("evidence_hash"),
817				ProposerAddress:    valset.Proposer.Address,
818			}
819
820			msg = &tendermint.MsgHeader{
821				Header: header,
822				Commit: &tendermint.Commit{
823					Height: height,
824					Round:  0,
825					BlockID: tendermint.BlockID{
826						Hash: blockhash,
827						PartSetHeader: tendermint.PartSetHeader{
828							Total: 1,
829							Hash:  parsethash,
830						},
831					},
832					Signatures: signatures,
833				},
834				ValidatorSet:      valset,
835				TrustedHeight:     types.Height{},
836				TrustedValidators: valset,
837			}
838
839			// expectedBytesToSign are computed from cometbft/types.Commit.VoteSignBytes() function
840			expectedBytesToSign = []string{
841				"220802116d382601000000002a0b088092b8c398feffffff0132086265657a65652d31",
842				"6d0802116d3826010000000022480a20abcd8f05a2e7e15837a0b9b4bfaef404e9b5032f8247af43c81a8d03f57ffe5e1224080112203ea06a2c509be988424191439ce787ce9d641c06cab3b7f01ea3b09e7f216b6b2a0c089196d5c60610c6af8fb20132086265657a65652d31",
843				"6d0802116d3826010000000022480a20abcd8f05a2e7e15837a0b9b4bfaef404e9b5032f8247af43c81a8d03f57ffe5e1224080112203ea06a2c509be988424191439ce787ce9d641c06cab3b7f01ea3b09e7f216b6b2a0c089196d5c60610bccbeebc0132086265657a65652d31",
844			}
845		)
846
847		testVotesBytesToSign(t, msg, expectedBytesToSign)
848	})
849}
850
851func testVotesBytesToSign(t *testing.T, msg *tendermint.MsgHeader, expectedBytesToSign []string) {
852	for idx, val := range msg.ValidatorSet.Validators {
853		bz := msg.Commit.BytesToSign(msg.Header.ChainID, idx)
854		if h := hex.EncodeToString(bz); h != expectedBytesToSign[idx] {
855			t.Errorf("wrong bytes-to-sign, got %s, want %s", h, expectedBytesToSign[idx])
856			continue
857		}
858		if msg.Commit.Signatures[idx].BlockIDFlag != tendermint.BlockIDFlagCommit {
859			// skip non signed block
860			continue
861		}
862
863		v := ed25519.Verify(val.PubKey, bz, msg.Commit.Signatures[idx].Signature)
864
865		if !v {
866			t.Errorf("failed to verify signature for validator index %d", idx)
867		}
868	}
869}
870
871func b64Dec(s string) []byte {
872	bz, err := base64.StdEncoding.DecodeString(s)
873	if err != nil {
874		panic(err)
875	}
876	return bz
877}
878
879func toTime(s string) time.Time {
880	t, err := time.Parse(time.RFC3339Nano, s)
881	if err != nil {
882		panic(err)
883	}
884	return t
885}
886
887func genAddr(seed string) []byte {
888	return hash(seed)[:tendermint.AddressSize]
889}
890
891func genSignature(seed string) []byte {
892	return hash(seed)[:tendermint.MaxSignatureSize]
893}
894
895func genPubkey(seed string) []byte {
896	return hash(seed)[:32]
897}
898
899func hash(s string) []byte {
900	h := sha256.Sum256([]byte(s))
901	return h[:]
902}
903
904func newMsgHeader(chainID string, timestamp time.Time,
905	apphash []byte, height uint64, trustedHeight types.Height,
906	valset, nextValset, trustedValset *tendermint.ValidatorSet,
907	signatures []tendermint.CommitSig) *tendermint.MsgHeader {
908	header := &tendermint.Header{
909		Version: tendermint.Consensus{
910			Block: tendermint.BlockProtocol,
911			App:   0,
912		},
913		ChainID: chainID,
914		Height:  height,
915		Time:    timestamp,
916		LastBlockID: tendermint.BlockID{
917			Hash: hash("last_block_hash"),
918			PartSetHeader: tendermint.PartSetHeader{
919				Total: 1,
920				Hash:  hash("last_block_partset_hash"),
921			},
922		},
923		LastCommitHash:     hash("last_commit_hash"),
924		DataHash:           hash("data_hash"),
925		ValidatorsHash:     valset.Hash(),
926		NextValidatorsHash: nextValset.Hash(),
927		ConsensusHash:      hash("consensus_hash"),
928		AppHash:            apphash,
929		LastResultsHash:    hash("last_results_hash"),
930		EvidenceHash:       hash("evidence_hash"),
931		ProposerAddress:    valset.Validators[0].Address,
932	}
933
934	return &tendermint.MsgHeader{
935		Header: header,
936		Commit: &tendermint.Commit{
937			Height: height,
938			Round:  0,
939			BlockID: tendermint.BlockID{
940				Hash: header.Hash(), // compute expected block hash
941				PartSetHeader: tendermint.PartSetHeader{
942					Total: 1,
943					Hash:  hash("block_partset_hash"),
944				},
945			},
946			Signatures: signatures,
947		},
948		ValidatorSet:      valset,
949		TrustedHeight:     trustedHeight,
950		TrustedValidators: trustedValset,
951	}
952}