OpenTimestamps anchor
OC Stamp envelopes carry an optional ots field that anchors the envelope's
id to a Bitcoin block header via
OpenTimestamps (OTS). @orangecheck/stamp-ots is
the thin client-side glue.
What OTS is (one paragraph)
OTS calendars batch many arbitrary-digest timestamp requests into a Merkle tree
and publish the tree root on-chain via an OP_RETURN transaction. A calendar
hands back a proof — a Merkle path from your digest to the tree root, plus a
reference to the on-chain transaction that committed the root. Anyone with an
honest Bitcoin node can verify the proof chain and conclude "this digest existed
before this block."
OTS is calendar-operator-trust-minimized (you can submit to multiple calendars; forgery requires block-level collusion), free to use, and widely deployed since 2016.
What @orangecheck/stamp-ots does
Four exports cover the whole flow:
submitToCalendars(id, opts)— POST an envelope's 32-byte digest to one or more OTS calendars. Returns a pendingOtsProofyou fold into the envelope'sotsfield.upgradeProof(proof, id, opts)— poll calendars for an upgraded proof once OTS has anchored the batch to a Bitcoin block. Returns a confirmedOtsProofwhen available, or the same pending proof if not yet confirmed.createCalendarClient(url)— low-level HTTP client implementing the minimal OTS calendar API (POST /digest,GET /timestamp/<hex>).makeAnchorVerifier({ walkProof, headerSource })— adapter that turns a proof-parser plus a block-header source into theverifyOtsAnchorfunctionstamp-core.verify()wants.
Submit
import { submitToCalendars } from '@orangecheck/stamp-ots';
env.ots = await submitToCalendars(env.id, {
calendars: [
'https://alice.btc.calendar.opentimestamps.org',
'https://bob.btc.calendar.opentimestamps.org',
],
});
The returned proof is pending: the calendar has accepted your digest but the OTS batch for the next hour (or whatever the calendar's cadence is) hasn't yet been anchored to Bitcoin. The envelope is still useful at this point — it proves authorship — it just doesn't have a time anchor yet.
Submit to at least two calendars operated by different parties. If one calendar goes dark before anchoring, the second one's receipt still lets you upgrade.
Upgrade
import { upgradeProof } from '@orangecheck/stamp-ots';
// Poll until confirmed. Typically minutes to hours.
env.ots = await upgradeProof(env.ots, env.id);
upgradeProof() asks each calendar "has the batch containing my digest been
anchored yet?" When the answer is yes, the calendar returns the Merkle path from
your digest to the batch root plus the Bitcoin tx that committed the root. At
this point the proof is confirmed and independently verifiable against any
Bitcoin node.
Cache the upgraded proof. Once the chain has confirmed, the proof never changes — you can verify it offline forever.
Verify
OTS proofs contain a binary Merkle-walk format that's non-trivial to parse.
@orangecheck/stamp-ots doesn't ship a full parser — it's a lot of code for a
narrow use case. Instead, it exposes an adapter:
import { makeAnchorVerifier } from '@orangecheck/stamp-ots';
import * as ots from 'javascript-opentimestamps'; // full parser, separate lib
const verifyOtsAnchor = makeAnchorVerifier({
walkProof: (bytes) => ots.parse(bytes), // your proof parser
headerSource: async (blockHeight) => fetchHeader(blockHeight), // your block-header source
});
// Feed into stamp-core.verify()
const result = await verify({
envelope,
content,
verifyBip322,
verifyOtsAnchor,
});
This lets consumers pick their own proof parser (we recommend
javascript-opentimestamps)
and their own block-header source (a local Bitcoin node, an Esplora mirror, a
light-client SPV header chain — your call). The result is the same: a verifier
that's fully offline-capable once it has the block header.
Offline verification
Given the envelope, the content, an upgraded OTS proof, and a cached block-header database, a verifier can confirm the stamp without any network call. This matters for:
- Air-gapped archives.
- Historical records (you want to verify a 2030 stamp in 2050 without trusting whatever OTS calendar operators happen to exist then).
- Adversarial environments where upstream calendars might be censored.
What could go wrong
- Calendar disappears before anchoring. Mitigation: submit to ≥2 independent calendars.
- Calendar anchors but lies about the path. Mitigation: the proof chain is cryptographically self-checking against the Bitcoin header. A dishonest calendar can't fake the path; they can only refuse to publish.
- Bitcoin block reorgs deeper than the anchor. Mitigation: wait for ≥6 confirmations before treating a stamp as permanent. OTS proofs still verify; you just care about rollback risk for whatever you're doing.
See also
- How it works — where
otsfits in the envelope flow - Use cases — when anchoring matters (release signing, publishing) and when it doesn't