Files
2026-07-16 20:36:25 -04:00

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

  1. Submit a hash to an OpenTimestamps calendar server (e.g., https://alice.btc.calendar.opentimestamps.org)
  2. The calendar server aggregates hashes from multiple users into a Merkle tree
  3. The calendar server commits the Merkle root to a Bitcoin transaction (OP_RETURN)
  4. The server returns a pending .ots file — a proof file that contains the Merkle path but no Bitcoin confirmation yet
  5. Wait for the Bitcoin transaction to confirm (typically 1-3 blocks, ~10-30 minutes)
  6. The .ots file can then be upgraded to include the Bitcoin confirmation proof
  7. 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 is SHA-256(serialized event))
  • The content MUST be the full content of an .ots file with at least one Bitcoin attestation
  • NIP-03 is marked unrecommended but 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:

  1. Convert the event id (hex string) to raw 32 bytes
  2. POST those bytes to the calendar server
  3. 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:

  1. Find the kind 1040 event referencing the NIP-QR event
  2. Extract the .ots file from the content (base64-decode)
  3. Verify the .ots proof against the Bitcoin blockchain
  4. 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.org
  • https://bob.btc.calendar.opentimestamps.org
  • https://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:

  1. Sign in with your Nostr identity
  2. Generate a quantum-safe seed phrase
  3. Derive post-quantum keys from seed
  4. Sign the NIP-QR migration event
  5. Publish event to relays
  6. Timestamp the event with OpenTimestamps (new)
  7. Verify the event signatures (link to verify.html)

After publishing to relays (step 5), the page automatically:

  1. Submits the event hash to the OpenTimestamps calendar
  2. Receives the pending .ots file
  3. Shows the .ots file (base64) and a download link
  4. Explains that the proof needs Bitcoin confirmation (10-30 minutes)
  5. Offers to check/upgrade the proof later

OTS Verification

For verifying .ots files in the browser, we can use the javascript-opentimestamps library:

This library can verify .ots files against a Bitcoin blockchain API (e.g., Blockstream's esplora API at https://blockstream.info/api).

Updated Checklist

  1. Sign in with your Nostr identity
  2. Generate a quantum-safe seed phrase
  3. Derive post-quantum keys from seed
  4. Sign the NIP-QR migration event
  5. Publish event to relays
  6. Timestamp the event with OpenTimestamps
  7. 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

  1. timestampEvent(eventIdHex) function — submits hash to OTS calendar, returns .ots file
  2. upgradeOts(otsBytes) function — upgrades pending .ots to confirmed
  3. UI for step 6 — shows OTS status, pending .ots file, download link, upgrade button
  4. Publish kind 1040 event — after .ots is confirmed, sign and publish the NIP-03 event
  5. OTS verification in verify.html — verify .ots proof against Bitcoin blockchain
  6. 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