1 Overview
Sometimes a client needs to prove that an event existed at or before a certain time, without trusting any single server to say so. OpenTimestamps solves this problem outside Nostr. It hashes a piece of data, submits the hash to calendar servers, and anchors it into a Bitcoin block through a Merkle tree. Once a Bitcoin block confirms the anchor, no one can move the timestamp earlier or later.
This NIP defines a way to carry an OpenTimestamps proof inside Nostr. A new event, kind 1040, wraps the raw bytes of an .ots proof file and points at the event it timestamps with an NIP-01 e tag. Anyone who has the target event and the kind-1040 attestation can verify, offline, that the target event existed before the Bitcoin block referenced in the proof.
The attestation is a separate event from the one it proves. A client publishes the target event first, waits for OpenTimestamps to produce a confirmed proof, then publishes the kind-1040 event that references it.
The Nostr NIPs project marks NIP-03 unrecommended. The source specification gives the reason as: vulnerable to one specific attack, and it needs an update. No replacement NIP is proposed as of this writing. Do not build new trust decisions on kind-1040 attestations until this NIP is revised.
The source markdown does not name the attack, describe its mechanism, or give a timeline for a fix. This manual restates the warning as written. Treat any kind-1040 attestation you encounter as informational only, not as cryptographic proof, until an updated specification exists.
2 The Attestation Event
A kind-1040 event follows the standard NIP-01 event envelope. This NIP defines the meaning of its tags and content fields only.
{
"id": <32-byte lowercase hex sha256 of the serialized event>
"pubkey": <32-byte lowercase hex public key of the creator>
"created_at": <unix timestamp, seconds>
"kind": 1040,
"tags": [
["e", <target-event-id>, <relay-url>],
["k", <target-event-kind, as a string>]
],
"content": <base64-encoded .ots file bytes>,
"sig": <64-byte lowercase hex Schnorr signature of the id>
}
The JSON example in the source markdown omits the comma after "kind": 1040, which makes it invalid JSON. The block above corrects it and fills in the standard NIP-01 fields the source example leaves out.
Kind 1040 falls in the regular range defined by NIP-01 (1000-9999). A relay is expected to store every kind-1040 event it accepts, not just the latest one.
| Tag | Full form | Rule |
|---|---|---|
| e | ["e", <target-event-id>, <relay-url>] | Required. Points at the event this attestation proves. The id in the referenced OpenTimestamps proof must match this event id exactly. |
| k | ["k", "<target-event-kind>"] | Required. The kind number of the target event, written as a string, so a client can filter attestations by the kind they cover. |
| Field | Type | Rule |
|---|---|---|
| content | base64 string | The full, unmodified bytes of an .ots proof file, base64-encoded. |
Three normative rules govern the proof itself:
- The OpenTimestamps proof must prove the event id in the
etag as its digest — the proof is worthless if it timestamps a different piece of data. - The
contentmust be the complete content of an.otsfile, and that file must contain at least one Bitcoin attestation. - The
.otsfile should contain a single Bitcoin attestation. One valid attestation is enough to prove the timestamp, and a smaller file costs less to transmit and store. The file should not reference "pending" attestations — an attestation that is not yet confirmed on a Bitcoin block proves nothing, so it only adds useless bytes.
3 Worked Example
First, a client publishes the target event and gets an OpenTimestamps proof for it (the details of talking to calendar servers are outside this NIP). Then it publishes the kind-1040 attestation.
{
"id": "7f0f4d1a…c92be3",
"pubkey": "22a12a12…ecf19a",
"created_at": 1749849000,
"kind": 1040,
"tags": [
["e", "e71c6ea722987debdb60f81f9ea4f604b5ac0664120dd64fb9d23abc4ec7c323", "wss://nostr-pub.wellorder.net"],
["k", "1"]
],
"content": "AE9PU1BST0YAAP42a3zoMdV…3h5ZzE=",
"sig": "9c1b0a3e…88f0d1"
}
Later, any client can fetch the target event, fetch this attestation, and verify the proof offline with an OpenTimestamps-aware tool. Using nak, jq, and ots:
~> nak req -i e71c6ea722987debdb60f81f9ea4f604b5ac0664120dd64fb9d23abc4ec7c323 wss://nostr-pub.wellorder.net | jq -r .content | ots verify > using an esplora server at https://blockstream.info/api - sequence ending on block 810391 is valid timestamp validated at block [810391]
The nak req command fetches the kind-1040 event by its id. jq -r .content pulls out the base64 .ots bytes. ots verify decodes them and checks the Bitcoin block, confirming the target event existed at or before block 810391.
4 Version History
Notable changes, newest first:
- 20262026-05Marked unrecommended and added the vulnerability warning to the NIP body.
- 20252025-08Fixed a missing
ktag in an example, as part of a wider tag-consistency pass across several NIPs (#2001). - 20232023-11Explained why publishers should include only a single Bitcoin attestation rather than several.
- 20232023-11Removed the NIP authors byline, part of a site-wide cleanup.
- 20232023-10Removed a leftover, unused "block" tag from the spec.
- 20232023-10Major rework: replaced the earlier design with today's
kind:1040event,e/ktags, and base64.otscontent (#804). - 20232023-01Quoted JSON field keys in the example, since unquoted keys are not valid JSON.
- Origin2022-05-01fiatjaf migrated NIP-03 into the dedicated
nostr-protocol/nipsrepository.
Original Proposal - Credits
| Author | fiatjaf |
| First commit | 2022-05-01 — "migrate nips from main nostr repo" |
| Status | draft - unrecommended - optional — deprecated, do not rely on it for new work |
| Source | 03.md on GitHub - full change history |
5 See Also
- NIP-01 — event envelope and kind storage classes; kind 1040 is a regular kind under these rules.
- NIP-40 — expiration timestamps on an event; a different kind of time claim, set by the author instead of anchored in Bitcoin.
- NIP-13 — proof of work; another way to attach a costly, independently checkable property to an event.