clone

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

dependabot/github_actions/actions-9b61906d8b
bindws / test / object / marmot.test.ts
marmot.test.ts 4.7K
1import { describe, expect, it } from "vitest";2import { generateSecretKey } from "nostr-tools/pure";3import { KIND_MARMOT_GROUP, KIND_MARMOT_KEY_PACKAGE } from "../../src/kinds.ts";4import { marmotShape } from "../../src/marmot.ts";5import { ev, info, pk, rpc } from "../helpers/relay.ts";6import { WS } from "../helpers/ws.ts";78const tags = [9 ["d", "00".repeat(32)],10 ["mls_protocol_version", "1.0"],11 ["i", "ab".repeat(16)],12 ["mls_ciphersuite", "0x0001"],13 ["mls_extensions", "0x0001"],14 ["mls_proposals", "0x0001"],15 ["app_components", "0x8009"],16];17const GROUP_CONTENT = "A".repeat(40); // 30 decoded bytes: nonce plus AEAD tag minimum.1819describe("marmot", () => {20 it("checks the exact KeyPackage and group envelope shapes", () => {21 const sk = generateSecretKey();22 expect(marmotShape(ev(sk, KIND_MARMOT_KEY_PACKAGE, "AQ==", tags))).toBe("");23 expect(marmotShape(ev(sk, KIND_MARMOT_KEY_PACKAGE, "AQ==", [...tags, ["d", "11".repeat(32)]]))).toMatch(/^invalid:/);24 expect(marmotShape(ev(sk, KIND_MARMOT_GROUP, GROUP_CONTENT, [["h", "ab".repeat(32)]]))).toBe("");25 expect(marmotShape(ev(sk, KIND_MARMOT_GROUP, "AQ==", [["h", "ab".repeat(32)]]))).toMatch(/^invalid:/);26 expect(marmotShape(ev(sk, KIND_MARMOT_GROUP, GROUP_CONTENT, [["h", "AB".repeat(32)]]))).toMatch(/^invalid:/);27 expect(marmotShape(ev(sk, KIND_MARMOT_GROUP, GROUP_CONTENT, [["h", "ab".repeat(32)], ["x", "no"]]))).toMatch(/^invalid:/);28 });2930 it("keeps Marmot off until the owner enables it and preserves NIP-29 h checks", async () => {31 const host = "marmot-off.bind.ws";32 const owner = generateSecretKey();33 const stranger = generateSecretKey();34 await rpc(host, owner, "claim");35 const c = await WS.connect(host);36 const group = ev(stranger, KIND_MARMOT_GROUP, GROUP_CONTENT, [["h", "ab".repeat(32)]]);37 expect((await c.ok(group)).msg).toMatch(/^restricted:/);38 expect((await c.query({ kinds: [KIND_MARMOT_GROUP] })).closed).toMatch(/^unsupported:/);39 await rpc(host, owner, "setpolicy", { features: { marmot: true } });40 expect((await c.ok(group)).ok).toBe(true);41 const foreign = ev(stranger, 1, "hello", [["h", "other"]]);42 expect((await c.ok(foreign)).msg).toMatch(/^blocked: this relay hosts/);43 expect((await info(host)).supported_nips).toContain(1);44 c.ws.close();45 await new Promise((resolve) => setTimeout(resolve, 100));46 });4748 it("the members template requires admitted account authentication while encrypted messages stay publicly readable", async () => {49 const host = "marmot-auth.bind.ws";50 const owner = generateSecretKey();51 const member = generateSecretKey();52 const ephemeral = generateSecretKey();53 await rpc(host, owner, "claim");54 await rpc(host, owner, "setmember", pk(member));55 await rpc(host, owner, "applypreset", "marmot-members");56 const c = await WS.connect(host);57 const group = () => ev(ephemeral, KIND_MARMOT_GROUP, GROUP_CONTENT, [["h", "cd".repeat(32)]]);58 expect((await c.ok(group())).msg).toMatch(/^auth-required:/);59 await c.auth(member, host);60 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();70 const reused = ev(ephemeral, KIND_MARMOT_GROUP, GROUP_CONTENT, [["h", "de".repeat(32)]]);71 expect((await c.ok(reused)).msg).toMatch(/fresh ephemeral author|already used/);72 c.ws.close();73 await new Promise((resolve) => setTimeout(resolve, 100));74 });7576 it("counts opaque group bytes against the authenticated account cap", async () => {77 const host = "marmot-cap.bind.ws";78 const owner = generateSecretKey();79 const member = generateSecretKey();80 await rpc(host, owner, "claim");81 await rpc(host, owner, "setmember", pk(member), { maxBytes: 700 });82 await rpc(host, owner, "applypreset", "marmot-members");83 const c = await WS.connect(host);84 await c.auth(member, host);85 const first = ev(generateSecretKey(), KIND_MARMOT_GROUP, GROUP_CONTENT, [["h", "ef".repeat(32)]]);86 expect((await c.ok(first)).ok).toBe(true);87 const second = ev(generateSecretKey(), KIND_MARMOT_GROUP, GROUP_CONTENT, [["h", "01".repeat(32)]]);88 expect((await c.ok(second)).msg).toMatch(/^restricted: you have reached your storage cap/);89 c.ws.close();90 await new Promise((resolve) => setTimeout(resolve, 100));91 });92});