package btcwire import ( "fmt" "git.smesh.lol/nostrino/pkg/crypto/chainhash" ) // Compact filter constants from BIP157 and BIP158. const ( // FilterTypeBasic is the basic compact filter type defined by BIP158. FilterTypeBasic uint8 = 0 // MaxFilterHashesPerMsg is the largest number of filter hashes a // cfheaders message may carry. BIP157 limits a request to 2000 blocks. MaxFilterHashesPerMsg = 2000 // MaxFilterHeadersPerMsg is the largest number of filter headers a // cfcheckpt message may carry. MaxFilterHeadersPerMsg = 1 << 16 // MaxCompactFilterSize is the largest compact filter accepted in a // cfilter message. MaxCompactFilterSize = MaxPayload ) // FilterHeader combines a compact filter hash with the previous filter header // to produce the next filter header, as defined by BIP157: // // SHA256d(filterHash || prevFilterHeader) // // The order of the two hashes matters: the new filter hash comes first. func FilterHeader(filterHash, prevFilterHeader chainhash.Hash) chainhash.Hash { var buf [2 * chainhash.HashSize]byte copy(buf[:chainhash.HashSize], filterHash[:]) copy(buf[chainhash.HashSize:], prevFilterHeader[:]) return chainhash.DoubleHashH(buf[:]) } // GetCFHeaders requests the compact filter hashes in a block range, together // with the filter header that precedes the range. type GetCFHeaders struct { // FilterType is the compact filter type, normally FilterTypeBasic. FilterType uint8 // StartHeight is the height of the first block in the range. StartHeight uint32 // StopHash is the hash of the last block in the range, or the zero hash // to request the maximum number of blocks. StopHash chainhash.Hash } // EncodePayload returns the wire encoding of the getcfheaders message. func (g *GetCFHeaders) EncodePayload() ([]byte, error) { var e Encoder e.Uint8(g.FilterType) e.Uint32(g.StartHeight) e.Hash(g.StopHash) return e.Bytes(), nil } // DecodePayload replaces g with the getcfheaders message decoded from b. func (g *GetCFHeaders) DecodePayload(b []byte) error { d := NewDecoder(b) filterType, err := d.Uint8() if err != nil { return fmt.Errorf("btcwire: decoding getcfheaders filter type: %w", err) } startHeight, err := d.Uint32() if err != nil { return fmt.Errorf("btcwire: decoding getcfheaders start height: %w", err) } stopHash, err := d.Hash() if err != nil { return fmt.Errorf("btcwire: decoding getcfheaders stop hash: %w", err) } *g = GetCFHeaders{ FilterType: filterType, StartHeight: startHeight, StopHash: stopHash, } return nil } // CFHeaders answers a getcfheaders message with the filter hashes of a block // range and the filter header that precedes it. type CFHeaders struct { // FilterType is the compact filter type, normally FilterTypeBasic. FilterType uint8 // StopHash is the hash of the last block in the range. StopHash chainhash.Hash // PrevFilterHeader is the filter header of the block before the range, // which anchors the chain of hashes that follows. PrevFilterHeader chainhash.Hash // FilterHashes holds the compact filter hash of each block in the range, // in height order. FilterHashes []chainhash.Hash } // EncodePayload returns the wire encoding of the cfheaders message. func (c *CFHeaders) EncodePayload() ([]byte, error) { if len(c.FilterHashes) > MaxFilterHashesPerMsg { return nil, fmt.Errorf( "btcwire: cfheaders carries %d filter hashes, maximum is %d: %w", len(c.FilterHashes), MaxFilterHashesPerMsg, ErrTooManyItems, ) } var e Encoder e.Uint8(c.FilterType) e.Hash(c.StopHash) e.Hash(c.PrevFilterHeader) encodeHashes(&e, c.FilterHashes) return e.Bytes(), nil } // DecodePayload replaces c with the cfheaders message decoded from b. func (c *CFHeaders) DecodePayload(b []byte) error { d := NewDecoder(b) filterType, err := d.Uint8() if err != nil { return fmt.Errorf("btcwire: decoding cfheaders filter type: %w", err) } stopHash, err := d.Hash() if err != nil { return fmt.Errorf("btcwire: decoding cfheaders stop hash: %w", err) } prevFilterHeader, err := d.Hash() if err != nil { return fmt.Errorf("btcwire: decoding cfheaders previous filter header: %w", err) } filterHashes, err := decodeHashes(d, MaxFilterHashesPerMsg, "cfheaders filter hash") if err != nil { return err } *c = CFHeaders{ FilterType: filterType, StopHash: stopHash, PrevFilterHeader: prevFilterHeader, FilterHashes: filterHashes, } return nil } // GetCFilters requests the compact filters of a block range. type GetCFilters struct { // FilterType is the compact filter type, normally FilterTypeBasic. FilterType uint8 // StartHeight is the height of the first block in the range. StartHeight uint32 // StopHash is the hash of the last block in the range, or the zero hash // to request the maximum number of blocks. StopHash chainhash.Hash } // EncodePayload returns the wire encoding of the getcfilters message. func (g *GetCFilters) EncodePayload() ([]byte, error) { var e Encoder e.Uint8(g.FilterType) e.Uint32(g.StartHeight) e.Hash(g.StopHash) return e.Bytes(), nil } // DecodePayload replaces g with the getcfilters message decoded from b. func (g *GetCFilters) DecodePayload(b []byte) error { d := NewDecoder(b) filterType, err := d.Uint8() if err != nil { return fmt.Errorf("btcwire: decoding getcfilters filter type: %w", err) } startHeight, err := d.Uint32() if err != nil { return fmt.Errorf("btcwire: decoding getcfilters start height: %w", err) } stopHash, err := d.Hash() if err != nil { return fmt.Errorf("btcwire: decoding getcfilters stop hash: %w", err) } *g = GetCFilters{ FilterType: filterType, StartHeight: startHeight, StopHash: stopHash, } return nil } // CFilter carries one block's compact filter. type CFilter struct { // FilterType is the compact filter type, normally FilterTypeBasic. FilterType uint8 // BlockHash is the hash of the block the filter covers. BlockHash chainhash.Hash // Filter is the raw compact filter, including its header byte. Filter []byte } // EncodePayload returns the wire encoding of the cfilter message. func (c *CFilter) EncodePayload() ([]byte, error) { var e Encoder e.Uint8(c.FilterType) e.Hash(c.BlockHash) if err := WriteVarBytes(&e, c.Filter); err != nil { return nil, fmt.Errorf("btcwire: encoding cfilter filter: %w", err) } return e.Bytes(), nil } // DecodePayload replaces c with the cfilter message decoded from b. func (c *CFilter) DecodePayload(b []byte) error { d := NewDecoder(b) filterType, err := d.Uint8() if err != nil { return fmt.Errorf("btcwire: decoding cfilter filter type: %w", err) } blockHash, err := d.Hash() if err != nil { return fmt.Errorf("btcwire: decoding cfilter block hash: %w", err) } filter, err := ReadVarBytes(d, MaxCompactFilterSize) if err != nil { return fmt.Errorf("btcwire: decoding cfilter filter: %w", err) } *c = CFilter{ FilterType: filterType, BlockHash: blockHash, Filter: filter, } return nil } // GetCFCheckpt requests the filter headers at fixed intervals up to a stop // hash, which lets a client verify a range of filter headers with a small // download. type GetCFCheckpt struct { // FilterType is the compact filter type, normally FilterTypeBasic. FilterType uint8 // StopHash is the hash of the last block of interest. StopHash chainhash.Hash } // EncodePayload returns the wire encoding of the getcfcheckpt message. func (g *GetCFCheckpt) EncodePayload() ([]byte, error) { var e Encoder e.Uint8(g.FilterType) e.Hash(g.StopHash) return e.Bytes(), nil } // DecodePayload replaces g with the getcfcheckpt message decoded from b. func (g *GetCFCheckpt) DecodePayload(b []byte) error { d := NewDecoder(b) filterType, err := d.Uint8() if err != nil { return fmt.Errorf("btcwire: decoding getcfcheckpt filter type: %w", err) } stopHash, err := d.Hash() if err != nil { return fmt.Errorf("btcwire: decoding getcfcheckpt stop hash: %w", err) } *g = GetCFCheckpt{FilterType: filterType, StopHash: stopHash} return nil } // CFCheckpt answers a getcfcheckpt message with the filter headers at every // interval boundary up to the stop hash. type CFCheckpt struct { // FilterType is the compact filter type, normally FilterTypeBasic. FilterType uint8 // StopHash is the hash of the last block covered. StopHash chainhash.Hash // FilterHeaders holds the filter headers, in height order and evenly // spaced 1000 blocks apart. FilterHeaders []chainhash.Hash } // EncodePayload returns the wire encoding of the cfcheckpt message. func (c *CFCheckpt) EncodePayload() ([]byte, error) { if len(c.FilterHeaders) > MaxFilterHeadersPerMsg { return nil, fmt.Errorf( "btcwire: cfcheckpt carries %d filter headers, maximum is %d: %w", len(c.FilterHeaders), MaxFilterHeadersPerMsg, ErrTooManyItems, ) } var e Encoder e.Uint8(c.FilterType) e.Hash(c.StopHash) encodeHashes(&e, c.FilterHeaders) return e.Bytes(), nil } // DecodePayload replaces c with the cfcheckpt message decoded from b. func (c *CFCheckpt) DecodePayload(b []byte) error { d := NewDecoder(b) filterType, err := d.Uint8() if err != nil { return fmt.Errorf("btcwire: decoding cfcheckpt filter type: %w", err) } stopHash, err := d.Hash() if err != nil { return fmt.Errorf("btcwire: decoding cfcheckpt stop hash: %w", err) } filterHeaders, err := decodeHashes(d, MaxFilterHeadersPerMsg, "cfcheckpt filter header") if err != nil { return err } *c = CFCheckpt{ FilterType: filterType, StopHash: stopHash, FilterHeaders: filterHeaders, } return nil } // encodeHashes appends a compact size count followed by the hashes. func encodeHashes(e *Encoder, hashes []chainhash.Hash) { e.CompactSize(uint64(len(hashes))) for i := range hashes { e.Hash(hashes[i]) } } // decodeHashes decodes a compact size count followed by that many hashes. func decodeHashes(d *Decoder, limit int, what string) ([]chainhash.Hash, error) { count, err := d.count(limit, chainhash.HashSize, what) if err != nil { return nil, err } hashes := make([]chainhash.Hash, count) for i := range hashes { if hashes[i], err = d.Hash(); err != nil { return nil, fmt.Errorf("btcwire: decoding %s %d: %w", what, i, err) } } return hashes, nil }