# Anchoring Verify

A standalone, backend-free verifier for [OpenTimestamps](https://opentimestamps.org)
Bitcoin-anchoring proofs (`.ots` files). One `index.html`, one JS module, no
account, no upload, no build step. Everything — hashing, `.ots` parsing,
Bitcoin header validation — runs in your browser tab.

Open `index.html` directly, or serve the directory locally:

## How verification works, layer by layer

1. **Hash the input.** If you drop the original file, its SHA-256 is computed
   locally (Web Crypto). If you paste a hash instead, that's used as-is — the
   result is then a "certificate only" check, since nothing here can confirm
   the hash actually belongs to *your* file.
2. **Parse the `.ots` proof.** The binary format is a tree: a starting digest
   (the file hash from inside the proof itself), a chain of operations
   (append/prepend/reverse/hexlify, SHA-1/SHA-256/RIPEMD-160), and attestation
   leaves — `PendingAttestation` (still waiting at a calendar server) or
   `BitcoinBlockHeaderAttestation` (claims a specific block height, with the
   op chain's result at that leaf being the block's claimed merkle root).
   RIPEMD-160 isn't available via Web Crypto in any browser, so it's
   implemented directly in `verify-core.js` — this isn't a rare edge case,
   since real Bitcoin-calendar proofs walk actual transaction bytes, and
   P2PKH scripts commit to `RIPEMD160(SHA256(pubkey))`.
3. **Fetch information for the Bitcoin block the proof claims — trust model
   depends on an Advanced-settings toggle.**

   - **Trust the block explorer (default).** The configured explorer
     (`blockstream.info` and `mempool.space` by default, editable in Advanced
     settings) is asked directly for the block at that height, and its JSON
     response (merkle root, timestamp, confirmations) is used as-is. This is
     fast — one or two HTTP requests per Bitcoin branch in the proof, nothing
     cached — but it means this mode is trusting the explorer to tell the
     truth; a compromised or malicious explorer could report a fabricated
     block, and nothing here would catch it.
   - **Local, self-validated Bitcoin header check (opt-in).** Checking the
     box in Advanced settings switches to a fully self-validated Bitcoin SPV
     header chain instead: the raw header bytes are never trusted directly.
     Proof-of-work is checked against the header's own claimed difficulty,
     the difficulty itself is checked against the standard retarget rule
     computed from prior headers, and each header must link by hash to the
     previous one — all the way back to a pinned checkpoint
     (`CHECKPOINT_HEIGHT`/`CHECKPOINT_HASH` in `verify-core.js`). A bounded
     reorg-recovery walk-back handles ordinary chain reorganizations; anything
     deeper is treated as a hard failure rather than silently "recovered."
     Validated headers are cached in IndexedDB so a repeat visit only extends
     from the cached tip instead of re-validating from the checkpoint. This
     is slower — a first run can take minutes while it builds the chain from
     the checkpoint — but the result trusts nothing but math and the pinned
     checkpoint, not the explorer's word.
4. **Compare the proof's digest to the block's actual merkle root.** A
   mismatch means the calendar's claimed height was wrong or fabricated —
   this is reported as `INVALID`, not "unverifiable."
5. **Check confirmation depth.** A block that's been seen but has fewer than
   6 confirmations is reported as pending, not anchored.

Results compose two independent axes, rather than collapsing to a single
valid/invalid/unverifiable value: whether the bytes you provided match the
proof (`Valid (bytes)` / `Certificate only` / `Artifact mismatch`), and
whether the ledger claim itself checks out (`ledger-confirmed` / `ledger
pending` / `ledger unverifiable` / `INVALID`). Every successful result
carries: **"VALID does not imply authorship, ownership, or truth."** — a
proof establishes that specific bytes existed no later than a specific
Bitcoin block, nothing more.

## Privacy

Nothing you drop into this page leaves your browser except the block-height
lookups sent to whichever Bitcoin explorer(s) you've configured — no file
contents, no hashes, no telemetry, no calls to umarise or anywhere else.

## License

[Unlicense](LICENSE) — public domain. Fork it, host it yourself, keep it
running if this copy ever goes offline; that's the point.
