§ 12

§ 12 · Networking

Light clients

Checking the mining work without keeping the blocks

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 client 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 certificate. 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.

Header explorer

Edit a real header and watch its hash change

4 bytes, shown as hex. No consensus meaning in Pearl.

2026-10-20 16:26:02 UTC

The target, in compact form.

The parent’s hash. This is what chains blocks together.

One hash committing to every transaction in the block.

SHA256d of the certificate’s version and public data (all zeros in genesis, which has no real certificate). Takes the place of Bitcoin’s nonce.

Serialized header 108 bytes
00000020999c961b04d94b8b21134e862dc31d5fa56cf9d1f027adb2a0cf95c6525c55eb6389faa36bd9daffdefc800dea88ac3ef5af1198f7d7179ef3b904420c1cc2c61a96d76aa6f36a174d52a3fd32160cb032e22edf76912f67dfb1b88946a215281f203836c77ae649

Little-endian numbers, and the three hashes stored byte-reversed: that’s why the hex here doesn’t read like the fields above.

Block hash ✓ matches block 130,827

1df2f2c1bf39b1a444e622c1788c11736da2f91f4e9a875a38dc23063c6d45b9

SHA256d of the 108 bytes above, shown byte-reversed. Change any field, even by one second, and it becomes a different hash.

In Pearl, this hash is never compared to the target

Read as a number, this block hash is far above the target, and that’s fine. In Bitcoin a miner changes the nonce until the hash drops under the target. Pearl’s header has no nonce: the winning ticket is a BLAKE3 hash inside the certificate, compared with the target times the work in a tile.

Runs in your browser · nothing is sent src/lib/block.ts · real mainnet headers

What does Pearl’s light client download?

Three kinds of data, from ordinary full nodes:

  • Headers with certificates. It always asks with includeCertificates set, 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:

  1. 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.

  2. Rules for the height

    The certificate must be the version this height requires, dense where dense-only applies, and within the rank penalty.

  3. 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):

Following the header chain to block 130,827 (rough arithmetic)
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 filters, 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 filters 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):

spv/blockmanager.go Go
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).

The codex

Suggested

Up and down arrows move through the results, Enter opens one, Escape closes. Anywhere on the site, slash or Command K opens this search, and the square brackets [ and ] go to the previous and next page.