clone

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

main
nip.guide / site / nips / 07.html
07.html 22.0K
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-07 - window.nostr Capability for Web Browsers — Nostr Instruction Manual</title>7<meta name="description" content="NIP-07: the window.nostr browser interface. How extensions expose getPublicKey, signEvent, and optional NIP-04/NIP-44 encryption to web pages without exposing the private key.">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 07 - Client API</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="07">07</div>48 <div class="titles">49 <div class="kicker">Nostr Implementation Possibility - Hex № 07</div>50 <h1>window.nostr Capability for Web Browsers</h1>51 <p class="sub">A standard JavaScript object that lets a web page ask a browser extension to hold the user's key and sign events, without the page ever touching the private key.</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 web client needs a private key to sign events. Pasting a raw private key into every website is dangerous. One malicious or buggy site can steal the key and impersonate the user forever.</p>60 <p>NIP-07 solves this problem. A browser extension (or, in principle, the browser itself) injects an object named <code>window.nostr</code> into every page. The page calls methods on this object instead of handling the key directly. The extension holds the key, asks the user to approve each request, and signs on the page's behalf.</p>61 <p>This turns the extension into a small, trusted signer that many different Nostr web apps can share. The user installs one extension, approves it once per site, and every <a href="01.html">NIP-01</a> compatible web client can publish signed events through it.</p>62 <p>A web page must check that <code>window.nostr</code> exists before using it. Not every browser has an extension installed, and not every browser will ever support this capability.</p>6364 <div class="box hint">65 <span class="tag">Hint!</span>66 <p>NIP-07 is one of three ways to keep a private key out of an app. Compare it with <a href="46.html">NIP-46</a> (a remote signer reached over the network) and <a href="55.html">NIP-55</a> (a signer app on the same Android device). All three follow the same idea: the app asks, the signer decides.</p>67 </div>6869 <h2 class="bar" id="spec"><span class="num">2</span> The window.nostr Object</h2>70 <p>An extension that implements NIP-07 must define <code>window.nostr</code> with two required methods. It may also define four optional encryption methods, grouped under <code>nip04</code> and <code>nip44</code> namespaces. Every method is asynchronous and returns a <code>Promise</code>.</p>7172 <div class="crt">73 <div class="titlebar">Wire format - required methods</div>74<pre><span class="c">async</span> window.nostr.<span class="k">getPublicKey</span>(): <span class="c">string</span>75 <span class="c">// returns the user's public key, as lowercase hex</span>7677<span class="c">async</span> window.nostr.<span class="k">signEvent</span>(event: {78 created_at: <span class="c">number</span>,79 kind: <span class="c">number</span>,80 tags: <span class="c">string[][]</span>,81 content: <span class="c">string</span>82}): <span class="c">Event</span>83 <span class="c">// takes an unsigned event, adds id, pubkey, and sig, and returns the full event</span></pre>84 </div>8586 <h2 class="bar"><span class="num">3</span> Required Methods</h2>87 <div class="tscroll"><table class="ref">88 <caption>Required methods</caption>89 <tr><th>Method</th><th>Returns</th><th>Rule</th></tr>90 <tr><td class="btn">getPublicKey()</td><td>string (64-char hex)</td><td>Returns the public key of the account the extension currently has selected.</td></tr>91 <tr><td class="btn">signEvent(event)</td><td>Event</td><td>Takes an unsigned event object. Adds <code>id</code>, <code>pubkey</code>, and <code>sig</code> following the <a href="01.html">NIP-01</a> rules, then returns the complete event.</td></tr>92 </table></div>9394 <p>The <code>event</code> argument to <code>signEvent</code> is an <strong>unsigned event</strong>. The page supplies only the fields it controls. The extension computes the rest.</p>95 <div class="tscroll"><table class="ref">96 <caption>Unsigned event object - argument to signEvent</caption>97 <tr><th>Field</th><th>Type</th><th>Supplied by</th></tr>98 <tr><td class="btn">created_at</td><td>number, unix seconds</td><td>the page</td></tr>99 <tr><td class="btn">kind</td><td>number</td><td>the page</td></tr>100 <tr><td class="btn">tags</td><td>array of arrays of strings</td><td>the page</td></tr>101 <tr><td class="btn">content</td><td>string</td><td>the page</td></tr>102 <tr><td class="btn">pubkey</td><td>hex string</td><td>the extension, on return</td></tr>103 <tr><td class="btn">id</td><td>hex string</td><td>the extension, on return</td></tr>104 <tr><td class="btn">sig</td><td>hex string</td><td>the extension, on return</td></tr>105 </table></div>106 <p>A page must never construct <code>id</code>, <code>pubkey</code>, or <code>sig</code> itself and pass them into <code>signEvent</code>. The extension owns those three fields. See <a href="01.html">NIP-01</a> for the full event format and the id-serialization rule.</p>107108 <h2 class="bar"><span class="num">4</span> Optional Encryption Methods</h2>109 <p>Aside from the two required methods, an extension may also implement encryption helpers. These let a page encrypt and decrypt content for direct messages without the page seeing the shared secret.</p>110111 <div class="crt">112 <div class="titlebar">Wire format - optional encryption methods</div>113<pre><span class="c">async</span> window.nostr.nip04.<span class="k">encrypt</span>(pubkey, plaintext): <span class="c">string</span>114 <span class="c">// returns ciphertext and iv, as specified in nip-04 (deprecated)</span>115116<span class="c">async</span> window.nostr.nip04.<span class="k">decrypt</span>(pubkey, ciphertext): <span class="c">string</span>117 <span class="c">// takes ciphertext and iv, as specified in nip-04 (deprecated)</span>118119<span class="c">async</span> window.nostr.nip44.<span class="k">encrypt</span>(pubkey, plaintext): <span class="c">string</span>120 <span class="c">// returns ciphertext, as specified in nip-44</span>121122<span class="c">async</span> window.nostr.nip44.<span class="k">decrypt</span>(pubkey, ciphertext): <span class="c">string</span>123 <span class="c">// takes ciphertext, as specified in nip-44</span></pre>124 </div>125126 <div class="tscroll"><table class="ref">127 <caption>Optional methods</caption>128 <tr><th>Method</th><th>Returns</th><th>Rule</th></tr>129 <tr><td class="btn">nip04.encrypt(pubkey, plaintext)</td><td>string</td><td>Encrypts <code>plaintext</code> for <code>pubkey</code> using the <a href="04.html">NIP-04</a> scheme. Deprecated; kept only for legacy content.</td></tr>130 <tr><td class="btn">nip04.decrypt(pubkey, ciphertext)</td><td>string</td><td>Decrypts a <a href="04.html">NIP-04</a> payload from <code>pubkey</code>. Deprecated; kept only for legacy content.</td></tr>131 <tr><td class="btn">nip44.encrypt(pubkey, plaintext)</td><td>string</td><td>Encrypts <code>plaintext</code> for <code>pubkey</code> using the current <a href="44.html">NIP-44</a> versioned scheme.</td></tr>132 <tr><td class="btn">nip44.decrypt(pubkey, ciphertext)</td><td>string</td><td>Decrypts a <a href="44.html">NIP-44</a> payload from <code>pubkey</code>.</td></tr>133 </table></div>134135 <div class="box warn">136 <span class="tag">Warning!</span>137 <p><a href="04.html">NIP-04</a> is deprecated in favor of <a href="17.html">NIP-17</a>. A page should prefer <code>nip44</code> for new encrypted content. Keep <code>nip04</code> calls only to read old messages.</p>138 </div>139140 <div class="box tech">141 <span class="tag">Tech Note</span>142 <p>Older revisions of this NIP also defined <code>window.nostr.getRelays()</code>, which returned the user's relay read/write policy. It was removed from the spec in 2025 without a replacement method. A page that needs a user's relay list should read the <code>kind:10002</code> event defined in <a href="65.html">NIP-65</a> instead of relying on an extension method.</p>143 </div>144145 <h2 class="bar" id="trust"><span class="num">5</span> The Trust Boundary</h2>146 <p>The whole point of NIP-07 is a boundary. The page and the extension run in separate JavaScript contexts. The page can call methods; it cannot read the extension's memory or storage. The private key stays inside the extension at all times.</p>147148 <figure class="diagram">149 <div class="frame">150 <svg viewBox="0 0 560 210" role="img" aria-label="Trust boundary: the web page calls window.nostr methods on the extension, which signs internally and never exposes the raw key">151 <text class="d-actor-label" x="10" y="18">Trust boundary</text>152 <line x1="185" y1="30" x2="185" y2="185" stroke="#8fa2b8" stroke-width="1.5" stroke-dasharray="4 5"/>153 <line x1="410" y1="30" x2="410" y2="185" stroke="#8fa2b8" stroke-width="1.5" stroke-dasharray="4 5"/>154 <rect class="d-actor" x="15" y="70" width="140" height="56" rx="8"/>155 <text class="d-actor-label" x="85" y="94" text-anchor="middle">Web Page</text>156 <text class="d-note" x="85" y="112" text-anchor="middle">page JS - no key access</text>157 <rect class="d-actor" x="215" y="70" width="150" height="56" rx="8"/>158 <text class="d-actor-label" x="290" y="94" text-anchor="middle">Extension</text>159 <text class="d-note" x="290" y="112" text-anchor="middle">isolated context - approval UI</text>160 <rect class="d-actor" x="430" y="70" width="115" height="56" rx="8"/>161 <text class="d-actor-label" x="487" y="94" text-anchor="middle">Key Store</text>162 <text class="d-note" x="487" y="112" text-anchor="middle">encrypted, local</text>163 <path class="d-msg d-flow" d="M157,88 C180,84 190,84 213,88"/>164 <path class="d-msg blue d-flow" d="M213,108 C190,112 180,112 157,108"/>165 <path class="d-msg d-flow" d="M367,92 L428,92"/>166 <path class="d-msg blue d-flow" d="M428,105 L367,105"/>167 <text class="d-label" x="185" y="55" text-anchor="middle">getPublicKey() / signEvent(event)</text>168 <text class="d-label" x="410" y="55" text-anchor="middle">sign internally</text>169 <text class="d-note" x="280" y="150" text-anchor="middle">pubkey / signed event (id, sig) return to the page</text>170 <text class="d-note" x="280" y="195" text-anchor="middle">The raw private key never crosses the dashed line back to the page.</text>171 <circle r="4" fill="#f26a00">172 <animateMotion dur="2.6s" repeatCount="indefinite" path="M157,88 C180,84 190,84 213,88"/>173 </circle>174 <circle r="4" fill="#2e5f9e">175 <animateMotion dur="2.6s" begin="1.3s" repeatCount="indefinite" path="M213,108 C190,112 180,112 157,108"/>176 </circle>177 </svg>178 </div>179 <figcaption>Fig. 7-1 — <b>Trust boundary.</b> The page calls the extension; the extension calls the key store. Only public results cross back.</figcaption>180 </figure>181182 <h3>Recommendation to Extension Authors</h3>183 <p>A page may check for <code>window.nostr</code> as soon as it loads. If the extension injects its script too late, the page finds nothing and assumes no signer is installed.</p>184 <div class="box hint">185 <span class="tag">Hint!</span>186 <p>Chromium and Firefox extension authors should set <code>"run_at": "document_end"</code> in the extension manifest. This makes sure <code>window.nostr</code> is ready before the page finishes loading.</p>187 </div>188 <p>This NIP does not define an implementation. A list of known browser extensions and other signers is kept at <a href="https://github.com/aljazceru/awesome-nostr#nip-07-browser-extensions">aljazceru/awesome-nostr</a> on GitHub.</p>189190 <h2 class="bar" id="examples"><span class="num">6</span> Worked Example</h2>191 <p>The code below runs in a web page. It checks for the extension, reads the public key, builds an unsigned kind-1 note, and asks the extension to sign it. A rejected or failed request throws, so the page wraps the call in <code>try/catch</code>.</p>192193 <div class="crt">194 <div class="titlebar">Page script - request a signed note</div>195<pre><span class="c">if</span> (window.nostr) {196 <span class="c">const</span> pubkey = <span class="c">await</span> window.nostr.<span class="k">getPublicKey</span>();197198 <span class="c">const</span> unsignedEvent = {199 created_at: Math.floor(Date.now() / 1000),200 kind: 1,201 tags: [],202 content: <span class="s">"Signed in the browser. The key never left the extension."</span>203 };204205 <span class="c">try</span> {206 <span class="c">const</span> signedEvent = <span class="c">await</span> window.nostr.<span class="k">signEvent</span>(unsignedEvent);207 <span class="c">// signedEvent now has id, pubkey, and sig - see NIP-01</span>208 relaySocket.send(JSON.stringify([<span class="s">"EVENT"</span>, signedEvent]));209 } <span class="c">catch</span> (err) {210 <span class="c">// the user clicked Reject, or the extension is locked</span>211 console.log(<span class="s">"Signing request refused:"</span>, err);212 }213} <span class="c">else</span> {214 <span class="c">// no NIP-07 extension found - fall back to another signer, e.g. NIP-46</span>215}</pre>216 </div>217218 <figure class="diagram">219 <div class="frame">220 <svg viewBox="0 0 560 290" role="img" aria-label="Sequence diagram: page calls signEvent, extension prompts the user, user approves, extension returns the signed event">221 <line class="d-wire" x1="80" y1="40" x2="80" y2="270" stroke-dasharray="3 4"/>222 <line class="d-wire" x1="280" y1="40" x2="280" y2="270" stroke-dasharray="3 4"/>223 <line class="d-wire" x1="480" y1="40" x2="480" y2="270" stroke-dasharray="3 4"/>224 <rect class="d-actor" x="25" y="8" width="110" height="30" rx="6"/>225 <text class="d-actor-label" x="80" y="28" text-anchor="middle">Page</text>226 <rect class="d-actor" x="225" y="8" width="110" height="30" rx="6"/>227 <text class="d-actor-label" x="280" y="28" text-anchor="middle">Extension</text>228 <rect class="d-actor" x="425" y="8" width="110" height="30" rx="6"/>229 <text class="d-actor-label" x="480" y="28" text-anchor="middle">User</text>230 <path class="d-msg" d="M80,62 L275,62"/>231 <text class="d-label" x="180" y="55" text-anchor="middle">window.nostr.signEvent(event)</text>232 <path class="d-msg" d="M280,95 L475,95"/>233 <text class="d-label" x="380" y="88" text-anchor="middle">show approval popup</text>234 <path class="d-msg blue" d="M480,140 L285,140"/>235 <text class="d-label" x="380" y="133" text-anchor="middle">Approve</text>236 <path class="d-msg blue d-flow" d="M280,185 L85,185"/>237 <text class="d-label" x="180" y="178" text-anchor="middle">Promise resolves: signed event</text>238 <text class="d-note" x="280" y="225" text-anchor="middle">If the user clicks Reject instead, the promise rejects and the page never sees the key.</text>239 </svg>240 </div>241 <figcaption>Fig. 7-2 — <b>Approval flow.</b> Every signing or decryption request can prompt the user before it resolves.</figcaption>242 </figure>243244 <h2 class="bar" id="history"><span class="num">7</span> Version History</h2>245 <p>NIP-07 has changed mostly by adding and later trimming its optional methods. Notable changes, newest first:</p>246 <ul class="patchnotes">247 <li><span class="ver">2025</span><span class="when">2025-02</span>Removed <code>getRelays()</code> from the required interface (#1779). No replacement method was defined; use <a href="65.html">NIP-65</a>.</li>248 <li><span class="ver">2024</span><span class="when">2024-07</span>Added the recommendation for extension authors to load scripts with <code>"run_at": "document_end"</code>.</li>249 <li><span class="ver">2024</span><span class="when">2024-02</span>Added <code>nip44.encrypt</code> and <code>nip44.decrypt</code> to the optional methods.</li>250 <li><span class="ver">2023</span><span class="when">2023-12</span><a href="44.html">NIP-44</a> version 2 was published; <code>nip44</code> calls were briefly added to this NIP, then reverted until the encryption spec stabilized.</li>251 <li><span class="ver">2023</span><span class="when">2023-10</span>Clarified the type of the unsigned event object passed into <code>signEvent</code>.</li>252 <li><span class="ver">2023</span><span class="when">2023-01</span>Extensions were required to also add <code>id</code> and <code>pubkey</code> when signing, not only <code>sig</code>.</li>253 <li><span class="ver">2022</span><span class="when">2022-12</span>Added <code>nip04.encrypt</code> and <code>nip04.decrypt</code> and marked all extra methods optional.</li>254 <li class="minor"><span class="ver">2022-2023</span><span class="when">various</span>Many community extensions (Alby, nos2x-fox, Blockcore, Flamingo, Horse, and others) were added to the implementations list.</li>255 <li><span class="ver">Origin</span><span class="when">2022-05-06</span>fiatjaf published NIP-07 with <code>getPublicKey</code> and <code>signEvent</code>.</li>256 </ul>257258 <div class="credits">259 <h3 class="cardhead">Original Proposal - Credits</h3>260 <table>261 <tr><td>Author</td><td>fiatjaf</td></tr>262 <tr><td>First commit</td><td>2022-05-06 — “publish NIP-07: window.nostr.”</td></tr>263 <tr><td>Status</td><td>draft - optional</td></tr>264 <tr><td>Source</td><td><a href="https://github.com/nostr-protocol/nips/blob/master/07.md">07.md on GitHub</a> - <a href="https://github.com/nostr-protocol/nips/commits/master/07.md">full change history</a></td></tr>265 </table>266 </div>267268 <h2 class="bar"><span class="num">8</span> See Also</h2>269 <ul>270 <li><a href="01.html">NIP-01</a> — the event format that <code>signEvent</code> produces and the id/sig rules the extension follows.</li>271 <li><a href="04.html">NIP-04</a> — the deprecated encryption scheme behind <code>nip04.encrypt</code>/<code>nip04.decrypt</code>.</li>272 <li><a href="44.html">NIP-44</a> — the current encryption scheme behind <code>nip44.encrypt</code>/<code>nip44.decrypt</code>.</li>273 <li><a href="46.html">NIP-46</a> — a remote signer reached over the network, for when no browser extension is available.</li>274 <li><a href="55.html">NIP-55</a> — the same idea as this NIP, but for Android apps instead of web pages.</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">optional</span></dd>285 <dt>Defines</dt><dd>window.nostr - getPublicKey - signEvent - nip04.* (optional) - nip44.* (optional)</dd>286 <dt>Kind(s)</dt><dd>none — this NIP defines an API, not an event kind</dd>287 <dt>Requires</dt><dd><a href="01.html">NIP-01</a> event format</dd>288 <dt>Optional pairs with</dt><dd><a href="04.html">NIP-04</a> - <a href="44.html">NIP-44</a></dd>289 <dt>Transport</dt><dd>in-page JavaScript object - no network</dd>290 <dt>Crypto</dt><dd>delegated to the extension; key never reaches the page</dd>291 <dt>First commit</dt><dd>2022-05-06</dd>292 <dt>Last updated</dt><dd>2025-02-13</dd>293 </dl>294 </div>295 </div>296 </aside>297 </main>298299 <nav class="navbtns">300 <a href="06.html">◀ NIP-06: Basic Key Derivation From Mnemonic Seed Phrase</a>301 <a href="08.html">NIP-08: Handling Mentions ▶</a>302 </nav>303304 <footer class="pagefoot">305 <a href="../index.html">Nostr Protocol Manual</a>306 <span class="pageno">NIP 07 / F4</span>307 </footer>308</div>309310<script src="../assets/webmcp.js" defer></script>311</body>312</html>