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