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-04 - Encrypted Direct Message — Nostr Instruction Manual</title>7<meta name="description" content="NIP-04: the original encrypted direct message format, kind 4. Deprecated in favor of NIP-17. Shared-secret derivation, aes-256-cbc content, and known weaknesses.">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 04 - Encryption</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="04">04</div>48 <div class="titles">49 <div class="kicker">Nostr Implementation Possibility - Hex № 04</div>50 <h1>Encrypted Direct Message</h1>51 <p class="sub">The original kind-4 encrypted note between two users. Kept in this manual for history and for reading old events, not for new work.</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>NIP-04 defines <strong>kind 4</strong>, the first encrypted direct message format in Nostr. A sender encrypts the <code>content</code> field so that only the receiver can read it.</p>61 <p>The encryption uses a shared secret. The sender computes this secret from their own private key and the receiver's public key. The receiver computes the same secret from their own private key and the sender's public key. Both sides land on the same secret without ever sending it over the wire. This technique is called ECDH, Elliptic Curve Diffie-Hellman.</p>62 <p>Relays still see the sender, the receiver, and the timing of every kind-4 event, even though they cannot read the message text. That gap between "encrypted content" and "private conversation" is the reason this NIP is deprecated.</p>6364 <div class="box danger">65 <span class="tag">Deprecated</span>66 <p>NIP-04 is <strong>unrecommended</strong>. Use <a href="17.html">NIP-17</a>, Private Direct Messages, for all new work. NIP-17 hides the sender, receiver, and event kind from relays, which NIP-04 cannot do. Read this chapter to understand old events and to migrate away from kind 4, not to build new features.</p>67 </div>6869 <h2 class="bar" id="spec"><span class="num">2</span> The Kind-4 Event</h2>70 <p>A kind-4 event carries one encrypted message. It must follow three rules, on top of the base event format from <a href="01.html">NIP-01</a>.</p>7172 <div class="tscroll"><table class="ref">73 <caption>Kind-4 rules</caption>74 <tr><th>Field</th><th>Rule</th></tr>75 <tr><td class="btn">kind</td><td>Must be <code>4</code>.</td></tr>76 <tr><td class="btn">content</td><td>Must be the base64-encoded, aes-256-cbc encrypted message, with the base64 initialization vector appended as a querystring-style parameter named <code>iv</code>. See below.</td></tr>77 <tr><td class="btn">tags</td><td>Must contain a <code>p</code> tag naming the receiver. May contain an <code>e</code> tag naming a prior message in the conversation. See §3.</td></tr>78 </table></div>7980 <h3>Deriving the shared secret</h3>81 <p>The sender combines their private key with the receiver's public key on the curve <code>secp256k1</code>. This produces a shared point. Nostr's shared secret is the <strong>X coordinate</strong> of that point only, kept as raw bytes.</p>82 <div class="box warn">83 <span class="tag">Warning!</span>84 <p>The default libsecp256k1 ECDH function hashes the shared point with sha256 and uses both X and Y coordinates. Nostr does not do this. Pass a custom <code>hashfp</code> function to <code>secp256k1_ecdh</code> that copies out the raw X coordinate and skips the hash. Using the library default produces a secret that does not match other Nostr clients.</p>85 </div>8687 <figure class="diagram">88 <div class="frame">89 <svg viewBox="0 0 560 210" role="img" aria-label="Sender private key and receiver public key combine via ECDH into a shared point; the X coordinate becomes the aes key; plaintext and a random IV go into aes-256-cbc to produce the content field">90 <rect class="d-actor" x="10" y="10" width="150" height="44" rx="8"/>91 <text class="d-actor-label" x="85" y="37" text-anchor="middle">Sender privkey</text>92 <rect class="d-actor" x="10" y="70" width="150" height="44" rx="8"/>93 <text class="d-actor-label" x="85" y="97" text-anchor="middle">Receiver pubkey</text>94 <rect class="d-actor" x="215" y="40" width="120" height="44" rx="8"/>95 <text class="d-actor-label" x="275" y="67" text-anchor="middle">ECDH</text>96 <path class="d-msg d-flow" d="M162,32 C185,32 195,45 213,55"/>97 <path class="d-msg d-flow" d="M162,92 C185,92 195,75 213,66"/>98 <rect class="d-actor" x="385" y="40" width="150" height="44" rx="8"/>99 <text class="d-actor-label" x="460" y="60" text-anchor="middle">Shared point</text>100 <text class="d-note" x="460" y="76" text-anchor="middle">take X only, no hash</text>101 <path class="d-msg d-flow" d="M337,62 L383,62"/>102 <rect class="d-actor" x="215" y="140" width="120" height="44" rx="8"/>103 <text class="d-actor-label" x="275" y="167" text-anchor="middle">aes-256-cbc</text>104 <rect class="d-actor" x="10" y="140" width="150" height="44" rx="8"/>105 <text class="d-actor-label" x="85" y="162" text-anchor="middle">plaintext + IV</text>106 <path class="d-msg d-flow" d="M162,162 L213,162"/>107 <path class="d-msg blue d-flow" d="M460,84 C460,110 400,120 337,150"/>108 <rect class="d-actor" x="385" y="140" width="150" height="44" rx="8"/>109 <text class="d-actor-label" x="460" y="160" text-anchor="middle">content field</text>110 <text class="d-note" x="460" y="176" text-anchor="middle">ciphertext?iv=...</text>111 <path class="d-msg d-flow" d="M337,162 L383,162"/>112 </svg>113 </div>114 <figcaption>Fig. 4-1 — <b>Encryption flow.</b> ECDH produces a shared point. Only its X coordinate, unhashed, becomes the aes-256-cbc key.</figcaption>115 </figure>116117 <h3>Building the content field</h3>118 <p>Follow these steps to encrypt a message:</p>119 <ol>120 <li>Compute the shared secret as described above.</li>121 <li>Generate a random 16-byte initialization vector, the IV.</li>122 <li>Encrypt the plaintext with <code>aes-256-cbc</code>, using the shared secret as the key and the IV.</li>123 <li>Base64-encode the ciphertext.</li>124 <li>Base64-encode the IV.</li>125 <li>Join them as <code><ciphertext_base64>?iv=<iv_base64></code> and set this string as <code>content</code>.</li>126 </ol>127 <div class="crt">128 <div class="titlebar">Wire format - content field</div>129<pre><span class="k">"content"</span>: <span class="s">"<encrypted_text>?iv=<initialization_vector>"</span></pre>130 </div>131132 <h2 class="bar" id="tags"><span class="num">3</span> Tags</h2>133 <div class="tscroll"><table class="ref">134 <caption>Kind-4 tags</caption>135 <tr><th>Tag</th><th>Required</th><th>Form</th><th>Purpose</th></tr>136 <tr><td class="btn">p</td><td>MUST</td><td><code>["p", "<receiver pubkey, hex>"]</code></td><td>Names the receiver. Lets a relay forward the event to them.</td></tr>137 <tr><td class="btn">e</td><td>MAY</td><td><code>["e", "<event id>"]</code></td><td>Names the previous message in the conversation, or the message this one replies to.</td></tr>138 </table></div>139140 <div class="box tech">141 <span class="tag">Tech Note</span>142 <p>The <code>p</code> tag is what lets a relay index and forward the message, but it also tells everyone who can read the relay's data exactly who is talking to whom. Encrypting <code>content</code> does not hide this. See §4.</p>143 </div>144145 <h2 class="bar" id="security"><span class="num">4</span> Security Warning</h2>146 <p>This format is far behind the current state of the art for encrypted peer-to-peer communication. It leaks metadata through the event fields.</p>147 <ul>148 <li>Do not use kind 4 for anything that truly needs to stay secret.</li>149 <li>If you must use it, only publish to relays that require <a href="42.html">NIP-42</a> <code>AUTH</code> and restrict who may fetch your kind-4 events.</li>150 <li>Sender, receiver, and timing are all visible to any relay and to anyone who can read the relay's stored events. Only the message text is hidden.</li>151 </ul>152153 <h2 class="bar" id="client-warning"><span class="num">5</span> Client Implementation Warning</h2>154 <p>Clients should not search the decrypted <code>content</code> for public key or note references and replace them, the way NIP-27 mention handling works for regular text notes.</p>155 <p>That process turns a reference such as <code>@npub1…</code> into <code>#[0]</code> and adds a matching <code>["p", "…"]</code> tag. Doing this to a direct message adds a plaintext <code>p</code> tag naming the mentioned user. The tag leaks the mention, and the mentioned user's client will show the encrypted message in their own inbox, even though they cannot read it.</p>156157 <h2 class="bar" id="examples"><span class="num">6</span> Worked Example</h2>158 <p>Alice sends Bob a kind-4 message, then Bob replies. Orange lines go to the relay. Blue lines come from the relay.</p>159 <div class="crt">160 <div class="titlebar">Session transcript - wss://relay.example.com</div>161<pre><span class="c">-- Alice computes the shared secret from her privkey and Bob's pubkey --</span>162<span class="c">-- sharedX = ECDH(alice_priv, bob_pub).x (unhashed)</span>163<span class="c">-- iv = random 16 bytes</span>164<span class="c">-- ciphertext = aes-256-cbc-encrypt("gm bob", key=sharedX, iv=iv)</span>165166<span class="send">→ ["EVENT", {"id":"7ac2a…","pubkey":"a1b2c3…","created_at":1749762600,167 "kind":4,"tags":[["p","bobpub00…"]],168 "content":"u3f9skA2==?iv=q8x2Lp0zK1==","sig":"9f21ab…"}]</span>169<span class="recv">← ["OK", "7ac2a…", true, ""]</span>170171<span class="c">-- Bob's client fetches messages addressed to him --</span>172<span class="send">→ ["REQ", "dm1", {"kinds":[4], "#p":["bobpub00…"], "limit":20}]</span>173<span class="recv">← ["EVENT", "dm1", {"id":"7ac2a…","kind":4,"tags":[["p","bobpub00…"]],174 "content":"u3f9skA2==?iv=q8x2Lp0zK1==", …}]</span>175<span class="recv">← ["EOSE", "dm1"]</span>176177<span class="c">-- Bob decrypts with ECDH(bob_priv, alice_pub).x and the iv from content --</span>178<span class="c">-- plaintext: "gm bob"</span>179180<span class="c">-- Bob replies, tagging Alice and referencing the prior event --</span>181<span class="send">→ ["EVENT", {"id":"c04e1…","pubkey":"bobpub00…","created_at":1749762660,182 "kind":4,"tags":[["p","a1b2c3…"],["e","7ac2a…"]],183 "content":"gm2==?iv=z9y1Nn3xR7==","sig":"3d8e2f…"}]</span>184<span class="recv">← ["OK", "c04e1…", true, ""]</span></pre>185 </div>186187 <h2 class="bar"><span class="num">7</span> Code Sample</h2>188 <p>The steps in §2 translate directly to code. This JavaScript sample uses <code>@noble/secp256k1</code> for the ECDH step and Node's built-in <code>crypto</code> for aes-256-cbc.</p>189 <div class="crt">190 <div class="titlebar">JavaScript - build a kind-4 event</div>191<pre><span class="c">import crypto from 'crypto'</span>192<span class="c">import * as secp from '@noble/secp256k1'</span>193194<span class="c">let sharedPoint = secp.getSharedSecret(ourPrivateKey, '02' + theirPublicKey)</span>195<span class="c">let sharedX = sharedPoint.slice(1, 33)</span>196197<span class="c">let iv = crypto.randomFillSync(new Uint8Array(16))</span>198<span class="c">let cipher = crypto.createCipheriv('aes-256-cbc', Buffer.from(sharedX), iv)</span>199<span class="c">let encryptedMessage = cipher.update(text, 'utf8', 'base64')</span>200<span class="c">encryptedMessage += cipher.final('base64')</span>201<span class="c">let ivBase64 = Buffer.from(iv.buffer).toString('base64')</span>202203<span class="c">let event = {</span>204<span class="c"> pubkey: ourPubKey,</span>205<span class="c"> created_at: Math.floor(Date.now() / 1000),</span>206<span class="c"> kind: 4,</span>207<span class="c"> tags: [['p', theirPublicKey]],</span>208<span class="c"> content: encryptedMessage + '?iv=' + ivBase64</span>209<span class="c">}</span></pre>210 </div>211 <p>The event still needs <code>id</code> and <code>sig</code>, computed with the serialization rule from <a href="01.html">NIP-01</a>.</p>212213 <h2 class="bar" id="history"><span class="num">8</span> Version History</h2>214 <p>NIP-04 is finalized and no longer expected to change. Notable changes, newest first:</p>215 <ul class="patchnotes">216 <li><span class="ver">2025</span><span class="when">2025-12</span>Added the <code>relay</code> capability tag to the metadata line. No spec change.</li>217 <li><span class="ver">2024</span><span class="when">2024-04</span>Deprecation target switched from <a href="44.html">NIP-44</a> to the newly published <a href="17.html">NIP-17</a>, which wraps NIP-44 in a full private-messaging design.</li>218 <li><span class="ver">2023</span><span class="when">2023-12</span><a href="44.html">NIP-44</a> published as a modern replacement encryption standard; NIP-04 marked <code>unrecommended</code> for the first time.</li>219 <li class="minor"><span class="ver">2023</span><span class="when">2023-03 to 2023-04</span>Added the security warning and the client mention-handling warning; fixed a bug and an import in the code sample; minor grammar fixes.</li>220 <li><span class="ver">2023</span><span class="when">2023-02</span>Clarified that the ECDH secret is the raw, unhashed X coordinate of the shared point, not the libsecp256k1 default.</li>221 <li><span class="ver">2022</span><span class="when">2022-11</span>Marked <code>final</code>: no further spec changes expected.</li>222 <li><span class="ver">Origin</span><span class="when">2022-05</span>Migrated into the <code>nostr-protocol/nips</code> repository, authored by arcbtc.</li>223 </ul>224225 <div class="credits">226 <h3 class="cardhead">Original Proposal - Credits</h3>227 <table>228 <tr><td>Author</td><td>arcbtc</td></tr>229 <tr><td>First commit</td><td>2022-05-01 — "migrate nips from main nostr repo" (attributed <code>author:arcbtc</code> in the original text)</td></tr>230 <tr><td>Status</td><td>final - unrecommended - optional — deprecated in favor of <a href="17.html">NIP-17</a></td></tr>231 <tr><td>Source</td><td><a href="https://github.com/nostr-protocol/nips/blob/master/04.md">04.md on GitHub</a> - <a href="https://github.com/nostr-protocol/nips/commits/master/04.md">full change history</a></td></tr>232 </table>233 </div>234235 <h2 class="bar"><span class="num">9</span> See Also</h2>236 <ul>237 <li><a href="17.html">NIP-17</a> — Private Direct Messages. The recommended replacement; hides sender, receiver, and kind from relays.</li>238 <li><a href="44.html">NIP-44</a> — Encrypted Payloads (Versioned). The modern encryption primitive that NIP-17 builds on.</li>239 <li><a href="59.html">NIP-59</a> — Gift Wrap. The event-wrapping technique NIP-17 uses to hide metadata that NIP-04 exposes.</li>240 <li><a href="01.html">NIP-01</a> — base event format, serialization, and signing that every kind-4 event still follows.</li>241 <li><a href="42.html">NIP-42</a> — relay <code>AUTH</code>. Recommended when kind-4 events must still be used.</li>242 </ul>243 </div>244245 <aside>246 <div class="cartridge">247 <div class="inner">248 <h3 class="cardhead">Spec Card</h3>249 <div class="lights"><span class="led err"></span><span class="led err"></span><span class="led err"></span></div>250 <dl>251 <dt>Status</dt><dd><span class="chip final">final</span><span class="chip unrecommended">unrecommended</span></dd>252 <dt>Defines</dt><dd>kind 4 - encrypted content field - p/e tags</dd>253 <dt>Kind</dt><dd>4</dd>254 <dt>Requires</dt><dd><a href="01.html">NIP-01</a></dd>255 <dt>Crypto</dt><dd>secp256k1 ECDH (X coord only) - aes-256-cbc</dd>256 <dt>Replaced by</dt><dd><a href="17.html">NIP-17</a></dd>257 <dt>First commit</dt><dd>2022-05-01</dd>258 <dt>Last updated</dt><dd>2025-12-24</dd>259 </dl>260 </div>261 </div>262 </aside>263 </main>264265 <nav class="navbtns">266 <a href="03.html">◀ NIP-03: OpenTimestamps Attestations for Events</a>267 <a href="05.html">NIP-05: Mapping Nostr keys to DNS-based internet identifiers ▶</a>268 </nav>269270 <footer class="pagefoot">271 <a href="../index.html">Nostr Protocol Manual</a>272 <span class="pageno">NIP 04 / F4</span>273 </footer>274</div>275276<script src="../assets/webmcp.js" defer></script>277</body>278</html>