clone

every nostr nip as a chapter of an illustrated, early-2000s printed game manual

main
nip.guide / site / nips / 37.html
37.html 19.9K
1<!DOCTYPE html>2<html lang="en">3<head>4<meta charset="utf-8">5<meta name="viewport" content="width=device-width, initial-scale=1">6<title>NIP-37 - Draft Wraps — Nostr Instruction Manual</title>7<meta name="description" content="NIP-37: Draft Wraps. Kind 31234 encrypted draft storage, kind 1234 checkpoints, and the kind 10013 private-content relay list. Complete manual page.">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 37 - Private Data</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="37">37</div>48 <div class="titles">49 <div class="kicker">Nostr Implementation Possibility - Hex № 37</div>50 <h1>Draft Wraps</h1>51 <p class="sub">Save unfinished events as encrypted drafts on your own relays, so no one reads a post before you publish it.</p>52 </div>53 </div>5455 <main class="spread">56 <div class="main">5758 <h2 class="bar" id="overview"><span class="num">1</span> Overview</h2>59 <p>A user often starts a post and finishes it later, on another device. Nostr has no server-side account to hold that unfinished text. This NIP fills the gap with a <strong>draft wrap</strong>: an encrypted container event for an unsigned draft of any other kind.</p>60 <p>The NIP defines three kinds. Kind <code>31234</code> is the draft wrap itself. Kind <code>1234</code> is a checkpoint that records a revision of a draft. Kind <code>10013</code> is a relay list for private content, which tells clients where to store draft wraps.</p>61 <p>The draft stays private because the client encrypts it with <a href="44.html">NIP-44</a> to the signer's own public key. Only the author can decrypt it. Relays see an opaque string, never the draft text.</p>6263 <div class="box hint">64 <span class="tag">Hint!</span>65 <p>A draft wrap is a container, not a post. The inner draft event is unsigned. When the user hits publish, the client signs the inner event, sends it as its real kind, and deletes the wrap.</p>66 </div>6768 <figure class="diagram">69 <div class="frame">70 <svg viewBox="0 0 560 200" role="img" aria-label="Device A encrypts a draft into a kind 31234 event and sends it to a private relay; device B fetches and decrypts it">71 <rect class="d-actor" x="10" y="70" width="120" height="50" rx="8"/>72 <text class="d-actor-label" x="70" y="92" text-anchor="middle">Device A</text>73 <text class="d-note" x="70" y="109" text-anchor="middle">writes draft</text>74 <rect class="d-actor" x="220" y="70" width="120" height="50" rx="8"/>75 <text class="d-actor-label" x="280" y="92" text-anchor="middle">Private relay</text>76 <text class="d-note" x="280" y="109" text-anchor="middle">from kind 10013</text>77 <rect class="d-actor" x="430" y="70" width="120" height="50" rx="8"/>78 <text class="d-actor-label" x="490" y="92" text-anchor="middle">Device B</text>79 <text class="d-note" x="490" y="109" text-anchor="middle">resumes draft</text>80 <path class="d-msg d-flow" d="M132,88 L217,88"/>81 <path class="d-msg blue d-flow" d="M342,102 L427,102"/>82 <text class="d-label" x="175" y="60" text-anchor="middle">EVENT kind 31234</text>83 <text class="d-label" x="385" y="140" text-anchor="middle">REQ / decrypt</text>84 <text class="d-note" x="280" y="170" text-anchor="middle">Content is NIP-44 encrypted to the author's own public key.</text>85 <circle r="4" fill="#f26a00">86 <animateMotion dur="2.6s" repeatCount="indefinite" path="M132,88 L217,88"/>87 </circle>88 <circle r="4" fill="#2e5f9e">89 <animateMotion dur="2.6s" begin="1.3s" repeatCount="indefinite" path="M342,102 L427,102"/>90 </circle>91 </svg>92 </div>93 <figcaption>Fig. 37-1 — <b>Draft sync.</b> One device stores an encrypted draft; another device of the same user fetches and decrypts it.</figcaption>94 </figure>9596 <h2 class="bar" id="spec"><span class="num">2</span> The Draft Wrap - Kind 31234</h2>97 <p>Kind <code>31234</code> is an addressable event (see <a href="01.html">NIP-01</a> storage classes). It stores one unsigned draft event of any other kind. Build it in three steps. Serialize the draft event to a JSON string. Encrypt that string with <a href="44.html">NIP-44</a> to the signer's own public key. Place the ciphertext in <code>content</code>.</p>9899 <div class="crt">100 <div class="titlebar">Wire format - kind 31234 draft wrap</div>101<pre>{102 <span class="k">"kind"</span>: 31234,103 <span class="k">"tags"</span>: [104 [<span class="s">"d"</span>, <span class="c">&lt;identifier&gt;</span>],105 [<span class="s">"k"</span>, <span class="c">&lt;kind of the draft event&gt;</span>], <span class="c">// required</span>106 [<span class="s">"expiration"</span>, <span class="c">&lt;unix ts, ~now + 90 days&gt;</span>] <span class="c">// recommended</span>107 ],108 <span class="k">"content"</span>: <span class="c">nip44Encrypt(JSON.stringify(draft_event))</span>,109 <span class="c">// other fields per NIP-01</span>110}</pre>111 </div>112113 <div class="tscroll"><table class="ref">114 <caption>Kind 31234 tag reference</caption>115 <tr><th>Tag</th><th>Required</th><th>Rule</th></tr>116 <tr><td class="btn">d</td><td>yes</td><td>The identifier of this draft. Makes the event addressable; a new version replaces the old one.</td></tr>117 <tr><td class="btn">k</td><td>yes</td><td>The kind number of the inner draft event. Lets clients filter drafts by target kind without decrypting.</td></tr>118 <tr><td class="btn">expiration</td><td>recommended</td><td>A <a href="40.html">NIP-40</a> expiration tag. The source suggests about 90 days from creation.</td></tr>119 </table></div>120121 <p>Two more rules apply to the wrap:</p>122 <ul>123 <li><strong>Deletion.</strong> A blank <code>content</code> field signals that the author deleted the draft. Publish a new version of the same addressable event with empty <code>content</code>.</li>124 <li><strong>Where to publish.</strong> Clients SHOULD publish kind <code>31234</code> events to the relays listed in the user's kind <code>10013</code> event (see §4).</li>125 </ul>126127 <div class="box tech">128 <span class="tag">Tech Note</span>129 <p>The source example writes the expiration value as the literal string <code>"now + 90 days"</code>. That is pseudocode. <a href="40.html">NIP-40</a> requires a unix timestamp string. Compute the timestamp; do not copy the literal.</p>130 </div>131132 <h2 class="bar" id="checkpoints"><span class="num">3</span> Checkpoints - Kind 1234</h2>133 <p>Kind <code>1234</code> defines a <strong>checkpoint</strong>. A checkpoint belongs to one parent kind <code>31234</code> draft wrap. Checkpoints can provide a revision history of the parent draft.</p>134 <p>A checkpoint points to its parent with an <code>a</code> tag. Its <code>content</code> holds the draft revision, encrypted the same way as the parent: JSON-stringify the draft event, then encrypt with <a href="44.html">NIP-44</a>.</p>135136 <div class="crt">137 <div class="titlebar">Wire format - kind 1234 checkpoint</div>138<pre>{139 <span class="k">"kind"</span>: 1234,140 <span class="k">"tags"</span>: [141 [<span class="s">"a"</span>, <span class="s">"31234:<span class="c">&lt;pubkey&gt;</span>:<span class="c">&lt;identifier&gt;</span>"</span>]142 ],143 <span class="k">"content"</span>: <span class="c">nip44Encrypt(JSON.stringify(draft_event))</span>144}</pre>145 </div>146147 <div class="box tech">148 <span class="tag">Tech Note</span>149 <p>Kind <code>1234</code> sits in the regular storage range, so relays keep every checkpoint. The spec does not say what happens to checkpoints when the parent draft is blanked or expires. Clients should add their own <a href="40.html">NIP-40</a> expiration tags to checkpoints, or delete them with <a href="09.html">NIP-09</a>.</p>150 </div>151152 <figure class="diagram">153 <div class="frame">154 <svg viewBox="0 0 560 210" role="img" aria-label="Structure: kind 10013 lists private relays; kind 31234 holds the encrypted draft; kind 1234 checkpoints point at the wrap with a tags">155 <rect class="d-actor" x="10" y="20" width="150" height="54" rx="8"/>156 <text class="d-actor-label" x="85" y="42" text-anchor="middle">kind 10013</text>157 <text class="d-note" x="85" y="60" text-anchor="middle">private relay list</text>158 <rect class="d-actor" x="205" y="78" width="150" height="54" rx="8"/>159 <text class="d-actor-label" x="280" y="100" text-anchor="middle">kind 31234</text>160 <text class="d-note" x="280" y="118" text-anchor="middle">draft wrap (latest)</text>161 <rect class="d-actor" x="400" y="20" width="150" height="46" rx="8"/>162 <text class="d-actor-label" x="475" y="40" text-anchor="middle">kind 1234</text>163 <text class="d-note" x="475" y="56" text-anchor="middle">checkpoint rev 1</text>164 <rect class="d-actor" x="400" y="144" width="150" height="46" rx="8"/>165 <text class="d-actor-label" x="475" y="164" text-anchor="middle">kind 1234</text>166 <text class="d-note" x="475" y="180" text-anchor="middle">checkpoint rev 2</text>167 <path class="d-msg d-flow" d="M163,58 C190,70 190,80 202,90"/>168 <text class="d-label" x="150" y="92" text-anchor="middle">stored on</text>169 <path class="d-msg blue" d="M400,52 C370,66 365,76 357,88"/>170 <path class="d-msg blue" d="M400,158 C370,144 365,134 357,122"/>171 <text class="d-label" x="392" y="110" text-anchor="middle">"a" tag</text>172 </svg>173 </div>174 <figcaption>Fig. 37-2 — <b>Event structure.</b> The relay list says where the wrap lives. Checkpoints reference the wrap with <code>a</code> tags.</figcaption>175 </figure>176177 <h2 class="bar" id="relaylist"><span class="num">4</span> Relay List for Private Content - Kind 10013</h2>178 <p>Kind <code>10013</code> is a replaceable event. It indicates the user's preferred relays for private events such as draft wraps. The public <code>tags</code> array stays empty. The relay list itself is private.</p>179 <p>The event MUST include a list of <code>relay</code> URLs in <strong>private tags</strong>. Private tags are a JSON-stringified tag array, encrypted with <a href="44.html">NIP-44</a> to the signer's own keys, and placed in <code>content</code>.</p>180181 <div class="crt">182 <div class="titlebar">Wire format - kind 10013 private relay list</div>183<pre>{184 <span class="k">"kind"</span>: 10013,185 <span class="k">"tags"</span>: [],186 <span class="k">"content"</span>: <span class="c">nip44Encrypt(187 JSON.stringify([188 ["relay", "wss://myrelay.mydomain.com"]189 ])190 )</span>191 <span class="c">// ...other fields per NIP-01</span>192}</pre>193 </div>194195 <ul>196 <li>Clients MUST publish kind <code>10013</code> events to the author's <a href="65.html">NIP-65</a> <code>write</code> relays. Any client of the user can then find the list.</li>197 <li>Private storage relays SHOULD require <a href="42.html">NIP-42</a> authentication. They SHOULD only allow downloads of events signed by the authenticated user. This recommendation keeps ciphertext and metadata away from strangers.</li>198 </ul>199200 <div class="box warn">201 <span class="tag">Warning!</span>202 <p>Encryption hides the draft text, not the metadata. The <code>k</code> tag, the <code>d</code> tag, and the timestamps are public on any relay that serves the event. Use an authenticated private relay if that metadata matters to you.</p>203 </div>204205 <h2 class="bar" id="examples"><span class="num">5</span> Worked Example</h2>206 <p>Alice starts a long-form article (<code>kind 30023</code>) on her laptop. Her client wraps the unsigned draft, stores it, and later blanks it after she publishes. The inner draft event looks like this before encryption:</p>207208 <div class="crt">209 <div class="titlebar">Inner draft event - unsigned, before encryption</div>210<pre>{211 <span class="k">"pubkey"</span>: <span class="s">"a1fc5dfd…9b03"</span>,212 <span class="k">"created_at"</span>: 1767225600,213 <span class="k">"kind"</span>: 30023,214 <span class="k">"tags"</span>: [[<span class="s">"d"</span>, <span class="s">"my-article"</span>], [<span class="s">"title"</span>, <span class="s">"Why relays matter"</span>]],215 <span class="k">"content"</span>: <span class="s">"Relays are simple servers that…"</span>216 <span class="c">// no id, no sig — the draft is unsigned</span>217}</pre>218 </div>219220 <div class="crt">221 <div class="titlebar">Session transcript - wss://myrelay.mydomain.com</div>222<pre><span class="c">-- laptop stores the draft wrap on the private relay --</span>223<span class="send">→ ["EVENT", {"kind":31234,"pubkey":"a1fc5dfd…9b03","created_at":1767225601,224 "tags":[["d","draft-my-article"],["k","30023"],["expiration","1775001601"]],225 "content":"AsWxoZ0e…Jw2Q==","id":"7d21c04b…","sig":"90ffab12…"}]</span>226<span class="recv">← ["OK", "7d21c04b…", true, ""]</span>227228<span class="c">-- phone finds the draft later --</span>229<span class="send">→ ["REQ", "drafts", {"kinds":[31234], "authors":["a1fc5dfd…9b03"]}]</span>230<span class="recv">← ["EVENT", "drafts", {"kind":31234, "tags":[["d","draft-my-article"],["k","30023"], …], …}]</span>231<span class="recv">← ["EOSE", "drafts"]</span>232<span class="c">-- the phone decrypts content with NIP-44 and restores the editor --</span>233234<span class="c">-- after publishing, the laptop blanks the wrap to delete the draft --</span>235<span class="send">→ ["EVENT", {"kind":31234, "tags":[["d","draft-my-article"],["k","30023"]],236 "content":"", "created_at":1767312000, …}]</span>237<span class="recv">← ["OK", "e93aa1c7…", true, ""]</span></pre>238 </div>239240 <p>Between edits, Alice's client may also store kind <code>1234</code> checkpoints with the tag <code>["a", "31234:a1fc5dfd…9b03:draft-my-article"]</code>. Each checkpoint keeps one encrypted revision, so she can roll back.</p>241242 <h2 class="bar" id="history"><span class="num">6</span> Version History</h2>243 <p>The draft-event idea started inside the old NIP-35 draft in March 2024 and moved to its own number soon after. Notable changes, newest first:</p>244 <ul class="patchnotes">245 <li><span class="ver">2026</span><span class="when">2026-05</span>Reworked as "Draft Wraps" (<a href="https://github.com/nostr-protocol/nips/pull/1653">PR #1653</a>): kind <code>31234</code> wraps, kind <code>1234</code> checkpoints, and the kind <code>10013</code> private-content relay list in its current form.</li>246 <li><span class="ver">2025</span><span class="when">2025-10</span>Improved the draft-event spec (<a href="https://github.com/nostr-protocol/nips/pull/2016">PR #2016</a>).</li>247 <li><span class="ver">2024</span><span class="when">2024-05</span>Moved from the NIP-35 draft into NIP-37; added private outbox relays for draft storage.</li>248 <li><span class="ver">2024</span><span class="when">2024-03</span>Added draft deletion by blanking <code>content</code>, so drafts die even on relays without deletion support.</li>249 <li><span class="ver">Origin</span><span class="when">2024-03</span>Vitor Pamplona wrote the first encrypted draft-event spec.</li>250 </ul>251252 <div class="box tech">253 <span class="tag">Tech Note</span>254 <p>Git rename detection chains this file into the unrelated history of the old NIP-35 pages (torrents, user discovery). The entries above keep only the commits about drafts.</p>255 </div>256257 <div class="credits">258 <h3 class="cardhead">Original Proposal - Credits</h3>259 <table>260 <tr><td>Author</td><td>Vitor Pamplona</td></tr>261 <tr><td>First commit</td><td>2024-05-30 — “moves from NIP-35 to NIP-37” (draft spec first written 2024-03)</td></tr>262 <tr><td>Status</td><td>draft - optional — clients may skip drafts entirely</td></tr>263 <tr><td>Source</td><td><a href="https://github.com/nostr-protocol/nips/blob/master/37.md">37.md on GitHub</a> - <a href="https://github.com/nostr-protocol/nips/commits/master/37.md">full change history</a></td></tr>264 </table>265 </div>266267 <h2 class="bar"><span class="num">7</span> See Also</h2>268 <ul>269 <li><a href="01.html">NIP-01</a> — the base protocol; addressable and replaceable storage classes used by kinds <code>31234</code> and <code>10013</code>.</li>270 <li><a href="44.html">NIP-44</a> — the encryption scheme that protects the draft content and the private relay list.</li>271 <li><a href="40.html">NIP-40</a> — expiration tags, recommended on every draft wrap.</li>272 <li><a href="42.html">NIP-42</a> — relay authentication, recommended for private storage relays.</li>273 <li><a href="65.html">NIP-65</a> — the relay list metadata; kind <code>10013</code> goes to its <code>write</code> relays.</li>274 <li><a href="09.html">NIP-09</a> — event deletion requests, useful for cleaning up checkpoints.</li>275 </ul>276 </div>277278 <aside>279 <div class="cartridge">280 <div class="inner">281 <h3 class="cardhead">Spec Card</h3>282 <div class="lights"><span class="led on"></span><span class="led on"></span><span class="led on"></span></div>283 <dl>284 <dt>Status</dt><dd><span class="chip draft">draft</span><span class="chip">optional</span></dd>285 <dt>Defines</dt><dd>draft wraps - checkpoints - private relay list - private tags</dd>286 <dt>Kinds</dt><dd>31234 - 1234 - 10013</dd>287 <dt>Requires</dt><dd><a href="01.html">NIP-01</a> - <a href="44.html">NIP-44</a> - <a href="65.html">NIP-65</a></dd>288 <dt>Crypto</dt><dd>NIP-44 encryption to own pubkey</dd>289 <dt>First commit</dt><dd>2024-05-30</dd>290 <dt>Last updated</dt><dd>2026-05-18</dd>291 </dl>292 </div>293 </div>294 </aside>295 </main>296297 <nav class="navbtns">298 <a href="36.html">◀ NIP-36: Sensitive Content / Content Warning</a>299 <a href="38.html">NIP-38: User Statuses ▶</a>300 </nav>301302 <footer class="pagefoot">303 <a href="../index.html">Nostr Protocol Manual</a>304 <span class="pageno">NIP 37 / F4</span>305 </footer>306</div>307308<script src="../assets/webmcp.js" defer></script>309</body>310</html>