Compact Block Filters: How Neutrino Light Clients Query Bitcoin Privately
How BIP 157/158 compact block filters enable Bitcoin light clients to find relevant transactions without revealing their addresses to servers.
Every Bitcoin wallet faces a fundamental question: how do you find your transactions without downloading the entire blockchain? Full nodes solve this by storing and indexing every block, but that requires hundreds of gigabytes of disk space and hours of initial sync. Most mobile users cannot run a full node, so they rely on light clients that query external servers. The problem is that querying a server about your addresses tells the server exactly which addresses are yours.
Compact block filters, defined in BIP 157 and BIP 158, flip the model. Instead of the client telling the server what it is looking for, the server gives every client the same compact summary of each block. The client checks that summary locally, in private, and only downloads full blocks when a match is found. No server ever learns which addresses or transactions the client cares about.
The Light Client Privacy Problem
Satoshi's original whitepaper described Simplified Payment Verification (SPV): a client downloads only block headers and requests Merkle proofs for specific transactions. The whitepaper left the filtering mechanism unspecified. BIP 37, introduced in 2012, filled this gap with bloom filters: probabilistic data structures that a client sends to a full node to request only transactions matching certain patterns.
The design seemed elegant. A bloom filter encodes the client's addresses with tunable false positive rates, so the serving node returns matching transactions plus some noise. In theory, the noise provides plausible deniability. In practice, the privacy guarantees collapse quickly.
How BIP 37 Bloom Filters Leak Information
A 2014 paper by Arthur Gervais, Ghassan Karame, Damian Gruber, and Srdjan Capkun, “On the Privacy Provisions of Bloom Filters in Lightweight Bitcoin Clients”, demonstrated that BIP 37 leaks nearly all address information to the serving node. A client with fewer than 20 addresses risks revealing almost every one of them, even with moderate false positive rates. The problem compounds: each time a client reconnects and sends a new bloom filter for the same address set, the server can intersect multiple filters to eliminate false positives.
Beyond privacy, BIP 37 imposes a denial-of-service vector on serving nodes. The full node must load each block from disk, test every transaction against the client's filter, and stream results. A malicious client can craft filters that force the node to scan the entire blockchain with minimal computational cost to the attacker. This led Bitcoin Core to disable bloom filter serving by default in version 0.19.0 (November 2019), with the -peerbloomfilters option set to 0.
The core issue: BIP 37 puts the filtering responsibility on the server. The client must reveal its interests so the server can filter on its behalf. Compact block filters invert this: the server creates a single filter per block, and the client does all filtering locally.
How Compact Block Filters Work
BIP 158 defines how to construct a compact filter for each block. BIP 157 defines the peer-to-peer protocol that clients use to fetch these filters from full nodes. Together, they enable a model where every client downloads the same set of filters and checks them independently.
Golomb-Rice Coded Sets
The filter data structure is a Golomb-Rice coded set (GCS), a space-efficient probabilistic data structure similar to a bloom filter but with better compression. Construction works in five steps:
- Each item (a script from the block) is hashed using SipHash-2-4 with the first 16 bytes of the block hash as the key, producing a 64-bit output.
- The hash is mapped uniformly to the range [0, N × M), where N is the number of items in the filter and M is the false positive rate parameter (784,931 for basic filters).
- The mapped values are sorted in ascending order.
- Consecutive differences (deltas) between sorted values are computed. Because the values are uniformly distributed, most deltas cluster near M.
- Each delta is encoded using Golomb-Rice coding with parameter P = 19. The value is split into a quotient (encoded in unary) and a remainder (encoded as 19 raw bits). Since deltas cluster near 2^P, most quotients are small, yielding excellent compression.
The result is a compact bitstream that encodes all script data from a block. A client can test whether a particular script might appear in the filter by hashing it, mapping it to the same range, and checking whether the value exists in the decoded set. The false positive rate is approximately 1 in 784,931 per queried element: low enough that false matches are rare, but nonzero, providing a small privacy benefit even for blocks that are downloaded.
What Goes Into a Basic Filter
The basic filter type (type 0x00) includes two categories of data from each block:
- The scriptPubKey of every transaction output, excluding OP_RETURN outputs
- The scriptPubKey being spent by every transaction input, excluding coinbase inputs (which have no previous output)
This means a client monitoring a set of addresses can check whether any block might contain a transaction that either pays to or spends from one of those addresses. Duplicates are removed, and empty items are excluded. OP_RETURN outputs are excluded because they represent data commitments rather than spendable outputs, and excluding them avoids interference with future soft-fork commitment schemes.
The Client Protocol: BIP 157
BIP 157 defines six new P2P network messages that let light clients download and verify filters from full nodes. The protocol supports three operations: fetching filters, fetching filter headers (for verification), and fetching checkpoints (for parallel sync).
| Message | Direction | Purpose |
|---|---|---|
getcfilters / cfilter | Request / Response | Fetch compact filters for a range of blocks (max 1,000) |
getcfheaders / cfheaders | Request / Response | Fetch filter headers for a range of blocks (max 2,000) |
getcfcheckpt / cfcheckpt | Request / Response | Fetch filter header checkpoints at every 1,000th block height |
Filter Header Chain
Filters are not trusted blindly. BIP 157 defines a filter header chain that works analogously to the block header chain. Each filter header is computed as:
filter_header = double-SHA256(filter_hash || previous_filter_header)
This creates a hash chain linking every filter to its predecessor. The genesis block uses a 32-byte zero array as its previous filter header. A client can verify the integrity of any filter by checking its hash against the corresponding filter header and tracing the chain back to a known checkpoint.
Sync and Verification Flow
When a Neutrino light client starts up, it follows a multi-step synchronization process:
- Sync block headers using the standard headers-first mechanism (about 70 MB for the full chain).
- Fetch filter header checkpoints at 1,000-block intervals from multiple peers.
- Download filter headers in ranges of up to 2,000, verifying each against the checkpoints.
- If peers disagree on a filter header, download the full block and compute the correct filter locally, identifying the dishonest peer.
- Download the actual filters, verifying each filter's hash against its corresponding header.
- Match filters against the client's addresses locally. Download full blocks only when a filter indicates a potential match.
Privacy detail: BIP 157 recommends that clients download matched blocks from random outbound peers rather than always requesting from the same peer that served the filter. This prevents any single peer from correlating which filters triggered a full block download.
Comparing Light Client Approaches
Three main approaches exist for Bitcoin light client transaction discovery. Each makes different tradeoffs between privacy, bandwidth, trust, and infrastructure requirements.
| Property | BIP 37 (Bloom Filters) | Electrum (Server Index) | BIP 157/158 (Compact Block Filters) |
|---|---|---|---|
| Who filters | Server | Server | Client |
| Server learns addresses | Yes (via filter analysis) | Yes (direct queries) | No |
| Bandwidth cost | Low (only matching txs) | Low (only matching txs) | Higher (all filters + matching blocks) |
| Server infrastructure | Full node | Full node + address index | Full node + filter index |
| DoS resistance | Poor (server CPU attack vector) | Moderate | Strong (precomputed filters) |
| Dishonest server detection | No | No (without additional verification) | Yes (via filter header chain) |
| Trust model | Trust connected peer | Trust server operator | Trustless with multiple peers |
| Default in Bitcoin Core | Disabled (since 0.19) | N/A (separate software) | Available (since 0.21) |
The Electrum model is worth examining in detail. Electrum servers maintain a full address index of the blockchain, allowing clients to query transaction history for specific script hashes. This is efficient: the client sends a script hash and receives exactly the matching transactions. But the server sees every address the client queries. Running your own Electrum server solves the privacy problem but reintroduces the full-node requirement, defeating the purpose of a light client for most users.
Bandwidth and Storage Tradeoffs
The privacy improvements of compact block filters come at a cost: bandwidth. Instead of asking a server for only matching transactions, the client downloads a filter for every block and occasionally downloads full blocks to check for matches. Understanding the concrete numbers helps evaluate whether this tradeoff is worthwhile.
Filter Size
The total size of all basic filters for Bitcoin's blockchain is approximately 4 GB as of mid-2026. This averages roughly 4 to 5 KB per block across the chain's history, though individual block filter sizes vary significantly. Early blocks with few transactions produce filters of just a few bytes, while recent blocks with thousands of transactions can produce filters of 15 to 20 KB.
For comparison, the full block header chain totals about 70 MB. A Neutrino client performing initial sync must download both: roughly 70 MB of headers plus approximately 4 GB of filters. This is substantial compared to the Electrum approach (headers only, plus individual transaction queries), but it is a fraction of the roughly 650 GB required for a full Bitcoin node.
Ongoing Bandwidth
After initial sync, the ongoing bandwidth cost is modest. Each new block produces one filter of a few kilobytes. A client checking one or two blocks per day for matches downloads negligible additional data. The main bandwidth spike occurs when a filter match triggers a full block download (typically 1 to 4 MB for recent blocks). For a wallet with a small number of addresses, false positive matches are rare enough that this overhead is minimal.
Implementation Status
Compact block filter support has been implemented across multiple Bitcoin software projects, though adoption and maturity vary.
Neutrino and LND
The primary consumer of BIP 157/158 is Neutrino, a light client library developed by Lightning Labs. Neutrino was designed specifically for LND (Lightning Network Daemon), enabling Lightning nodes to operate without a full Bitcoin node backend. The BIP authors, Olaoluwa Osuntokun (roasbeef) and Alex Akselrod, both worked at Lightning Labs, and the specifications were motivated directly by the need for private, lightweight Lightning node backends.
LND v0.5-beta (September 2018) included the first Neutrino implementation, initially limited to testnet. LND v0.8.0-beta (October 2019) was the first release where Neutrino was considered stable enough for mainnet use, with the developers noting it was functional but still early. Since then, Neutrino has become one of the standard backend options for LND alongside btcd and bitcoind.
Bitcoin Core
Bitcoin Core added BIP 158 filter index construction in version 0.19.0 (November 2019) via the -blockfilterindex configuration option. This allowed nodes to build and query filters locally but did not serve them to peers over the P2P network.
Full BIP 157 peer serving support arrived in Bitcoin Core 0.21.0 (January 2021). Nodes running with both -blockfilterindex=1 and -peerblockfilters=1 advertise the NODE_COMPACT_FILTERS service bit (bit 6) and respond to all six BIP 157 message types. The path to merge was deliberate: the implementation was split across four separate pull requests over the course of 2020 to ensure thorough review.
Other Implementations
The btcd full node implementation (also from the Lightning Labs ecosystem) supports compact block filters natively. Several wallet libraries have integrated BIP 158 filter matching, including Bitcoin Dev Kit (BDK), which offers compact block filter support as a chain data source alongside Electrum and RPC backends.
Security Considerations
Compact block filters are not a complete replacement for running a full node. Several security and privacy nuances deserve attention.
Filter Omission Attacks
A malicious full node could serve a filter that omits entries, causing the client to miss relevant transactions. BIP 157 mitigates this through the filter header chain and multi-peer verification. If two peers serve different filter headers for the same block, the client downloads the full block and computes the correct filter locally, identifying and banning the dishonest peer. This requires connecting to at least one honest peer, a reasonable assumption for most network conditions.
Network-Level Correlation
While the content of a client's queries is private, network-level metadata is not. A network observer can see that a client downloaded filter headers, and can observe which full blocks the client subsequently requests. The block download pattern could reveal which blocks contain the client's transactions. BIP 157 recommends downloading matched blocks from random peers to reduce this correlation, and using Tor or a VPN adds an additional privacy layer.
False Positive Considerations
The false positive rate of 1 in 784,931 means that for a wallet monitoring a single address, approximately one in every 784,931 blocks will produce a spurious match. At Bitcoin's current block height of roughly 850,000, the expected number of false positives across the entire chain for a single address is just over one. For wallets with many addresses, the false positive rate scales linearly, but remains manageable: a wallet with 100 addresses would see roughly 100 false block downloads across the entire chain history.
Why Private Light Clients Matter for Layer 2
Light client infrastructure is not just about running a Bitcoin wallet on a phone. It underpins the entire mobile wallet ecosystem across Bitcoin Layer 2 protocols. Lightning nodes, statechain-based systems, and other off-chain protocols all need a way to monitor the base layer for relevant transactions: channel opens, closes, force-closes, and settlement events. If that monitoring leaks address information, the privacy properties of the Layer 2 are undermined at the base layer.
This is why LND was the driving force behind compact block filters. A mobile Lightning wallet using Neutrino can monitor the blockchain for channel state changes without revealing which channels belong to the user. Watchtowers can monitor for breach attempts without the watchtower operator learning which channels it is protecting.
The same principle applies to Spark and other statechain-based protocols, where clients need to watch for on-chain exit transactions. Lightweight verification approaches used by Spark-powered wallets benefit from the same client-side filtering model: the wallet can monitor for relevant on-chain activity without disclosing its UTXOs or exit paths to any server. As Bitcoin Layer 2 adoption grows, private light client infrastructure becomes a foundational requirement rather than a nice-to-have.
Remaining Limitations and Future Directions
Compact block filters represent a significant step forward for light client privacy, but they are not a perfect solution.
- Initial sync cost of approximately 4 GB is significant on mobile networks or metered connections, and this figure grows as Bitcoin blocks get larger
- The approach requires connecting to at least one honest BIP 157 peer; if a client is eclipsed by malicious nodes, filter omission attacks become possible
- Compact block filters do not help with transaction broadcasting privacy; a client still reveals its transactions when submitting them to the network
- The basic filter type does not include witness data or outpoints, limiting the queries that can be performed without downloading full blocks
Proposals for additional filter types could address some of these gaps. The original BIP 158 draft included an extended filter type (0x01) containing outpoint data, but it was removed in July 2018 due to the lack of a clear use case at the time. The specification is designed to accommodate new filter types in the future if compelling applications emerge.
Projects like Utreexo take a complementary approach, reducing the state that nodes must store rather than the data clients must download. Combined with compact block filters, Utreexo could enable even lighter node configurations that still preserve strong privacy properties.
Getting Started
For developers building Bitcoin applications that require base-layer monitoring, compact block filters provide the strongest privacy guarantees available without running a full node. The Bitcoin Dev Kit supports compact block filters as a chain data source, making integration straightforward for wallet developers. For Lightning implementations, LND's Neutrino backend is production-ready and actively maintained.
If you are building on Spark or exploring Bitcoin Layer 2 wallet infrastructure, the Spark SDK documentation covers how Spark handles base-layer monitoring. For broader context on how light clients fit into Bitcoin's scaling story, see our deep dive on Bitcoin light clients and SPV and the overview of Bitcoin's privacy landscape in 2026.
This article is for educational purposes only. It does not constitute financial or investment advice. Bitcoin and Layer 2 protocols involve technical and financial risk. Always do your own research and understand the tradeoffs before using any protocol.

