clone

nostr relays on demand at <name>.bind.ws: claim one with your key, pay in zaps past the free allowance

relay-templates: Marmot members admits accountable publishers with public encrypted reads, alongside the open transport preset

Dami committed 05788ff
commit
05788ff3b9677949578c0ec39971aa7c2a85dbea
parent
3e88db02da1f060d25a747a6493533429c3b0249
author
Dami <dami.etoile@gmail.com>
10 files changed, +92 -12
MREADME.md2+-
Mdocs/01-relay-configuration.md2+-
Mdocs/04-data-and-names.md1+
Mdocs/21-marmot-transport.md19+++++++++++++++---
Mrelay-templates/12-marmot.jsonc5++++-
Arelay-templates/14-marmot-members.jsonc24+++++++++++++++++++++++
Mrelay-templates/README.md3++-
Msrc/gen/templates.ts31+++++++++++++++++++++++++++++-
Mtest/object/marmot.test.ts15++++++++++++---
Mtest/object/presets.test.ts2+-
M README.md +1 -1
@@ -21,7 +21,7 @@ That answers with a relay at a memorable name, open to anyone for 14 days. Claim
2121 - **Encrypted group transport.** Opt-in Marmot KeyPackages and opaque MLS group messages, with account admission and storage caps for ephemeral authors. Clients manage encryption and group membership.2222 - **A Git host.** Opt-in GRASP-01 Smart HTTP, backed by [ntig](https://github.com/FelineStateMachine/ntig). Signed NIP-34 state authorizes branch and tag changes; events wait for their Git objects before publication. Hosting has [explicit storage and transaction limits](docs/22-grasp-01-git-hosting.md#git-http).2323 - **A file host.** Blossom and NIP-96 on the same bucket, with mirroring, pre-flight checks and file reports.24-- **A console.** Nine tabs on the relay's own page, signed by a browser extension or a remote signer app on a phone. Presets set up an outbox, an inbox, a private relay, a chat, a search replica, a media host or a quiet relay with every costly feature off, in one click. The template collection also includes static sites, Marmot transport and Git repositories.24+- **A console.** Nine tabs on the relay's own page, signed by a browser extension or a remote signer app on a phone. Presets set up an outbox, an inbox, a private relay, a chat, a search replica, a media host or a quiet relay with every costly feature off, in one click. The template collection also includes static sites, public or member-published Marmot transport and Git repositories.2525 - **A file.** Everything the console sets, as one document with a schema: check it offline, see what applying would change, apply it, pull it back. Keep your relay in your repository. The presets are the same files, in `relay-templates/`.2626 - **Jobs.** Pull another relay in, keep a standing mirror, fetch your own history from your relay list, rebroadcast to other relays, and dump everything to a file on a schedule.2727 - **Your own domain.** `wss://relay.example.com` onto your relay with one CNAME, or a custom hostname targeting one of your static sites.
M docs/01-relay-configuration.md +1 -1
@@ -69,7 +69,7 @@ The per-address limit is what stops a client from opening many sockets to multip
6969 7070 **Features** are the doors and the costs an owner may do without. Search indexes the prose kinds by default (notes, threads, comments, highlights, articles, wiki pages); *full* indexes every public kind with content, and *off* refuses search filters. A change applies to events from then on; the index is not rebuilt. Sync (NIP-77) reads the whole matching set per reconciliation, counts (NIP-45) keep sketches, the discovery record (NIP-66) is what crawlers find, names (NIP-05) publish members under the domain, files are the Blossom and NIP-96 doors, pages are notes and articles as web pages with the feed, and signer traffic is NIP-46 carried for anyone. What is off leaves `supported_nips`, answers 404 at its door, and is refused at the socket with an `unsupported:` reason. The bridge, groups, the roster, reports and management are not features: the console runs on them.7171 72-Three features add bounded services beside the relay feed. **Sites** is on by default and **Mirror site files** is on by default inside it: members can publish NIP-5A manifests, and the relay can verify and retain their files or fetch a verified cache miss from the manifest's sources. A site hostname is isolated from the relay's event and management doors. The [NIP-5A static websites](20-nip-5a-static-websites.md) guide describes the origins and custom-domain choices. **Marmot** is off by default and carries signed KeyPackages and opaque MLS group envelopes, with the ordinary write, read, ban, rate and fuel rules still applied. **GRASP Git hosting** is off by default, needs open reads, and admits repository announcements, state and related NIP-34 events before exposing Git Smart HTTP; the [GRASP-01 Git hosting](22-grasp-01-git-hosting.md) guide lists its bounded repository and object rules. The `marmot` and `grasp` templates select these features for you.72+Three features add bounded services beside the relay feed. **Sites** is on by default and **Mirror site files** is on by default inside it: members can publish NIP-5A manifests, and the relay can verify and retain their files or fetch a verified cache miss from the manifest's sources. A site hostname is isolated from the relay's event and management doors. The [NIP-5A static websites](20-nip-5a-static-websites.md) guide describes the origins and custom-domain choices. **Marmot** is off by default and carries signed KeyPackages and opaque MLS group envelopes, with the ordinary write, read, ban, rate and fuel rules still applied. **GRASP Git hosting** is off by default, needs open reads, and admits repository announcements, state and related NIP-34 events before exposing Git Smart HTTP; the [GRASP-01 Git hosting](22-grasp-01-git-hosting.md) guide lists its bounded repository and object rules. The `marmot`, `marmot-members` and `grasp` templates select these features for you. **Marmot** accepts public writes; **Marmot members** admits member publishers and requires account authentication for group envelopes. Both keep encrypted messages publicly readable, with recipient checks on gift wraps.7373 7474 ### Presets7575
M docs/04-data-and-names.md +1 -0
M docs/21-marmot-transport.md +16 -3
@@ -13,11 +13,24 @@ encrypted group message and routes it with one `h` tag containing the random
1313 32-byte group id. The relay checks the signed envelope and leaves MLS1414 verification to clients.1515 16-The transport is off on a fresh relay. An owner enables it with the Marmot17-template or `setpolicy` with `{"features":{"marmot":true}}`. The template18-also allows kinds `10002`, `10050`, `1059`, `30443` and `445`, which are the16+The transport is off on a fresh relay. You enable it with **Marmot**,17+**Marmot members** or `setpolicy` with `{"features":{"marmot":true}}`.18+Both templates allow kinds `10002`, `10050`, `1059`, `30443` and `445`, which are the1919 account relay list, inbox list, gift wrap, KeyPackage and group message paths.2020 21+Choose **Marmot** for public opaque transport: anyone can publish, including22+group envelopes without account authentication. Choose **Marmot members**23+when you admit the publishers. Add their account npubs on the People tab;24+their clients authenticate with NIP-42 before publishing group envelopes.25+Both templates keep encrypted messages publicly readable and retain the26+recipient checks on gift wraps. Relay membership controls who writes; your27+clients control who belongs to an MLS group and holds its decryption keys.28+29+**Marmot members** charges each envelope against its authenticated account's30+member limits. Both templates use the relay owner's existing fuel allowance31+and balance for storage and metered work; neither sets a new price or cap.32+Other feature settings stay as they are.33+2134 Kind `445` uses a fresh ephemeral author, so that author is not a membership2235 identity. An open relay may accept it anonymously. A relay with an allowlist,2336 web of trust or owner-only write rule requires the socket to authenticate an
M relay-templates/12-marmot.jsonc +4 -1
A relay-templates/14-marmot-members.jsonc +24 -0
M relay-templates/README.md +2 -1
M src/gen/templates.ts +30 -1
M test/object/marmot.test.ts +12 -3
@@ -45,19 +45,28 @@ describe("marmot", () => {
4545 await new Promise((resolve) => setTimeout(resolve, 100));4646 });4747 48- it("requires an authenticated admitted account for group envelopes on limited relays", async () => {48+ it("the members template requires admitted account authentication while encrypted messages stay publicly readable", async () => {4949 const host = "marmot-auth.bind.ws";5050 const owner = generateSecretKey();5151 const member = generateSecretKey();5252 const ephemeral = generateSecretKey();5353 await rpc(host, owner, "claim");5454 await rpc(host, owner, "setmember", pk(member));55- await rpc(host, owner, "setpolicy", { features: { marmot: true }, writes: "allowlist" });55+ await rpc(host, owner, "applypreset", "marmot-members");5656 const c = await WS.connect(host);5757 const group = () => ev(ephemeral, KIND_MARMOT_GROUP, GROUP_CONTENT, [["h", "cd".repeat(32)]]);5858 expect((await c.ok(group())).msg).toMatch(/^auth-required:/);5959 await c.auth(member, host);6060 expect((await c.ok(group())).ok).toBe(true);61+ const stranger = await WS.connect(host);62+ await stranger.auth(generateSecretKey(), host);63+ const denied = ev(generateSecretKey(), KIND_MARMOT_GROUP, GROUP_CONTENT, [["h", "ee".repeat(32)]]);64+ expect((await stranger.ok(denied)).msg).toMatch(/^auth-required:/);65+ stranger.ws.close();66+ const reader = await WS.connect(host);67+ const messages = await reader.query({ kinds: [KIND_MARMOT_GROUP], "#h": ["cd".repeat(32)] });68+ expect(messages.events.map((e) => e.pubkey)).toEqual([pk(ephemeral)]);69+ reader.ws.close();6170 const reused = ev(ephemeral, KIND_MARMOT_GROUP, GROUP_CONTENT, [["h", "de".repeat(32)]]);6271 expect((await c.ok(reused)).msg).toMatch(/fresh ephemeral author|already used/);6372 c.ws.close();
@@ -70,7 +79,7 @@ describe("marmot", () => {
7079 const member = generateSecretKey();7180 await rpc(host, owner, "claim");7281 await rpc(host, owner, "setmember", pk(member), { maxBytes: 700 });73- await rpc(host, owner, "setpolicy", { features: { marmot: true }, writes: "allowlist" });82+ await rpc(host, owner, "applypreset", "marmot-members");7483 const c = await WS.connect(host);7584 await c.auth(member, host);7685 const first = ev(generateSecretKey(), KIND_MARMOT_GROUP, GROUP_CONTENT, [["h", "ef".repeat(32)]]);
M test/object/presets.test.ts +1 -1