What SPV actually verifies — and what it doesn't

Tilde checks proof-of-work headers and Urkel name-ownership proofs on your phone. The honest scope of that, including the parts we don't verify yet.

5 August 2026 · 7 min read

“Verify, don’t trust” is easy to put on a landing page and harder to say precisely. This is the precise version: what a Handshake light client on a phone checks for itself, what it takes on faith, and where our implementation is currently weaker than the theory allows.

If you only read one paragraph: Tilde verifies proof-of-work on every block header it accepts, and verifies name ownership with Urkel tree proofs against those headers, so no server can lie to you about who owns a name or what its DNS records say. It does not validate every transaction in every block. That’s a full node’s job, and we don’t claim to do it.

What Tilde verifies on your device

Proof-of-work, per header. Tilde downloads block headers over Handshake’s P2P network and checks the work on each one. Handshake’s proof-of-work isn’t a single hash of the header: it’s a two-phase construction — BLAKE2b-512 and SHA3-256 over a split pre-header and sub-header, combined with an XOR mask. One consequence worth knowing, because it shapes light-client design: a Handshake block hash is its proof-of-work hash. There is no cheap way to identify a block without doing the work of verifying it.

Name ownership, by Urkel proof. Every Handshake block header commits the root of the Urkel tree — the authenticated data structure holding every name’s state. When Tilde looks up a name, it asks a peer for a proof and verifies that proof hashes to the root in a header it validated itself. A peer that wants to lie about a name’s owner, auction phase, or DNS records would have to produce a proof that hashes to a root committed by real proof-of-work. It can’t.

Absence, too. Proofs come in four shapes: one for existence, and three for non-existence (dead-end, short, and collision proofs). That second category is what makes “this name is unclaimed” a verified statement rather than a peer’s say-so — which matters when the next thing you do is bid on it.

Inclusion of the blocks you care about. When the wallet fetches a block to find its own transactions, that block is merkle-verified against a header it has already validated. Handshake uses bcrypto’s merkle construction, with domain-separated leaf and internal hashing rather than Bitcoin’s duplicate-last rule — a detail we match byte-for-byte against the reference implementation and pin with test vectors.

The tip, across several peers. Tilde doesn’t take one peer’s word for the chain tip. It cross-checks across multiple peers and compares accumulated chainwork before believing a tip.

The connection itself. Handshake’s P2P transport is encrypted and authenticated to the peer’s static public key (a Noise_XK handshake that Handshake calls “brontide”). Someone watching your network sees encrypted traffic to Handshake nodes, not your queries. Note the honest limit: your peers still see your IP address, exactly as in any peer-to-peer protocol. We say so on the privacy page rather than in the small print.

What SPV does not verify

Every transaction in every block. Tilde does not check the signatures, scripts, or covenant rules of transactions that aren’t its own, and it does not maintain the full UTXO set. If a block contains an invalid transaction that miners nonetheless built on, a light client follows along.

The money supply. A full node can prove nobody minted coins out of nothing. A light client cannot. It sees headers and the transactions it asked about.

That the most-work chain is honest. SPV follows the chain with the most accumulated work. An attacker with majority hashrate could produce a chain that is internally consistent, passes every check above, and is still a lie. This is the irreducible tradeoff of light clients on any proof-of-work chain, Bitcoin included. Cross-peer chainwork comparison raises the cost of fooling one wallet; it does not eliminate the class of attack.

Two gaps that are ours, not the model’s. We would rather you hear these from us:

  • Difficulty-retarget validation isn’t implemented yet. Tilde verifies that each header meets its stated target, but does not yet independently recompute Handshake’s difficulty adjustment to confirm the target is the one consensus requires. Compiled-in checkpoints plus the cross-peer chainwork quorum cover this in practice today; the full check is on the list, not done.
  • Eclipse resistance is incomplete. An attacker who controls every peer you can reach can withhold information from you (it still cannot forge a name proof or fake proof-of-work). Our peer book only grows from peers that completed a real encrypted handshake, which helps, but this is not a solved problem in our implementation.

Where the bundled bootstrap packs fit

Tilde ships with pre-verified chain data pinned to the app binary: a header pack, a compact filter pack, and an index of live names. They make first sync and wallet restore fast, and they make name search work with no network at all. They are a speed feature, and it’s worth being exact about why they don’t weaken anything above.

  • The header pack self-validates on open: a whole-file checksum, a full prev-hash linkage walk across the chain, compiled-in checkpoints, and a deterministic proof-of-work spot check at fixed heights. A pack shipping fabricated hashes fails that check on every device, deterministically.
  • Restore uses the filter pack to match your addresses against per-block filters locally — no network requests, and no addresses revealed to anyone. Your wallet’s birth height is then derived from the lowest match rather than guessed. Every block it decides to fetch is still merkle-verified against a proof-of-work-verified header, so the packs cannot invent history.
  • The name index is discovery only. It makes search and browse instant, and it can mis-rank a suggestion, but every name you actually act on is re-verified through a live Urkel proof — including proofs of non-existence — at the moment you act.

The one property that is trusted rather than proven is filter completeness: a filter missing an entry would hide one of your own coins from a restore rather than invent a fake one. That’s carried by pinned checksums and re-derivation checks in CI, not by a proof on your device. Everything above the pack’s cut height uses the normal trustless path.

How that compares

Custodial / API walletTilde (SPV over P2P)Full node
Who holds your keysSomeone else, or your device with a server in the loopYour deviceYour device
How name ownership is establishedWhatever the API repliesUrkel proof against a PoW-verified headerThe tree you built yourself
Every transaction validatedNoNoYes
Money supply verifiedNoNoYes
Resists a lying serverNoYesYes
Detects invalid blocks miners built onNoNoYes
Runs on a phoneYesYesNo

A full node is strictly better security. If you can run one, run one — and point Tilde at it: “My node” mode takes your own node’s address, and a node you control never falls back to anything else.

Questions people actually ask

Is SPV good enough for managing valuable names?

For proving who owns a name and what it resolves to, the Urkel proof chain is strong: a peer cannot forge it. The residual risk is a majority-hashrate attacker rewriting history, and that risk applies to your names in a full node too — a full node just detects invalid blocks that a light client would accept.

Does Tilde phone home to a Tilde server?

There is no Tilde server. The default mode connects to the Handshake P2P network directly. A community HTTPS gateway exists only as an explicit rescue option you have to choose on a hostile network, it discloses what its operator sees, and it reverts to P2P automatically.

Can a peer see which names or addresses I care about?

In the default block-scan mode, no: your wallet downloads whole blocks and finds its own transactions locally, so nothing distinguishes your addresses from anyone else’s. If you switch to gateway mode, that gateway learns which addresses you asked about. The security page states both sides of that tradeoff in a table.

Why not use compact block filters live, like Bitcoin wallets do?

Handshake’s reference node doesn’t implement BIP157/158 compact filter serving. Live discovery is therefore either a BIP37 bloom filter (which leaks a fuzzy picture of your addresses to a peer) or a full-block scan (which leaks nothing). Tilde defaults to the scan, and ships filters in the bootstrap pack so the slow first pass happens locally instead of over the network.


Related: How to renew Handshake names · Handshake name auctions, explained from your phone · Security, in detail

Carry your names home.

Tilde is a native iPhone wallet for Handshake (HNS). Non-custodial by construction, verified on your device over encrypted P2P — browse, bid, reveal, renew.

All posts FAQ What is Handshake?