encoding.gno
4.35 Kb · 199 lines
1package cometbls
2
3import (
4 "errors"
5
6 "gno.land/p/aib/encoding/proto"
7 abienc "gno.land/p/onbloc/encoding/abi"
8 "gno.land/p/onbloc/ibc/union/types"
9)
10
11// Wire formats for CometBLS client messages.
12//
13// Each light client owns the parsing of its own client messages, since the
14// header layout differs per client. CometBLS uses:
15// - Header: Ethereum ABI tuple (mirrors Union's Solidity Header struct)
16// - Misbehaviour: protobuf with two embedded ABI-encoded headers
17//
18// Header ABI layout (Solidity params tuple form):
19//
20// [0] uint64 Height
21// [1] uint64 TimeSeconds
22// [2] uint64 TimeNanos
23// [3] bytes32 ValidatorsHash
24// [4] bytes32 NextValidatorsHash
25// [5] bytes32 AppHash
26// [6] uint64 TrustedHeight
27// [7] bytes ZeroKnowledgeProof (dynamic)
28var headerSchema = abienc.Schema{Fields: []abienc.Field{
29 {Type: abienc.TypeUint64},
30 {Type: abienc.TypeUint64},
31 {Type: abienc.TypeUint64},
32 {Type: abienc.TypeBytes32},
33 {Type: abienc.TypeBytes32},
34 {Type: abienc.TypeBytes32},
35 {Type: abienc.TypeUint64},
36 {Type: abienc.TypeBytes},
37}}
38
39// maxTimeNanos bounds the sub-second component of a header timestamp.
40const maxTimeNanos = 1_000_000_000
41
42// EncodeHeader ABI-encodes a Header. ValidatorsHash, NextValidatorsHash and
43// AppHash must each be exactly 32 bytes.
44func EncodeHeader(h *Header) ([]byte, error) {
45 if h == nil || h.SignedHeader == nil {
46 return nil, errors.New("nil header")
47 }
48
49 lh := h.SignedHeader
50
51 vh, err := toBytes32(lh.ValidatorsHash)
52 if err != nil {
53 return nil, err
54 }
55
56 nvh, err := toBytes32(lh.NextValidatorsHash)
57 if err != nil {
58 return nil, err
59 }
60
61 ah, err := toBytes32(lh.AppHash)
62 if err != nil {
63 return nil, err
64 }
65
66 var trustedHeight uint64
67 if h.TrustedHeight != nil {
68 trustedHeight = h.TrustedHeight.RevisionHeight
69 }
70
71 return abienc.Encode(headerSchema, []any{
72 uint64(lh.Height),
73 uint64(lh.TimeSeconds),
74 uint64(lh.TimeNanos),
75 vh, nvh, ah,
76 trustedHeight,
77 h.ZeroKnowledgeProof,
78 })
79}
80
81// DecodeHeader ABI-decodes a Header.
82func DecodeHeader(buf []byte) (*Header, error) {
83 vals, err := abienc.Decode(headerSchema, buf)
84 if err != nil {
85 return nil, errors.New("" + err.Error())
86 }
87
88 timeSeconds := vals[1].(uint64)
89
90 timeNanos := vals[2].(uint64)
91 if timeNanos >= maxTimeNanos {
92 return nil, errors.New("TimeNanos out of range")
93 }
94
95 vh := vals[3].([32]byte)
96 nvh := vals[4].([32]byte)
97 ah := vals[5].([32]byte)
98
99 trustedHeight := types.NewHeight(vals[6].(uint64))
100
101 return &Header{
102 SignedHeader: &LightHeader{
103 Height: int64(vals[0].(uint64)),
104 TimeSeconds: int64(timeSeconds),
105 TimeNanos: int32(timeNanos),
106 ValidatorsHash: vh[:],
107 NextValidatorsHash: nvh[:],
108 AppHash: ah[:],
109 },
110 TrustedHeight: &trustedHeight,
111 ZeroKnowledgeProof: vals[7].([]byte),
112 }, nil
113}
114
115// EncodeMisbehaviour protobuf-encodes a Misbehaviour: field 1 is Header1 and
116// field 2 is Header2, each an ABI-encoded header.
117func EncodeMisbehaviour(m *Misbehaviour) ([]byte, error) {
118 if m == nil {
119 return nil, errors.New("nil misbehaviour")
120 }
121
122 h1, err := EncodeHeader(m.Header1)
123 if err != nil {
124 return nil, err
125 }
126
127 h2, err := EncodeHeader(m.Header2)
128 if err != nil {
129 return nil, err
130 }
131
132 buf := make([]byte, 0, len(h1)+len(h2)+8)
133 buf = proto.AppendLengthDelimited(buf, 1, h1)
134 buf = proto.AppendLengthDelimited(buf, 2, h2)
135
136 return buf, nil
137}
138
139// DecodeMisbehaviour protobuf-decodes a Misbehaviour.
140func DecodeMisbehaviour(buf []byte) (*Misbehaviour, error) {
141 m := &Misbehaviour{}
142
143 pos := 0
144 for pos < len(buf) {
145 fieldNum, wireType, newPos, err := decodeTag(buf, pos)
146 if err != nil {
147 return nil, err
148 }
149
150 pos = newPos
151
152 if wireType != proto.LEN {
153 pos, err = skipField(buf, pos, wireType)
154 if err != nil {
155 return nil, err
156 }
157
158 continue
159 }
160
161 s, newPos, err := proto.DecodeString(buf, pos)
162 if err != nil {
163 return nil, err
164 }
165
166 pos = newPos
167
168 header, err := DecodeHeader([]byte(s))
169 if err != nil {
170 return nil, err
171 }
172
173 switch fieldNum {
174 case 1:
175 m.Header1 = header
176 case 2:
177 m.Header2 = header
178 }
179 }
180
181 if m.Header1 == nil || m.Header2 == nil {
182 return nil, errors.New("misbehaviour missing a header")
183 }
184
185 return m, nil
186}
187
188// toBytes32 converts a 32-byte slice into a [32]byte array, returning an error
189// for any other length.
190func toBytes32(b []byte) ([32]byte, error) {
191 var out [32]byte
192 if len(b) != 32 {
193 return out, errors.New("must be 32 bytes")
194 }
195
196 copy(out[:], b)
197
198 return out, nil
199}