cfilter.go raw
1 package btcwire
2
3 import (
4 "fmt"
5
6 "git.smesh.lol/nostrino/pkg/crypto/chainhash"
7 )
8
9 // Compact filter constants from BIP157 and BIP158.
10 const (
11 // FilterTypeBasic is the basic compact filter type defined by BIP158.
12 FilterTypeBasic uint8 = 0
13
14 // MaxFilterHashesPerMsg is the largest number of filter hashes a
15 // cfheaders message may carry. BIP157 limits a request to 2000 blocks.
16 MaxFilterHashesPerMsg = 2000
17
18 // MaxFilterHeadersPerMsg is the largest number of filter headers a
19 // cfcheckpt message may carry.
20 MaxFilterHeadersPerMsg = 1 << 16
21
22 // MaxCompactFilterSize is the largest compact filter accepted in a
23 // cfilter message.
24 MaxCompactFilterSize = MaxPayload
25 )
26
27 // FilterHeader combines a compact filter hash with the previous filter header
28 // to produce the next filter header, as defined by BIP157:
29 //
30 // SHA256d(filterHash || prevFilterHeader)
31 //
32 // The order of the two hashes matters: the new filter hash comes first.
33 func FilterHeader(filterHash, prevFilterHeader chainhash.Hash) chainhash.Hash {
34 var buf [2 * chainhash.HashSize]byte
35 copy(buf[:chainhash.HashSize], filterHash[:])
36 copy(buf[chainhash.HashSize:], prevFilterHeader[:])
37 return chainhash.DoubleHashH(buf[:])
38 }
39
40 // GetCFHeaders requests the compact filter hashes in a block range, together
41 // with the filter header that precedes the range.
42 type GetCFHeaders struct {
43 // FilterType is the compact filter type, normally FilterTypeBasic.
44 FilterType uint8
45
46 // StartHeight is the height of the first block in the range.
47 StartHeight uint32
48
49 // StopHash is the hash of the last block in the range, or the zero hash
50 // to request the maximum number of blocks.
51 StopHash chainhash.Hash
52 }
53
54 // EncodePayload returns the wire encoding of the getcfheaders message.
55 func (g *GetCFHeaders) EncodePayload() ([]byte, error) {
56 var e Encoder
57 e.Uint8(g.FilterType)
58 e.Uint32(g.StartHeight)
59 e.Hash(g.StopHash)
60 return e.Bytes(), nil
61 }
62
63 // DecodePayload replaces g with the getcfheaders message decoded from b.
64 func (g *GetCFHeaders) DecodePayload(b []byte) error {
65 d := NewDecoder(b)
66 filterType, err := d.Uint8()
67 if err != nil {
68 return fmt.Errorf("btcwire: decoding getcfheaders filter type: %w", err)
69 }
70 startHeight, err := d.Uint32()
71 if err != nil {
72 return fmt.Errorf("btcwire: decoding getcfheaders start height: %w", err)
73 }
74 stopHash, err := d.Hash()
75 if err != nil {
76 return fmt.Errorf("btcwire: decoding getcfheaders stop hash: %w", err)
77 }
78 *g = GetCFHeaders{
79 FilterType: filterType,
80 StartHeight: startHeight,
81 StopHash: stopHash,
82 }
83 return nil
84 }
85
86 // CFHeaders answers a getcfheaders message with the filter hashes of a block
87 // range and the filter header that precedes it.
88 type CFHeaders struct {
89 // FilterType is the compact filter type, normally FilterTypeBasic.
90 FilterType uint8
91
92 // StopHash is the hash of the last block in the range.
93 StopHash chainhash.Hash
94
95 // PrevFilterHeader is the filter header of the block before the range,
96 // which anchors the chain of hashes that follows.
97 PrevFilterHeader chainhash.Hash
98
99 // FilterHashes holds the compact filter hash of each block in the range,
100 // in height order.
101 FilterHashes []chainhash.Hash
102 }
103
104 // EncodePayload returns the wire encoding of the cfheaders message.
105 func (c *CFHeaders) EncodePayload() ([]byte, error) {
106 if len(c.FilterHashes) > MaxFilterHashesPerMsg {
107 return nil, fmt.Errorf(
108 "btcwire: cfheaders carries %d filter hashes, maximum is %d: %w",
109 len(c.FilterHashes), MaxFilterHashesPerMsg, ErrTooManyItems,
110 )
111 }
112 var e Encoder
113 e.Uint8(c.FilterType)
114 e.Hash(c.StopHash)
115 e.Hash(c.PrevFilterHeader)
116 encodeHashes(&e, c.FilterHashes)
117 return e.Bytes(), nil
118 }
119
120 // DecodePayload replaces c with the cfheaders message decoded from b.
121 func (c *CFHeaders) DecodePayload(b []byte) error {
122 d := NewDecoder(b)
123 filterType, err := d.Uint8()
124 if err != nil {
125 return fmt.Errorf("btcwire: decoding cfheaders filter type: %w", err)
126 }
127 stopHash, err := d.Hash()
128 if err != nil {
129 return fmt.Errorf("btcwire: decoding cfheaders stop hash: %w", err)
130 }
131 prevFilterHeader, err := d.Hash()
132 if err != nil {
133 return fmt.Errorf("btcwire: decoding cfheaders previous filter header: %w", err)
134 }
135 filterHashes, err := decodeHashes(d, MaxFilterHashesPerMsg, "cfheaders filter hash")
136 if err != nil {
137 return err
138 }
139 *c = CFHeaders{
140 FilterType: filterType,
141 StopHash: stopHash,
142 PrevFilterHeader: prevFilterHeader,
143 FilterHashes: filterHashes,
144 }
145 return nil
146 }
147
148 // GetCFilters requests the compact filters of a block range.
149 type GetCFilters struct {
150 // FilterType is the compact filter type, normally FilterTypeBasic.
151 FilterType uint8
152
153 // StartHeight is the height of the first block in the range.
154 StartHeight uint32
155
156 // StopHash is the hash of the last block in the range, or the zero hash
157 // to request the maximum number of blocks.
158 StopHash chainhash.Hash
159 }
160
161 // EncodePayload returns the wire encoding of the getcfilters message.
162 func (g *GetCFilters) EncodePayload() ([]byte, error) {
163 var e Encoder
164 e.Uint8(g.FilterType)
165 e.Uint32(g.StartHeight)
166 e.Hash(g.StopHash)
167 return e.Bytes(), nil
168 }
169
170 // DecodePayload replaces g with the getcfilters message decoded from b.
171 func (g *GetCFilters) DecodePayload(b []byte) error {
172 d := NewDecoder(b)
173 filterType, err := d.Uint8()
174 if err != nil {
175 return fmt.Errorf("btcwire: decoding getcfilters filter type: %w", err)
176 }
177 startHeight, err := d.Uint32()
178 if err != nil {
179 return fmt.Errorf("btcwire: decoding getcfilters start height: %w", err)
180 }
181 stopHash, err := d.Hash()
182 if err != nil {
183 return fmt.Errorf("btcwire: decoding getcfilters stop hash: %w", err)
184 }
185 *g = GetCFilters{
186 FilterType: filterType,
187 StartHeight: startHeight,
188 StopHash: stopHash,
189 }
190 return nil
191 }
192
193 // CFilter carries one block's compact filter.
194 type CFilter struct {
195 // FilterType is the compact filter type, normally FilterTypeBasic.
196 FilterType uint8
197
198 // BlockHash is the hash of the block the filter covers.
199 BlockHash chainhash.Hash
200
201 // Filter is the raw compact filter, including its header byte.
202 Filter []byte
203 }
204
205 // EncodePayload returns the wire encoding of the cfilter message.
206 func (c *CFilter) EncodePayload() ([]byte, error) {
207 var e Encoder
208 e.Uint8(c.FilterType)
209 e.Hash(c.BlockHash)
210 if err := WriteVarBytes(&e, c.Filter); err != nil {
211 return nil, fmt.Errorf("btcwire: encoding cfilter filter: %w", err)
212 }
213 return e.Bytes(), nil
214 }
215
216 // DecodePayload replaces c with the cfilter message decoded from b.
217 func (c *CFilter) DecodePayload(b []byte) error {
218 d := NewDecoder(b)
219 filterType, err := d.Uint8()
220 if err != nil {
221 return fmt.Errorf("btcwire: decoding cfilter filter type: %w", err)
222 }
223 blockHash, err := d.Hash()
224 if err != nil {
225 return fmt.Errorf("btcwire: decoding cfilter block hash: %w", err)
226 }
227 filter, err := ReadVarBytes(d, MaxCompactFilterSize)
228 if err != nil {
229 return fmt.Errorf("btcwire: decoding cfilter filter: %w", err)
230 }
231 *c = CFilter{
232 FilterType: filterType,
233 BlockHash: blockHash,
234 Filter: filter,
235 }
236 return nil
237 }
238
239 // GetCFCheckpt requests the filter headers at fixed intervals up to a stop
240 // hash, which lets a client verify a range of filter headers with a small
241 // download.
242 type GetCFCheckpt struct {
243 // FilterType is the compact filter type, normally FilterTypeBasic.
244 FilterType uint8
245
246 // StopHash is the hash of the last block of interest.
247 StopHash chainhash.Hash
248 }
249
250 // EncodePayload returns the wire encoding of the getcfcheckpt message.
251 func (g *GetCFCheckpt) EncodePayload() ([]byte, error) {
252 var e Encoder
253 e.Uint8(g.FilterType)
254 e.Hash(g.StopHash)
255 return e.Bytes(), nil
256 }
257
258 // DecodePayload replaces g with the getcfcheckpt message decoded from b.
259 func (g *GetCFCheckpt) DecodePayload(b []byte) error {
260 d := NewDecoder(b)
261 filterType, err := d.Uint8()
262 if err != nil {
263 return fmt.Errorf("btcwire: decoding getcfcheckpt filter type: %w", err)
264 }
265 stopHash, err := d.Hash()
266 if err != nil {
267 return fmt.Errorf("btcwire: decoding getcfcheckpt stop hash: %w", err)
268 }
269 *g = GetCFCheckpt{FilterType: filterType, StopHash: stopHash}
270 return nil
271 }
272
273 // CFCheckpt answers a getcfcheckpt message with the filter headers at every
274 // interval boundary up to the stop hash.
275 type CFCheckpt struct {
276 // FilterType is the compact filter type, normally FilterTypeBasic.
277 FilterType uint8
278
279 // StopHash is the hash of the last block covered.
280 StopHash chainhash.Hash
281
282 // FilterHeaders holds the filter headers, in height order and evenly
283 // spaced 1000 blocks apart.
284 FilterHeaders []chainhash.Hash
285 }
286
287 // EncodePayload returns the wire encoding of the cfcheckpt message.
288 func (c *CFCheckpt) EncodePayload() ([]byte, error) {
289 if len(c.FilterHeaders) > MaxFilterHeadersPerMsg {
290 return nil, fmt.Errorf(
291 "btcwire: cfcheckpt carries %d filter headers, maximum is %d: %w",
292 len(c.FilterHeaders), MaxFilterHeadersPerMsg, ErrTooManyItems,
293 )
294 }
295 var e Encoder
296 e.Uint8(c.FilterType)
297 e.Hash(c.StopHash)
298 encodeHashes(&e, c.FilterHeaders)
299 return e.Bytes(), nil
300 }
301
302 // DecodePayload replaces c with the cfcheckpt message decoded from b.
303 func (c *CFCheckpt) DecodePayload(b []byte) error {
304 d := NewDecoder(b)
305 filterType, err := d.Uint8()
306 if err != nil {
307 return fmt.Errorf("btcwire: decoding cfcheckpt filter type: %w", err)
308 }
309 stopHash, err := d.Hash()
310 if err != nil {
311 return fmt.Errorf("btcwire: decoding cfcheckpt stop hash: %w", err)
312 }
313 filterHeaders, err := decodeHashes(d, MaxFilterHeadersPerMsg, "cfcheckpt filter header")
314 if err != nil {
315 return err
316 }
317 *c = CFCheckpt{
318 FilterType: filterType,
319 StopHash: stopHash,
320 FilterHeaders: filterHeaders,
321 }
322 return nil
323 }
324
325 // encodeHashes appends a compact size count followed by the hashes.
326 func encodeHashes(e *Encoder, hashes []chainhash.Hash) {
327 e.CompactSize(uint64(len(hashes)))
328 for i := range hashes {
329 e.Hash(hashes[i])
330 }
331 }
332
333 // decodeHashes decodes a compact size count followed by that many hashes.
334 func decodeHashes(d *Decoder, limit int, what string) ([]chainhash.Hash, error) {
335 count, err := d.count(limit, chainhash.HashSize, what)
336 if err != nil {
337 return nil, err
338 }
339 hashes := make([]chainhash.Hash, count)
340 for i := range hashes {
341 if hashes[i], err = d.Hash(); err != nil {
342 return nil, fmt.Errorf("btcwire: decoding %s %d: %w", what, i, err)
343 }
344 }
345 return hashes, nil
346 }
347