12<html lang="en">3<head>4<meta charset="utf-8">5<meta name="viewport" content="width=device-width, initial-scale=1">6<title>NIP-03 - OpenTimestamps Attestations for Events — Nostr Instruction Manual</title>7<meta name="description" content="NIP-03: attach a Bitcoin-anchored OpenTimestamps proof to a Nostr event with a kind:1040 attestation event. Deprecated — read the warning first.">8<link rel="preload" href="../assets/fonts/michroma-400-latin.woff2" as="font" type="font/woff2" crossorigin>9<link rel="preload" href="../assets/fonts/exo2-var-latin.woff2" as="font" type="font/woff2" crossorigin>10<link rel="preload" href="../assets/fonts/sairacondensed-600-latin.woff2" as="font" type="font/woff2" crossorigin>11<link rel="preload" href="../assets/fonts/sairacondensed-700-latin.woff2" as="font" type="font/woff2" crossorigin>12<link rel="preload" href="../assets/fonts/sairacondensed-500-latin.woff2" as="font" type="font/woff2" crossorigin>13<link rel="preload" href="../assets/fonts/sharetechmono-400-latin.woff2" as="font" type="font/woff2" crossorigin>14<link rel="preload" href="../assets/fonts/exo2-400i-latin.woff2" as="font" type="font/woff2" crossorigin>15<link rel="icon" href="../assets/favicon.svg" type="image/svg+xml">16<link rel="stylesheet" href="../assets/manual.css">17</head>18<body>1920<svg width="0" height="0" style="position:absolute">21 <defs>22 <marker id="arr" markerWidth="9" markerHeight="9" refX="7" refY="4.5" orient="auto">23 <path d="M1,1 L8,4.5 L1,8 Z" fill="#f26a00"/>24 </marker>25 <marker id="arrb" markerWidth="9" markerHeight="9" refX="7" refY="4.5" orient="auto">26 <path d="M1,1 L8,4.5 L1,8 Z" fill="#2e5f9e"/>27 </marker>28 </defs>29</svg>3031<div class="page">32 <span class="staple s1"></span><span class="staple s2"></span>3334 <header class="band">35 <a class="logo" href="../index.html">NOSTR<b>▸</b>PROTOCOL MANUAL</a>36 <span class="booklet-note">Chapter 03 - Timestamping</span>37 </header>38 <nav class="tabs">39 <a href="../index.html">Contents</a>40 <a href="#overview" class="on">Overview</a>41 <a href="#spec">Spec</a>42 <a href="#examples">Examples</a>43 <a href="#history">History</a>44 </nav>4546 <div class="chapter">47 <div class="nipno" data-n="03">03</div>48 <div class="titles">49 <div class="kicker">Nostr Implementation Possibility - Hex № 03</div>50 <h1>OpenTimestamps Attestations for Events</h1>51 <p class="sub">Attach a Bitcoin-anchored OpenTimestamps proof to any event, to show it existed before a certain time. Deprecated — read the warning below first.</p>52 </div>53 <div class="stamp">Deprecated</div>54 </div>5556 <main class="spread">57 <div class="main">5859 <h2 class="bar" id="overview"><span class="num">1</span> Overview</h2>60 <p>Sometimes a client needs to prove that an event existed at or before a certain time, without trusting any single server to say so. <a href="https://opentimestamps.org/">OpenTimestamps</a> 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.</p>61 <p>This NIP defines a way to carry an OpenTimestamps proof inside Nostr. A new event, <strong>kind 1040</strong>, wraps the raw bytes of an <code>.ots</code> proof file and points at the event it timestamps with an <a href="01.html">NIP-01</a> <code>e</code> 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.</p>62 <p>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.</p>6364 <div class="box danger">65 <span class="tag">Deprecated</span>66 <p>The Nostr NIPs project marks NIP-03 <strong>unrecommended</strong>. 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.</p>67 </div>6869 <div class="box tech">70 <span class="tag">Tech Note</span>71 <p>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.</p>72 </div>7374 <h2 class="bar" id="spec"><span class="num">2</span> The Attestation Event</h2>75 <p>A kind-1040 event follows the standard <a href="01.html">NIP-01</a> event envelope. This NIP defines the meaning of its <code>tags</code> and <code>content</code> fields only.</p>7677 <div class="crt">78 <div class="titlebar">Wire format - kind 1040 attestation (corrected)</div>79<pre>{80 <span class="k">"id"</span>: <span class="c"><32-byte lowercase hex sha256 of the serialized event></span>81 <span class="k">"pubkey"</span>: <span class="c"><32-byte lowercase hex public key of the creator></span>82 <span class="k">"created_at"</span>: <span class="c"><unix timestamp, seconds></span>83 <span class="k">"kind"</span>: <span class="s">1040</span>,84 <span class="k">"tags"</span>: [85 [<span class="s">"e"</span>, <span class="c"><target-event-id></span>, <span class="c"><relay-url></span>],86 [<span class="s">"k"</span>, <span class="c"><target-event-kind, as a string></span>]87 ],88 <span class="k">"content"</span>: <span class="c"><base64-encoded .ots file bytes></span>,89 <span class="k">"sig"</span>: <span class="c"><64-byte lowercase hex Schnorr signature of the id></span>90}</pre>91 </div>92 <div class="box tech">93 <span class="tag">Tech Note</span>94 <p>The JSON example in the source markdown omits the comma after <code>"kind": 1040</code>, which makes it invalid JSON. The block above corrects it and fills in the standard <a href="01.html">NIP-01</a> fields the source example leaves out.</p>95 </div>9697 <p>Kind 1040 falls in the <strong>regular</strong> range defined by <a href="01.html">NIP-01</a> (1000-9999). A relay is expected to store every kind-1040 event it accepts, not just the latest one.</p>9899 <div class="tscroll"><table class="ref">100 <caption>Tags defined by this NIP</caption>101 <tr><th>Tag</th><th>Full form</th><th>Rule</th></tr>102 <tr><td class="btn">e</td><td><code>["e", <target-event-id>, <relay-url>]</code></td><td>Required. Points at the event this attestation proves. The <code>id</code> in the referenced OpenTimestamps proof must match this event id exactly.</td></tr>103 <tr><td class="btn">k</td><td><code>["k", "<target-event-kind>"]</code></td><td>Required. The <code>kind</code> number of the target event, written as a string, so a client can filter attestations by the kind they cover.</td></tr>104 </table></div>105106 <div class="tscroll"><table class="ref">107 <caption>Content field</caption>108 <tr><th>Field</th><th>Type</th><th>Rule</th></tr>109 <tr><td class="btn">content</td><td>base64 string</td><td>The full, unmodified bytes of an <code>.ots</code> proof file, base64-encoded.</td></tr>110 </table></div>111112 <p>Three normative rules govern the proof itself:</p>113 <ul>114 <li>The OpenTimestamps proof <strong>must</strong> prove the event id in the <code>e</code> tag as its digest — the proof is worthless if it timestamps a different piece of data.</li>115 <li>The <code>content</code> <strong>must</strong> be the complete content of an <code>.ots</code> file, and that file <strong>must</strong> contain at least one Bitcoin attestation.</li>116 <li>The <code>.ots</code> file <strong>should</strong> contain a <strong>single</strong> Bitcoin attestation. One valid attestation is enough to prove the timestamp, and a smaller file costs less to transmit and store. The file <strong>should not</strong> reference "pending" attestations — an attestation that is not yet confirmed on a Bitcoin block proves nothing, so it only adds useless bytes.</li>117 </ul>118119 <figure class="diagram">120 <div class="frame">121 <svg viewBox="0 0 620 170" role="img" aria-label="Pipeline: an event is hashed and stamped by OpenTimestamps, confirmed on a Bitcoin block, then wrapped in a kind 1040 event and published to a relay">122 <rect class="d-actor" x="10" y="40" width="110" height="50" rx="8"/>123 <text class="d-actor-label" x="65" y="62" text-anchor="middle">Event</text>124 <text class="d-note" x="65" y="79" text-anchor="middle">signed, has id</text>125 <rect class="d-actor" x="155" y="40" width="140" height="50" rx="8"/>126 <text class="d-actor-label" x="225" y="62" text-anchor="middle">OpenTimestamps</text>127 <text class="d-note" x="225" y="79" text-anchor="middle">digest = event id</text>128 <rect class="d-actor" x="330" y="40" width="110" height="50" rx="8"/>129 <text class="d-actor-label" x="385" y="62" text-anchor="middle">Bitcoin</text>130 <text class="d-note" x="385" y="79" text-anchor="middle">block confirms</text>131 <rect class="d-actor" x="475" y="40" width="135" height="50" rx="8"/>132 <text class="d-actor-label" x="542" y="58" text-anchor="middle">kind:1040</text>133 <text class="d-note" x="542" y="74" text-anchor="middle">e + k tags, .ots</text>134 <path class="d-msg d-flow" d="M122,65 L152,65"/>135 <path class="d-msg d-flow" d="M297,65 L327,65"/>136 <path class="d-msg d-flow" d="M442,65 L472,65"/>137 <rect class="d-actor" x="475" y="128" width="135" height="34" rx="8"/>138 <text class="d-actor-label" x="542" y="150" text-anchor="middle">Relay</text>139 <path class="d-msg blue d-flow" d="M542,92 L542,125"/>140 <text class="d-label" x="588" y="112" text-anchor="middle">EVENT</text>141 </svg>142 </div>143 <figcaption>Fig. 3-1 — <b>Attestation pipeline.</b> OpenTimestamps anchors the event id into a Bitcoin block; the proof is then wrapped in a kind:1040 event and published like any other event.</figcaption>144 </figure>145146 <h2 class="bar" id="examples"><span class="num">3</span> Worked Example</h2>147 <p>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.</p>148149 <div class="crt">150 <div class="titlebar">Example event - kind 1040 attestation</div>151<pre>{152 <span class="k">"id"</span>: <span class="s">"7f0f4d1a…c92be3"</span>,153 <span class="k">"pubkey"</span>: <span class="s">"22a12a12…ecf19a"</span>,154 <span class="k">"created_at"</span>: <span class="s">1749849000</span>,155 <span class="k">"kind"</span>: <span class="s">1040</span>,156 <span class="k">"tags"</span>: [157 [<span class="s">"e"</span>, <span class="s">"e71c6ea722987debdb60f81f9ea4f604b5ac0664120dd64fb9d23abc4ec7c323"</span>, <span class="s">"wss://nostr-pub.wellorder.net"</span>],158 [<span class="s">"k"</span>, <span class="s">"1"</span>]159 ],160 <span class="k">"content"</span>: <span class="s">"AE9PU1BST0YAAP42a3zoMdV…3h5ZzE="</span>,161 <span class="k">"sig"</span>: <span class="s">"9c1b0a3e…88f0d1"</span>162}</pre>163 </div>164165 <p>Later, any client can fetch the target event, fetch this attestation, and verify the proof offline with an OpenTimestamps-aware tool. Using <a href="https://github.com/fiatjaf/nak">nak</a>, <a href="https://jqlang.github.io/jq/">jq</a>, and <a href="https://github.com/fiatjaf/ots">ots</a>:</p>166167 <div class="crt">168 <div class="titlebar">Terminal - verify an OpenTimestamps proof</div>169<pre>~> nak req -i e71c6ea722987debdb60f81f9ea4f604b5ac0664120dd64fb9d23abc4ec7c323 wss://nostr-pub.wellorder.net | jq -r .content | ots verify170<span class="c">> using an esplora server at https://blockstream.info/api</span>171<span class="c">- sequence ending on block 810391 is valid</span>172timestamp validated at block [810391]</pre>173 </div>174 <p>The <code>nak req</code> command fetches the kind-1040 event by its id. <code>jq -r .content</code> pulls out the base64 <code>.ots</code> bytes. <code>ots verify</code> decodes them and checks the Bitcoin block, confirming the target event existed at or before block 810391.</p>175176 <h2 class="bar" id="history"><span class="num">4</span> Version History</h2>177 <p>Notable changes, newest first:</p>178 <ul class="patchnotes">179 <li><span class="ver">2026</span><span class="when">2026-05</span>Marked <strong>unrecommended</strong> and added the vulnerability warning to the NIP body.</li>180 <li class="minor"><span class="ver">2025</span><span class="when">2025-08</span>Fixed a missing <code>k</code> tag in an example, as part of a wider tag-consistency pass across several NIPs (<a href="https://github.com/nostr-protocol/nips/pull/2001">#2001</a>).</li>181 <li><span class="ver">2023</span><span class="when">2023-11</span>Explained why publishers should include only a single Bitcoin attestation rather than several.</li>182 <li class="minor"><span class="ver">2023</span><span class="when">2023-11</span>Removed the NIP authors byline, part of a site-wide cleanup.</li>183 <li><span class="ver">2023</span><span class="when">2023-10</span>Removed a leftover, unused "block" tag from the spec.</li>184 <li><span class="ver">2023</span><span class="when">2023-10</span>Major rework: replaced the earlier design with today's <code>kind:1040</code> event, <code>e</code>/<code>k</code> tags, and base64 <code>.ots</code> content (<a href="https://github.com/nostr-protocol/nips/pull/804">#804</a>).</li>185 <li class="minor"><span class="ver">2023</span><span class="when">2023-01</span>Quoted JSON field keys in the example, since unquoted keys are not valid JSON.</li>186 <li><span class="ver">Origin</span><span class="when">2022-05-01</span>fiatjaf migrated NIP-03 into the dedicated <code>nostr-protocol/nips</code> repository.</li>187 </ul>188189 <div class="credits">190 <h3 class="cardhead">Original Proposal - Credits</h3>191 <table>192 <tr><td>Author</td><td>fiatjaf</td></tr>193 <tr><td>First commit</td><td>2022-05-01 — "migrate nips from main nostr repo"</td></tr>194 <tr><td>Status</td><td>draft - unrecommended - optional — deprecated, do not rely on it for new work</td></tr>195 <tr><td>Source</td><td><a href="https://github.com/nostr-protocol/nips/blob/master/03.md">03.md on GitHub</a> - <a href="https://github.com/nostr-protocol/nips/commits/master/03.md">full change history</a></td></tr>196 </table>197 </div>198199 <h2 class="bar"><span class="num">5</span> See Also</h2>200 <ul>201 <li><a href="01.html">NIP-01</a> — event envelope and kind storage classes; kind 1040 is a regular kind under these rules.</li>202 <li><a href="40.html">NIP-40</a> — expiration timestamps on an event; a different kind of time claim, set by the author instead of anchored in Bitcoin.</li>203 <li><a href="13.html">NIP-13</a> — proof of work; another way to attach a costly, independently checkable property to an event.</li>204 </ul>205 </div>206207 <aside>208 <div class="cartridge">209 <div class="inner">210 <h3 class="cardhead">Spec Card</h3>211 <div class="lights"><span class="led err"></span><span class="led err"></span><span class="led err"></span></div>212 <dl>213 <dt>Status</dt><dd><span class="chip draft">draft</span><span class="chip unrecommended">unrecommended</span><span class="chip">optional</span></dd>214 <dt>Defines</dt><dd>kind 1040 - e/k tags - .ots content</dd>215 <dt>Kind</dt><dd>1040 (regular)</dd>216 <dt>Requires</dt><dd><a href="01.html">NIP-01</a></dd>217 <dt>Transport</dt><dd>WebSocket - JSON arrays</dd>218 <dt>Crypto</dt><dd>sha256 event id - Bitcoin block via OpenTimestamps</dd>219 <dt>First commit</dt><dd>2022-05-01</dd>220 <dt>Last updated</dt><dd>2026-05-31</dd>221 </dl>222 </div>223 </div>224 </aside>225 </main>226227 <nav class="navbtns">228 <a href="02.html">◀ NIP-02: Follow List</a>229 <a href="04.html">NIP-04: Encrypted Direct Message ▶</a>230 </nav>231232 <footer class="pagefoot">233 <a href="../index.html">Nostr Protocol Manual</a>234 <span class="pageno">NIP 03 / F4</span>235 </footer>236</div>237238<script src="../assets/webmcp.js" defer></script>239</body>240</html>