diff options
| -rw-r--r-- | 5A.md | 163 |
1 files changed, 163 insertions, 0 deletions
| @@ -0,0 +1,163 @@ | |||
| 1 | NIP-5A | ||
| 2 | ====== | ||
| 3 | |||
| 4 | Pubkey Static Websites | ||
| 5 | ---------------------- | ||
| 6 | |||
| 7 | `draft` `optional` | ||
| 8 | |||
| 9 | This nip describes a method by which static websites can be hosted under public keys using specialized host servers | ||
| 10 | |||
| 11 | ### Site Manifest Definition | ||
| 12 | |||
| 13 | A site manifest event MUST be a replaceable or an addressable event as defined in [NIP-01](01.md). There are two types of site manifest event kinds: | ||
| 14 | |||
| 15 | - **Root site**: Uses kind `15128` and MUST NOT include a `d` tag. This is a single replaceable event per pubkey and serves as the root site for the pubkey. | ||
| 16 | - **Named sites**: Uses kind `35128` and MUST have a `d` tag containing the site identifier. These can be smaller websites under a pubkey and can be throught of as sub-domains. | ||
| 17 | |||
| 18 | The event MUST include one or more `path` tags that map absolute paths to sha256 hashes. Each `path` tag MUST have the format `["path", "/absolute/path", "sha256hash"]` where: | ||
| 19 | - The first element is the literal string `"path"` | ||
| 20 | - The second element is an absolute path ending with a filename and extension | ||
| 21 | - The third element is the sha256 hash of the file that will be served under this path | ||
| 22 | |||
| 23 | The event MAY include `server` tags that hint at which blossom servers can be used to find the blobs associated with the hashes. | ||
| 24 | |||
| 25 | The event MAY include `title` and `description` tags that provide simple site information. | ||
| 26 | |||
| 27 | The event MAY include a `source` tag that links to the site's source code repository or source archive. The `source` tag MUST have the format `["source", "<url>"]`, where `<url>` is an absolute `http` or `https` URL. | ||
| 28 | |||
| 29 | The site icon SHOULD be provided by setting the `/favicon.ico` path in the manifest. | ||
| 30 | |||
| 31 | For example, a root site manifest: | ||
| 32 | |||
| 33 | ```jsonc | ||
| 34 | { | ||
| 35 | "content": "", | ||
| 36 | "created_at": 1727373475, | ||
| 37 | "id": "5324d695ed7abf7cdd2a48deb881c93b7f4e43de702989bbfb55a1b97b35a3de", | ||
| 38 | "kind": 15128, | ||
| 39 | "pubkey": "266815e0c9210dfa324c6cba3573b14bee49da4209a9456f9484e5106cd408a5", | ||
| 40 | "sig": "f4e4a9e785f70e9fcaa855d769438fea10781e84cd889e3fcb823774f83d094cf2c05d5a3ac4aebc1227a4ebc3d56867286c15a6df92d55045658bb428fd5fb5", | ||
| 41 | "tags": [ | ||
| 42 | // path mappings: absolute path -> sha256 hash | ||
| 43 | ["path", "/index.html", "186ea5fd14e88fd1ac49351759e7ab906fa94892002b60bf7f5a428f28ca1c99"], | ||
| 44 | ["path", "/about.html", "a1b2c3d4e5f6789012345678901234567890abcdef1234567890abcdef123456"], | ||
| 45 | ["path", "/favicon.ico", "fedcba0987654321fedcba0987654321fedcba0987654321fedcba0987654321"], | ||
| 46 | // optional: blossom server hints | ||
| 47 | ["server", "https://blossom.example.com"], | ||
| 48 | // optional: site metadata | ||
| 49 | ["title", "My Nostr Site"], | ||
| 50 | ["description", "A static website hosted on Nostr"], | ||
| 51 | // optional: source code location | ||
| 52 | ["source", "https://github.com/example/my-nostr-site"] | ||
| 53 | ] | ||
| 54 | } | ||
| 55 | ``` | ||
| 56 | |||
| 57 | And a named site manifest: | ||
| 58 | |||
| 59 | ```jsonc | ||
| 60 | { | ||
| 61 | "content": "", | ||
| 62 | "created_at": 1727373475, | ||
| 63 | "id": "a1b2c3d4e5f6789012345678901234567890abcdef1234567890abcdef123456", | ||
| 64 | "kind": 35128, | ||
| 65 | "pubkey": "266815e0c9210dfa324c6cba3573b14bee49da4209a9456f9484e5106cd408a5", | ||
| 66 | "sig": "f4e4a9e785f70e9fcaa855d769438fea10781e84cd889e3fcb823774f83d094cf2c05d5a3ac4aebc1227a4ebc3d56867286c15a6df92d55045658bb428fd5fb5", | ||
| 67 | "tags": [ | ||
| 68 | // site identifier | ||
| 69 | ["d", "blog"], | ||
| 70 | // path mappings: absolute path -> sha256 hash | ||
| 71 | ["path", "/index.html", "186ea5fd14e88fd1ac49351759e7ab906fa94892002b60bf7f5a428f28ca1c99"], | ||
| 72 | ["path", "/post.html", "a1b2c3d4e5f6789012345678901234567890abcdef1234567890abcdef123456"], | ||
| 73 | // optional: blossom server hints | ||
| 74 | ["server", "https://blossom.example.com"], | ||
| 75 | // optional: site metadata | ||
| 76 | ["title", "My Blog"], | ||
| 77 | ["description", "A blog hosted on Nostr"], | ||
| 78 | // optional: source code location | ||
| 79 | ["source", "https://github.com/example/my-nostr-blog"] | ||
| 80 | ] | ||
| 81 | } | ||
| 82 | ``` | ||
| 83 | |||
| 84 | ### Host server implementation | ||
| 85 | |||
| 86 | A host server is a HTTP server that is responsible for serving pubkey static websites | ||
| 87 | |||
| 88 | #### Resolving Pubkeys | ||
| 89 | |||
| 90 | For interoperability, host servers SHOULD use the following canonical URL formats: | ||
| 91 | |||
| 92 | - Root site: `<npub>.nsite-host.com` | ||
| 93 | - Named site: `<pubkeyB36><dTag>.nsite-host.com` | ||
| 94 | |||
| 95 | `pubkeyB36` is the author's raw 32-byte pubkey encoded with base36 (lowercase, digits `0-9` then letters `a-z`, no padding) and is always exactly 50 characters. | ||
| 96 | |||
| 97 | `dTag` is the site identifier (`d` tag value) as plain text. It is appended directly after `pubkeyB36` with no separator. | ||
| 98 | |||
| 99 | For canonical named-site URLs, `dTag` MUST match `^[a-z0-9-]{1,13}$` and MUST NOT end with `-`. | ||
| 100 | |||
| 101 | Because DNS labels are limited to 63 characters and `pubkeyB36` uses 50 of them, `dTag` MUST be 1-13 characters. | ||
| 102 | |||
| 103 | This single-label format avoids wildcard certificate limitations with multi-level subdomains. | ||
| 104 | |||
| 105 | If the host server is using subdomain routing it MAY serve anything at its own root domain `nsite-host.com` (a landing page for example). | ||
| 106 | |||
| 107 | Example subdomains: | ||
| 108 | - Root site: `npub10phxfsms72rhafrklqdyhempujs9h67nye0p67qe424dyvcx0dkqgvap0e.nsite-host.com` | ||
| 109 | - Named site: `<50-char-pubkeyB36><dTag>.nsite-host.com` | ||
| 110 | |||
| 111 | #### Resolving Paths | ||
| 112 | |||
| 113 | When the host server receives a request and is able to determine the pubkey and identifier, it should fetch the users `10002` [NIP-65](https://github.com/nostr-protocol/nips/blob/master/65.md) relay list and lookup the site manifest event for the pubkey and identifier. | ||
| 114 | |||
| 115 | For canonical subdomain formats, the host server MUST parse the left-most DNS label as follows: | ||
| 116 | |||
| 117 | 1. If the label is a valid `npub`, decode it and query for the root site manifest. | ||
| 118 | 2. Otherwise, if the label matches `^[0-9a-z]{50}[a-z0-9-]{1,13}$` and does not end with `-`, treat it as a named-site label where: | ||
| 119 | - `pubkeyB36` is the first 50 characters | ||
| 120 | - `dTag` is the remaining 1-13 characters | ||
| 121 | - decode `pubkeyB36` to a 32-byte pubkey | ||
| 122 | - use `dTag` as the identifier (`d` tag value) | ||
| 123 | |||
| 124 | If parsing fails, the host server MUST treat the site as not found. | ||
| 125 | |||
| 126 | The host server should query for the site manifest event: | ||
| 127 | |||
| 128 | ```jsonc | ||
| 129 | // For root site (kind 15128, no d tag) | ||
| 130 | { "kinds": [15128], "authors": [<pubkey>] } | ||
| 131 | |||
| 132 | // For named site (kind 35128, with d tag) | ||
| 133 | { "kinds": [35128], "authors": [<pubkey>], "#d": [<identifier>] } | ||
| 134 | ``` | ||
| 135 | |||
| 136 | Once the site manifest event is found, the host server MUST extract the path-to-hash mappings from the `path` tags in the manifest. The host server should look for a `path` tag where the second element matches the requested path. | ||
| 137 | |||
| 138 | If the request path does not end with a filename the host server MUST fallback to using the `index.html` filename | ||
| 139 | |||
| 140 | For example: `/` -> `/index.html` or `/blog/` -> `/blog/index.html` | ||
| 141 | |||
| 142 | #### Resolving Files | ||
| 143 | |||
| 144 | Once the host server has found the site manifest event and located the matching `path` tag for the requested path, it should use the sha256 hash defined in the third element of the `path` tag to retrieve the file. | ||
| 145 | |||
| 146 | The host server SHOULD prioritize using `server` tags from the site manifest event as hints for which blossom servers to query. If the manifest includes `server` tags, the host server SHOULD attempt to retrieve the file from those servers first. | ||
| 147 | |||
| 148 | If the pubkey has a `10063` [BUD-03 user servers](https://github.com/hzrd149/blossom/blob/master/buds/03.md) event the server MUST attempt to retrieve the file from the listed servers using the path defined in [BUD-01](https://github.com/hzrd149/blossom/blob/master/buds/01.md#get-sha256---get-blob) | ||
| 149 | If a pubkey does not have a `10063` event and no `server` tags are found in the manifest, the host server MUST respond with a status code 404 | ||
| 150 | |||
| 151 | The host server MUST forward the `Content-Type`, and `Content-Length` headers from the Blossom server. If none are defined the host server MAY set `Content-Type` from the file extension in the requested path | ||
| 152 | |||
| 153 | #### Handling Not Found | ||
| 154 | |||
| 155 | If a host server is unable to find a site manifest event or a matching `path` tag for the requested path, it MUST use `/404.html` as a fallback path | ||
| 156 | |||
| 157 | ### Legacy Support | ||
| 158 | |||
| 159 | Kind `34128` is marked as legacy/deprecated. This kind was used for individual static file events where each file was represented by a separate event with a `d` tag for the path and an `x` tag for the sha256 hash. | ||
| 160 | |||
| 161 | Host servers MAY still support kind `34128` for backward compatibility with existing sites, but new sites SHOULD use kind `15128` (root site manifest) or kind `35128` (named site manifest) instead. | ||
| 162 | |||
| 163 | Read the [legacy version](https://github.com/hzrd149/nips/blob/41e77b45a1e8a8d170097e363f7d7254797cc5c5/nsite.md) for more details. | ||