How NakedPnL Anchors Performance Data to Bitcoin via OpenTimestamps
A technical explainer of how OpenTimestamps batches NakedPnL's daily Merkle root into a Bitcoin transaction, and how anyone can independently verify the proof.
- OpenTimestamps is a free, open standard for timestamping arbitrary data by aggregating it into a Merkle tree and committing the root to a Bitcoin transaction.
- NakedPnL builds one Merkle root per day from every active entity's chain head, submits the root to four independent OTS calendar servers, and stores the resulting attestation proofs in the AnchorRecord table.
- Each proof transitions from PENDING to UPGRADED once the calendar's aggregated tree is included in a Bitcoin block — typically within a few hours, occasionally up to ~24 hours.
- The public endpoint GET /api/verify/[date] returns the proof bytes plus the underlying Merkle root, so anyone can re-verify the anchor with the standard ots-cli tool against an independent Bitcoin node.
Cryptographic timestamping answers a deceptively narrow question: as of when did this exact byte sequence exist? The naive answer is to ask a trusted third party — a notary, a certificate authority, an RFC 3161 timestamping server — to sign the digest with the current date. That works, but it pins the trust on the notary. If the notary is compromised, coerced, or simply stops being maintained, every certificate it issued loses its evidentiary weight.
OpenTimestamps replaces the notary with a public, decentralised, proof-of-work-secured ledger: Bitcoin. Once a digest is committed inside a Bitcoin transaction and that transaction is buried under a few thousand subsequent blocks, rewriting history would cost an attacker hundreds of millions of dollars in mining hardware and energy. NakedPnL uses this property to anchor a daily snapshot of the entire registry to Bitcoin, producing a public, independently verifiable timestamp on every trader's verified track record.
The trusted-timestamping problem
Suppose a trader publishes a 12-month verified track record on NakedPnL and an allocator wants to confirm, six months later, that the early entries have not been retroactively edited. A SHA-256 hash chain (covered in our companion article) detects in-place modification of any record, but only if the verifier already observed the chain head before the modification. If the entire database were replaced, the verifier has no external reference point.
Trusted timestamping fixes that by anchoring the chain head to an external observation. The classical solution is RFC 3161 — a TSA (Time Stamping Authority) signs the digest with its private key and the current time. The signature is verifiable as long as the TSA's public key is trusted. The three weak points: the TSA can be compelled, the TSA can be hacked, and the TSA can stop existing. Every signature it produced becomes a question of how strong its archival posture is.
How OpenTimestamps actually works
OpenTimestamps is a protocol, a specification, and a small ecosystem of free public servers (calendars). It batches submitted digests into Merkle trees and commits the roots to Bitcoin on a schedule. The full flow:
- A client computes a SHA-256 digest of the data it wants to timestamp (in our case, the daily Merkle root of all NakedPnL chain heads).
- The client POSTs the 32 raw digest bytes to a calendar server (for example, https://a.pool.opentimestamps.org/digest).
- The calendar adds the digest as a leaf in its own running Merkle tree, returns a partial proof that links the digest up to the calendar's pending tree, and stores the digest internally.
- Periodically (every ~10 minutes for the public calendars), the calendar takes the root of its current tree, embeds it as an OP_RETURN payload in a Bitcoin transaction, and broadcasts it.
- Once the transaction is mined and the calendar verifies the inclusion, it extends the original proof with the path from the leaf, through the calendar's tree, into the Bitcoin block header.
- The client polls the calendar for the upgraded proof, which now contains the Bitcoin block height and transaction ID. Anyone with a Bitcoin node (or a trusted block-header source) can verify it offline.
The .ots file format is a tag-length-value binary blob that encodes the Merkle path along with a Bitcoin block-header attestation (magic bytes 0x0588960d73d71901). The OTS reference client and the standalone ots-cli tool both speak this format and can verify a proof against any Bitcoin node — including a trustless SPV client.
Calendar redundancy and the trust model
OTS calendars are convenience services, not trust roots. NakedPnL submits each daily root to four independent calendars run by separate operators, with an SSRF-safe allowlist enforced in lib/ots/anchor.ts:
const ALLOWED_CALENDARS = new Set([
"https://a.pool.opentimestamps.org",
"https://b.pool.opentimestamps.org",
"https://a.pool.eternitywall.com",
"https://btc.calendar.catallaxy.com",
]);
function validateCalendarUrl(url: string): void {
if (!ALLOWED_CALENDARS.has(url)) {
throw new Error(`Untrusted calendar URL: ${url}`);
}
}If any single calendar disappears, the proofs from the other three remain valid forever — they each independently anchor the same digest to Bitcoin. This is the same defence-in-depth argument that motivates running multiple DNS servers or multiple time servers. A calendar's job ends the moment the proof is upgraded; the proof itself does not depend on the calendar's continued existence.
Why anchor a daily root, not every snapshot
Bitcoin transaction fees are non-trivial and confirmation time is measured in blocks (~10 minutes each). Anchoring every individual NavSnapshot — typically 365 per trader per year, scaled across thousands of traders — would be financially and architecturally absurd. Instead, NakedPnL builds one Merkle tree per day from all chain heads and anchors only the root.
Because the Merkle tree is deterministic (leaves sorted by entityId + chainType, see lib/ots/merkle.ts), anyone can reconstruct the same root from a public snapshot of the registry on a given day. And because each leaf is an entity's chain head, the root transitively commits to every NavSnapshot in every chain. One Bitcoin transaction commits to millions of underlying records.
| Strategy | BTC tx per day | Records anchored / tx | Latency to BTC confirmation |
|---|---|---|---|
| Per-snapshot | ~10,000+ | 1 | ~10 min — 24 hr |
| Per-trader chain head | ~10,000 | ~365 | ~10 min — 24 hr |
| Daily Merkle root (NakedPnL) | 1 (via batched calendar) | Millions | Typically <6 hr |
The PENDING → UPGRADED state machine
An OTS proof has two distinct states. NakedPnL persists this in the AnchorRecord.status field and reflects it in the public verifier output:
- PENDING: the calendar has accepted the digest and returned an initial proof, but the calendar's aggregated Merkle root has not yet been included in a Bitcoin block. The proof is valid as a calendar receipt but does not yet have Bitcoin-level guarantees.
- UPGRADED: the calendar's root has been confirmed in a Bitcoin block, and the proof has been extended with a Bitcoin block-header attestation. The proof now includes the block height and transaction ID and can be verified against any Bitcoin node, even offline.
- FAILED: if a proof has been pending for more than 7 days without upgrading, NakedPnL marks it FAILED and re-submits the underlying digest to the calendars. The most common cause is a calendar outage, not a Bitcoin issue.
The cron at /api/cron/ots-anchor runs daily at 00:05 UTC, builds the Merkle root, submits to all four calendars, and writes the PENDING records. A second cron at 00:30 UTC, /api/cron/ots-upgrade, walks every PENDING proof older than ~6 hours and attempts to upgrade it. The upgrade detection logic (parseUpgradedProof in lib/ots/anchor.ts) scans for the Bitcoin attestation magic bytes, reads the block height as a 4-byte little-endian integer, and extracts the 32-byte transaction ID. If the response is the same size as the original pending proof, no upgrade has happened yet and the row stays PENDING.
Verifying a NakedPnL anchor proof yourself
The public endpoint GET /api/verify/[date] returns the AnchorRecord for the requested date, including the Merkle root, the proof bytes (base64), the calendar URL, the status, and (for UPGRADED rows) the Bitcoin block height and transaction ID. Anyone can take that proof and verify it offline with the standard ots-cli tool.
# 1. Fetch the anchor record for a specific date.
curl -s https://nakedpnl.com/api/verify/2026-05-06 > anchor.json
# 2. Extract the Merkle root and the proof bytes.
jq -r .merkleRoot anchor.json > root.txt
jq -r .proofBase64 anchor.json | base64 -d > anchor.ots
# 3. Save the root as the file being verified. OTS verifies a digest,
# so we feed it the raw 32 bytes whose hex is the merkleRoot.
xxd -r -p root.txt > root.bin
# 4. Run the standard ots-cli verifier against your local Bitcoin node.
ots --bitcoin-node http://user:pass@127.0.0.1:8332 verify anchor.ots root.bin
# Expected output:
# Got 1 attestation(s) from https://a.pool.opentimestamps.org
# Success! Bitcoin block 901234 attests existence as of 2026-05-06 00:08:13 UTCThe verification depends only on the Bitcoin block header your node has independently downloaded. NakedPnL is not in the loop. If a future version of NakedPnL claims a different historical chain head for that date, the proof will fail to verify and the discrepancy is publicly demonstrable.
What the anchor proves and does not prove
An UPGRADED OTS proof on the Merkle root M for date D establishes a single, narrow fact: the bytes of M existed at or before the timestamp of the Bitcoin block referenced in the proof. Combined with the deterministic Merkle construction in lib/ots/merkle.ts, that means every chain head included in the tree existed at or before that block time. Combined with the per-trader hash chain, that means every NavSnapshot below those chain heads existed at or before that block time.
- It does prove: the historical record cannot have been written or rewritten after the Bitcoin block was mined. Retroactive editing is mathematically excluded.
- It does not prove: that the underlying venue data was honest, that the trader is who they say they are, or that the snapshot reflects all of the trader's positions on other accounts.
- It is not RFC 3161-equivalent: it does not produce an X.509 signed token. It produces something stronger — a publicly auditable commitment in the most-mined proof-of-work chain.
Operational realities
A few things that come up in production and are worth knowing:
- Latency: typical PENDING → UPGRADED transition is under 6 hours. Bitcoin block intervals are stochastic, so individual proofs can take up to ~24 hours under normal network conditions.
- Calendar outages: if all four calendars are simultaneously unreachable for a 24-hour window, that day's anchor cron logs a failure and is retried. Because the underlying chain heads are append-only and deterministic, the digest can be re-submitted on any later day with no loss of information.
- Bitcoin reorgs: a 1- or 2-block reorg can briefly invalidate a freshly upgraded proof. NakedPnL's verifier requires at least 6 confirmations (≈1 hour) before a proof is considered final, matching standard Bitcoin practice.
- No new dependencies: the implementation in lib/ots/anchor.ts speaks the OTS HTTP protocol directly using fetch(). No npm package wraps it. This is intentional — the protocol surface is small and stable, and we want zero supply-chain risk on the most security-critical code path.