§ 12 · Networking
Light clients
Checking the mining work without keeping the blocks
On this page 7 sections
A full node downloads and checks every block ever made. That’s the gold standard, but it’s a lot to ask of a phone or a small wallet. A light clientLight client: Software that downloads block headers instead of whole blocks. Pearl's light client still checks every header's certificate, so it is lighter than a full node but much heavier than a Bitcoin one. takes a shortcut: it follows the chain of block headers, checks the mining work behind them, and fetches only the few blocks that concern its own wallet.
Bitcoin’s light clients check the work cheaply, one hash per 80-byte header. Pearl’s can’t: a Pearl block hash proves nothing, because the work is proven by the block’s certificateCertificate: The proof of work that travels in front of every block: about 60 KB, mostly a zero-knowledge proof. Any node can check it in milliseconds without seeing the miner's matrices.. So Pearl’s light client does something heavier and more honest. It downloads every certificate and verifies every proof, then keeps only the headers.
Try it
A header is all a light client keeps. Edit a field and the hash changes, breaking the link to the next block; the client would notice at once.
What does Pearl’s light client download?
Three kinds of data, from ordinary full nodes:
- Headers with certificates. It always asks with
includeCertificatesset, so every header arrives with its ~60 KB certificate. See messages. - Filter headers and filters. Small summaries of each block’s scripts (BIP-157/158), to find its own transactions without saying which addresses it owns.
- The occasional block. Only blocks whose filter matches the wallet, fetched when needed and not saved.
The client is in Pearl’s repository as spv/, derived from Neutrino, a Go light client built on the same filters.
What does it check for every header?
The same header checks a full node runs, one header at a time:
-
Context
The bits must equal what WTEMA computes from the parent, the timestamp must be at least 1 second after the parent’s, and the header must match any checkpoint at its height.
-
Rules for the height
The certificate must be the version this height requires, dense where dense-only applies, and within the rank penalty.
-
Sanity and proof of work
The certificate’s size and version must be valid, the timestamp at most 5 minutes ahead, and the full zero-knowledge proof must verify against the header. The shipped binaries include the verifier.
Then the certificate is dropped. The client stores only the 108-byte header.
When two branches compete, it chooses the same way a full node does, by most work with the quarter-block margin.
How much heavier than Bitcoin’s is it?
Per header, about 750 times more data, and a proof check instead of a hash. Rough arithmetic for syncing from genesis to block 130,827 (20 October 2026):
| Topic | Bitcoin | Pearl |
|---|---|---|
| Data per header | 80 bytes | 108 + about 59,768 bytes of certificate |
| Check per header | One double SHA-256 | One zero-knowledge proof: 7–21 ms on an Apple M5 Max CPU |
| Downloaded for 130,827 headers | About 10.5 MB | About 7.8 GB |
| Proof checking for 130,827 headers | Negligible | About 15 to 46 minutes of CPU at those speeds |
| Stored | 80 bytes per header (10.5 MB) | 108 bytes per header (14.1 MB) |
The Bitcoin column is what the same number of Bitcoin headers would cost, for scale. What Pearl gets in return: its light client never takes a peer’s word for the mining work. It checks it.
How does it find its own transactions?
With compact block filtersCompact block filters: Small summaries of each block that let a light client ask “might this block involve my addresses?” without telling anyone which addresses., exactly as in Bitcoin’s BIP-157/158. For each block, a full node builds a small “basic” filter containing every output script in the block (except OP_RETURN outputs) and every script the block’s inputs spent. The light client downloads filters, tests its own scripts against them locally, and fetches a block only on a match.
- Coding parameter
- P = 19
- Golomb-Rice bits per entry
- Hash range
- M = 784,931
- false positives about 1 in 784,931 per script tested
- Filter key
- first 16 bytes
- of the block hash
- Filters per request
- up to 1,000
- Filter headers per message
- up to 2,000
- Checkpoint interval
- every 1,000 blocks
Nobody learns which addresses are the wallet’s: every client downloads the same filters. To make sure a peer isn’t lying about a filter, the client checks the filter-header chain (each filter header is SHA256d of the filter’s hash and the previous filter header) and cross-checks it between several peers.
What about the older bloom-filter method?
BIP-37 bloom filtersBloom filter: An older way for a light wallet to ask a node for only its own transactions (BIP-37). The node replies with merkleblock messages, which on Pearl carry the header but not the certificate. still exist: pearld advertises NODE_BLOOM by default and answers filterload with merkleblock messages. But a merkleblock carries the 108-byte header without its certificate. A client that relied on it alone couldn’t check the mining work for itself, and BIP-37 also tells the node which addresses the client cares about. Pearl’s own light client doesn’t use it.
What can’t a light client check?
Everything below the headers. It verifies that a lot of real work went into the chain, and that a given transaction is in a block of that chain. It doesn’t check every other transaction’s signatures, or that no coin was spent twice. It trusts that miners and full nodes wouldn’t build that much work on invalid blocks. That’s the bargain of every light client, Bitcoin’s included; Pearl’s makes the “work” part of it as strong as a full node’s.
Make a guess
A malicious peer sends a light client a header whose certificate is copied from a different, real block. What happens?
Think of your answer first, then check it.
Answer It's rejected.
The certificate names the block it belongs to: its block hash must equal the header’s hash, and the header’s proof commitment must equal SHA256d of the certificate’s version and public data. A copied certificate fails the first check before the proof is even looked at.
Code: the light client's header check
From spv/blockmanager.go (spv/blockmanager.go:2853-2895 at v1.5.2, on GitHub):
err := blockchain.CheckBlockHeaderContext(blockHeader, parentHeaderCtx, flags, chainCtx, false)
if err != nil {
return err
}
if err := blockchain.CheckCertificateRules(
blockHeader, cert, prevNodeHeight+1, &b.cfg.ChainParams, flags,
); err != nil {
return err
}
return blockchain.CheckBlockHeaderSanity(
blockHeader, cert, b.cfg.ChainParams.PowLimit, b.cfg.TimeSource,
b.cfg.ChainParams.MaxTimeOffsetMinutes, flags,
) Headers are stored at their fixed size,
BlockHeaderSize = wire.MaxBlockHeaderPayload (108 bytes), in
spv/headerfs/index.go (spv/headerfs/index.go:57-61 at v1.5.2, on GitHub).