8.1 KiB
OpenTimestamps Integration Plan
Why OpenTimestamps?
The NIP-QR migration event establishes a link between a Nostr identity and post-quantum keys. This link must be provably created before quantum computers exist. If an attacker later breaks secp256k1 via Shor's algorithm, they could forge a fraudulent NIP-QR event with a different set of PQ keys. Without timestamping, clients cannot distinguish the real event from a forged one.
OpenTimestamps anchors the event to a Bitcoin block, proving it existed at a specific time. Re-mining historical Bitcoin blocks is infeasible even with a quantum computer (Grover's algorithm gives only a quadratic speedup on mining, which doesn't help retroactively alter past blocks).
How OpenTimestamps Works
- Submit a hash to an OpenTimestamps calendar server (e.g.,
https://alice.btc.calendar.opentimestamps.org) - The calendar server aggregates hashes from multiple users into a Merkle tree
- The calendar server commits the Merkle root to a Bitcoin transaction (OP_RETURN)
- The server returns a pending .ots file — a proof file that contains the Merkle path but no Bitcoin confirmation yet
- Wait for the Bitcoin transaction to confirm (typically 1-3 blocks, ~10-30 minutes)
- The .ots file can then be upgraded to include the Bitcoin confirmation proof
- The final .ots file can be verified against the Bitcoin blockchain
NIP-03: OpenTimestamps Attestations for Events
NIP-03 defines kind: 1040 — an event that contains an OTS proof for another event:
{
"kind": 1040,
"tags": [
["e", "<target-event-id>", "<relay-url>"],
["k", "<target-event-kind>"]
],
"content": "<base64-encoded OTS file data>"
}
- The OTS proof MUST prove the referenced event's
id(which isSHA-256(serialized event)) - The content MUST be the full content of an
.otsfile with at least one Bitcoin attestation - NIP-03 is marked
unrecommendedbut is the only standard for OTS in Nostr
The Plan
Step 1: Submit the NIP-QR event hash to OpenTimestamps
After the NIP-QR event is signed (step 4 of our flow), before or after publishing to relays:
POST https://alice.btc.calendar.opentimestamps.org/timestamp
Content-Type: application/x-www-form-urlencoded
Body: <raw 32-byte SHA-256 hash of the event id>
The event id IS the SHA-256 hash (it's SHA-256(serialized event) in hex). We need to:
- Convert the event
id(hex string) to raw 32 bytes - POST those bytes to the calendar server
- Receive the .ots file (binary) in the response
Step 2: Save the pending .ots file
The response from the calendar server is a binary .ots file (pending proof). Store this:
- In browser memory (for immediate use)
- Offer the user a download link (so they can keep it)
Step 3: Wait for Bitcoin confirmation
The .ots proof is initially "pending" — it contains the Merkle path but no Bitcoin block confirmation. The user needs to wait for the calendar server to commit to Bitcoin (typically 1-3 blocks).
Options:
- Option A: Show the user a "pending" status and a link to upgrade later
- Option B: Poll the calendar server's upgrade endpoint periodically
- Option C: Use a WebSocket or long-poll to wait for confirmation
For the demo, Option A is simplest: show the pending .ots file, explain it needs time to confirm, and provide instructions for upgrading later.
Step 4: Upgrade the .ots file (later)
The pending .ots file can be upgraded by submitting it to the calendar server:
POST https://alice.btc.calendar.opentimestamps.org
Content-Type: application/x-www-form-urlencoded
Body: <raw .ots file bytes>
The server returns the upgraded .ots file with Bitcoin confirmation proof.
For the demo, this can be a manual step the user does later (or we can poll automatically).
Step 5: Publish the NIP-03 event (kind 1040)
Once the .ots file is confirmed (has a Bitcoin attestation), publish a kind: 1040 event:
{
"kind": 1040,
"pubkey": "<user's pubkey>",
"tags": [
["e", "<NIP-QR event id>", "<relay-url>"],
["k", "30078"]
],
"content": "<base64-encoded .ots file>"
}
This event is signed by the user's secp256k1 key (via window.nostr.signEvent()) and published to relays.
Step 6: Verify the OTS proof
The verification page (verify.html) can also verify the OTS proof:
- Find the kind 1040 event referencing the NIP-QR event
- Extract the .ots file from the content (base64-decode)
- Verify the .ots proof against the Bitcoin blockchain
- This proves the NIP-QR event existed at the time of the Bitcoin block
Implementation Details
API Endpoints
| Purpose | URL | Method |
|---|---|---|
| Submit hash for timestamping | https://alice.btc.calendar.opentimestamps.org/timestamp |
POST (raw bytes) |
| Upgrade pending .ots | https://alice.btc.calendar.opentimestamps.org |
POST (raw .ots bytes) |
| Verify .ots proof | Client-side verification (no server needed) | — |
Multiple calendar servers can be used for redundancy:
https://alice.btc.calendar.opentimestamps.orghttps://bob.btc.calendar.opentimestamps.orghttps://btc.calendar.catallaxy.com
Browser Implementation
The OpenTimestamps API uses raw binary POST requests, which can be done with fetch():
// Submit event hash for timestamping
async function timestampEvent(eventIdHex) {
const hashBytes = hexToBytes(eventIdHex); // 32 bytes
const response = await fetch('https://alice.btc.calendar.opentimestamps.org/timestamp', {
method: 'POST',
body: hashBytes
});
const otsFile = await response.arrayBuffer(); // binary .ots file
return new Uint8Array(otsFile);
}
UI Changes
Add a new step to the checklist:
- Sign in with your Nostr identity
- Generate a quantum-safe seed phrase
- Derive post-quantum keys from seed
- Sign the NIP-QR migration event
- Publish event to relays
- Timestamp the event with OpenTimestamps (new)
- Verify the event signatures (link to verify.html)
After publishing to relays (step 5), the page automatically:
- Submits the event hash to the OpenTimestamps calendar
- Receives the pending .ots file
- Shows the .ots file (base64) and a download link
- Explains that the proof needs Bitcoin confirmation (10-30 minutes)
- Offers to check/upgrade the proof later
OTS Verification
For verifying .ots files in the browser, we can use the javascript-opentimestamps library:
- npm:
javascript-opentimestamps - GitHub: https://github.com/opentimestamps/javascript-opentimestamps
This library can verify .ots files against a Bitcoin blockchain API (e.g., Blockstream's esplora API at https://blockstream.info/api).
Updated Checklist
- Sign in with your Nostr identity
- Generate a quantum-safe seed phrase
- Derive post-quantum keys from seed
- Sign the NIP-QR migration event
- Publish event to relays
- Timestamp the event with OpenTimestamps
- Verify the event signatures (link to verify.html)
Updated Flow
Sign in → Generate seed → Derive PQ keys → Sign event → Publish to relays
→ Submit hash to OpenTimestamps → Receive pending .ots
→ (wait for Bitcoin confirmation)
→ Upgrade .ots → Publish kind 1040 event → Done
What to Build
timestampEvent(eventIdHex)function — submits hash to OTS calendar, returns .ots fileupgradeOts(otsBytes)function — upgrades pending .ots to confirmed- UI for step 6 — shows OTS status, pending .ots file, download link, upgrade button
- Publish kind 1040 event — after .ots is confirmed, sign and publish the NIP-03 event
- OTS verification in verify.html — verify .ots proof against Bitcoin blockchain
- Install
javascript-opentimestamps— for client-side .ots verification
Considerations
- CORS: The OpenTimestamps calendar servers support CORS, so browser requests should work
- Time: Bitcoin confirmation takes 10-30 minutes — the user may need to come back later
- Multiple calendars: For redundancy, submit to multiple calendar servers
- NIP-03 unrecommended status: We use it anyway because it's the only standard, and the concept is sound
- The event id IS the hash: Nostr event ids are SHA-256 hashes, so we timestamp the id directly