Canary names the silent-payments server that leaves out a recent entry it signed for.
A light wallet can't tell "nobody paid you" from "the server left your payment out." Canary checks the tweak list a server sends against the record the same server signed for that block. If the list leaves out an entry the record includes, and the block is less than 144 blocks deep, Canary names the server and the block.
It makes a server accountable. It does not remove the need to trust one. The claim needs at least one honest server that publishes signed records, and a path to those records that nobody censors. Version 1 is built and tested on regtest only.
An evidence file holds a server's signed record, the exact bytes it served and its signature over them. canary verify checks it with no network, and trusts nothing the file says about itself. This page runs the same check in your browser.
The sample comes from our recorded regtest run on 1 Oct 2026, against Bitcoin Core v31.1.0. See the recorded run
The tampered copy changes one byte of the original. Byte 2285, inside proof.siblings[0], reads "1" where the original has "0". That one change makes the Inclusion step fail.
Built from commit 6d1af9d with Go 1.27.0.
The checker needs JavaScript. Until it starts, download a file and run canary verify on it in a terminal. canary verify FILE
The check runs these eight steps in order, and stops at the first that fails.
- Read the file
- Record signature
- Signer
- Block
- Inclusion
- Receipt
- Served list
- Retention window
The problem
A silent-payments light wallet asks a server for the tweaks it scans with, because it lacks the data to compute them. If the server leaves out the tweak for your payment, the wallet shows the balance it would show if nobody had paid you. There is no error.
A server can't tell which transactions pay you without your scan key. So hiding one payment takes outside knowledge of it, and the sender always has that knowledge.
The exchange that pays you can also run the server that tells you whether you were paid.
How it works
Honest servers filter differently, so comparing what two servers send raises false alarms. Canary asks each server to sign for the complete list, then lets it serve less.
When a server indexes a block, it signs a record: the entry count and a Merkle root over every eligible transaction.
It serves a tweak list with exactly that many positions. Each carries the entry, its hash or nothing, and a receipt signs the exact bytes.
Canary fills every gap it can from another server or a payment you declared. Only then does it recompute the root and compare it with the signed one.
An entry sent as nothing while the block is less than 144 blocks deep is an omission. Canary names the server and, when it can recover the entry, writes an evidence file.
Every block ends in one of six states, shown as a symbol, a word and a colour together.
- Checked
The tweak list matched the record its server signed.
- Checked, gap filled
The tweak list matched its signed record once Canary filled the gaps.
- Can't be checked
Canary could not recompute the root for this block.
- Not checked
There was no signed record to check against.
- Servers disagree
Two servers signed different records for this block.
- Data withheld
A named server left out an entry it had signed for, or the entry for a payment you declared.
A balance computed over blocks you could not check is a lower bound, not a balance.
What it proves, and what it does not
Canary separates knowing from proving to others. You know whatever your own check saw. Someone else can confirm a finding only from signed data they check themselves.
- Others can check it
- A server signed for an entry, then sent nothing for it inside the 144-block window and signed a receipt for what it sent. canary verify checks the evidence file offline.
- Others see inclusion only
- The same omission without a receipt. The file shows that the server signed for the entry, not what it sent.
- Only you know it
- A false chain claim, a declared payment left out, or a list of the wrong length. Version 1 has no evidence format for these.
- Two servers, nobody accused
- Servers disagree means two servers signed different roots for the same block. At least one is wrong, and Canary does not say which.
- No accusation
- Can't be checked is neither a pass nor an accusation. A warning names a server that did something odd, and proves nothing.
Limits of version 1
Each claim stops somewhere, and the limit belongs next to it.
| What Canary says | Where it stops |
|---|---|
| A block reads Checked | Canary checked the tweak list, not the output data a wallet matches against. A server can send the right tweak and hide the output, and the block still reads Checked. Output keys in the entry are planned for version 2. |
| A server that serves less than it signed for gets named | Not when it sends the entry's hash under a declared pruning policy. The block then reads Checked, gap filled. |
| Omission is detected | Only when you run canary check. Version 1 has no wallet in the loop, so nothing stops a wallet from using a block Canary flagged. |
| Detection works | Version 1 is built and tested on regtest only, against its own reference indexer, which shares the checker's canonical package. No deployed server speaks this protocol yet. |
| One server is enough | One server alone can't show its record is complete. If the record leaves out your entry, the block still reads Checked. A second server makes that block read Servers disagree, accusing nobody. A payment you declared names the server, but you can't prove it to others. |
| Several servers catch a lying one | If every server colludes, only a tripwire helps, and only for payments you know exist. |
| Records are public | Version 1 fetches each server's records from that server over HTTP, and publishes them nowhere else yet. |
| A lying server gets caught | Canary looks for entries left out, never for fake ones. Adding fake entries is a different attack. |
Run it yourself
Build Canary once with a network. Then turn the network off and check the evidence file in the repository. canary verify opens no connection.
git clone https://github.com/Sky-walkerX/canary && cd canarygo build -o canary ./cmd/canary./canary verify evidence/omission-regtest-351-ad56b9bb-db614560.json
You need git and Go. For the real file, canary verify prints "Checks out." For a tampered copy, it names the step that failed.