clone

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

main
nip.guide / site / nips / 5a.html
5a.html 38.3K
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-5A - Static Websites (nsites) — Nostr Instruction Manual</title>7<meta name="description" content="NIP-5A: static websites hosted from Blossom assets. Site manifests, named sites, snapshots, aggregate hashes, copying, and host server rules. 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 5A - Web Hosting</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="5A">5A</div>48 <div class="titles">49 <div class="kicker">Nostr Implementation Possibility - Hex № 5A</div>50 <h1>Static Websites (nsites)</h1>51 <p class="sub">Publish a whole static website as one signed event: a manifest that maps paths to Blossom file hashes, served by any host server.</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>This NIP defines the <strong>nsite</strong>: a static website hosted from Blossom assets. Blossom is a file storage protocol. It stores files as blobs and addresses each blob by its sha256 hash.</p>60 <p>An author uploads the site files to Blossom servers. Then the author publishes a <strong>site manifest</strong> event. The manifest maps each absolute path, such as <code>/index.html</code>, to the sha256 hash of the file at that path. A <strong>host server</strong> is a HTTP server that serves these pubkey static websites. It reads the manifest from relays, fetches the blobs from Blossom, and serves them to normal web browsers.</p>61 <p>The site lives in the author's signature, not on any one server. Any host server can serve it. Any Blossom server can store the files. The spec also defines named sub-sites, immutable snapshots, a deterministic aggregate hash for each site version, and rules for copying a site into another author's namespace.</p>6263 <div class="box hint">64 <span class="tag">Hint!</span>65 <p>Think of the manifest as a tiny signed file-system table. The website itself never touches a relay. Relays carry only the map; Blossom carries the bytes.</p>66 </div>6768 <figure class="diagram">69 <div class="frame">70 <svg viewBox="0 0 560 210" role="img" aria-label="Author publishes a manifest to a relay and uploads blobs to Blossom; a host server reads both and serves the site to a visitor over HTTPS">71 <rect class="d-actor" x="10" y="78" width="96" height="50" rx="8"/>72 <text class="d-actor-label" x="58" y="107" text-anchor="middle">Author</text>73 <rect class="d-actor" x="200" y="16" width="100" height="44" rx="8"/>74 <text class="d-actor-label" x="250" y="43" text-anchor="middle">Relay</text>75 <rect class="d-actor" x="200" y="146" width="100" height="44" rx="8"/>76 <text class="d-actor-label" x="250" y="173" text-anchor="middle">Blossom</text>77 <rect class="d-actor" x="352" y="78" width="104" height="50" rx="8"/>78 <text class="d-actor-label" x="404" y="107" text-anchor="middle">Host srv</text>79 <rect class="d-actor" x="488" y="78" width="62" height="50" rx="8"/>80 <text class="d-actor-label" x="519" y="107" text-anchor="middle">Visitor</text>81 <path class="d-msg d-flow" d="M108,92 C150,76 160,52 197,40"/>82 <path class="d-msg d-flow" d="M108,114 C150,130 160,154 197,166"/>83 <path class="d-msg blue" d="M302,44 C335,54 355,64 382,76"/>84 <path class="d-msg blue" d="M302,162 C335,152 355,142 382,130"/>85 <path class="d-msg blue d-flow" d="M458,103 L485,103"/>86 <text class="d-label" x="140" y="52" text-anchor="middle">15128</text>87 <text class="d-label" x="140" y="162" text-anchor="middle">PUT blob</text>88 <text class="d-label" x="342" y="48" text-anchor="middle">REQ</text>89 <text class="d-label" x="352" y="164" text-anchor="middle">GET &lt;sha256&gt;</text>90 <text class="d-label" x="471" y="94" text-anchor="middle">HTTPS</text>91 <circle r="4" fill="#f26a00">92 <animateMotion dur="2.6s" repeatCount="indefinite" path="M108,92 C150,76 160,52 197,40"/>93 </circle>94 <circle r="4" fill="#2e5f9e">95 <animateMotion dur="2.6s" begin="1.3s" repeatCount="indefinite" path="M458,103 L485,103"/>96 </circle>97 </svg>98 </div>99 <figcaption>Fig. 5A-1 — <b>The nsite pipeline.</b> The author publishes the manifest to relays and the files to Blossom. The host server joins the two for the visitor.</figcaption>100 </figure>101102 <h2 class="bar" id="spec"><span class="num">2</span> Site Manifest Kinds</h2>103 <p>A site manifest event must be a regular, replaceable, or addressable event as defined in <a href="01.html">NIP-01</a>. The spec uses one kind from each storage class:</p>104105 <div class="tscroll"><table class="ref">106 <caption>Manifest kinds</caption>107 <tr><th>Kind</th><th>Storage class</th><th>Purpose</th></tr>108 <tr><td class="btn">15128</td><td>replaceable</td><td><strong>Root site.</strong> One per pubkey. Must not include a <code>d</code> tag. This is the pubkey's main site.</td></tr>109 <tr><td class="btn">35128</td><td>addressable</td><td><strong>Named site.</strong> A smaller site under the same pubkey; think of it as a subdomain. Must include a <code>d</code> tag with the site identifier.</td></tr>110 <tr><td class="btn">5128</td><td>regular</td><td><strong>Manifest snapshot.</strong> An immutable copy of a root or named site at one point in time. See §5.</td></tr>111 </table></div>112113 <h3>Named sites and the d tag</h3>114 <p>A named site uses kind <code>35128</code> and must include a <code>d</code> tag. The <code>d</code> tag value is the site identifier inside the author's namespace.</p>115 <p>For canonical named-site URLs, the <code>d</code> value must match <code>^[a-z0-9-]{1,13}$</code> and must not end with <code>-</code>. The length limit comes from DNS: a DNS label holds at most 63 characters, and the base36 pubkey uses 50 of them. That leaves 1 to 13 characters for the identifier.</p>116 <div class="crt">117 <div class="titlebar">Named site manifest - minimum shape</div>118<pre>{119 <span class="k">"kind"</span>: 35128,120 <span class="k">"content"</span>: <span class="s">""</span>,121 <span class="k">"tags"</span>: [122 [<span class="s">"d"</span>, <span class="s">"blog"</span>],123 [<span class="s">"path"</span>, <span class="s">"/index.html"</span>, <span class="s">"186ea5fd…ca1c99"</span>],124 [<span class="s">"path"</span>, <span class="s">"/post.html"</span>, <span class="s">"a1b2c3d4…123456"</span>],125 [<span class="s">"x"</span>, <span class="c">&lt;site aggregate sha256&gt;</span>, <span class="s">"aggregate"</span>]126 ],127 <span class="c">…pubkey, created_at, id, sig as in NIP-01…</span>128}</pre>129 </div>130131 <h2 class="bar" id="tags"><span class="num">3</span> Manifest Tags</h2>132 <p>The manifest's <code>content</code> carries no site data; everything lives in the tags. The full tag set:</p>133134 <div class="tscroll"><table class="ref">135 <caption>Nsite event tags</caption>136 <tr><th>Tag</th><th>Role</th><th>Required</th></tr>137 <tr><td class="btn">path</td><td>Maps an absolute path to the sha256 hash of the file served at that path.</td><td>Yes, one or more</td></tr>138 <tr><td class="btn">x</td><td>Carries the aggregate hash of the full manifest, so the site version is indexable and discoverable.</td><td>Optional, recommended (mandatory in snapshots, see §5)</td></tr>139 <tr><td class="btn">d</td><td>Short identifier of a named site. Used only on kind <code>35128</code>.</td><td>Named sites only</td></tr>140 <tr><td class="btn">a</td><td>References the immediate parent nsite that this copied site was made from.</td><td>Copied sites only (and snapshots, see §5)</td></tr>141 <tr><td class="btn">A</td><td>References the origin nsite of a copied site's lineage.</td><td>Copied sites only</td></tr>142 <tr><td class="btn">server</td><td>Hints which Blossom servers can serve the blobs.</td><td>No</td></tr>143 <tr><td class="btn">title</td><td>Human-readable site title.</td><td>No</td></tr>144 <tr><td class="btn">description</td><td>Short human-readable description of the site.</td><td>No</td></tr>145 <tr><td class="btn">source</td><td>Points to the site's source code repository or source archive.</td><td>No</td></tr>146 <tr><td class="btn">app</td><td>References an upstream app descriptor event that the manifest is part of. See §6.</td><td>No</td></tr>147 </table></div>148149 <h3>Tag formats</h3>150 <ul>151 <li><strong>path</strong> — the event must include one or more <code>path</code> tags. Each has the exact format <code>["path", "/absolute/path", "sha256hash"]</code>. The first element is the literal string <code>"path"</code>. The second element is an absolute path that ends with a filename and extension. The third element is the sha256 hash of the file served under that path.</li>152 <li><strong>x</strong> — the event should include one <code>x</code> tag with the site aggregate hash (§4). Its format is <code>["x", "&lt;sha256-hex&gt;", "aggregate"]</code>, where the hash is lowercase hexadecimal.</li>153 <li><strong>server</strong> — the event may include <code>server</code> tags that hint which Blossom servers hold the blobs.</li>154 <li><strong>title / description</strong> — the event may include these tags for simple site information.</li>155 <li><strong>source</strong> — the event may include one <code>source</code> tag with the format <code>["source", "&lt;url&gt;"]</code>. For a source code repository, the URL may be a <code>nostr://</code> git URL as defined by <a href="34.html">NIP-34</a>, or an absolute <code>https://</code> git URL. For a source archive, the URL must be an absolute <code>https://</code> URL.</li>156 </ul>157158 <div class="box tech">159 <span class="tag">Tech Note</span>160 <p>The source spec's own tag table is out of sync with its prose in three places. It omits the <code>app</code> tag, which the app-descriptor section defines. It marks <code>x</code> as optional, but snapshots (kind <code>5128</code>) require exactly one. It marks <code>a</code> as "copied sites only", but snapshots also require exactly one <code>a</code> tag. The table above restores all three.</p>161 </div>162163 <h2 class="bar" id="aggregate"><span class="num">4</span> The Aggregate Hash</h2>164 <p>The aggregate hash is a deterministic hash of the site's <code>path</code> tags. It identifies one specific site version. Two site manifest events are <strong>equivalent</strong> if and only if they produce the same aggregate hash, even when different pubkeys publish them.</p>165 <p>The hash must be computed from the <code>path</code> tags only. It must not depend on the order of tags in the manifest. It must ignore every other tag and every other event field, including <code>app</code> tags. Compute it like this:</p>166 <ol>167 <li>Collect every <code>path</code> tag.</li>168 <li>For each tag, produce one line in the exact format <code>&lt;sha256hash&gt; &lt;absolute-path&gt;\n</code>.</li>169 <li>Sort all lines in ascending lexicographic order.</li>170 <li>Concatenate the sorted lines as UTF-8 bytes.</li>171 <li>Compute the SHA-256 hash of the bytes.</li>172 </ol>173 <p>Encode the digest as lowercase hexadecimal. Put it in the manifest as <code>["x", "&lt;sha256-hex&gt;", "aggregate"]</code> so it is indexable for discovery and lookup. Root and named sites <em>should</em> carry this tag. Snapshot events of kind <code>5128</code> <em>must</em> carry exactly one, and it must exactly match the parent nsite's aggregate <code>x</code> tag.</p>174175 <div class="crt">176 <div class="titlebar">Aggregate input - sorted lines</div>177<pre><span class="s">186ea5fd14e88fd1ac49351759e7ab906fa94892002b60bf7f5a428f28ca1c99 /index.html</span>178<span class="s">fedcba0987654321fedcba0987654321fedcba0987654321fedcba0987654321 /favicon.ico</span></pre>179 </div>180181 <div class="crt">182 <div class="titlebar">Computing the aggregate - shell and JavaScript</div>183<pre><span class="c"># shell, using nak + jq</span>184nak req -k 15128 -a <span class="c">&lt;pubkey&gt;</span> --limit 1 wss://relay.example.com \185 | jq -r <span class="s">'.tags[] | select(.[0] == "path") | "\(.[2]) \(.[1])"'</span> \186 | sort | sha256sum187188<span class="c">// JavaScript</span>189const aggregateHash = sha256(190 event.tags.filter(t =&gt; t[0] === <span class="s">"path"</span>)191 .map(([, path, hash]) =&gt; <span class="s">`${hash} ${path}\n`</span>)192 .sort().join(<span class="s">""</span>))</pre>193 </div>194195 <h2 class="bar" id="snapshots"><span class="num">5</span> Manifest Snapshots</h2>196 <p>A manifest snapshot uses kind <code>5128</code> and is a regular event. It captures the state of a root site or named site at one point in time. Root and named sites are replaceable, so old versions vanish; a snapshot keeps a historical version addressable by its own event id. Authors may publish snapshots alongside site updates to preserve version history.</p>197 <p>A snapshot of an existing root or named site follows these rules:</p>198 <ul>199 <li>It must copy the source manifest's <code>path</code> tags.</li>200 <li>It must include exactly one <code>x</code> tag whose value exactly matches the source manifest's aggregate <code>x</code> tag.</li>201 <li>It must include exactly one <code>a</code> tag that references the source root site or named site.</li>202 <li>If the source manifest includes an <code>A</code> tag, the snapshot must copy that <code>A</code> tag unchanged. If the source has no <code>A</code> tag, the snapshot should omit it.</li>203 <li>Other valid nsite tags — <code>title</code>, <code>description</code>, <code>source</code>, <code>app</code>, <code>server</code>, relay hints — may be copied if the author wants to preserve them. They are optional.</li>204 </ul>205 <p>The <code>a</code> tag identifies <em>which</em> site the snapshot captures. The snapshot's own <code>created_at</code> is the version timestamp of that snapshot.</p>206207 <div class="crt">208 <div class="titlebar">Manifest snapshot - kind 5128</div>209<pre>{210 <span class="k">"kind"</span>: 5128,211 <span class="k">"pubkey"</span>: <span class="s">"266815e0…cd408a5"</span>,212 <span class="k">"created_at"</span>: 1727373475, <span class="c">// the version timestamp</span>213 <span class="k">"content"</span>: <span class="s">""</span>,214 <span class="k">"tags"</span>: [215 [<span class="s">"a"</span>, <span class="s">"35128:266815e0…cd408a5:blog"</span>], <span class="c">// the snapshotted site</span>216 [<span class="s">"path"</span>, <span class="s">"/index.html"</span>, <span class="s">"186ea5fd…ca1c99"</span>],217 [<span class="s">"path"</span>, <span class="s">"/post.html"</span>, <span class="s">"a1b2c3d4…123456"</span>],218 [<span class="s">"x"</span>, <span class="c">&lt;site aggregate sha256&gt;</span>, <span class="s">"aggregate"</span>],219 [<span class="s">"server"</span>, <span class="s">"https://blossom.example.com"</span>],220 [<span class="s">"title"</span>, <span class="s">"My Blog v1"</span>],221 [<span class="s">"description"</span>, <span class="s">"An immutable snapshot of a blog hosted on Nostr"</span>],222 [<span class="s">"source"</span>, <span class="s">"https://github.com/example/my-nostr-blog"</span>]223 ]224}</pre>225 </div>226227 <h2 class="bar" id="appdesc"><span class="num">6</span> Upstream App Descriptors</h2>228 <p>An nsite may include one or more <code>app</code> tags. Each references an upstream app descriptor event that the manifest is part of. The format is <code>["app", "&lt;kind&gt;:&lt;pubkey&gt;:&lt;d-tag&gt;", "&lt;relay&gt;"]</code>. The second element is an addressable event reference to the app descriptor. The third element is a relay hint for locating that event.</p>229 <p>This linkage is optional. It makes room for application-level discovery and packaging flows without extending the nsite manifest itself. The descriptor kind is intentionally generic: an nsite can reference <a href="89.html">NIP-89</a> applications and other present or future descriptor forms. App store metadata, discoverability, and runtime compatibility belong in those descriptor events. The nsite manifest stays focused on describing the site's files.</p>230231 <h2 class="bar" id="copying"><span class="num">7</span> Copying nsites</h2>232 <p>An nsite may be created by copying another root site or named site. The copy pins that site under a different author's namespace at a specific state. The new author then maintains it independently. Authors who copy a site should copy all tags from the source whenever they remain applicable: <code>path</code>, <code>x</code>, <code>app</code>, <code>server</code>, <code>title</code>, <code>description</code>, and <code>source</code>.</p>233 <p>A copy does not have to keep the original kind or identifier. A copied root site may become a named site. A copied named site may become a root site. A copied named site may use a different <code>d</code> value than its parent. Because kind and identifier can change, the lineage tags below are the stable, indexable references:</p>234 <ul>235 <li>A copied site <strong>must</strong> include exactly one lowercase <code>a</code> tag that references the immediate parent nsite it was copied from.</li>236 <li>A copied site <strong>must</strong> include exactly one uppercase <code>A</code> tag that references the origin nsite of the copy lineage.</li>237 <li>For a direct copy of an original site, <code>a</code> and <code>A</code> may reference the same nsite.</li>238 <li>For a copy of a copied site, the <code>A</code> tag must be copied unchanged from the parent site.</li>239 <li>Sites that were not copied should not include <code>a</code> or <code>A</code> tags.</li>240 </ul>241242 <figure class="diagram">243 <div class="frame">244 <svg viewBox="0 0 560 190" role="img" aria-label="Copy lineage: each copy points to its immediate parent with an a tag and to the origin site with an A tag">245 <rect class="d-actor" x="10" y="55" width="150" height="60" rx="8"/>246 <text class="d-actor-label" x="85" y="80" text-anchor="middle">Origin (Alice)</text>247 <text class="d-note" x="85" y="98" text-anchor="middle">35128 - d:blog</text>248 <rect class="d-actor" x="205" y="55" width="150" height="60" rx="8"/>249 <text class="d-actor-label" x="280" y="80" text-anchor="middle">Copy #1 (Bob)</text>250 <text class="d-note" x="280" y="98" text-anchor="middle">35128 - d:blog</text>251 <rect class="d-actor" x="400" y="55" width="150" height="60" rx="8"/>252 <text class="d-actor-label" x="475" y="80" text-anchor="middle">Copy #2 (Carol)</text>253 <text class="d-note" x="475" y="98" text-anchor="middle">35128 - d:blog</text>254 <path class="d-msg" d="M203,85 L164,85"/>255 <text class="d-label" x="183" y="77" text-anchor="middle">a</text>256 <path class="d-msg" d="M398,85 L359,85"/>257 <text class="d-label" x="378" y="77" text-anchor="middle">a</text>258 <path class="d-msg blue" d="M280,118 C230,152 160,152 105,120"/>259 <text class="d-label" x="196" y="152" text-anchor="middle">A</text>260 <path class="d-msg blue" d="M470,118 C400,180 170,180 90,122"/>261 <text class="d-label" x="300" y="178" text-anchor="middle">A</text>262 </svg>263 </div>264 <figcaption>Fig. 5A-2 — <b>Copy lineage.</b> The <code>a</code> tag names the immediate parent. The <code>A</code> tag always names the origin site, copied unchanged down the chain.</figcaption>265 </figure>266267 <div class="crt">268 <div class="titlebar">Copied site - Carol copies Bob's copy of Alice's blog</div>269<pre>{270 <span class="k">"kind"</span>: 35128,271 <span class="k">"pubkey"</span>: <span class="s">"7a6815e0…cd4bbbb"</span>, <span class="c">// Carol</span>272 <span class="k">"content"</span>: <span class="s">""</span>,273 <span class="k">"tags"</span>: [274 [<span class="s">"d"</span>, <span class="s">"blog"</span>],275 <span class="c">// immediate parent: Bob's copied site, not the origin</span>276 [<span class="s">"a"</span>, <span class="s">"35128:9f6815e0…cd4000ff:blog"</span>],277 <span class="c">// origin of the lineage: Alice's site, copied unchanged from Bob</span>278 [<span class="s">"A"</span>, <span class="s">"35128:266815e0…cd408a5:blog"</span>],279 [<span class="s">"path"</span>, <span class="s">"/index.html"</span>, <span class="s">"186ea5fd…ca1c99"</span>],280 [<span class="s">"path"</span>, <span class="s">"/post.html"</span>, <span class="s">"c1b2c3d4…123456"</span>],281 [<span class="s">"x"</span>, <span class="c">&lt;site aggregate sha256&gt;</span>, <span class="s">"aggregate"</span>],282 [<span class="s">"title"</span>, <span class="s">"My Derived Blog"</span>]283 ]284}</pre>285 </div>286 <p>A snapshot may also capture a copied site. The snapshot's <code>a</code> tag then points at the copied site being captured, and the copied site's <code>A</code> tag is carried over unchanged, so the origin lineage survives in the snapshot.</p>287288 <h2 class="bar" id="hostserver"><span class="num">8</span> Host Server Behavior</h2>289 <p>A host server is a HTTP server responsible for serving pubkey static websites. This section defines how it maps hostnames to manifests and paths to blobs.</p>290291 <h3>Address formats</h3>292 <p>For interoperability, host servers should use these canonical URL formats. Each site fits in a single DNS label, which avoids wildcard-certificate limitations with multi-level subdomains.</p>293 <div class="tscroll"><table class="ref">294 <caption>Canonical subdomain formats</caption>295 <tr><th>Site type</th><th>Label format</th><th>Example</th></tr>296 <tr><td class="btn">Root site</td><td><code>&lt;npub&gt;.nsite-host.com</code></td><td><code>npub10phxfsms72rhafrklqdyhempujs9h67nye0p67qe424dyvcx0dkqgvap0e.nsite-host.com</code></td></tr>297 <tr><td class="btn">Snapshot</td><td><code>v&lt;snapshotIdB36&gt;.nsite-host.com</code></td><td><code>v&lt;50-char-snapshotIdB36&gt;.nsite-host.com</code></td></tr>298 <tr><td class="btn">Named site</td><td><code>&lt;pubkeyB36&gt;&lt;dTag&gt;.nsite-host.com</code></td><td><code>&lt;50-char-pubkeyB36&gt;&lt;dTag&gt;.nsite-host.com</code></td></tr>299 </table></div>300 <ul>301 <li><code>pubkeyB36</code> is the author's raw 32-byte pubkey in base36: lowercase, digits <code>0-9</code> then letters <code>a-z</code>, no padding. It is always exactly 50 characters.</li>302 <li><code>snapshotIdB36</code> is the raw 32-byte snapshot event id in the same base36 encoding, also always exactly 50 characters.</li>303 <li><code>dTag</code> is the <code>d</code> tag value as plain text, appended directly after <code>pubkeyB36</code> with no separator.</li>304 </ul>305 <p>A host server that uses subdomain routing may serve anything at its own root domain, <code>nsite-host.com</code> — a landing page, for example.</p>306307 <h3>Parsing the label</h3>308 <p>For canonical subdomain formats, the host server must parse the left-most DNS label in this order:</p>309 <ol>310 <li>If the label is a valid <code>npub</code> (<a href="19.html">NIP-19</a>), decode it and resolve the root site manifest (kind <code>15128</code>).</li>311 <li>Otherwise, if the label matches <code>^v[0-9a-z]{50}$</code>, treat it as a snapshot label. The final 50 characters after the leading <code>v</code> are <code>snapshotIdB36</code>. Decode them to the 32-byte event id. Resolve the kind <code>5128</code> snapshot with that id.</li>312 <li>Otherwise, if the label matches <code>^[0-9a-z]{50}[a-z0-9-]{1,13}$</code> and does not end with <code>-</code>, treat it as a named-site label. The first 50 characters are <code>pubkeyB36</code>; decode them to a 32-byte pubkey. The remaining 1-13 characters are the <code>dTag</code>. Resolve the kind <code>35128</code> manifest for that pubkey and identifier.</li>313 <li>If parsing fails, the host server must treat the site as not found.</li>314 </ol>315316 <h3>Resolving paths</h3>317 <p>Once the host server finds the manifest event, it must extract the path-to-hash mappings from the <code>path</code> tags. It should look for a <code>path</code> tag whose second element matches the requested path. When it finds the match, it uses the sha256 hash in the third element to retrieve the file.</p>318 <p><strong>Index pages.</strong> If the request path does not end with a filename, the host server must fall back to the <code>index.html</code> filename. For example, <code>/</code> becomes <code>/index.html</code> and <code>/blog/</code> becomes <code>/blog/index.html</code>.</p>319320 <h3>Retrieving blobs</h3>321 <ul>322 <li>The host server should prioritize the manifest's <code>server</code> tags as hints. If the manifest has <code>server</code> tags, try those Blossom servers first.</li>323 <li>If the pubkey has a kind <code>10063</code> <a href="https://github.com/hzrd149/blossom/blob/master/buds/03.md">BUD-03 user servers</a> event, the host server must attempt to retrieve the file from the listed servers, using the <code>GET /&lt;sha256&gt;</code> path defined in <a href="https://github.com/hzrd149/blossom/blob/master/buds/01.md#get-sha256---get-blob">BUD-01</a>.</li>324 <li>If the pubkey has no <code>10063</code> event and the manifest has no <code>server</code> tags, the host server must respond with status code <code>404</code>.</li>325 </ul>326 <p>The host server should verify that the sha256 hash of any served blob matches the hash in the corresponding <code>path</code> tag. If the hash does not match, the host server should treat the site as not found.</p>327 <p>The host server must forward the <code>Content-Type</code> and <code>Content-Length</code> headers from the Blossom server. If neither is defined, it may set <code>Content-Type</code> from the file extension in the requested path.</p>328329 <h3>Not found</h3>330 <p>If the host server cannot find a site manifest event, or cannot find a matching <code>path</code> tag for the requested path, it must use <code>/404.html</code> as a fallback path.</p>331332 <div class="box warn">333 <span class="tag">Warning!</span>334 <p>Serve only bytes whose sha256 matches the manifest. A Blossom server can return anything; the hash check is the only thing that binds the response to the author's signature.</p>335 </div>336337 <figure class="diagram">338 <div class="frame">339 <svg viewBox="0 0 560 330" role="img" aria-label="Sequence diagram: browser requests a page, host server parses the label, fetches the manifest from a relay and the blob from Blossom, verifies the hash, and responds">340 <line class="d-wire" x1="70" y1="42" x2="70" y2="315" stroke-dasharray="3 4"/>341 <line class="d-wire" x1="230" y1="42" x2="230" y2="315" stroke-dasharray="3 4"/>342 <line class="d-wire" x1="390" y1="42" x2="390" y2="315" stroke-dasharray="3 4"/>343 <line class="d-wire" x1="505" y1="42" x2="505" y2="315" stroke-dasharray="3 4"/>344 <rect class="d-actor" x="25" y="8" width="90" height="30" rx="6"/>345 <text class="d-actor-label" x="70" y="28" text-anchor="middle">Browser</text>346 <rect class="d-actor" x="185" y="8" width="90" height="30" rx="6"/>347 <text class="d-actor-label" x="230" y="28" text-anchor="middle">Host srv</text>348 <rect class="d-actor" x="345" y="8" width="90" height="30" rx="6"/>349 <text class="d-actor-label" x="390" y="28" text-anchor="middle">Relay</text>350 <rect class="d-actor" x="460" y="8" width="90" height="30" rx="6"/>351 <text class="d-actor-label" x="505" y="28" text-anchor="middle">Blossom</text>352 <path class="d-msg" d="M70,64 L225,64"/>353 <text class="d-label" x="148" y="57" text-anchor="middle">GET /blog/</text>354 <text class="d-note" x="230" y="92" text-anchor="middle">parse label → pubkey - path → /blog/index.html</text>355 <path class="d-msg" d="M230,118 L385,118"/>356 <text class="d-label" x="308" y="111" text-anchor="middle">["REQ", {"kinds":[15128]}]</text>357 <path class="d-msg blue" d="M390,150 L235,150"/>358 <text class="d-label" x="312" y="143" text-anchor="middle">["EVENT", manifest]</text>359 <path class="d-msg" d="M230,185 L500,185"/>360 <text class="d-label" x="365" y="178" text-anchor="middle">GET /186ea5fd…ca1c99</text>361 <path class="d-msg blue" d="M505,218 L235,218"/>362 <text class="d-label" x="370" y="211" text-anchor="middle">blob + Content-Type/Length</text>363 <text class="d-note" x="230" y="248" text-anchor="middle">verify sha256(blob) == path hash</text>364 <path class="d-msg blue d-flow" d="M230,278 L75,278"/>365 <text class="d-label" x="152" y="271" text-anchor="middle">200 OK + headers</text>366 <text class="d-note" x="280" y="308" text-anchor="middle">On any failure: fall back to /404.html.</text>367 </svg>368 </div>369 <figcaption>Fig. 5A-3 — <b>One page load.</b> Label to pubkey, pubkey to manifest, path to hash, hash to blob, blob to browser.</figcaption>370 </figure>371372 <h2 class="bar" id="examples"><span class="num">9</span> Worked Example</h2>373 <p>Alice publishes her root site. The manifest below is the complete event she signs and sends to her relays. The three files were uploaded to <code>blossom.example.com</code> first.</p>374375 <div class="crt">376 <div class="titlebar">Root site manifest - kind 15128</div>377<pre>{378 <span class="k">"content"</span>: <span class="s">""</span>,379 <span class="k">"created_at"</span>: 1727373475,380 <span class="k">"id"</span>: <span class="s">"5324d695ed7abf7cdd2a48deb881c93b7f4e43de702989bbfb55a1b97b35a3de"</span>,381 <span class="k">"kind"</span>: 15128,382 <span class="k">"pubkey"</span>: <span class="s">"266815e0c9210dfa324c6cba3573b14bee49da4209a9456f9484e5106cd408a5"</span>,383 <span class="k">"sig"</span>: <span class="s">"f4e4a9e785f70e9fcaa855d769438fea10781e84cd889e3fcb823774f83d094c…"</span>,384 <span class="k">"tags"</span>: [385 [<span class="s">"path"</span>, <span class="s">"/index.html"</span>,386 <span class="s">"186ea5fd14e88fd1ac49351759e7ab906fa94892002b60bf7f5a428f28ca1c99"</span>],387 [<span class="s">"path"</span>, <span class="s">"/about.html"</span>,388 <span class="s">"a1b2c3d4e5f6789012345678901234567890abcdef1234567890abcdef123456"</span>],389 [<span class="s">"path"</span>, <span class="s">"/favicon.ico"</span>,390 <span class="s">"fedcba0987654321fedcba0987654321fedcba0987654321fedcba0987654321"</span>],391 [<span class="s">"x"</span>, <span class="c">&lt;site aggregate sha256&gt;</span>, <span class="s">"aggregate"</span>],392 [<span class="s">"app"</span>, <span class="s">"31990:266815e0…cd408a5:my-app"</span>, <span class="s">"wss://relay.example.com"</span>],393 [<span class="s">"app"</span>, <span class="s">"32267:266815e0…cd408a5:com.example.webapp"</span>, <span class="s">"wss://relay.example.com"</span>],394 [<span class="s">"server"</span>, <span class="s">"https://blossom.example.com"</span>],395 [<span class="s">"title"</span>, <span class="s">"My Nostr Site"</span>],396 [<span class="s">"description"</span>, <span class="s">"A static website hosted on Nostr"</span>],397 [<span class="s">"source"</span>, <span class="s">"https://github.com/example/my-nostr-site"</span>]398 ]399}</pre>400 </div>401402 <p>A visitor opens <code>https://npub10phxfsms72rha….nsite-host.com/about.html</code>. The host server resolves the request like this:</p>403 <div class="crt">404 <div class="titlebar">Host server session - resolving /about.html</div>405<pre><span class="c">-- browser to host server --</span>406<span class="send">→ GET /about.html Host: npub10phxfsms72rha….nsite-host.com</span>407<span class="c">-- label is a valid npub: decode → pubkey 266815e0…cd408a5 --</span>408409<span class="c">-- host server to relay --</span>410<span class="send">→ ["REQ", "site", {"kinds":[15128], "authors":["266815e0…cd408a5"], "limit":1}]</span>411<span class="recv">← ["EVENT", "site", {"kind":15128, "tags":[["path","/about.html","a1b2c3d4…"], …]}]</span>412<span class="recv">← ["EOSE", "site"]</span>413414<span class="c">-- host server to blossom (server tag hint tried first) --</span>415<span class="send">→ GET https://blossom.example.com/a1b2c3d4e5f6…ef123456</span>416<span class="recv">← 200 OK Content-Type: text/html Content-Length: 1832</span>417<span class="c">-- verify sha256(body) == a1b2c3d4e5f6…ef123456 : match --</span>418419<span class="c">-- host server to browser --</span>420<span class="recv">← 200 OK Content-Type: text/html Content-Length: 1832 (forwarded)</span>421422<span class="c">-- a request for /missing.png finds no path tag --</span>423<span class="send">→ GET /missing.png</span>424<span class="recv">← 200 OK (body of /404.html, the mandatory fallback path)</span></pre>425 </div>426427 <h2 class="bar" id="history"><span class="num">10</span> Version History</h2>428 <p>NIP-5A is young and moves fast. Notable changes, newest first:</p>429 <ul class="patchnotes">430 <li><span class="ver">2026</span><span class="when">2026-06</span>Added the aggregate hash, the <code>x</code> tag, and manifest snapshots (kind <code>5128</code>).</li>431 <li class="minor"><span class="ver">2026</span><span class="when">2026-06</span>Typo fixes and code-fence formatting cleanups.</li>432 <li><span class="ver">2026</span><span class="when">2026-04</span>Renamed the source file to <code>5A.md</code> for clarity.</li>433 <li><span class="ver">Origin</span><span class="when">2026-03</span>hzrd149 published NIP-5A: root sites, named sites, copying, and the host server rules.</li>434 </ul>435436 <div class="credits">437 <h3 class="cardhead">Original Proposal - Credits</h3>438 <table>439 <tr><td>Author</td><td>hzrd149</td></tr>440 <tr><td>First commit</td><td>2026-03-25 — “NIP-5A: Static Websites (#1538)” - <a href="https://github.com/nostr-protocol/nips/pull/1538">PR #1538</a></td></tr>441 <tr><td>Status</td><td>draft - optional</td></tr>442 <tr><td>Source</td><td><a href="https://github.com/nostr-protocol/nips/blob/master/5A.md">5A.md on GitHub</a> - <a href="https://github.com/nostr-protocol/nips/commits/master/5A.md">full change history</a></td></tr>443 </table>444 </div>445446 <h2 class="bar"><span class="num">11</span> See Also</h2>447 <ul>448 <li><a href="01.html">NIP-01</a> — regular, replaceable, and addressable events; the storage classes the three manifest kinds use.</li>449 <li><a href="19.html">NIP-19</a> — the <code>npub</code> encoding parsed from root-site subdomain labels.</li>450 <li><a href="34.html">NIP-34</a> — <code>nostr://</code> git URLs accepted in the <code>source</code> tag.</li>451 <li><a href="89.html">NIP-89</a> — app handler events, one target of the <code>app</code> descriptor tag.</li>452 <li><a href="https://github.com/hzrd149/blossom">Blossom (BUD-01, BUD-03)</a> — blob storage, <code>GET /&lt;sha256&gt;</code>, and the kind <code>10063</code> user server list.</li>453 </ul>454 </div>455456 <aside>457 <div class="cartridge">458 <div class="inner">459 <h3 class="cardhead">Spec Card</h3>460 <div class="lights"><span class="led on"></span><span class="led on"></span><span class="led on"></span></div>461 <dl>462 <dt>Status</dt><dd><span class="chip draft">draft</span><span class="chip">optional</span></dd>463 <dt>Defines</dt><dd>site manifests - snapshots - aggregate hash - host server rules</dd>464 <dt>Kinds</dt><dd>15128 (root) - 35128 (named) - 5128 (snapshot)</dd>465 <dt>Tags</dt><dd>path - x - d - a - A - server - title - description - source - app</dd>466 <dt>Requires</dt><dd><a href="01.html">NIP-01</a> - <a href="19.html">NIP-19</a> - Blossom BUD-01/03</dd>467 <dt>Transport</dt><dd>WebSocket (manifests) - HTTP (files)</dd>468 <dt>Crypto</dt><dd>sha256 per file - sha256 aggregate</dd>469 <dt>First commit</dt><dd>2026-03-25</dd>470 <dt>Last updated</dt><dd>2026-06-16</dd>471 </dl>472 </div>473 </div>474 </aside>475 </main>476477 <nav class="navbtns">478 <a href="59.html">◀ NIP-59: Gift Wrap</a>479 <a href="60.html">NIP-60: Cashu Wallets ▶</a>480 </nav>481482 <footer class="pagefoot">483 <a href="../index.html">Nostr Protocol Manual</a>484 <span class="pageno">NIP 5A / F4</span>485 </footer>486</div>487488<script src="../assets/webmcp.js" defer></script>489</body>490</html>