clone

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

main
nip.guide / site / nips / 05.html
05.html 23.4K
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-05 - Mapping Nostr keys to DNS-based internet identifiers — Nostr Instruction Manual</title>7<meta name="description" content="NIP-05: attach a human-readable name@domain identifier to a Nostr public key, verified through a well-known HTTPS document on that domain.">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 05 - Identity</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="05">05</div>48 <div class="titles">49 <div class="kicker">Nostr Implementation Possibility - Hex № 05</div>50 <h1>Mapping Nostr Keys to DNS-Based Internet Identifiers</h1>51 <p class="sub">A human-readable name@domain label for a public key, checked against a small JSON file the domain owner controls.</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 Nostr public key is a 64-character hex string. It is precise, but nobody can read it at a glance or remember it. <a href="01.html">NIP-01</a> defines the <strong>kind 0</strong> user metadata event, where a user publishes a display name, an avatar, and other profile fields.</p>60 <p>NIP-05 adds one more optional metadata field: <code>nip05</code>. Its value looks like an email address, for example <code>bob@example.com</code>. A client that sees this field can ask the domain <code>example.com</code>, over plain HTTPS, which public key the name <code>bob</code> belongs to. If the answer matches the event's own <code>pubkey</code>, the client treats the identifier as valid and may display it.</p>61 <p>This check needs no new cryptography and no new server type. The domain owner edits or serves one JSON file. Any web host that can serve a static file, or run a small script, can support NIP-05.</p>62 <p>NIP-05 does not replace the public key. It only attaches a friendly label to it, and lets anyone confirm that the label and the key currently agree. The label can change or disappear; the public key stays the same identity forever.</p>6364 <div class="box hint">65 <span class="tag">Hint!</span>66 <p>Think of <code>nip05</code> like a business card, not a passport. It helps people find and recognize a key. It does not, by itself, prove who controls that key.</p>67 </div>6869 <h2 class="bar" id="spec"><span class="num">2</span> The nip05 Field and Identifier Format</h2>70 <p>A client that wants to publish an identifier adds a <code>nip05</code> key to the stringified JSON in the <code>content</code> field of its kind 0 event. The value is an <a href="https://datatracker.ietf.org/doc/html/rfc5322#section-3.4.1">internet identifier</a>: a string shaped like <code>&lt;local-part&gt;@&lt;domain&gt;</code>.</p>7172 <div class="crt">73 <div class="titlebar">Wire format - kind 0 content, with nip05</div>74<pre>{75 <span class="k">"name"</span>: <span class="s">"bob"</span>,76 <span class="k">"about"</span>: <span class="c">&lt;string, optional&gt;</span>,77 <span class="k">"nip05"</span>: <span class="s">"bob@example.com"</span>78}</pre>79 </div>8081 <p>The internet-identifier specification linked above is very liberal about what a local part may contain. NIP-05 is stricter. Follow this rule exactly:</p>8283 <div class="tscroll"><table class="ref">84 <caption>Identifier format rules</caption>85 <tr><th>Part</th><th>Rule</th></tr>86 <tr><td class="btn">&lt;local-part&gt;</td><td>MUST use only the characters <code>a-z</code>, <code>0-9</code>, <code>-</code>, <code>_</code>, and <code>.</code>. No uppercase letters, no spaces, no other symbols.</td></tr>87 <tr><td class="btn">&lt;domain&gt;</td><td>A normal DNS domain name. The client sends its verification request here.</td></tr>88 </table></div>8990 <div class="box warn">91 <span class="tag">Warning!</span>92 <p>The local part is case-sensitive in the lookup, and both it and the returned public keys must already be lowercase. A client must not lowercase or otherwise normalize the local part before building the request URL.</p>93 </div>9495 <h2 class="bar"><span class="num">3</span> The Well-Known Endpoint and Verification Flow</h2>96 <p>To verify a <code>nip05</code> value, the client splits it into <code>&lt;local-part&gt;</code> and <code>&lt;domain&gt;</code> at the <code>@</code> character. It then makes an HTTPS <code>GET</code> request to a fixed, well-known path on that domain:</p>9798 <div class="crt">99 <div class="titlebar">Well-known URL template</div>100<pre>https://<span class="k">&lt;domain&gt;</span>/.well-known/nostr.json?name=<span class="k">&lt;local-part&gt;</span></pre>101 </div>102103 <p>The server answers with a JSON object. It must contain a <code>names</code> key: a mapping from plain names to hex-formatted public keys, in lowercase.</p>104105 <div class="crt">106 <div class="titlebar">Response shape - /.well-known/nostr.json</div>107<pre>{108 <span class="k">"names"</span>: { <span class="s">"&lt;name&gt;"</span>: <span class="c">&lt;lowercase hex pubkey&gt;</span>, ... }109}</pre>110 </div>111112 <p>The client reads the value at <code>names[&lt;local-part&gt;]</code>. If that hex string equals the <code>pubkey</code> of the kind 0 event that carried the <code>nip05</code> field, the identifier is valid. The client may now display it. If the key is missing, or does not match, the identifier is not valid and the client should not display it as verified.</p>113114 <figure class="diagram">115 <div class="frame">116 <svg viewBox="0 0 560 300" role="img" aria-label="Sequence diagram: client requests the well-known nostr.json document over HTTPS, the domain returns a names mapping, and the client compares the returned key to the event pubkey">117 <line class="d-wire" x1="105" y1="46" x2="105" y2="285" stroke-dasharray="3 4"/>118 <line class="d-wire" x1="460" y1="46" x2="460" y2="285" stroke-dasharray="3 4"/>119 <rect class="d-actor" x="50" y="8" width="110" height="34" rx="6"/>120 <text class="d-actor-label" x="105" y="30" text-anchor="middle">Client</text>121 <rect class="d-actor" x="375" y="8" width="170" height="34" rx="6"/>122 <text class="d-actor-label" x="460" y="30" text-anchor="middle">example.com (HTTPS)</text>123 <path class="d-msg d-flow" d="M105,80 L455,80"/>124 <text class="d-label" x="282" y="70" text-anchor="middle">GET /.well-known/nostr.json?name=bob</text>125 <circle r="4" fill="#f26a00">126 <animateMotion dur="2.6s" repeatCount="indefinite" path="M105,80 L455,80"/>127 </circle>128 <path class="d-msg blue d-flow" d="M460,130 L110,130"/>129 <text class="d-label" x="282" y="120" text-anchor="middle">200 OK {"names":{"bob":"b0635d6a…"}}</text>130 <circle r="4" fill="#2e5f9e">131 <animateMotion dur="2.6s" begin="1.3s" repeatCount="indefinite" path="M460,130 L110,130"/>132 </circle>133 <rect x="150" y="175" width="260" height="70" rx="8" fill="#182430" stroke="#3a4653"/>134 <text class="d-note" x="280" y="198" text-anchor="middle">Client compares names.bob</text>135 <text class="d-note" x="280" y="214" text-anchor="middle">to the kind 0 event's pubkey</text>136 <text class="d-label" x="280" y="235" text-anchor="middle" fill="#57c26b">MATCH → nip05 identifier is valid</text>137 <text class="d-note" x="280" y="270" text-anchor="middle">No match, or key missing → identifier is not valid</text>138 </svg>139 </div>140 <figcaption>Fig. 5-1 — <b>Verification flow.</b> The client, not the domain, decides whether the identifier is valid. It only compares two hex strings it already trusts.</figcaption>141 </figure>142143 <h2 class="bar"><span class="num">4</span> The relays Attribute</h2>144 <p>The response object may also carry a <strong>recommended</strong> <code>relays</code> key. It maps each public key to a list of relay URLs where that user's events may be found.</p>145146 <div class="crt">147 <div class="titlebar">Response shape - with relays</div>148<pre>{149 <span class="k">"names"</span>: {150 <span class="s">"bob"</span>: <span class="s">"b0635d6a9851d3aed0cd6c495b282167acf761729078d975fc341b22650b07b9"</span>151 },152 <span class="k">"relays"</span>: {153 <span class="s">"b0635d6a9851d3aed0cd6c495b282167acf761729078d975fc341b22650b07b9"</span>: [154 <span class="s">"wss://relay.example.com"</span>, <span class="s">"wss://relay2.example.com"</span>155 ]156 }157}</pre>158 </div>159160 <p>A client may use this list the same way it uses a <a href="65.html">NIP-65</a> relay list, to find where a newly-discovered user actually publishes. A web server that builds this file dynamically, per query, should also include the matching <code>relays</code> entry for any name it serves, whenever that data is available.</p>161162 <h2 class="bar"><span class="num">5</span> Finding Users by Identifier</h2>163 <p>A client may also run the flow in reverse: start from an identifier typed by a user, such as a search box entry, and end with a full profile.</p>164 <ol>165 <li>Split the typed identifier into <code>&lt;local-part&gt;</code> and <code>&lt;domain&gt;</code>.</li>166 <li>Fetch the well-known URL and read the public key from <code>names</code>.</li>167 <li>Fetch the newest kind 0 event for that public key.</li>168 <li>Check that the event's own <code>nip05</code> field matches the identifier the user typed.</li>169 </ol>170 <p>This lets a client offer a search box where a user types <code>bob@example.com</code> and the client resolves it straight to a pubkey and a profile.</p>171172 <figure class="diagram">173 <div class="frame">174 <svg viewBox="0 0 560 130" role="img" aria-label="Pipeline: identifier is split, resolved to a pubkey via the well-known document, then checked against the user's kind 0 event">175 <rect class="d-actor" x="6" y="35" width="118" height="50" rx="8"/>176 <text class="d-actor-label" x="65" y="57" text-anchor="middle">Typed ID</text>177 <text class="d-note" x="65" y="74" text-anchor="middle">bob@example.com</text>178 <rect class="d-actor" x="152" y="35" width="118" height="50" rx="8"/>179 <text class="d-actor-label" x="211" y="57" text-anchor="middle">well-known</text>180 <text class="d-note" x="211" y="74" text-anchor="middle">→ pubkey</text>181 <rect class="d-actor" x="298" y="35" width="118" height="50" rx="8"/>182 <text class="d-actor-label" x="357" y="57" text-anchor="middle">kind 0</text>183 <text class="d-note" x="357" y="74" text-anchor="middle">fetch by pubkey</text>184 <rect class="d-actor" x="444" y="35" width="112" height="50" rx="8"/>185 <text class="d-actor-label" x="500" y="57" text-anchor="middle">Check</text>186 <text class="d-note" x="500" y="74" text-anchor="middle">nip05 matches?</text>187 <path class="d-msg d-flow" d="M126,60 L149,60"/>188 <path class="d-msg d-flow" d="M272,60 L295,60"/>189 <path class="d-msg d-flow" d="M418,60 L441,60"/>190 </svg>191 </div>192 <figcaption>Fig. 5-2 — <b>Reverse discovery.</b> The same two checks as Fig. 5-1, run starting from a typed name instead of an existing event.</figcaption>193 </figure>194195 <h2 class="bar"><span class="num">6</span> Security Rules and Client Behavior</h2>196197 <div class="box danger">198 <span class="tag">Warning!</span>199 <p>The <code>/.well-known/nostr.json</code> endpoint MUST NOT return an HTTP redirect. A fetcher MUST ignore any redirect this endpoint sends. Do not follow it, even automatically at the HTTP-library level.</p>200 </div>201202 <p>A few more rules protect users from small mistakes turning into identity confusion:</p>203 <ul>204 <li><strong>Keys are hex, always.</strong> The <code>names</code> mapping must use lowercase hex public keys. <a href="19.html">NIP-19</a> <code>npub</code> values are for display in a client's user interface only. Never put an <code>npub</code> in a NIP-05 response.</li>205 <li><strong>Follow the pubkey, not the address.</strong> Suppose a client resolves <code>bob@bob.com</code> to the key <code>abc...def</code> and a user chooses to follow that profile. The client stores <code>abc...def</code> as the follow target. If <code>bob.com</code> later starts returning a different key, <code>1d2...e3f</code>, the client must not silently switch the followed key. It should instead stop showing <code>bob@bob.com</code> as a valid label for the old key, because the identifier no longer matches.</li>206 <li><strong>The root identifier.</strong> A domain owner who does not want a redundant-looking address like <code>bob@bob.com</code> may register the local part <code>_</code> instead, giving <code>_@bob.com</code>. Clients should show and treat <code>_@domain</code> as just <code>domain</code>.</li>207 </ul>208209 <div class="box tech">210 <span class="tag">Tech Note</span>211 <p>Why a query string, <code>?name=&lt;local-part&gt;</code>, instead of a path segment? A query string lets one <code>/.well-known/nostr.json</code> address serve both a dynamic server, which computes the answer per request, and a static file that simply lists every registered name at once.</p>212 </div>213214 <p>Browser <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS">CORS</a> policy can block a JavaScript client from reading <code>/.well-known/nostr.json</code> on a domain it does not own. A blocked CORS request looks exactly like a missing file: pure-JavaScript code cannot tell the two apart. If a JS client cannot verify a NIP-05 identifier, it may suggest the user check their server's CORS headers with a command like the one in the worked example below. Domain owners should serve <code>/.well-known/nostr.json</code> with the header <code>Access-Control-Allow-Origin: *</code> so that browser-based clients can read it.</p>215216 <div class="box hint">217 <span class="tag">Hint!</span>218 <p>NIP-05 identifies, it does not verify trust by itself. Anyone can register any domain and claim any name on it. The exception: a person or project already known to control a well-known domain — a company, an established project — gains real trust value from a NIP-05 match on that domain, because the domain itself is the trust anchor people already recognize.</p>219 </div>220221 <h2 class="bar" id="examples"><span class="num">7</span> Worked Example</h2>222 <p>Client sees the kind 0 event below and wants to verify the <code>nip05</code> field.</p>223224 <div class="crt">225 <div class="titlebar">Kind 0 event (fields relevant to this NIP)</div>226<pre>{227 <span class="k">"pubkey"</span>: <span class="s">"b0635d6a9851d3aed0cd6c495b282167acf761729078d975fc341b22650b07b9"</span>,228 <span class="k">"kind"</span>: 0,229 <span class="k">"content"</span>: <span class="s">"{\"name\": \"bob\", \"nip05\": \"bob@example.com\"}"</span>230 <span class="c">// id, created_at, tags, sig omitted</span>231}</pre>232 </div>233234 <p>The identifier splits into local part <code>bob</code> and domain <code>example.com</code>. The client makes this HTTPS request:</p>235236 <div class="crt">237 <div class="titlebar">HTTP session - https://example.com/.well-known/nostr.json</div>238<pre><span class="send">→ GET /.well-known/nostr.json?name=bob HTTP/1.1</span>239<span class="send">→ Host: example.com</span>240<span class="send">→ Accept: application/json</span>241242<span class="recv">← HTTP/1.1 200 OK</span>243<span class="recv">← Content-Type: application/json</span>244<span class="recv">← Access-Control-Allow-Origin: *</span>245<span class="recv">←</span>246<span class="recv">← {</span>247<span class="recv">← "names": {</span>248<span class="recv">← "bob": "b0635d6a9851d3aed0cd6c495b282167acf761729078d975fc341b22650b07b9"</span>249<span class="recv">← },</span>250<span class="recv">← "relays": {</span>251<span class="recv">← "b0635d6a9851d3aed0cd6c495b282167acf761729078d975fc341b22650b07b9": [</span>252<span class="recv">← "wss://relay.example.com", "wss://relay2.example.com"</span>253<span class="recv">← ]</span>254<span class="recv">← }</span>255<span class="recv">← }</span>256257<span class="c">-- names.bob equals event.pubkey, so the client marks bob@example.com valid --</span></pre>258 </div>259260 <p>If a JavaScript client instead sees a network failure at this step, it cannot tell whether the file is missing or CORS blocked it. The domain owner can check their own server from a terminal:</p>261262 <div class="crt">263 <div class="titlebar">Checking CORS headers - terminal</div>264<pre><span class="send">$ curl -sI https://example.com/.well-known/nostr.json?name=bob | grep -i ^Access-Control</span>265<span class="recv">Access-Control-Allow-Origin: *</span></pre>266 </div>267268 <h2 class="bar" id="history"><span class="num">8</span> Version History</h2>269 <p>Notable changes to NIP-05, newest first:</p>270 <ul class="patchnotes">271 <li><span class="ver">2026</span><span class="when">2026-02</span>Made explicit that both hex public keys and local-part names must be lowercase (#2208).</li>272 <li><span class="ver">2024</span><span class="when">2024-08</span>Added the identification-versus-verification distinction: NIP-05 identifies a key, it does not by itself prove trust.</li>273 <li><span class="ver">2024</span><span class="when">2024-06</span>Renamed the informal "set_metadata" wording to "user metadata," matching NIP-01 terminology.</li>274 <li><span class="ver">2024</span><span class="when">2024-03</span>Upgraded the <code>relays</code> attribute from a plain suggestion to <strong>recommended</strong>.</li>275 <li class="minor"><span class="ver">2023</span><span class="when">2023-02</span>A proposed security extension (per-account allowed paths) was merged, then reverted days later. No trace remains in the current spec.</li>276 <li><span class="ver">2023</span><span class="when">2023-01</span>A short-lived NIP-35 on DNS-identifier reflectivity was merged into NIP-05; the number 35 was later reused for an unrelated proposal (Torrents).</li>277 <li><span class="ver">2022</span><span class="when">2022-12</span>Clarified that clients must keep following the pubkey, not the nip05 address, if the two ever disagree later; required hex-only keys in responses.</li>278 <li><span class="ver">2022</span><span class="when">2022-05</span>Added the CORS warning and the <code>Access-Control-Allow-Origin</code> recommendation for JavaScript clients.</li>279 <li><span class="ver">Origin</span><span class="when">2022-05</span>Migrated into the <code>nostr-protocol/nips</code> repository from the original <code>fiatjaf/nostr</code> repository.</li>280 </ul>281282 <div class="box tech">283 <span class="tag">Tech Note</span>284 <p>The source history is confusing on one point: an early commit message says "merge NIP-35 into NIP-05," but the current manifest lists NIP-35 as an unrelated, active proposal (Torrents). The number was evidently freed and reused later. Nothing in the current 05.md depends on that old NIP-35 content, so this page describes only the present spec.</p>285 </div>286287 <div class="credits">288 <h3 class="cardhead">Original Proposal - Credits</h3>289 <table>290 <tr><td>Author</td><td>fiatjaf</td></tr>291 <tr><td>First commit</td><td>2022-05-01 — "migrate nips from main nostr repo"</td></tr>292 <tr><td>Status</td><td>final - optional</td></tr>293 <tr><td>Source</td><td><a href="https://github.com/nostr-protocol/nips/blob/master/05.md">05.md on GitHub</a> - <a href="https://github.com/nostr-protocol/nips/commits/master/05.md">full change history</a></td></tr>294 </table>295 </div>296297 <h2 class="bar"><span class="num">9</span> See Also</h2>298 <ul>299 <li><a href="01.html">NIP-01</a> — defines the kind 0 user metadata event that carries the <code>nip05</code> field.</li>300 <li><a href="19.html">NIP-19</a> — bech32 <code>npub</code> encoding, for display only; never used inside a NIP-05 response.</li>301 <li><a href="65.html">NIP-65</a> — relay list metadata; a more complete alternative to the optional <code>relays</code> attribute here.</li>302 <li><a href="39.html">NIP-39</a> — linking a profile to other platforms; a related but separate identity-claim mechanism.</li>303 </ul>304 </div>305306 <aside>307 <div class="cartridge">308 <div class="inner">309 <h3 class="cardhead">Spec Card</h3>310 <div class="lights"><span class="led on"></span><span class="led on"></span><span class="led on"></span></div>311 <dl>312 <dt>Status</dt><dd><span class="chip final">final</span><span class="chip optional">optional</span></dd>313 <dt>Defines</dt><dd><code>nip05</code> field - /.well-known/nostr.json - names/relays mapping</dd>314 <dt>Kind(s)</dt><dd>uses kind 0 (defined in NIP-01)</dd>315 <dt>Requires</dt><dd><a href="01.html">NIP-01</a> — kind 0 user metadata</dd>316 <dt>Transport</dt><dd>HTTPS - JSON</dd>317 <dt>Crypto</dt><dd>none — string comparison of an existing hex pubkey</dd>318 <dt>First commit</dt><dd>2022-05-01</dd>319 <dt>Last updated</dt><dd>2026-06-13</dd>320 </dl>321 </div>322 </div>323 </aside>324 </main>325326 <nav class="navbtns">327 <a href="04.html">◀ NIP-04: Encrypted Direct Message</a>328 <a href="06.html">NIP-06: Basic Key Derivation from Mnemonic Seed Phrase ▶</a>329 </nav>330331 <footer class="pagefoot">332 <a href="../index.html">Nostr Protocol Manual</a>333 <span class="pageno">NIP 05 / F4</span>334 </footer>335</div>336337<script src="../assets/webmcp.js" defer></script>338</body>339</html>