You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
BDK's chain sources — bdk_electrum, bdk_electrum_streaming, bdk_esplora — currently fully trust the data they receive from their backend. CheckPoint accepts whatever block hashes a server claims, with no proof-of-work verification anywhere in the data path. bdk_electrum_streaming retains full Header objects in a side cache (used for merkle-proof verification of confirmations), but those headers are stripped down to BlockHash before they reach the CheckPoint chain. We never call header.validate_pow(header.target()) on anything.
A malicious or compromised server can therefore convince a wallet of an arbitrary block history at zero cost: fake confirmations, fake reorgs, a fake tip at any height.
This is a discussion issue, not a proposal.
Imposing a cost on a malicious server
I think yes — and two reasonably simple techniques cover most of what we'd want, framed as cost-to-fake rather than "prove honest."
We can't prove honesty without either the full header chain or commitments Bitcoin doesn't have (FlyClient's MMR, NIPoPoW's interlink). But we can make sure a server lying to us had to do at least some real PoW, and report that to the application.
Technique A — probabilistic history sampling
This makes it hard to present low-pow blocks and just have them accepted by querying about the history of the chain that let to the difficulty drop.
Walk back from the claimed tip to a known anchor (embedded checkpoint, last verified state, etc.), querying random headers along the way. The more queries the server answers consistently, the more PoW the server must have actually done; each answered query is a brick in the cost-to-fake floor.
The interesting refinement is where to sample. The attacker's cheapest play is to ratchet difficulty down across consecutive retargets, so they pay less and less per block as you walk back. Their pain points are the epochs immediately before a difficulty drop — those are the epochs they had to mine at high difficulty to justify the next epoch's lower bits. Concentrating samples in pre-drop epochs (where bitsᵢ > bitsᵢ₊₁) maximises forced cost.
Mechanically, the chain source can challenge the server by sampling extra headers as part of its normal sync and returning them in the update. Since the chain source and the core app usually share the same checkpoint chain, subsequent queries/updates will need fewer or no checks.
A much simpler check that catches most malicious servers cheaply: reject chains whose claimed difficulty drops below a configurable floor. The application picks the floor; a sensible default is something like 100 EH/s (roughly 1/10 of current mainnet hashrate as of mid-2026), with apps free to set it stricter or looser.
This catches the lazy attacker who just claims pow_limit everywhere. It costs nothing at runtime, doesn't need to issue any verification queries beyond what we'd fetch anyway, and the floor can be a default in BDK with an application override.
A and B compose: B catches the lazy attacker for free; A catches the more ambitious ratchet attacker for the cost of a few extra header queries per sync.
Other approaches
Query multiple servers as a consistency check. Not as a vote that amplifies trust, but as a way to surface disagreement: if two servers return different hashes for the same height, at least one is lying. Cheap (headers are 80 bytes) and a meaningful uplift against single-server compromise.
Fetch all headers from checkpoints. SPV-style end-to-end PoW + retarget + chain-linkage validation, anchored on the hardcoded bdk curated checkpoints. Tens of MB on disk; sledgehammer answer to the trust question but it might be the right answer rather than focusing on things like this.
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
Background
BDK's chain sources —
bdk_electrum,bdk_electrum_streaming,bdk_esplora— currently fully trust the data they receive from their backend.CheckPointaccepts whatever block hashes a server claims, with no proof-of-work verification anywhere in the data path.bdk_electrum_streamingretains fullHeaderobjects in a side cache (used for merkle-proof verification of confirmations), but those headers are stripped down toBlockHashbefore they reach theCheckPointchain. We never callheader.validate_pow(header.target())on anything.A malicious or compromised server can therefore convince a wallet of an arbitrary block history at zero cost: fake confirmations, fake reorgs, a fake tip at any height.
This is a discussion issue, not a proposal.
Imposing a cost on a malicious server
I think yes — and two reasonably simple techniques cover most of what we'd want, framed as cost-to-fake rather than "prove honest."
We can't prove honesty without either the full header chain or commitments Bitcoin doesn't have (FlyClient's MMR, NIPoPoW's interlink). But we can make sure a server lying to us had to do at least some real PoW, and report that to the application.
Technique A — probabilistic history sampling
This makes it hard to present low-pow blocks and just have them accepted by querying about the history of the chain that let to the difficulty drop.
Walk back from the claimed tip to a known anchor (embedded checkpoint, last verified state, etc.), querying random headers along the way. The more queries the server answers consistently, the more PoW the server must have actually done; each answered query is a brick in the cost-to-fake floor.
The interesting refinement is where to sample. The attacker's cheapest play is to ratchet difficulty down across consecutive retargets, so they pay less and less per block as you walk back. Their pain points are the epochs immediately before a difficulty drop — those are the epochs they had to mine at high difficulty to justify the next epoch's lower bits. Concentrating samples in pre-drop epochs (where
bitsᵢ > bitsᵢ₊₁) maximises forced cost.Mechanically, the chain source can challenge the server by sampling extra headers as part of its normal sync and returning them in the update. Since the chain source and the core app usually share the same checkpoint chain, subsequent queries/updates will need fewer or no checks.
Technique B — difficulty-floor sanity check
h/t @5t34k
A much simpler check that catches most malicious servers cheaply: reject chains whose claimed difficulty drops below a configurable floor. The application picks the floor; a sensible default is something like 100 EH/s (roughly 1/10 of current mainnet hashrate as of mid-2026), with apps free to set it stricter or looser.
This catches the lazy attacker who just claims
pow_limiteverywhere. It costs nothing at runtime, doesn't need to issue any verification queries beyond what we'd fetch anyway, and the floor can be a default in BDK with an application override.A and B compose: B catches the lazy attacker for free; A catches the more ambitious ratchet attacker for the cost of a few extra header queries per sync.
Other approaches
Beta Was this translation helpful? Give feedback.
All reactions