clone

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

test: the conformance suite is one file per NIP, with the tracked connect in the helpers

Dami committed 9f31ca0
commit
9f31ca0021600323745bda45e5c5cf709e092296
parent
d8ac77518ee0d4946ed1c304d7656f4a2ac8fb82
author
Dami <dami.etoile@gmail.com>
14 files changed, +309 -286
Rtest/conformance/media.test.ts → test/conformance/blossom.test.ts4+-
Mtest/conformance/helpers.ts13++
Mtest/conformance/nip01.test.ts41+------
Atest/conformance/nip09.test.ts33++++++
Atest/conformance/nip11.test.ts15+++
Atest/conformance/nip17.test.ts47++++++++
Atest/conformance/nip40.test.ts21++++
Mtest/conformance/nip42.test.ts70+-----------
Atest/conformance/nip45.test.ts41+++++++
Atest/conformance/nip50.test.ts27+++++
Atest/conformance/nip62.test.ts39+++++++
Atest/conformance/nip70.test.ts19+++
Atest/conformance/nip77.test.ts47++++++++
Dtest/conformance/nips.test.ts178------------------------------
R test/conformance/media.test.ts → test/conformance/blossom.test.ts +1 -3
M test/conformance/helpers.ts +13 -0
M test/conformance/nip01.test.ts +3 -38
@@ -1,13 +1,7 @@
1-import { describe, it, expect, afterEach } from "vitest";2-import { Client, newEvent, newKey, pub, now, rand } from "./helpers.ts";1+import { describe, it, expect } from "vitest";2+import { newEvent, newKey, pub, now, rand, sockets } from "./helpers.ts";33 4-const open: Client[] = [];5-const connect = async () => {6- const c = await Client.connect();7- open.push(c);8- return c;9-};10-afterEach(() => open.splice(0).forEach((c) => c.close()));4+const connect = sockets();115 126 describe("NIP-01 core", () => {137 it("publishes, pushes live, dedups, rejects tampering, queries, replaces, limits, closes", async () => {
@@ -99,32 +93,3 @@ describe("NIP-01 core", () => {
9993 await c.expect("EOSE");10094 });10195 });102-103-describe("NIP-09 deletion", () => {104- it("lets only the author delete, keeps deleted ids out, ignores strangers", async () => {105- const sk = newKey();106- const other = newKey();107- const alice = await connect();108- const stranger = await connect();109- const carol = await connect();110- const note = newEvent(sk, 1, "regret", [], now() - 100);111- await alice.publish(note);112-113- await stranger.publish(newEvent(other, 5, "", [["e", note.id]]));114- expect((await carol.req({ ids: [note.id] })).events.length).toBe(1);115-116- await alice.publish(newEvent(sk, 5, "", [["e", note.id], ["k", "1"]]));117- expect((await carol.req({ ids: [note.id] })).events.length).toBe(0);118- alice.send("EVENT", note);119- expect(await alice.expectOK(note.id, false)).toMatch(/^blocked:/);120- });121-122- it("deletes addressable versions up to the request time via a tags", async () => {123- const sk = newKey();124- const c = await connect();125- const t0 = now() - 100;126- await c.publish(newEvent(sk, 30023, "v1", [["d", "x"]], t0));127- await c.publish(newEvent(sk, 5, "", [["a", `30023:${pub(sk)}:x`]], t0 + 1));128- expect((await c.req({ kinds: [30023], authors: [pub(sk)], "#d": ["x"] })).events.length).toBe(0);129- });130-});
A test/conformance/nip09.test.ts +33 -0
A test/conformance/nip11.test.ts +15 -0
A test/conformance/nip17.test.ts +47 -0
@@ -0,0 +1,47 @@
1+import { describe, it, expect } from "vitest";2+import { newEvent, newKey, pub, now, sockets } from "./helpers.ts";3+4+const connect = sockets();5+6+describe("NIP-17 / NIP-59 private kinds", () => {7+ it("serves gift wraps only to their recipients, stored and live, and lets recipients delete them", async () => {8+ const sender = newKey();9+ const recipient = newKey();10+ const stranger = newKey();11+ const s = await connect();12+ const wrap = newEvent(sender, 1059, "ciphertext", [["p", pub(recipient)]], now() - 100);13+ await s.publish(wrap);14+15+ const x = await connect();16+ x.send("REQ", "dm", { kinds: [1059], "#p": [pub(recipient)] });17+ await x.expectClosed("auth-required:");18+ let r = await x.req({ "#p": [pub(recipient)] });19+ expect(r.events).toEqual([]);20+ expect(r.hints).toContain("auth");21+ x.send("COUNT", "n", { "#p": [pub(recipient)] });22+ expect((await x.expect("COUNT"))[2].count).toBe(0);23+24+ await x.auth(stranger);25+ r = await x.req({ kinds: [1059], "#p": [pub(recipient)] });26+ expect(r.events).toEqual([]);27+ x.send("REQ", "live", { kinds: [1059], "#p": [pub(recipient)] });28+ await x.expect("EOSE");29+ await s.publish(newEvent(sender, 1059, "ciphertext2", [["p", pub(recipient)]], now() - 99));30+ await x.expectNothing();31+32+ const rc = await connect();33+ await rc.auth(recipient);34+ r = await rc.req({ kinds: [1059], "#p": [pub(recipient)] });35+ expect(r.events.length).toBe(2);36+ rc.send("REQ", "live", { kinds: [1059], "#p": [pub(recipient)] });37+ await rc.drain();38+ const wrap3 = newEvent(sender, 1059, "ciphertext3", [["p", pub(recipient)]], now() - 98);39+ await s.publish(wrap3);40+ expect((await rc.expect("EVENT"))[2].id).toBe(wrap3.id);41+42+ await rc.publish(newEvent(recipient, 5, "", [["e", wrap3.id]]));43+ expect((await rc.req({ ids: [wrap3.id] })).events).toEqual([]);44+ s.send("EVENT", wrap3);45+ expect(await s.expectOK(wrap3.id, false)).toMatch(/^blocked:/);46+ });47+});
A test/conformance/nip40.test.ts +21 -0
M test/conformance/nip42.test.ts +3 -67
@@ -1,13 +1,7 @@
1-import { describe, it, expect, afterEach } from "vitest";2-import { Client, newEvent, newKey, pub, now, RELAY_URL } from "./helpers.ts";1+import { describe, it, expect } from "vitest";2+import { newEvent, newKey, now, RELAY_URL, sockets } from "./helpers.ts";33 4-const open: Client[] = [];5-const connect = async () => {6- const c = await Client.connect();7- open.push(c);8- return c;9-};10-afterEach(() => open.splice(0).forEach((c) => c.close()));4+const connect = sockets();115 126 describe("NIP-42 AUTH", () => {137 it("refuses bad challenges, wrong relays, stale timestamps, wrong kinds; accepts several keys", async () => {
@@ -34,61 +28,3 @@ describe("NIP-42 AUTH", () => {
3428 await c.auth(newKey());3529 });3630 });37-38-describe("NIP-70 protected events", () => {39- it("accepts ['-'] events only from a connection authenticated as the author", async () => {40- const sk = newKey();41- const c = await connect();42- const e = newEvent(sk, 1, "members only", [["-"]]);43- c.send("EVENT", e);44- expect(await c.expectOK(e.id, false)).toMatch(/^auth-required:/);45- await c.auth(newKey());46- c.send("EVENT", e);47- await c.expectOK(e.id, false);48- await c.auth(sk);49- await c.publish(e);50- });51-});52-53-describe("NIP-17 / NIP-59 private kinds", () => {54- it("serves gift wraps only to their recipients, stored and live, and lets recipients delete them", async () => {55- const sender = newKey();56- const recipient = newKey();57- const stranger = newKey();58- const s = await connect();59- const wrap = newEvent(sender, 1059, "ciphertext", [["p", pub(recipient)]], now() - 100);60- await s.publish(wrap);61-62- const x = await connect();63- x.send("REQ", "dm", { kinds: [1059], "#p": [pub(recipient)] });64- await x.expectClosed("auth-required:");65- let r = await x.req({ "#p": [pub(recipient)] });66- expect(r.events).toEqual([]);67- expect(r.hints).toContain("auth");68- x.send("COUNT", "n", { "#p": [pub(recipient)] });69- expect((await x.expect("COUNT"))[2].count).toBe(0);70-71- await x.auth(stranger);72- r = await x.req({ kinds: [1059], "#p": [pub(recipient)] });73- expect(r.events).toEqual([]);74- x.send("REQ", "live", { kinds: [1059], "#p": [pub(recipient)] });75- await x.expect("EOSE");76- await s.publish(newEvent(sender, 1059, "ciphertext2", [["p", pub(recipient)]], now() - 99));77- await x.expectNothing();78-79- const rc = await connect();80- await rc.auth(recipient);81- r = await rc.req({ kinds: [1059], "#p": [pub(recipient)] });82- expect(r.events.length).toBe(2);83- rc.send("REQ", "live", { kinds: [1059], "#p": [pub(recipient)] });84- await rc.drain();85- const wrap3 = newEvent(sender, 1059, "ciphertext3", [["p", pub(recipient)]], now() - 98);86- await s.publish(wrap3);87- expect((await rc.expect("EVENT"))[2].id).toBe(wrap3.id);88-89- await rc.publish(newEvent(recipient, 5, "", [["e", wrap3.id]]));90- expect((await rc.req({ ids: [wrap3.id] })).events).toEqual([]);91- s.send("EVENT", wrap3);92- expect(await s.expectOK(wrap3.id, false)).toMatch(/^blocked:/);93- });94-});
A test/conformance/nip45.test.ts +41 -0
@@ -0,0 +1,41 @@
1+import { describe, it, expect } from "vitest";2+import { newEvent, newKey, pub, now, randHex, sockets } from "./helpers.ts";3+4+const connect = sockets();5+6+describe("NIP-45 COUNT", () => {7+ it("counts across OR'd filters without double counting, and adds an hll sketch for tag filters", async () => {8+ const sk = newKey();9+ const c = await connect();10+ const target = randHex();11+ for (let i = 0; i < 3; i++) await c.publish(newEvent(sk, 1, "n", [["t", target]], now() - 10 + i));12+ await c.publish(newEvent(sk, 7, "+", [["e", target]], now() - 5));13+14+ c.send("COUNT", "n", { kinds: [1], authors: [pub(sk)] }, { kinds: [7], authors: [pub(sk)] });15+ expect((await c.expect("COUNT"))[2].count).toBe(4);16+ c.send("COUNT", "n", { kinds: [1], authors: [pub(sk)] }, { "#t": [target], authors: [pub(sk)] });17+ expect((await c.expect("COUNT"))[2].count).toBe(3);18+19+ c.send("COUNT", "r", { kinds: [7], "#e": [target] });20+ const res = (await c.expect("COUNT"))[2];21+ expect(res.count).toBe(1);22+ expect(res.hll).toMatch(/^[0-9a-f]{512}$/);23+ // Register: offset = nibble 32 of the target + 8; byte[offset] indexes,24+ // zero run after it plus one is the value.25+ const offset = parseInt(target[32], 16) + 8;26+ const pk = pub(sk);27+ const regIndex = parseInt(pk.substr(offset * 2, 2), 16);28+ const rest = pk.slice((offset + 1) * 2);29+ let zeros = 0;30+ for (const ch of rest) {31+ const n = parseInt(ch, 16);32+ if (n === 0) {33+ zeros += 4;34+ continue;35+ }36+ zeros += Math.clz32(n) - 28;37+ break;38+ }39+ expect(parseInt(res.hll.substr(regIndex * 2, 2), 16)).toBe(zeros + 1);40+ });41+});
A test/conformance/nip50.test.ts +27 -0
A test/conformance/nip62.test.ts +39 -0
@@ -0,0 +1,39 @@
1+import { describe, it, expect } from "vitest";2+import { newEvent, newKey, pub, now, RELAY_URL, sockets } from "./helpers.ts";3+4+const connect = sockets();5+6+describe("NIP-62 vanish", () => {7+ it("deletes everything from the pubkey up to the request, plus gift wraps to it, and blocks resurrection", async () => {8+ const sk = newKey();9+ const other = newKey();10+ const c = await connect();11+ const t0 = now() - 100;12+ const note = newEvent(sk, 1, "regret", [], t0);13+ const meta = newEvent(sk, 0, "{}", [], t0);14+ const wrap = newEvent(other, 1059, "dm", [["p", pub(sk)]], t0);15+ const keep = newEvent(other, 1, "unrelated", [], t0);16+ for (const e of [note, meta, wrap, keep]) await c.publish(e);17+18+ let v = newEvent(sk, 62, "", [["relay", "wss://other.example"]], t0 + 1);19+ c.send("EVENT", v);20+ await c.expectOK(v.id, false);21+ expect((await c.req({ authors: [pub(sk)] })).events.length).toBe(2);22+23+ v = newEvent(sk, 62, "bye", [["relay", RELAY_URL]], t0 + 1);24+ await c.publish(v);25+ expect((await c.req({ authors: [pub(sk)] })).events).toEqual([]);26+ expect((await c.req({ ids: [keep.id] })).events.length).toBe(1);27+ const rc = await connect();28+ await rc.auth(sk);29+ expect((await rc.req({ kinds: [1059], "#p": [pub(sk)] })).events).toEqual([]);30+31+ c.send("EVENT", note);32+ expect(await c.expectOK(note.id, false)).toMatch(/^blocked:/);33+ await c.publish(newEvent(sk, 1, "fresh start", [], t0 + 2));34+35+ v = newEvent(other, 62, "", [["relay", "ALL_RELAYS"]], t0 + 3);36+ await c.publish(v);37+ expect((await c.req({ authors: [pub(other)] })).events).toEqual([]);38+ });39+});
A test/conformance/nip70.test.ts +19 -0
A test/conformance/nip77.test.ts +47 -0
@@ -0,0 +1,47 @@
1+import { describe, it, expect } from "vitest";2+import { newEvent, newKey, pub, now, item, sockets } from "./helpers.ts";3+4+const connect = sockets();5+6+describe("NIP-77 negentropy", () => {7+ it("reconciles a client set against the relay's, respecting visibility", async () => {8+ const sk = newKey();9+ const recipient = newKey();10+ const c = await connect();11+ const t0 = now() - 1000;12+ const relayHas = [];13+ for (let i = 0; i < 100; i++) relayHas.push(await (async () => {14+ const e = newEvent(sk, 1, String(i), [], t0 + Math.floor(i / 3));15+ await c.publish(e);16+ return e;17+ })());18+ const local = [];19+ for (let i = 0; i < 5; i++) local.push(newEvent(sk, 1, "local " + i, [], t0 + 10 + i));20+ const wrap = newEvent(sk, 1059, "dm", [["p", pub(recipient)]], t0 + 5);21+ await c.publish(wrap);22+23+ const clientItems = [...relayHas.slice(0, 60), ...local].map(item);24+ const { have, need } = await c.sync(clientItems, { kinds: [1], authors: [pub(sk)] });25+ expect(have).toEqual(local.map((e) => e.id).sort());26+ expect(need).toEqual(relayHas.slice(60).map((e) => e.id).sort());27+28+ // Strangers never learn the gift wrap; the recipient does.29+ let r = await c.sync(relayHas.map(item), { authors: [pub(sk)] });30+ expect(r.need).toEqual([]);31+ const rc = await connect();32+ await rc.auth(recipient);33+ r = await rc.sync(relayHas.map(item), { authors: [pub(sk)] });34+ expect(r.need).toEqual([wrap.id]);35+36+ // Error paths.37+ c.send("NEG-MSG", "nope", "61");38+ expect((await c.expect("NEG-ERR"))[2]).toMatch(/^closed:/);39+ c.send("NEG-OPEN", "bad", {}, "zz");40+ expect((await c.expect("NEG-ERR"))[2]).toMatch(/^invalid:/);41+ const x = await connect();42+ x.send("NEG-OPEN", "dm", { kinds: [1059] }, "61");43+ expect((await x.expect("NEG-ERR"))[2]).toMatch(/^auth-required:/);44+ x.send("NEG-OPEN", "v", { kinds: [1], authors: [pub(sk)] }, "62");45+ expect((await x.expect("NEG-MSG"))[2]).toBe("61");46+ });47+});
D test/conformance/nips.test.ts +0 -178
@@ -1,178 +0,0 @@
1-import { describe, it, expect, afterEach } from "vitest";2-import { Client, newEvent, newKey, pub, now, rand, randHex, sleep, item, RELAY_URL, HTTP_URL } from "./helpers.ts";3-4-const open: Client[] = [];5-const connect = async () => {6- const c = await Client.connect();7- open.push(c);8- return c;9-};10-afterEach(() => open.splice(0).forEach((c) => c.close()));11-12-describe("NIP-11", () => {13- it("serves the information document with limitations", async () => {14- const resp = await fetch(HTTP_URL, { headers: { Accept: "application/nostr+json" } });15- expect(resp.status).toBe(200);16- expect(resp.headers.get("content-type")).toContain("application/nostr+json");17- const doc: any = await resp.json();18- expect(typeof doc.name).toBe("string");19- for (const n of [1, 9, 11, 40, 42, 45, 50, 62, 67, 70, 77]) expect(doc.supported_nips).toContain(n);20- expect(doc.limitation.max_subid_length).toBeGreaterThanOrEqual(64);21- expect(typeof doc.limitation.max_limit).toBe("number");22- });23-});24-25-describe("NIP-45 COUNT", () => {26- it("counts across OR'd filters without double counting, and adds an hll sketch for tag filters", async () => {27- const sk = newKey();28- const c = await connect();29- const target = randHex();30- for (let i = 0; i < 3; i++) await c.publish(newEvent(sk, 1, "n", [["t", target]], now() - 10 + i));31- await c.publish(newEvent(sk, 7, "+", [["e", target]], now() - 5));32-33- c.send("COUNT", "n", { kinds: [1], authors: [pub(sk)] }, { kinds: [7], authors: [pub(sk)] });34- expect((await c.expect("COUNT"))[2].count).toBe(4);35- c.send("COUNT", "n", { kinds: [1], authors: [pub(sk)] }, { "#t": [target], authors: [pub(sk)] });36- expect((await c.expect("COUNT"))[2].count).toBe(3);37-38- c.send("COUNT", "r", { kinds: [7], "#e": [target] });39- const res = (await c.expect("COUNT"))[2];40- expect(res.count).toBe(1);41- expect(res.hll).toMatch(/^[0-9a-f]{512}$/);42- // Register: offset = nibble 32 of the target + 8; byte[offset] indexes,43- // zero run after it plus one is the value.44- const offset = parseInt(target[32], 16) + 8;45- const pk = pub(sk);46- const regIndex = parseInt(pk.substr(offset * 2, 2), 16);47- const rest = pk.slice((offset + 1) * 2);48- let zeros = 0;49- for (const ch of rest) {50- const n = parseInt(ch, 16);51- if (n === 0) {52- zeros += 4;53- continue;54- }55- zeros += Math.clz32(n) - 28;56- break;57- }58- expect(parseInt(res.hll.substr(regIndex * 2, 2), 16)).toBe(zeros + 1);59- });60-});61-62-describe("NIP-40 expiration", () => {63- it("refuses expired events, serves unexpired ones, and stops serving after expiry", async () => {64- const sk = newKey();65- const c = await connect();66- const dead = newEvent(sk, 1, "gone", [["expiration", String(now() - 1)]]);67- c.send("EVENT", dead);68- expect(await c.expectOK(dead.id, false)).toMatch(/expired/);69- const soon = newEvent(sk, 1, "soon", [["expiration", String(now() + 2)]]);70- await c.publish(soon);71- expect((await c.req({ ids: [soon.id] })).events.length).toBe(1);72- await sleep(2500);73- expect((await c.req({ ids: [soon.id] })).events.length).toBe(0);74- c.send("COUNT", "q", { ids: [soon.id] });75- expect((await c.expect("COUNT"))[2].count).toBe(0);76- });77-});78-79-describe("NIP-50 search", () => {80- it("matches words in content, case-insensitively, ignores extensions, is injection-safe, and matches live", async () => {81- const sk = newKey();82- const c = await connect();83- const w = rand();84- const texts = [`Purple ${w}elephants dance`, `${w}orange juice`, `the ${w}elephant in the room`];85- for (const [i, t] of texts.entries()) await c.publish(newEvent(sk, 1, t, [], now() - 10 + i));86- await c.publish(newEvent(sk, 4, `${w}elephant encrypted`, [["p", pub(sk)]], now() - 5));87-88- let r = await c.req({ search: `${w}elephant`, authors: [pub(sk)] });89- expect(r.events.map((e) => e.content)).toEqual([texts[2]]);90- r = await c.req({ search: `PURPLE include:spam dance ${w}elephants`, authors: [pub(sk)] });91- expect(r.events.map((e) => e.content)).toEqual([texts[0]]);92- r = await c.req({ search: `${w}juice OR NOT "`, authors: [pub(sk)] });93- expect(r.hints).toContain("finish");94-95- c.send("REQ", "live", { search: `${w}banana`, authors: [pub(sk)] });96- await c.drain();97- await c.publish(newEvent(sk, 1, `a ${w}Banana split`));98- await c.expect("EVENT");99- });100-});101-102-describe("NIP-62 vanish", () => {103- it("deletes everything from the pubkey up to the request, plus gift wraps to it, and blocks resurrection", async () => {104- const sk = newKey();105- const other = newKey();106- const c = await connect();107- const t0 = now() - 100;108- const note = newEvent(sk, 1, "regret", [], t0);109- const meta = newEvent(sk, 0, "{}", [], t0);110- const wrap = newEvent(other, 1059, "dm", [["p", pub(sk)]], t0);111- const keep = newEvent(other, 1, "unrelated", [], t0);112- for (const e of [note, meta, wrap, keep]) await c.publish(e);113-114- let v = newEvent(sk, 62, "", [["relay", "wss://other.example"]], t0 + 1);115- c.send("EVENT", v);116- await c.expectOK(v.id, false);117- expect((await c.req({ authors: [pub(sk)] })).events.length).toBe(2);118-119- v = newEvent(sk, 62, "bye", [["relay", RELAY_URL]], t0 + 1);120- await c.publish(v);121- expect((await c.req({ authors: [pub(sk)] })).events).toEqual([]);122- expect((await c.req({ ids: [keep.id] })).events.length).toBe(1);123- const rc = await connect();124- await rc.auth(sk);125- expect((await rc.req({ kinds: [1059], "#p": [pub(sk)] })).events).toEqual([]);126-127- c.send("EVENT", note);128- expect(await c.expectOK(note.id, false)).toMatch(/^blocked:/);129- await c.publish(newEvent(sk, 1, "fresh start", [], t0 + 2));130-131- v = newEvent(other, 62, "", [["relay", "ALL_RELAYS"]], t0 + 3);132- await c.publish(v);133- expect((await c.req({ authors: [pub(other)] })).events).toEqual([]);134- });135-});136-137-describe("NIP-77 negentropy", () => {138- it("reconciles a client set against the relay's, respecting visibility", async () => {139- const sk = newKey();140- const recipient = newKey();141- const c = await connect();142- const t0 = now() - 1000;143- const relayHas = [];144- for (let i = 0; i < 100; i++) relayHas.push(await (async () => {145- const e = newEvent(sk, 1, String(i), [], t0 + Math.floor(i / 3));146- await c.publish(e);147- return e;148- })());149- const local = [];150- for (let i = 0; i < 5; i++) local.push(newEvent(sk, 1, "local " + i, [], t0 + 10 + i));151- const wrap = newEvent(sk, 1059, "dm", [["p", pub(recipient)]], t0 + 5);152- await c.publish(wrap);153-154- const clientItems = [...relayHas.slice(0, 60), ...local].map(item);155- const { have, need } = await c.sync(clientItems, { kinds: [1], authors: [pub(sk)] });156- expect(have).toEqual(local.map((e) => e.id).sort());157- expect(need).toEqual(relayHas.slice(60).map((e) => e.id).sort());158-159- // Strangers never learn the gift wrap; the recipient does.160- let r = await c.sync(relayHas.map(item), { authors: [pub(sk)] });161- expect(r.need).toEqual([]);162- const rc = await connect();163- await rc.auth(recipient);164- r = await rc.sync(relayHas.map(item), { authors: [pub(sk)] });165- expect(r.need).toEqual([wrap.id]);166-167- // Error paths.168- c.send("NEG-MSG", "nope", "61");169- expect((await c.expect("NEG-ERR"))[2]).toMatch(/^closed:/);170- c.send("NEG-OPEN", "bad", {}, "zz");171- expect((await c.expect("NEG-ERR"))[2]).toMatch(/^invalid:/);172- const x = await connect();173- x.send("NEG-OPEN", "dm", { kinds: [1059] }, "61");174- expect((await x.expect("NEG-ERR"))[2]).toMatch(/^auth-required:/);175- x.send("NEG-OPEN", "v", { kinds: [1], authors: [pub(sk)] }, "62");176- expect((await x.expect("NEG-MSG"))[2]).toBe("61");177- });178-});