Your Wallet Shouldn't Have to Confess Everything to Find Anything
You open your mobile wallet on a slow connection, hit refresh, and somewhere across the internet a full node learns exactly which Bitcoin addresses you own. You didn't sign anything. You didn't agree to that. It just happened, because that's how the old model worked.
Early light clients broadcast their addresses to full nodes, which handed back relevant transactions. Convenient, yes. Private, not even close. It was roughly equivalent to handing a librarian a list of every book you've ever read just to find out whether one specific title arrived this week.
Compact filters invert the whole model. Instead of the server learning what you're looking for, the client downloads a small cryptographic summary of each block and checks it locally. No address ever leaves your device. The mechanism lives in BIP 157 and BIP 158, and understanding it requires exactly one moderately clever data structure.
The Bloom Filter Problem That Compact Filters Replaced
Before BIP 158, light clients used BIP 37 Bloom filters. A wallet would build a probabilistic filter encoding its addresses and hand it to a full node. The node sent back only transactions matching that filter, saving bandwidth.
Two problems killed it. First, a well-tuned Bloom filter leaks enough information that a determined node can reconstruct the address set with modest computation. Researchers demonstrated this. Second, the server does all the filtering work, which means it can log exactly which wallets care about which addresses and correlate that with IP addresses. The privacy was always theater.
BIP 37 was deprecated in Bitcoin Core 0.21.0. It's not coming back.
What a Compact Filter Actually Is
A compact filter, as defined by BIP 158, is a deterministic encoding of every scriptPubKey appearing in a given block (and sometimes txid-derived data). The encoding uses a Golomb-coded set: a compression technique that hashes a list of items into a uniform range, sorts them, and stores only the differences between consecutive values. Those differences cluster near small numbers when the hash space dwarfs the item count, so they compress cleanly.
Practical upshot: a filter for a block with 2,000 transactions typically runs a few kilobytes. Filters for every block in a chain of 800,000 blocks fit in a few gigabytes total, which is large but manageable for a syncing client, and microscopic next to the full chain.
The lookup flow is straightforward. Say your wallet holds three receiving addresses. You download the compact filter for block 840,000. Locally, you hash each address using the same deterministic function the filter was built with and check whether each hash appears in the Golomb-coded set. No hits: skip the block entirely. One hit: fetch the full block from any node (it doesn't need to be the same node that served the filter), extract your transaction, move on.
The node that served the filter learned nothing about your addresses. The node that served the full block knows you wanted that block, not why. And because you might fetch blocks for reasons unrelated to your own transactions (wallet rescans, running a watchtower for someone else), even that minimal signal is noisy.
False Positives Are a Feature, Not a Bug
Golomb-coded sets are probabilistic. A false-positive rate is baked in at construction time. BIP 158 targets roughly 1 in 2^19 per item per filter. Very low. Not zero.
That sounds like a flaw. It isn't.
If compact filters had zero false positives, every block fetch you trigger would correspond with certainty to a real transaction you care about. A surveillance node watching your downloads could reconstruct your transaction history with high confidence. False positives force you to fetch blocks containing nothing relevant, adding genuine ambiguity to whatever an observer thinks they see.
Consider two users. Priya's light wallet had a transaction in block 839,450. She fetches that block. She also gets a false-positive hit on block 839,211 and fetches that one too, finding nothing useful. Marcus, an adversarial node operator logging her requests, sees two block fetches and cannot distinguish the real hit from the noise. Scale that across hundreds of blocks and the signal degrades considerably.
Which raises the obvious question: when did you last check which protocol your wallet actually uses for transaction lookup?
What the Filter Doesn't Protect Against
Compact filters are a meaningful privacy improvement. They are not a complete solution, and treating them as one is the most common mistake people make when reading about BIP 158.
A client must connect to at least one full node to get filters. If you connect to a single node over a cleartext connection and that node is logging, it can observe your IP address, the filters you requested (revealing which block range you're scanning), and the full blocks you subsequently download. Tor or a VPN removes the IP linkage. Using distinct nodes for filter requests versus full block fetches adds another layer of ambiguity.
Then there's the filter commitment question. BIP 157 specifies that filter hashes should be committed in block headers so clients can verify they haven't received a manipulated filter. That commitment is not yet enforced by consensus on mainnet. A malicious node could serve a filter omitting your transaction, causing you to skip a block you should have fetched. The practical risk is modest (you'd notice missing funds eventually), but it is a real gap in the current deployment, not a theoretical footnote.
Finally, compact filters say nothing about network-layer privacy. Electrum servers, which many light wallets use instead of BIP 157/158, operate on an entirely different model: you query the server by address directly. Connect to a public Electrum server without Tor and you've handed your address list to whoever runs that server. The filter mechanism and the Electrum model are not the same thing, though both get loosely called "light client" solutions. That conflation bothers me every time I see it.
The Engineering Tradeoff You're Actually Making
Running a full node gives you perfect privacy in transaction lookup: you have the whole chain, you check it yourself, nobody sees your queries. Compact filters give you most of that privacy at a fraction of the bandwidth cost, at the price of trusting that the filters you receive aren't manipulated (until header commitments are enforced).
That tradeoff is genuinely good for mobile wallets and constrained devices. Downloading a few gigabytes of filter data on first sync, then a few kilobytes per block thereafter, is feasible on a phone. Syncing hundreds of gigabytes of block data is not.
Think of compact filters as the tires on your wallet, not the engine. Key management, signing, fee estimation: none of that changes. But tires matter. Bad ones and every mile you drive leaks your destination to anyone watching the road, quietly, without asking permission.