@qntx/nostr 0.11.0 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{builder-GBddxj8i.d.mts → builder-C72nJITG.d.mts} +2 -2
- package/dist/{builder-BQbFDO6S.mjs → builder-CGgM_QHT.mjs} +5 -5
- package/dist/{builder-BQbFDO6S.mjs.map → builder-CGgM_QHT.mjs.map} +1 -1
- package/dist/{client-CTmp5w84.mjs → client-COkEihj_.mjs} +87 -79
- package/dist/client-COkEihj_.mjs.map +1 -0
- package/dist/client.d.mts +1 -1
- package/dist/client.mjs +1 -1
- package/dist/core.d.mts +7 -7
- package/dist/core.mjs +9 -9
- package/dist/{emitter-DkCY6NkU.mjs → emitter-u4kJqQDk.mjs} +2 -2
- package/dist/{emitter-DkCY6NkU.mjs.map → emitter-u4kJqQDk.mjs.map} +1 -1
- package/dist/{error-BquXDmIN.d.mts → error-BPqAU5RB.d.mts} +2 -6
- package/dist/{error-BD_0QFKT.d.mts → error-BjSUsx6M.d.mts} +2 -2
- package/dist/{error-UHkqzWL7.mjs → error-Dyb89ahb.mjs} +7 -3
- package/dist/error-Dyb89ahb.mjs.map +1 -0
- package/dist/{error-DH4guike.mjs → error-QyECUDIp.mjs} +2 -2
- package/dist/{error-DH4guike.mjs.map → error-QyECUDIp.mjs.map} +1 -1
- package/dist/{event-B3ua2_oL.mjs → event-DvM1f03b.mjs} +7 -3
- package/dist/event-DvM1f03b.mjs.map +1 -0
- package/dist/{filter-B16P33dz.mjs → filter-3jOh4uLN.mjs} +50 -20
- package/dist/filter-3jOh4uLN.mjs.map +1 -0
- package/dist/{filter-PG7r79rt.d.mts → filter-oYG2zjJ3.d.mts} +6 -2
- package/dist/{gossip-BTMdlUI7.d.mts → gossip-BvCItMVZ.d.mts} +3 -3
- package/dist/{gossip-K_JIexQs.mjs → gossip-CRyhK6E6.mjs} +4 -4
- package/dist/{gossip-K_JIexQs.mjs.map → gossip-CRyhK6E6.mjs.map} +1 -1
- package/dist/gossip.d.mts +1 -1
- package/dist/gossip.mjs +1 -1
- package/dist/http-CuazNRIP.mjs.map +1 -1
- package/dist/{index-DD0Ywpay.d.mts → index-B2JlP2Jf.d.mts} +9 -9
- package/dist/{index-BP94lABY.d.mts → index-DCobP2Jj.d.mts} +10 -10
- package/dist/{index-Hp26bgYG.d.mts → index-Dpj_MUxb.d.mts} +8 -5
- package/dist/{index-rKcmI6ex.d.mts → index-DzHmhHqD.d.mts} +3 -3
- package/dist/{index-N7HUGgwo.d.mts → index-YID-zPDF.d.mts} +58 -8
- package/dist/{index-CSsnE32i.d.mts → index-muydfuYD.d.mts} +21 -21
- package/dist/index.d.mts +19 -20
- package/dist/index.mjs +20 -20
- package/dist/{key-k8LDEPZh.mjs → key-D_oEutQ6.mjs} +32 -9
- package/dist/key-D_oEutQ6.mjs.map +1 -0
- package/dist/{key-CyKN1RTi.d.mts → key-DojW2QZl.d.mts} +14 -4
- package/dist/{kind-D8fENiiL.mjs → kind-I2Tvbe82.mjs} +6 -5
- package/dist/kind-I2Tvbe82.mjs.map +1 -0
- package/dist/{loaders-Dp2nXEFk.mjs → loaders-BRh2riV9.mjs} +17 -16
- package/dist/loaders-BRh2riV9.mjs.map +1 -0
- package/dist/loaders.d.mts +2 -2
- package/dist/loaders.mjs +1 -1
- package/dist/{memory-VyPnOBU3.mjs → memory-D_GdEQYG.mjs} +2 -2
- package/dist/memory-D_GdEQYG.mjs.map +1 -0
- package/dist/memory-index-3GSrPIWj.mjs +671 -0
- package/dist/memory-index-3GSrPIWj.mjs.map +1 -0
- package/dist/{message-pYfVKxPn.mjs → message-BMfreCQc.mjs} +6 -6
- package/dist/message-BMfreCQc.mjs.map +1 -0
- package/dist/{nip17-B7MI82Iq.d.mts → nip17-BZvFQLcs.d.mts} +5 -5
- package/dist/{nip19-DpPN17bY.d.mts → nip19-D4RKn6GC.d.mts} +2 -2
- package/dist/{nip46-BHaxC9TZ.d.mts → nip46-i8L8_nIm.d.mts} +2 -2
- package/dist/{nip51-O_vhOiK2.d.mts → nip51-C_kTYcud.d.mts} +2 -2
- package/dist/{nip59-CdDoJptA.d.mts → nip59-DfJ6Moeu.d.mts} +15 -7
- package/dist/{nip65-BL79HI8N.d.mts → nip65-CPNE0SV0.d.mts} +2 -2
- package/dist/{nip77-wHA_nEV5.d.mts → nip77-DhpoL7d8.d.mts} +2 -2
- package/dist/nips/blossom.d.mts +2 -2
- package/dist/nips/blossom.mjs +3 -3
- package/dist/nips/blossom.mjs.map +1 -1
- package/dist/nips/nip04.d.mts +11 -4
- package/dist/nips/nip04.mjs +44 -21
- package/dist/nips/nip04.mjs.map +1 -1
- package/dist/nips/nip05.d.mts +6 -3
- package/dist/nips/nip05.mjs +6 -3
- package/dist/nips/nip05.mjs.map +1 -1
- package/dist/nips/nip10.d.mts +2 -2
- package/dist/nips/nip10.mjs +4 -4
- package/dist/nips/nip11.d.mts +1 -1
- package/dist/nips/nip11.mjs +1 -1
- package/dist/nips/nip13.d.mts +1 -1
- package/dist/nips/nip13.mjs +1 -1
- package/dist/nips/nip13.mjs.map +1 -1
- package/dist/nips/nip17.d.mts +1 -1
- package/dist/nips/nip17.mjs +5 -5
- package/dist/nips/nip17.mjs.map +1 -1
- package/dist/nips/nip19.d.mts +1 -1
- package/dist/nips/nip19.mjs +2 -2
- package/dist/nips/nip19.mjs.map +1 -1
- package/dist/nips/nip21.d.mts +6 -3
- package/dist/nips/nip21.mjs +8 -5
- package/dist/nips/nip21.mjs.map +1 -1
- package/dist/nips/nip27.d.mts +1 -1
- package/dist/nips/nip27.mjs +15 -9
- package/dist/nips/nip27.mjs.map +1 -1
- package/dist/nips/nip42.mjs +2 -2
- package/dist/nips/nip44.d.mts +5 -0
- package/dist/nips/nip44.mjs +10 -3
- package/dist/nips/nip44.mjs.map +1 -1
- package/dist/nips/nip46.d.mts +1 -1
- package/dist/nips/nip46.mjs +9 -7
- package/dist/nips/nip46.mjs.map +1 -1
- package/dist/nips/nip49.d.mts +8 -2
- package/dist/nips/nip49.mjs +17 -3
- package/dist/nips/nip49.mjs.map +1 -1
- package/dist/nips/nip51.d.mts +1 -1
- package/dist/nips/nip51.mjs +5 -5
- package/dist/nips/nip51.mjs.map +1 -1
- package/dist/nips/nip57.mjs +6 -5
- package/dist/nips/nip57.mjs.map +1 -1
- package/dist/nips/nip59.d.mts +1 -1
- package/dist/nips/nip59.mjs +51 -23
- package/dist/nips/nip59.mjs.map +1 -1
- package/dist/nips/nip65.d.mts +1 -1
- package/dist/nips/nip65.mjs +3 -3
- package/dist/nips/nip77.d.mts +1 -1
- package/dist/nips/nip77.mjs +1 -1
- package/dist/nips/nip77.mjs.map +1 -1
- package/dist/nips/nip98.d.mts +1 -1
- package/dist/nips/nip98.mjs +18 -7
- package/dist/nips/nip98.mjs.map +1 -1
- package/dist/{pool-DUuYJ0x4.d.mts → pool-MehgIIzm.d.mts} +20 -16
- package/dist/{pool-BMUsw2un.mjs → pool-lMT_mvzn.mjs} +25 -25
- package/dist/pool-lMT_mvzn.mjs.map +1 -0
- package/dist/{put-DRmV1wHD.mjs → put-BEM5j8xK.mjs} +5 -5
- package/dist/{put-DRmV1wHD.mjs.map → put-BEM5j8xK.mjs.map} +1 -1
- package/dist/{reactive-jyKrA9ej.d.mts → reactive-B7lPFgTS.d.mts} +3 -3
- package/dist/{reactive--pDpLt2H.mjs → reactive-DFHY-f00.mjs} +24 -9
- package/dist/reactive-DFHY-f00.mjs.map +1 -0
- package/dist/{relay-DXtwUEdX.mjs → relay-DIR77xQ2.mjs} +102 -89
- package/dist/relay-DIR77xQ2.mjs.map +1 -0
- package/dist/relay.d.mts +5 -5
- package/dist/relay.mjs +3 -3
- package/dist/{report-CHld4uWj.mjs → report-D2BM5dgn.mjs} +2 -2
- package/dist/{report-CHld4uWj.mjs.map → report-D2BM5dgn.mjs.map} +1 -1
- package/dist/{signer-CWtxpOxc.mjs → signer-DJcmC4nA.mjs} +176 -17
- package/dist/signer-DJcmC4nA.mjs.map +1 -0
- package/dist/signer.d.mts +2 -2
- package/dist/signer.mjs +3 -3
- package/dist/{storage-B5Wdt_Mn.mjs → storage-n2BE1bdY.mjs} +108 -179
- package/dist/storage-n2BE1bdY.mjs.map +1 -0
- package/dist/storage.d.mts +3 -3
- package/dist/storage.mjs +4 -4
- package/dist/store.d.mts +1 -1
- package/dist/store.mjs +1 -1
- package/dist/testing.d.mts +4 -4
- package/dist/testing.mjs +35 -10
- package/dist/testing.mjs.map +1 -1
- package/dist/{types-CqhnT-Mx.d.mts → types-DiVVyf1e.d.mts} +2 -2
- package/dist/{util-DDpPkiED.mjs → util-DfsyJ9nz.mjs} +30 -10
- package/dist/util-DfsyJ9nz.mjs.map +1 -0
- package/dist/{verifier-CjH1vnP2.mjs → verifier-DEHUy4AD.mjs} +3 -3
- package/dist/{verifier-CjH1vnP2.mjs.map → verifier-DEHUy4AD.mjs.map} +1 -1
- package/dist/{verifier-Cl24c9ph.d.mts → verifier-DhAbqVJF.d.mts} +2 -2
- package/dist/{websocket-CmeqvAkp.d.mts → websocket-DaCvJkF9.d.mts} +2 -5
- package/package.json +4 -3
- package/dist/client-CTmp5w84.mjs.map +0 -1
- package/dist/error-UHkqzWL7.mjs.map +0 -1
- package/dist/event-B3ua2_oL.mjs.map +0 -1
- package/dist/filter-B16P33dz.mjs.map +0 -1
- package/dist/key-k8LDEPZh.mjs.map +0 -1
- package/dist/kind-D8fENiiL.mjs.map +0 -1
- package/dist/loaders-Dp2nXEFk.mjs.map +0 -1
- package/dist/memory-VyPnOBU3.mjs.map +0 -1
- package/dist/memory-index-Bhwe0ZdG.mjs +0 -374
- package/dist/memory-index-Bhwe0ZdG.mjs.map +0 -1
- package/dist/message-pYfVKxPn.mjs.map +0 -1
- package/dist/pool-BMUsw2un.mjs.map +0 -1
- package/dist/reactive--pDpLt2H.mjs.map +0 -1
- package/dist/relay-DXtwUEdX.mjs.map +0 -1
- package/dist/signer-CWtxpOxc.mjs.map +0 -1
- package/dist/storage-B5Wdt_Mn.mjs.map +0 -1
- package/dist/util-DDpPkiED.mjs.map +0 -1
package/dist/nips/nip59.mjs
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { t as Kind } from "../kind-
|
|
3
|
-
import { g as Tag, h as validateSignedEvent, m as validateEvent, n as getEventHash } from "../event-
|
|
4
|
-
import {
|
|
5
|
-
import { n as verifyEvent } from "../verifier-
|
|
6
|
-
import {
|
|
1
|
+
import { n as assertHex32, p as nowSeconds, u as isRecord, x as NostrError } from "../util-DfsyJ9nz.mjs";
|
|
2
|
+
import { t as Kind } from "../kind-I2Tvbe82.mjs";
|
|
3
|
+
import { g as Tag, h as validateSignedEvent, m as validateEvent, n as getEventHash } from "../event-DvM1f03b.mjs";
|
|
4
|
+
import { n as SecretKey, o as signEvent, t as Keys } from "../key-D_oEutQ6.mjs";
|
|
5
|
+
import { n as verifyEvent } from "../verifier-DEHUy4AD.mjs";
|
|
6
|
+
import { encrypt, getConversationKey } from "./nip44.mjs";
|
|
7
7
|
import { randomBytes } from "@noble/hashes/utils.js";
|
|
8
|
+
import { secp256k1 } from "@noble/curves/secp256k1.js";
|
|
8
9
|
//#region src/nips/nip59.ts
|
|
9
10
|
/**
|
|
10
11
|
* NIP-59: Gift Wrap. Rumor → NIP-44 seal (kind 13) → gift wrap (kind 1059). Does not import signer,
|
|
@@ -61,17 +62,17 @@ function assertExpiration(ts) {
|
|
|
61
62
|
if (!Number.isSafeInteger(ts) || ts < 0) throw new Nip59Error("expiration must be a non-negative safe integer");
|
|
62
63
|
return ts;
|
|
63
64
|
}
|
|
64
|
-
function defaultRandomInt(maxExclusive) {
|
|
65
|
-
if (!Number.isSafeInteger(maxExclusive) || maxExclusive <= 0 || maxExclusive > 4294967296) throw new Nip59Error("
|
|
65
|
+
function defaultRandomInt(maxExclusive, drawBytes) {
|
|
66
|
+
if (!Number.isSafeInteger(maxExclusive) || maxExclusive <= 0 || maxExclusive > 4294967296) throw new Nip59Error("random bound must be a positive integer within uint32 range");
|
|
66
67
|
const limit = Math.floor(4294967296 / maxExclusive) * maxExclusive;
|
|
67
68
|
for (;;) {
|
|
68
|
-
const bytes =
|
|
69
|
+
const bytes = drawBytes(4);
|
|
69
70
|
const n = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength).getUint32(0);
|
|
70
71
|
if (n < limit) return n % maxExclusive;
|
|
71
72
|
}
|
|
72
73
|
}
|
|
73
74
|
function randomPastTimestamp(opts) {
|
|
74
|
-
return (opts?.now ?? nowSeconds()) - (opts?.
|
|
75
|
+
return (opts?.now ?? nowSeconds()) - defaultRandomInt(TWO_DAYS_SECS, opts?.randomBytes ?? randomBytes);
|
|
75
76
|
}
|
|
76
77
|
function createRumor(pubkey, template) {
|
|
77
78
|
const unsigned = {
|
|
@@ -88,13 +89,13 @@ function createRumor(pubkey, template) {
|
|
|
88
89
|
}
|
|
89
90
|
async function createSeal(crypto, recipient, rumor, opts) {
|
|
90
91
|
const recipientPk = assertHex32(recipient, "public key");
|
|
92
|
+
const created_at = opts?.timestamps?.seal ?? (opts?.randomize === "wrap" ? rumor.created_at : randomPastTimestamp(opts));
|
|
91
93
|
let content;
|
|
92
94
|
try {
|
|
93
95
|
content = await crypto.nip44Encrypt(recipientPk, rumorToJson(rumor));
|
|
94
96
|
} catch (error) {
|
|
95
97
|
throw new Nip59Error("failed to encrypt", { cause: error });
|
|
96
98
|
}
|
|
97
|
-
const created_at = opts?.timestamps?.seal ?? (opts?.randomize === "wrap" ? rumor.created_at : randomPastTimestamp(opts));
|
|
98
99
|
const tags = [];
|
|
99
100
|
if (opts?.expiration !== void 0) tags.push(["expiration", String(assertExpiration(opts.expiration))]);
|
|
100
101
|
const pubkey = await crypto.getPublicKey();
|
|
@@ -108,18 +109,45 @@ async function createSeal(crypto, recipient, rumor, opts) {
|
|
|
108
109
|
}
|
|
109
110
|
function createGiftWrap(seal, recipient, opts) {
|
|
110
111
|
const recipientPk = assertHex32(recipient, "public key");
|
|
111
|
-
const
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
112
|
+
const drawBytes = opts?.randomBytes ?? randomBytes;
|
|
113
|
+
let secretKey;
|
|
114
|
+
for (;;) {
|
|
115
|
+
const candidate = drawBytes(32);
|
|
116
|
+
if (secp256k1.utils.isValidSecretKey(candidate)) {
|
|
117
|
+
secretKey = SecretKey.fromBytes(candidate);
|
|
118
|
+
candidate.fill(0);
|
|
119
|
+
break;
|
|
120
|
+
}
|
|
121
|
+
candidate.fill(0);
|
|
122
|
+
}
|
|
123
|
+
try {
|
|
124
|
+
const ephemeral = Keys.fromSecretKey(secretKey);
|
|
125
|
+
const created_at = opts?.timestamps?.wrap ?? randomPastTimestamp({
|
|
126
|
+
...opts,
|
|
127
|
+
randomBytes: drawBytes
|
|
128
|
+
});
|
|
129
|
+
const secretBytes = secretKey.bytes;
|
|
130
|
+
let conversationKey;
|
|
131
|
+
try {
|
|
132
|
+
conversationKey = getConversationKey(secretBytes, recipientPk);
|
|
133
|
+
const content = encrypt(eventToJson(seal), conversationKey, drawBytes(32));
|
|
134
|
+
const tags = [Tag.p(recipientPk, opts?.relayHint)];
|
|
135
|
+
if (opts?.expiration !== void 0) tags.push(["expiration", String(assertExpiration(opts.expiration))]);
|
|
136
|
+
tags.push(...opts?.extraTags ?? []);
|
|
137
|
+
return signEvent({
|
|
138
|
+
kind: opts?.ephemeral === true ? Kind.GiftWrapEphemeral : Kind.GiftWrap,
|
|
139
|
+
content,
|
|
140
|
+
created_at,
|
|
141
|
+
tags,
|
|
142
|
+
pubkey: ephemeral.publicKey
|
|
143
|
+
}, ephemeral, drawBytes(32));
|
|
144
|
+
} finally {
|
|
145
|
+
secretBytes.fill(0);
|
|
146
|
+
conversationKey?.fill(0);
|
|
147
|
+
}
|
|
148
|
+
} finally {
|
|
149
|
+
secretKey.zeroize();
|
|
150
|
+
}
|
|
123
151
|
}
|
|
124
152
|
async function wrap(crypto, recipient, rumor, opts) {
|
|
125
153
|
return createGiftWrap(await createSeal(crypto, recipient, rumor, opts), recipient, opts);
|
package/dist/nips/nip59.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"nip59.mjs","names":["TagBuilder"],"sources":["../../src/nips/nip59.ts"],"sourcesContent":["/**\n * NIP-59: Gift Wrap. Rumor → NIP-44 seal (kind 13) → gift wrap (kind 1059). Does not import signer,\n * relay, or client. Crypto is a structural type.\n *\n * @see https://github.com/nostr-protocol/nips/blob/master/59.md\n */\nimport { randomBytes } from \"@noble/hashes/utils.js\";\n\nimport { NostrError } from \"../core/error.ts\";\nimport type { Event, UnsignedEvent } from \"../core/event.ts\";\nimport { getEventHash, validateEvent, validateSignedEvent } from \"../core/event.ts\";\nimport { Keys, finalizeEvent } from \"../core/key.ts\";\nimport { Kind } from \"../core/kind.ts\";\nimport type { Tag } from \"../core/tag.ts\";\nimport { Tag as TagBuilder } from \"../core/tag.ts\";\nimport { assertHex32, isRecord, nowSeconds } from \"../core/util.ts\";\nimport { verifyEvent } from \"../core/verifier.ts\";\nimport { encryptToPubkey } from \"./nip44.ts\";\n\n/** Unsigned event with a computed id. Never has `sig`. */\nexport type Rumor = UnsignedEvent & {\n readonly id: string;\n};\n\n/**\n * Structural crypto used by NIP-59. Satisfied by NostrSigner when nip44Encrypt/nip44Decrypt are\n * present. This module must not import src/signer/.\n */\nexport type Nip59Crypto = {\n getPublicKey: () => Promise<string>;\n signEvent: (unsigned: UnsignedEvent) => Promise<Event>;\n nip44Encrypt: (peer: string, plaintext: string) => Promise<string>;\n nip44Decrypt: (peer: string, payload: string) => Promise<string>;\n};\n\nexport type Nip44Decryptor = {\n nip44Decrypt: (peer: string, payload: string) => Promise<string>;\n};\n\nexport type GiftWrapTimestamps = {\n readonly seal: number;\n readonly wrap: number;\n};\n\n/** `\"wrap\"` randomizes only the gift wrap; `\"seal+wrap\"` (default) randomizes both. */\nexport type TimestampRandomize = \"wrap\" | \"seal+wrap\";\n\nexport type WrapOptions = {\n /** Unix seconds used as the randomization window end. Default: floor(Date.now()/1000). */\n readonly now?: number | undefined;\n /** Uniform integer in [0, maxExclusive). Default: CSPRNG via @noble/hashes randomBytes. */\n readonly randomInt?: ((maxExclusive: number) => number) | undefined;\n /** When set, used as-is. Overrides now/randomInt for this call. */\n readonly timestamps?: GiftWrapTimestamps | undefined;\n readonly relayHint?: string | undefined;\n /** Appended after the required wrap `p` tag. Never applied to the seal. */\n readonly extraTags?: ReadonlyArray<Tag> | undefined;\n /**\n * NIP-17 disappearing-message expiry (unix seconds): sets `[\"expiration\", ts]` on the seal and on\n * the gift wrap, signalling relays to discard the wrap after that time.\n */\n readonly expiration?: number | undefined;\n /** Use ephemeral gift wrap kind 21059 instead of 1059 (NIP-59; relays MUST NOT store). */\n readonly ephemeral?: boolean | undefined;\n /**\n * Default `\"seal+wrap\"` (NIP-59). `\"wrap\"` = only wrap timestamp is randomized; seal uses\n * rumor.created_at.\n */\n readonly randomize?: TimestampRandomize | undefined;\n};\n\nexport type SealOptions = Pick<\n WrapOptions,\n \"now\" | \"randomInt\" | \"timestamps\" | \"expiration\" | \"randomize\"\n>;\n\nexport class Nip59Error extends NostrError {\n override name = \"Nip59Error\";\n}\n\nexport const TWO_DAYS_SECS: number = 2 * 24 * 60 * 60;\n\nexport function isGiftWrapKind(kind: number): boolean {\n return kind === Kind.GiftWrap || kind === Kind.GiftWrapEphemeral;\n}\n\ntype Nip59CryptoInput = {\n getPublicKey: () => Promise<string>;\n signEvent: (unsigned: UnsignedEvent) => Promise<Event>;\n nip44Encrypt?: (peer: string, plaintext: string) => Promise<string>;\n nip44Decrypt?: (peer: string, payload: string) => Promise<string>;\n};\n\n/** Narrow a signer-shaped object to {@link Nip59Crypto}, or throw. */\nexport function requireNip59Crypto(crypto: Nip59CryptoInput): Nip59Crypto {\n const encrypt = crypto.nip44Encrypt;\n const decrypt = crypto.nip44Decrypt;\n if (!encrypt || !decrypt) {\n throw new Nip59Error(\"NIP-44 is required\");\n }\n return {\n getPublicKey: async () => crypto.getPublicKey(),\n signEvent: async (unsigned) => crypto.signEvent(unsigned),\n nip44Encrypt: async (peer, plaintext) => encrypt.call(crypto, peer, plaintext),\n nip44Decrypt: async (peer, payload) => decrypt.call(crypto, peer, payload),\n };\n}\n\nexport function requireNip44Decryptor(crypto: {\n nip44Decrypt?: (peer: string, payload: string) => Promise<string>;\n}): Nip44Decryptor {\n const decrypt = crypto.nip44Decrypt;\n if (!decrypt) {\n throw new Nip59Error(\"NIP-44 is required\");\n }\n return {\n nip44Decrypt: async (peer, payload) => decrypt.call(crypto, peer, payload),\n };\n}\n\nexport function rumorToJson(rumor: Rumor): string {\n return JSON.stringify({\n id: rumor.id,\n pubkey: rumor.pubkey,\n created_at: rumor.created_at,\n kind: rumor.kind,\n tags: rumor.tags,\n content: rumor.content,\n });\n}\n\nexport function eventToJson(event: Event): string {\n return JSON.stringify({\n id: event.id,\n pubkey: event.pubkey,\n created_at: event.created_at,\n kind: event.kind,\n tags: event.tags,\n content: event.content,\n sig: event.sig,\n });\n}\n\nfunction assertExpiration(ts: number): number {\n if (!Number.isSafeInteger(ts) || ts < 0) {\n throw new Nip59Error(\"expiration must be a non-negative safe integer\");\n }\n return ts;\n}\n\nfunction defaultRandomInt(maxExclusive: number): number {\n if (!Number.isSafeInteger(maxExclusive) || maxExclusive <= 0 || maxExclusive > 0x100000000) {\n throw new Nip59Error(\"randomInt bound must be a positive integer within uint32 range\");\n }\n // rejection sampling: discard draws ≥ the largest multiple of maxExclusive that fits uint32\n const limit = Math.floor(0x100000000 / maxExclusive) * maxExclusive;\n for (;;) {\n const bytes = randomBytes(4);\n const n = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength).getUint32(0);\n if (n < limit) {\n return n % maxExclusive;\n }\n }\n}\n\nexport function randomPastTimestamp(opts?: {\n now?: number | undefined;\n randomInt?: ((maxExclusive: number) => number) | undefined;\n}): number {\n const now = opts?.now ?? nowSeconds();\n const offset = (opts?.randomInt ?? defaultRandomInt)(TWO_DAYS_SECS);\n return now - offset;\n}\n\nexport function createRumor(\n pubkey: string,\n template: {\n kind: number;\n content?: string | undefined;\n tags?: ReadonlyArray<Tag> | undefined;\n created_at?: number | undefined;\n },\n): Rumor {\n const unsigned: UnsignedEvent = {\n kind: template.kind,\n content: template.content ?? \"\",\n tags: template.tags ? [...template.tags] : [],\n created_at: template.created_at ?? nowSeconds(),\n pubkey: assertHex32(pubkey, \"public key\"),\n };\n return { ...unsigned, id: getEventHash(unsigned) };\n}\n\nexport async function createSeal(\n crypto: Nip59Crypto,\n recipient: string,\n rumor: Rumor,\n opts?: SealOptions,\n): Promise<Event> {\n const recipientPk = assertHex32(recipient, \"public key\");\n let content: string;\n try {\n content = await crypto.nip44Encrypt(recipientPk, rumorToJson(rumor));\n } catch (error) {\n throw new Nip59Error(\"failed to encrypt\", { cause: error });\n }\n const created_at =\n opts?.timestamps?.seal ??\n (opts?.randomize === \"wrap\" ? rumor.created_at : randomPastTimestamp(opts));\n const tags: Tag[] = [];\n if (opts?.expiration !== undefined) {\n tags.push([\"expiration\", String(assertExpiration(opts.expiration))]);\n }\n const pubkey = await crypto.getPublicKey();\n return crypto.signEvent({\n kind: Kind.Seal,\n content,\n created_at,\n tags,\n pubkey,\n });\n}\n\nexport function createGiftWrap(seal: Event, recipient: string, opts?: WrapOptions): Event {\n const recipientPk = assertHex32(recipient, \"public key\");\n const ephemeral = Keys.generate();\n const content = encryptToPubkey(eventToJson(seal), ephemeral.secretKey.bytes, recipientPk);\n const created_at = opts?.timestamps?.wrap ?? randomPastTimestamp(opts);\n const tags: Tag[] = [TagBuilder.p(recipientPk, opts?.relayHint)];\n if (opts?.expiration !== undefined) {\n tags.push([\"expiration\", String(assertExpiration(opts.expiration))]);\n }\n tags.push(...(opts?.extraTags ?? []));\n return finalizeEvent(\n {\n kind: opts?.ephemeral === true ? Kind.GiftWrapEphemeral : Kind.GiftWrap,\n content,\n created_at,\n tags,\n },\n ephemeral.secretKey,\n );\n}\n\nexport async function wrap(\n crypto: Nip59Crypto,\n recipient: string,\n rumor: Rumor,\n opts?: WrapOptions,\n): Promise<Event> {\n const seal = await createSeal(crypto, recipient, rumor, opts);\n return createGiftWrap(seal, recipient, opts);\n}\n\nfunction parseJson(text: string): unknown {\n try {\n return JSON.parse(text);\n } catch (error) {\n throw new Nip59Error(\"invalid JSON\", { cause: error });\n }\n}\n\nfunction parseRumor(value: unknown): Rumor {\n if (!isRecord(value)) {\n throw new Nip59Error(\"invalid rumor\");\n }\n if (Object.hasOwn(value, \"sig\")) {\n throw new Nip59Error(\"rumor must be unsigned\");\n }\n const givenId = value[\"id\"];\n if (!validateEvent(value)) {\n throw new Nip59Error(\"invalid rumor\");\n }\n const unsigned: UnsignedEvent = {\n kind: value.kind,\n tags: value.tags,\n content: value.content,\n created_at: value.created_at,\n pubkey: value.pubkey.toLowerCase(),\n };\n const expected = getEventHash(unsigned);\n if (typeof givenId === \"string\" && givenId.toLowerCase() !== expected) {\n throw new Nip59Error(\"invalid rumor\");\n }\n return { ...unsigned, id: expected };\n}\n\nasync function decryptLayer(\n crypto: Nip44Decryptor,\n peer: string,\n payload: string,\n): Promise<string> {\n try {\n return await crypto.nip44Decrypt(peer, payload);\n } catch (error) {\n throw new Nip59Error(\"failed to decrypt\", { cause: error });\n }\n}\n\nexport async function unwrap(crypto: Nip44Decryptor, giftWrap: Event): Promise<Rumor> {\n const decryptor = requireNip44Decryptor(crypto);\n if (!validateSignedEvent(giftWrap) || !isGiftWrapKind(giftWrap.kind)) {\n throw new Nip59Error(\"expected gift wrap\");\n }\n if (!verifyEvent(giftWrap)) {\n throw new Nip59Error(\"gift wrap signature\");\n }\n\n const sealJson = await decryptLayer(decryptor, giftWrap.pubkey, giftWrap.content);\n const sealRaw = parseJson(sealJson);\n if (!validateSignedEvent(sealRaw)) {\n throw new Nip59Error(\"expected seal\");\n }\n if (sealRaw.kind !== Kind.Seal) {\n throw new Nip59Error(\"expected seal\");\n }\n // NIP-17: seals may carry an [\"expiration\", <unix ts>] tag for disappearing messages.\n for (const tag of sealRaw.tags) {\n if (tag[0] !== \"expiration\" || typeof tag[1] !== \"string\" || !/^\\d+$/.test(tag[1])) {\n throw new Nip59Error(\"seal tags must be empty\");\n }\n }\n if (!verifyEvent(sealRaw)) {\n throw new Nip59Error(\"seal signature\");\n }\n\n const rumorJson = await decryptLayer(decryptor, sealRaw.pubkey, sealRaw.content);\n const rumor = parseRumor(parseJson(rumorJson));\n if (sealRaw.pubkey.toLowerCase() !== rumor.pubkey.toLowerCase()) {\n throw new Nip59Error(\"seal pubkey does not match rumor pubkey\");\n }\n return rumor;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"nip59.mjs","names":["TagBuilder"],"sources":["../../src/nips/nip59.ts"],"sourcesContent":["/**\n * NIP-59: Gift Wrap. Rumor → NIP-44 seal (kind 13) → gift wrap (kind 1059). Does not import signer,\n * relay, or client. Crypto is a structural type.\n *\n * @see https://github.com/nostr-protocol/nips/blob/master/59.md\n */\nimport { secp256k1 } from \"@noble/curves/secp256k1.js\";\nimport { randomBytes } from \"@noble/hashes/utils.js\";\n\nimport { NostrError } from \"../core/error.ts\";\nimport type { Event, UnsignedEvent } from \"../core/event.ts\";\nimport { getEventHash, validateEvent, validateSignedEvent } from \"../core/event.ts\";\nimport { Keys, SecretKey, signEvent } from \"../core/key.ts\";\nimport { Kind } from \"../core/kind.ts\";\nimport type { Tag } from \"../core/tag.ts\";\nimport { Tag as TagBuilder } from \"../core/tag.ts\";\nimport { assertHex32, isRecord, nowSeconds } from \"../core/util.ts\";\nimport { verifyEvent } from \"../core/verifier.ts\";\nimport { encrypt, getConversationKey } from \"./nip44.ts\";\n\n/** Unsigned event with a computed id. Never has `sig`. */\nexport type Rumor = UnsignedEvent & {\n readonly id: string;\n};\n\n/**\n * Structural crypto used by NIP-59. Satisfied by NostrSigner when nip44Encrypt/nip44Decrypt are\n * present. This module must not import src/signer/.\n */\nexport type Nip59Crypto = {\n getPublicKey: () => Promise<string>;\n signEvent: (unsigned: UnsignedEvent) => Promise<Event>;\n nip44Encrypt: (peer: string, plaintext: string) => Promise<string>;\n nip44Decrypt: (peer: string, payload: string) => Promise<string>;\n};\n\nexport type Nip44Decryptor = {\n nip44Decrypt: (peer: string, payload: string) => Promise<string>;\n};\n\nexport type GiftWrapTimestamps = {\n readonly seal: number;\n readonly wrap: number;\n};\n\n/** `\"wrap\"` randomizes only the gift wrap; `\"seal+wrap\"` (default) randomizes both. */\nexport type TimestampRandomize = \"wrap\" | \"seal+wrap\";\n\nexport type WrapOptions = {\n /** Unix seconds used as the randomization window end. Default: floor(Date.now()/1000). */\n readonly now?: number | undefined;\n /**\n * Entropy source replacing @noble/hashes `randomBytes` for everything `wrap`/`createGiftWrap`\n * draws — in stream order: the ephemeral secret key (32-byte draws, redrawn while not a valid\n * scalar), the timestamp offset (4-byte big-endian u32 draws under the [0, 2 days) rejection\n * bound), the NIP-44 nonce (32 bytes), and the BIP-340 auxiliary randomness (32 bytes). In\n * `createSeal` only the timestamp offset is drawn here; the seal's nonce and aux belong to the\n * injected {@link Nip59Crypto}. `wrap` = seal draws then gift-wrap draws. Default: noble\n * `randomBytes`.\n */\n readonly randomBytes?: ((n: number) => Uint8Array) | undefined;\n /** When set, used as-is. Overrides now/randomBytes for this call. */\n readonly timestamps?: GiftWrapTimestamps | undefined;\n readonly relayHint?: string | undefined;\n /** Appended after the required wrap `p` tag. Never applied to the seal. */\n readonly extraTags?: ReadonlyArray<Tag> | undefined;\n /**\n * NIP-17 disappearing-message expiry (unix seconds): sets `[\"expiration\", ts]` on the seal and on\n * the gift wrap, signalling relays to discard the wrap after that time.\n */\n readonly expiration?: number | undefined;\n /** Use ephemeral gift wrap kind 21059 instead of 1059 (NIP-59; relays MUST NOT store). */\n readonly ephemeral?: boolean | undefined;\n /**\n * Default `\"seal+wrap\"` (NIP-59). `\"wrap\"` = only wrap timestamp is randomized; seal uses\n * rumor.created_at.\n */\n readonly randomize?: TimestampRandomize | undefined;\n};\n\nexport type SealOptions = Pick<\n WrapOptions,\n \"now\" | \"randomBytes\" | \"timestamps\" | \"expiration\" | \"randomize\"\n>;\n\nexport class Nip59Error extends NostrError {\n override name = \"Nip59Error\";\n}\n\nexport const TWO_DAYS_SECS: number = 2 * 24 * 60 * 60;\n\nexport function isGiftWrapKind(kind: number): boolean {\n return kind === Kind.GiftWrap || kind === Kind.GiftWrapEphemeral;\n}\n\ntype Nip59CryptoInput = {\n getPublicKey: () => Promise<string>;\n signEvent: (unsigned: UnsignedEvent) => Promise<Event>;\n nip44Encrypt?: (peer: string, plaintext: string) => Promise<string>;\n nip44Decrypt?: (peer: string, payload: string) => Promise<string>;\n};\n\n/** Narrow a signer-shaped object to {@link Nip59Crypto}, or throw. */\nexport function requireNip59Crypto(crypto: Nip59CryptoInput): Nip59Crypto {\n const encrypt = crypto.nip44Encrypt;\n const decrypt = crypto.nip44Decrypt;\n if (!encrypt || !decrypt) {\n throw new Nip59Error(\"NIP-44 is required\");\n }\n return {\n getPublicKey: async () => crypto.getPublicKey(),\n signEvent: async (unsigned) => crypto.signEvent(unsigned),\n nip44Encrypt: async (peer, plaintext) => encrypt.call(crypto, peer, plaintext),\n nip44Decrypt: async (peer, payload) => decrypt.call(crypto, peer, payload),\n };\n}\n\nexport function requireNip44Decryptor(crypto: {\n nip44Decrypt?: (peer: string, payload: string) => Promise<string>;\n}): Nip44Decryptor {\n const decrypt = crypto.nip44Decrypt;\n if (!decrypt) {\n throw new Nip59Error(\"NIP-44 is required\");\n }\n return {\n nip44Decrypt: async (peer, payload) => decrypt.call(crypto, peer, payload),\n };\n}\n\nexport function rumorToJson(rumor: Rumor): string {\n return JSON.stringify({\n id: rumor.id,\n pubkey: rumor.pubkey,\n created_at: rumor.created_at,\n kind: rumor.kind,\n tags: rumor.tags,\n content: rumor.content,\n });\n}\n\nexport function eventToJson(event: Event): string {\n return JSON.stringify({\n id: event.id,\n pubkey: event.pubkey,\n created_at: event.created_at,\n kind: event.kind,\n tags: event.tags,\n content: event.content,\n sig: event.sig,\n });\n}\n\nfunction assertExpiration(ts: number): number {\n if (!Number.isSafeInteger(ts) || ts < 0) {\n throw new Nip59Error(\"expiration must be a non-negative safe integer\");\n }\n return ts;\n}\n\nfunction defaultRandomInt(maxExclusive: number, drawBytes: (n: number) => Uint8Array): number {\n if (!Number.isSafeInteger(maxExclusive) || maxExclusive <= 0 || maxExclusive > 0x100000000) {\n throw new Nip59Error(\"random bound must be a positive integer within uint32 range\");\n }\n // rejection sampling: discard draws ≥ the largest multiple of maxExclusive that fits uint32\n const limit = Math.floor(0x100000000 / maxExclusive) * maxExclusive;\n for (;;) {\n const bytes = drawBytes(4);\n const n = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength).getUint32(0);\n if (n < limit) {\n return n % maxExclusive;\n }\n }\n}\n\nexport function randomPastTimestamp(opts?: {\n now?: number | undefined;\n randomBytes?: ((n: number) => Uint8Array) | undefined;\n}): number {\n const now = opts?.now ?? nowSeconds();\n const offset = defaultRandomInt(TWO_DAYS_SECS, opts?.randomBytes ?? randomBytes);\n return now - offset;\n}\n\nexport function createRumor(\n pubkey: string,\n template: {\n kind: number;\n content?: string | undefined;\n tags?: ReadonlyArray<Tag> | undefined;\n created_at?: number | undefined;\n },\n): Rumor {\n const unsigned: UnsignedEvent = {\n kind: template.kind,\n content: template.content ?? \"\",\n tags: template.tags ? [...template.tags] : [],\n created_at: template.created_at ?? nowSeconds(),\n pubkey: assertHex32(pubkey, \"public key\"),\n };\n return { ...unsigned, id: getEventHash(unsigned) };\n}\n\nexport async function createSeal(\n crypto: Nip59Crypto,\n recipient: string,\n rumor: Rumor,\n opts?: SealOptions,\n): Promise<Event> {\n const recipientPk = assertHex32(recipient, \"public key\");\n // The timestamp offset is drawn from `opts.randomBytes` before the crypto's nonce and aux so\n // a single recorded entropy stream replays in a fixed order: offset → nonce → aux.\n const created_at =\n opts?.timestamps?.seal ??\n (opts?.randomize === \"wrap\" ? rumor.created_at : randomPastTimestamp(opts));\n let content: string;\n try {\n content = await crypto.nip44Encrypt(recipientPk, rumorToJson(rumor));\n } catch (error) {\n throw new Nip59Error(\"failed to encrypt\", { cause: error });\n }\n const tags: Tag[] = [];\n if (opts?.expiration !== undefined) {\n tags.push([\"expiration\", String(assertExpiration(opts.expiration))]);\n }\n const pubkey = await crypto.getPublicKey();\n return crypto.signEvent({\n kind: Kind.Seal,\n content,\n created_at,\n tags,\n pubkey,\n });\n}\n\nexport function createGiftWrap(seal: Event, recipient: string, opts?: WrapOptions): Event {\n const recipientPk = assertHex32(recipient, \"public key\");\n const drawBytes = opts?.randomBytes ?? randomBytes;\n // Ephemeral key: redraw 32-byte candidates until a valid scalar. Stream order: key → offset →\n // nonce → aux. The candidates, the ephemeral secret key, and the conversation key are wiped\n // after use.\n let secretKey: SecretKey;\n for (;;) {\n const candidate = drawBytes(32);\n if (secp256k1.utils.isValidSecretKey(candidate)) {\n secretKey = SecretKey.fromBytes(candidate);\n candidate.fill(0);\n break;\n }\n candidate.fill(0);\n }\n try {\n const ephemeral = Keys.fromSecretKey(secretKey);\n const created_at =\n opts?.timestamps?.wrap ?? randomPastTimestamp({ ...opts, randomBytes: drawBytes });\n const secretBytes = secretKey.bytes;\n let conversationKey: Uint8Array | undefined;\n try {\n conversationKey = getConversationKey(secretBytes, recipientPk);\n const content = encrypt(eventToJson(seal), conversationKey, drawBytes(32));\n const tags: Tag[] = [TagBuilder.p(recipientPk, opts?.relayHint)];\n if (opts?.expiration !== undefined) {\n tags.push([\"expiration\", String(assertExpiration(opts.expiration))]);\n }\n tags.push(...(opts?.extraTags ?? []));\n return signEvent(\n {\n kind: opts?.ephemeral === true ? Kind.GiftWrapEphemeral : Kind.GiftWrap,\n content,\n created_at,\n tags,\n pubkey: ephemeral.publicKey,\n },\n ephemeral,\n drawBytes(32),\n );\n } finally {\n secretBytes.fill(0);\n conversationKey?.fill(0);\n }\n } finally {\n secretKey.zeroize();\n }\n}\n\nexport async function wrap(\n crypto: Nip59Crypto,\n recipient: string,\n rumor: Rumor,\n opts?: WrapOptions,\n): Promise<Event> {\n const seal = await createSeal(crypto, recipient, rumor, opts);\n return createGiftWrap(seal, recipient, opts);\n}\n\nfunction parseJson(text: string): unknown {\n try {\n return JSON.parse(text);\n } catch (error) {\n throw new Nip59Error(\"invalid JSON\", { cause: error });\n }\n}\n\nfunction parseRumor(value: unknown): Rumor {\n if (!isRecord(value)) {\n throw new Nip59Error(\"invalid rumor\");\n }\n if (Object.hasOwn(value, \"sig\")) {\n throw new Nip59Error(\"rumor must be unsigned\");\n }\n const givenId = value[\"id\"];\n if (!validateEvent(value)) {\n throw new Nip59Error(\"invalid rumor\");\n }\n const unsigned: UnsignedEvent = {\n kind: value.kind,\n tags: value.tags,\n content: value.content,\n created_at: value.created_at,\n pubkey: value.pubkey.toLowerCase(),\n };\n const expected = getEventHash(unsigned);\n if (typeof givenId === \"string\" && givenId.toLowerCase() !== expected) {\n throw new Nip59Error(\"invalid rumor\");\n }\n return { ...unsigned, id: expected };\n}\n\nasync function decryptLayer(\n crypto: Nip44Decryptor,\n peer: string,\n payload: string,\n): Promise<string> {\n try {\n return await crypto.nip44Decrypt(peer, payload);\n } catch (error) {\n throw new Nip59Error(\"failed to decrypt\", { cause: error });\n }\n}\n\nexport async function unwrap(crypto: Nip44Decryptor, giftWrap: Event): Promise<Rumor> {\n const decryptor = requireNip44Decryptor(crypto);\n if (!validateSignedEvent(giftWrap) || !isGiftWrapKind(giftWrap.kind)) {\n throw new Nip59Error(\"expected gift wrap\");\n }\n if (!verifyEvent(giftWrap)) {\n throw new Nip59Error(\"gift wrap signature\");\n }\n\n const sealJson = await decryptLayer(decryptor, giftWrap.pubkey, giftWrap.content);\n const sealRaw = parseJson(sealJson);\n if (!validateSignedEvent(sealRaw)) {\n throw new Nip59Error(\"expected seal\");\n }\n if (sealRaw.kind !== Kind.Seal) {\n throw new Nip59Error(\"expected seal\");\n }\n // NIP-17: seals may carry an [\"expiration\", <unix ts>] tag for disappearing messages.\n for (const tag of sealRaw.tags) {\n if (tag[0] !== \"expiration\" || typeof tag[1] !== \"string\" || !/^\\d+$/.test(tag[1])) {\n throw new Nip59Error(\"seal tags must be empty\");\n }\n }\n if (!verifyEvent(sealRaw)) {\n throw new Nip59Error(\"seal signature\");\n }\n\n const rumorJson = await decryptLayer(decryptor, sealRaw.pubkey, sealRaw.content);\n const rumor = parseRumor(parseJson(rumorJson));\n if (sealRaw.pubkey.toLowerCase() !== rumor.pubkey.toLowerCase()) {\n throw new Nip59Error(\"seal pubkey does not match rumor pubkey\");\n }\n return rumor;\n}\n"],"mappings":"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"}
|
package/dist/nips/nip65.d.mts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as relayListEventBuilder, i as readRelays, n as RelayMarker, o as relayListToTags, r as parseRelayList, s as writeRelays, t as RelayListItem } from "../nip65-
|
|
1
|
+
import { a as relayListEventBuilder, i as readRelays, n as RelayMarker, o as relayListToTags, r as parseRelayList, s as writeRelays, t as RelayListItem } from "../nip65-CPNE0SV0.mjs";
|
|
2
2
|
export { RelayListItem, RelayMarker, parseRelayList, readRelays, relayListEventBuilder, relayListToTags, writeRelays };
|
package/dist/nips/nip65.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { t as Kind } from "../kind-
|
|
3
|
-
import { t as EventBuilder } from "../builder-
|
|
1
|
+
import { f as normalizeURL, v as EventValidationError } from "../util-DfsyJ9nz.mjs";
|
|
2
|
+
import { t as Kind } from "../kind-I2Tvbe82.mjs";
|
|
3
|
+
import { t as EventBuilder } from "../builder-CGgM_QHT.mjs";
|
|
4
4
|
//#region src/nips/nip65.ts
|
|
5
5
|
/** Parse a kind:10002 NIP-65 event into relay list entries. */
|
|
6
6
|
function parseRelayList(event) {
|
package/dist/nips/nip77.d.mts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as NegentropyStorageVector, c as ReconcileOutcome, d as storageFromItems, i as Negentropy, l as runNegSession, n as MAX_NEG_ROUNDS, o as Nip77Error, r as NegItem, s as PROTOCOL_VERSION, t as DEFAULT_FRAME_SIZE_LIMIT, u as storageFromEvents } from "../nip77-
|
|
1
|
+
import { a as NegentropyStorageVector, c as ReconcileOutcome, d as storageFromItems, i as Negentropy, l as runNegSession, n as MAX_NEG_ROUNDS, o as Nip77Error, r as NegItem, s as PROTOCOL_VERSION, t as DEFAULT_FRAME_SIZE_LIMIT, u as storageFromEvents } from "../nip77-DhpoL7d8.mjs";
|
|
2
2
|
export { DEFAULT_FRAME_SIZE_LIMIT, MAX_NEG_ROUNDS, NegItem, Negentropy, NegentropyStorageVector, Nip77Error, PROTOCOL_VERSION, ReconcileOutcome, runNegSession, storageFromEvents, storageFromItems };
|
package/dist/nips/nip77.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { i as bytesToHex, n as assertHex32,
|
|
1
|
+
import { i as bytesToHex, n as assertHex32, s as hexToBytes, x as NostrError } from "../util-DfsyJ9nz.mjs";
|
|
2
2
|
import { sha256 } from "@noble/hashes/sha2.js";
|
|
3
3
|
//#region src/nips/nip77.ts
|
|
4
4
|
/**
|
package/dist/nips/nip77.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"nip77.mjs","names":[],"sources":["../../src/nips/nip77.ts"],"sourcesContent":["/**\n * NIP-77: Negentropy Syncing. Transport-free V1 algorithm. Does not import relay or client.\n *\n * @see https://github.com/nostr-protocol/nips/blob/master/77.md\n */\nimport { sha256 } from \"@noble/hashes/sha2.js\";\n\nimport { NostrError } from \"../core/error.ts\";\nimport type { Event } from \"../core/event.ts\";\nimport { assertHex32, bytesToHex, hexToBytes } from \"../core/util.ts\";\n\nexport const PROTOCOL_VERSION = 0x61;\nexport const DEFAULT_FRAME_SIZE_LIMIT = 60_000;\nexport const MAX_NEG_ROUNDS = 1024;\n\nconst ID_SIZE = 32;\nconst FINGERPRINT_SIZE = 16;\nconst INFINITY = Number.MAX_VALUE;\nconst BUCKETS = 16;\nconst ID_LIST_THRESHOLD = BUCKETS * 2;\n\nconst Mode = {\n Skip: 0,\n Fingerprint: 1,\n IdList: 2,\n} as const;\n\nexport class Nip77Error extends NostrError {\n override name = \"Nip77Error\";\n}\n\nexport type NegItem = {\n readonly timestamp: number;\n readonly id: Uint8Array;\n};\n\nexport type ReconcileOutcome = {\n /** Event ids the local side holds that the peer does not. */\n readonly have: ReadonlyArray<string>;\n /** Event ids the peer holds that the local side does not. */\n readonly need: ReadonlyArray<string>;\n /** Next NEG-MSG hex, or undefined when the session converged. */\n readonly nextMessage: string | undefined;\n};\n\n/**\n * Byte buffer that is either an append target (`extend`/`unwrap`, `#pos` stays 0) or a read cursor\n * (`read`/`readN` advance `#pos`). `length` is bytes remaining ahead of `#pos`.\n */\nclass EncodedBuf {\n #raw: Uint8Array;\n #pos = 0;\n #end = 0;\n\n constructor(buffer?: Uint8Array | number) {\n if (typeof buffer === \"number\") {\n this.#raw = new Uint8Array(buffer);\n } else if (buffer) {\n this.#raw = new Uint8Array(buffer);\n this.#end = buffer.length;\n } else {\n this.#raw = new Uint8Array(512);\n }\n }\n\n get length(): number {\n return this.#end - this.#pos;\n }\n\n /** Unread (or written) bytes — never a live view for appends after reads. */\n unwrap(): Uint8Array {\n return this.#raw.subarray(this.#pos, this.#end);\n }\n\n get capacity(): number {\n return this.#raw.byteLength;\n }\n\n extend(buf: Uint8Array | EncodedBuf): void {\n const bytes = buf instanceof EncodedBuf ? buf.unwrap() : buf;\n const targetSize = bytes.length + this.#end;\n if (this.capacity < targetSize) {\n const old = this.#raw;\n const next = new Uint8Array(Math.max(this.capacity * 2, targetSize));\n next.set(old);\n this.#raw = next;\n }\n this.#raw.set(bytes, this.#end);\n this.#end += bytes.length;\n }\n\n read(): number {\n if (this.length === 0) {\n throw new Nip77Error(\"parse ends prematurely\");\n }\n const first = this.#raw[this.#pos];\n if (first === undefined) {\n throw new Nip77Error(\"parse ends prematurely\");\n }\n this.#pos += 1;\n return first;\n }\n\n readN(n: number): Uint8Array {\n if (this.length < n) {\n throw new Nip77Error(\"parse ends prematurely\");\n }\n const head = this.#raw.subarray(this.#pos, this.#pos + n);\n this.#pos += n;\n return head;\n }\n}\n\nfunction decodeVarInt(buf: EncodedBuf): number {\n let res = 0;\n for (;;) {\n const byte = buf.read();\n res = res * 128 + (byte & 0x7f);\n if (res > Number.MAX_SAFE_INTEGER) {\n throw new Nip77Error(\"varint exceeds max safe integer\");\n }\n if ((byte & 0x80) === 0) {\n break;\n }\n }\n return res;\n}\n\nfunction encodeVarInt(n: number): EncodedBuf {\n if (!Number.isSafeInteger(n) || n < 0) {\n throw new Nip77Error(\"varint must be a non-negative safe integer\");\n }\n if (n === 0) {\n return new EncodedBuf(new Uint8Array([0]));\n }\n const digits: number[] = [];\n let value = n;\n while (value !== 0) {\n digits.push(value % 128);\n value = Math.floor(value / 128);\n }\n digits.reverse();\n const encoded = digits.map((digit, i) => (i === digits.length - 1 ? digit : digit | 0x80));\n return new EncodedBuf(new Uint8Array(encoded));\n}\n\nfunction getBytes(buf: EncodedBuf, n: number): Uint8Array {\n return buf.readN(n);\n}\n\nclass Accumulator {\n readonly #buf: Uint8Array;\n\n constructor() {\n this.#buf = new Uint8Array(ID_SIZE);\n }\n\n add(other: Uint8Array): void {\n const view = new DataView(this.#buf.buffer, this.#buf.byteOffset, this.#buf.byteLength);\n const otherView = new DataView(other.buffer, other.byteOffset, other.byteLength);\n let carry = 0;\n for (let i = 0; i < 8; i++) {\n const offset = i * 4;\n const next = view.getUint32(offset, true) + carry + otherView.getUint32(offset, true);\n carry = next > 0xffffffff ? 1 : 0;\n view.setUint32(offset, next >>> 0, true);\n }\n }\n\n fingerprint(n: number): Uint8Array {\n const input = new EncodedBuf();\n input.extend(this.#buf);\n input.extend(encodeVarInt(n));\n return sha256(input.unwrap()).subarray(0, FINGERPRINT_SIZE);\n }\n}\n\nfunction compareBytes(a: Uint8Array, b: Uint8Array): number {\n const n = Math.min(a.byteLength, b.byteLength);\n for (let i = 0; i < n; i++) {\n const x = a[i];\n const y = b[i];\n if (x === undefined || y === undefined) {\n break;\n }\n if (x < y) {\n return -1;\n }\n if (x > y) {\n return 1;\n }\n }\n if (a.byteLength > b.byteLength) {\n return 1;\n }\n if (a.byteLength < b.byteLength) {\n return -1;\n }\n return 0;\n}\n\nfunction compareNegItems(a: NegItem, b: NegItem): number {\n if (a.timestamp !== b.timestamp) {\n return a.timestamp - b.timestamp;\n }\n return compareBytes(a.id, b.id);\n}\n\nexport class NegentropyStorageVector {\n readonly #items: NegItem[] = [];\n #sealed = false;\n\n insert(timestamp: number, id: string): void {\n if (this.#sealed) {\n throw new Nip77Error(\"already sealed\");\n }\n if (timestamp === INFINITY) {\n throw new Nip77Error(\"timestamp is reserved infinity\");\n }\n const idb = hexToBytes(assertHex32(id, \"event id\"));\n this.#items.push({ timestamp, id: idb });\n }\n\n seal(): void {\n if (this.#sealed) {\n throw new Nip77Error(\"already sealed\");\n }\n this.#sealed = true;\n this.#items.sort(compareNegItems);\n for (let i = 1; i < this.#items.length; i++) {\n const prev = this.#items[i - 1];\n const item = this.#items[i];\n if (prev !== undefined && item !== undefined && compareNegItems(prev, item) === 0) {\n throw new Nip77Error(\"duplicate item inserted\");\n }\n }\n }\n\n size(): number {\n this.#checkSealed();\n return this.#items.length;\n }\n\n iterate(begin: number, end: number, cb: (item: NegItem, i: number) => boolean): void {\n this.#checkSealed();\n this.#checkBounds(begin, end);\n for (let i = begin; i < end; i++) {\n const item = this.#items[i];\n if (item === undefined || !cb(item, i)) {\n break;\n }\n }\n }\n\n findLowerBound(begin: number, end: number, bound: NegItem): number {\n this.#checkSealed();\n this.#checkBounds(begin, end);\n let first = begin;\n let count = end - begin;\n while (count > 0) {\n const step = Math.floor(count / 2);\n const it = first + step;\n const item = this.#items[it];\n if (item === undefined) {\n break;\n }\n if (compareNegItems(item, bound) < 0) {\n first = it + 1;\n count -= step + 1;\n } else {\n count = step;\n }\n }\n return first;\n }\n\n fingerprint(begin: number, end: number): Uint8Array {\n const acc = new Accumulator();\n this.iterate(begin, end, (item) => {\n acc.add(item.id);\n return true;\n });\n return acc.fingerprint(end - begin);\n }\n\n #checkSealed(): void {\n if (!this.#sealed) {\n throw new Nip77Error(\"not sealed\");\n }\n }\n\n #checkBounds(begin: number, end: number): void {\n if (begin > end || end > this.#items.length) {\n throw new Nip77Error(\"bad range\");\n }\n }\n}\n\nexport function storageFromItems(\n items: ReadonlyArray<{ id: string; created_at: number }>,\n): NegentropyStorageVector {\n const storage = new NegentropyStorageVector();\n const seen = new Set<string>();\n for (const item of items) {\n const id = item.id.toLowerCase();\n if (seen.has(id)) {\n continue;\n }\n seen.add(id);\n storage.insert(item.created_at, id);\n }\n storage.seal();\n return storage;\n}\n\nexport function storageFromEvents(\n events: ReadonlyArray<Pick<Event, \"id\" | \"created_at\">>,\n): NegentropyStorageVector {\n return storageFromItems(events);\n}\n\ntype Bound = { timestamp: number; id: Uint8Array };\n\nexport class Negentropy {\n readonly #storage: NegentropyStorageVector;\n readonly #frameSizeLimit: number;\n #initiator = false;\n #lastTimestampIn = 0;\n #lastTimestampOut = 0;\n\n constructor(storage: NegentropyStorageVector, frameSizeLimit: number = DEFAULT_FRAME_SIZE_LIMIT) {\n if (frameSizeLimit !== 0 && frameSizeLimit < 4096) {\n throw new Nip77Error(\"frameSizeLimit too small\");\n }\n this.#storage = storage;\n this.#frameSizeLimit = frameSizeLimit;\n }\n\n initiate(): string {\n this.#initiator = true;\n const output = new EncodedBuf();\n output.extend(new Uint8Array([PROTOCOL_VERSION]));\n this.#splitRange(0, this.#storage.size(), this.#bound(INFINITY), output);\n return bytesToHex(output.unwrap());\n }\n\n reconcile(queryMsg: string): ReconcileOutcome {\n const have: string[] = [];\n const need: string[] = [];\n const query = new EncodedBuf(hexToBytes(queryMsg));\n\n this.#lastTimestampIn = 0;\n this.#lastTimestampOut = 0;\n\n const fullOutput = new EncodedBuf();\n fullOutput.extend(new Uint8Array([PROTOCOL_VERSION]));\n\n const protocolVersion = query.read();\n if (protocolVersion < 0x60 || protocolVersion > 0x6f) {\n throw new Nip77Error(\"invalid negentropy protocol version byte\");\n }\n if (protocolVersion !== PROTOCOL_VERSION) {\n // The initiator is the client: nobody downgrades on its behalf, so a mismatched\n // version is a hard failure. The responder answers with its highest version so the\n // peer can downgrade (NIP-77).\n if (this.#initiator) {\n throw new Nip77Error(\n `unsupported negentropy protocol version ${protocolVersion - 0x60} (peer), we speak ${PROTOCOL_VERSION - 0x60}`,\n );\n }\n return { have, need, nextMessage: bytesToHex(fullOutput.unwrap()) };\n }\n\n const storageSize = this.#storage.size();\n let prevBound = this.#bound(0);\n let prevIndex = 0;\n let skip = false;\n\n while (query.length > 0) {\n const o = new EncodedBuf();\n\n const currBound = this.#decodeBound(query);\n const mode = decodeVarInt(query);\n const lower = prevIndex;\n const upper = this.#storage.findLowerBound(prevIndex, storageSize, currBound);\n\n if (mode === Mode.Skip) {\n skip = true;\n } else if (mode === Mode.Fingerprint) {\n const theirFingerprint = getBytes(query, FINGERPRINT_SIZE);\n const ourFingerprint = this.#storage.fingerprint(lower, upper);\n if (compareBytes(theirFingerprint, ourFingerprint) === 0) {\n skip = true;\n } else {\n if (skip) {\n skip = false;\n this.#encodeSkipInto(o, prevBound);\n }\n this.#splitRange(lower, upper, currBound, o);\n }\n } else if (mode === Mode.IdList) {\n const numIds = decodeVarInt(query);\n const theirElems = new Map<string, Uint8Array>();\n for (let i = 0; i < numIds; i++) {\n const e = getBytes(query, ID_SIZE);\n theirElems.set(bytesToHex(e), e);\n }\n\n if (this.#initiator) {\n skip = true;\n this.#storage.iterate(lower, upper, (item) => {\n const id = bytesToHex(item.id);\n if (theirElems.has(id)) {\n theirElems.delete(id);\n } else {\n have.push(id);\n }\n return true;\n });\n for (const id of theirElems.keys()) {\n need.push(id);\n }\n } else {\n if (skip) {\n skip = false;\n this.#encodeSkipInto(o, prevBound);\n }\n o.extend(this.#encodeBound(currBound));\n o.extend(encodeVarInt(Mode.IdList));\n const ourIds: Uint8Array[] = [];\n this.#storage.iterate(lower, upper, (item) => {\n ourIds.push(item.id);\n return true;\n });\n o.extend(encodeVarInt(ourIds.length));\n for (const id of ourIds) {\n o.extend(id);\n }\n }\n } else {\n throw new Nip77Error(\"unexpected mode\");\n }\n\n if (this.#exceededFrameSizeLimit(fullOutput.length + o.length)) {\n const remainingFingerprint = this.#storage.fingerprint(upper, storageSize);\n fullOutput.extend(this.#encodeBound(this.#bound(INFINITY)));\n fullOutput.extend(encodeVarInt(Mode.Fingerprint));\n fullOutput.extend(remainingFingerprint);\n break;\n }\n fullOutput.extend(o);\n prevIndex = upper;\n prevBound = currBound;\n }\n\n return {\n have,\n need,\n nextMessage: fullOutput.length === 1 ? undefined : bytesToHex(fullOutput.unwrap()),\n };\n }\n\n #bound(timestamp: number, id?: Uint8Array): Bound {\n return { timestamp, id: id ?? new Uint8Array(0) };\n }\n\n #splitRange(lower: number, upper: number, upperBound: Bound, o: EncodedBuf): void {\n const numElems = upper - lower;\n if (numElems < ID_LIST_THRESHOLD) {\n o.extend(this.#encodeBound(upperBound));\n o.extend(encodeVarInt(Mode.IdList));\n o.extend(encodeVarInt(numElems));\n this.#storage.iterate(lower, upper, (item) => {\n o.extend(item.id);\n return true;\n });\n return;\n }\n\n const itemsPerBucket = Math.floor(numElems / BUCKETS);\n const bucketsWithExtra = numElems % BUCKETS;\n let curr = lower;\n for (let i = 0; i < BUCKETS; i++) {\n const bucketSize = itemsPerBucket + (i < bucketsWithExtra ? 1 : 0);\n const ourFingerprint = this.#storage.fingerprint(curr, curr + bucketSize);\n curr += bucketSize;\n let nextBound: Bound;\n if (curr === upper) {\n nextBound = upperBound;\n } else {\n const at = curr - 1;\n let prevItem: NegItem | undefined;\n let currItem: NegItem | undefined;\n this.#storage.iterate(at, at + 2, (item, index) => {\n if (index === at) {\n prevItem = item;\n } else {\n currItem = item;\n }\n return true;\n });\n if (prevItem === undefined || currItem === undefined) {\n throw new Nip77Error(\"missing bound items\");\n }\n nextBound = this.#minimalBound(prevItem, currItem);\n }\n o.extend(this.#encodeBound(nextBound));\n o.extend(encodeVarInt(Mode.Fingerprint));\n o.extend(ourFingerprint);\n }\n }\n\n #exceededFrameSizeLimit(n: number): boolean {\n if (this.#frameSizeLimit === 0) {\n return false;\n }\n return n > this.#frameSizeLimit - 200;\n }\n\n #decodeTimestampIn(encoded: EncodedBuf): number {\n let timestamp = decodeVarInt(encoded);\n timestamp = timestamp === 0 ? INFINITY : timestamp - 1;\n if (this.#lastTimestampIn === INFINITY || timestamp === INFINITY) {\n this.#lastTimestampIn = INFINITY;\n return INFINITY;\n }\n timestamp += this.#lastTimestampIn;\n this.#lastTimestampIn = timestamp;\n return timestamp;\n }\n\n #decodeBound(encoded: EncodedBuf): Bound {\n const timestamp = this.#decodeTimestampIn(encoded);\n const len = decodeVarInt(encoded);\n if (len > ID_SIZE) {\n throw new Nip77Error(\"bound key too long\");\n }\n return { timestamp, id: getBytes(encoded, len) };\n }\n\n #encodeTimestampOut(timestamp: number): EncodedBuf {\n if (timestamp === INFINITY) {\n this.#lastTimestampOut = INFINITY;\n return encodeVarInt(0);\n }\n const delta = timestamp - this.#lastTimestampOut;\n this.#lastTimestampOut = timestamp;\n return encodeVarInt(delta + 1);\n }\n\n #encodeSkipInto(o: EncodedBuf, bound: Bound): void {\n o.extend(this.#encodeBound(bound));\n o.extend(encodeVarInt(Mode.Skip));\n }\n\n #encodeBound(key: Bound): EncodedBuf {\n const output = new EncodedBuf();\n output.extend(this.#encodeTimestampOut(key.timestamp));\n output.extend(encodeVarInt(key.id.length));\n output.extend(key.id);\n return output;\n }\n\n #minimalBound(prev: NegItem, curr: NegItem): Bound {\n if (curr.timestamp !== prev.timestamp) {\n return this.#bound(curr.timestamp);\n }\n let shared = 0;\n for (let i = 0; i < ID_SIZE; i++) {\n if (curr.id[i] !== prev.id[i]) {\n break;\n }\n shared += 1;\n }\n return this.#bound(curr.timestamp, curr.id.subarray(0, shared + 1));\n }\n}\n\n/** Drive initiate/reconcile until nextMessage is null. Transport-free. */\nexport async function runNegSession(opts: {\n storage: NegentropyStorageVector;\n frameSizeLimit?: number;\n openingSend: (hex: string) => void;\n msgSend: (hex: string) => void;\n next: () => Promise<string>;\n}): Promise<{ have: string[]; need: string[] }> {\n const neg = new Negentropy(opts.storage, opts.frameSizeLimit);\n const have = new Set<string>();\n const need = new Set<string>();\n opts.openingSend(neg.initiate());\n for (let round = 0; round < MAX_NEG_ROUNDS; round++) {\n // oxlint-disable-next-line no-await-in-loop -- protocol rounds are sequential by definition\n const out = neg.reconcile(await opts.next());\n for (const id of out.have) {\n have.add(id);\n }\n for (const id of out.need) {\n need.add(id);\n }\n if (out.nextMessage === undefined) {\n return { have: [...have], need: [...need] };\n }\n opts.msgSend(out.nextMessage);\n }\n throw new Nip77Error(\"negentropy exceeded max rounds\");\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"nip77.mjs","names":[],"sources":["../../src/nips/nip77.ts"],"sourcesContent":["/**\n * NIP-77: Negentropy Syncing. Transport-free V1 algorithm. Does not import relay or client.\n *\n * @see https://github.com/nostr-protocol/nips/blob/master/77.md\n */\nimport { sha256 } from \"@noble/hashes/sha2.js\";\n\nimport { NostrError } from \"../core/error.ts\";\nimport type { Event } from \"../core/event.ts\";\nimport { assertHex32, bytesToHex, hexToBytes } from \"../core/util.ts\";\n\nexport const PROTOCOL_VERSION = 0x61;\nexport const DEFAULT_FRAME_SIZE_LIMIT = 60_000;\nexport const MAX_NEG_ROUNDS = 1024;\n\nconst ID_SIZE = 32;\nconst FINGERPRINT_SIZE = 16;\nconst INFINITY = Number.MAX_VALUE;\nconst BUCKETS = 16;\nconst ID_LIST_THRESHOLD = BUCKETS * 2;\n\nconst Mode = {\n Skip: 0,\n Fingerprint: 1,\n IdList: 2,\n} as const;\n\nexport class Nip77Error extends NostrError {\n override name = \"Nip77Error\";\n}\n\nexport type NegItem = {\n readonly timestamp: number;\n readonly id: Uint8Array;\n};\n\nexport type ReconcileOutcome = {\n /** Event ids the local side holds that the peer does not. */\n readonly have: ReadonlyArray<string>;\n /** Event ids the peer holds that the local side does not. */\n readonly need: ReadonlyArray<string>;\n /** Next NEG-MSG hex, or undefined when the session converged. */\n readonly nextMessage: string | undefined;\n};\n\n/**\n * Byte buffer that is either an append target (`extend`/`unwrap`, `#pos` stays 0) or a read cursor\n * (`read`/`readN` advance `#pos`). `length` is bytes remaining ahead of `#pos`.\n */\nclass EncodedBuf {\n #raw: Uint8Array;\n #pos = 0;\n #end = 0;\n\n constructor(buffer?: Uint8Array | number) {\n if (typeof buffer === \"number\") {\n this.#raw = new Uint8Array(buffer);\n } else if (buffer) {\n this.#raw = new Uint8Array(buffer);\n this.#end = buffer.length;\n } else {\n this.#raw = new Uint8Array(512);\n }\n }\n\n get length(): number {\n return this.#end - this.#pos;\n }\n\n /** Unread (or written) bytes — never a live view for appends after reads. */\n unwrap(): Uint8Array {\n return this.#raw.subarray(this.#pos, this.#end);\n }\n\n get capacity(): number {\n return this.#raw.byteLength;\n }\n\n extend(buf: Uint8Array | EncodedBuf): void {\n const bytes = buf instanceof EncodedBuf ? buf.unwrap() : buf;\n const targetSize = bytes.length + this.#end;\n if (this.capacity < targetSize) {\n const old = this.#raw;\n const next = new Uint8Array(Math.max(this.capacity * 2, targetSize));\n next.set(old);\n this.#raw = next;\n }\n this.#raw.set(bytes, this.#end);\n this.#end += bytes.length;\n }\n\n read(): number {\n if (this.length === 0) {\n throw new Nip77Error(\"parse ends prematurely\");\n }\n const first = this.#raw[this.#pos];\n if (first === undefined) {\n throw new Nip77Error(\"parse ends prematurely\");\n }\n this.#pos += 1;\n return first;\n }\n\n readN(n: number): Uint8Array {\n if (this.length < n) {\n throw new Nip77Error(\"parse ends prematurely\");\n }\n const head = this.#raw.subarray(this.#pos, this.#pos + n);\n this.#pos += n;\n return head;\n }\n}\n\nfunction decodeVarInt(buf: EncodedBuf): number {\n let res = 0;\n for (;;) {\n const byte = buf.read();\n res = res * 128 + (byte & 0x7f);\n if (res > Number.MAX_SAFE_INTEGER) {\n throw new Nip77Error(\"varint exceeds max safe integer\");\n }\n if ((byte & 0x80) === 0) {\n break;\n }\n }\n return res;\n}\n\nfunction encodeVarInt(n: number): EncodedBuf {\n if (!Number.isSafeInteger(n) || n < 0) {\n throw new Nip77Error(\"varint must be a non-negative safe integer\");\n }\n if (n === 0) {\n return new EncodedBuf(new Uint8Array([0]));\n }\n const digits: number[] = [];\n let value = n;\n while (value !== 0) {\n digits.push(value % 128);\n value = Math.floor(value / 128);\n }\n digits.reverse();\n const encoded = digits.map((digit, i) => (i === digits.length - 1 ? digit : digit | 0x80));\n return new EncodedBuf(new Uint8Array(encoded));\n}\n\nfunction getBytes(buf: EncodedBuf, n: number): Uint8Array {\n return buf.readN(n);\n}\n\nclass Accumulator {\n readonly #buf: Uint8Array;\n\n constructor() {\n this.#buf = new Uint8Array(ID_SIZE);\n }\n\n add(other: Uint8Array): void {\n const view = new DataView(this.#buf.buffer, this.#buf.byteOffset, this.#buf.byteLength);\n const otherView = new DataView(other.buffer, other.byteOffset, other.byteLength);\n let carry = 0;\n for (let i = 0; i < 8; i++) {\n const offset = i * 4;\n const next = view.getUint32(offset, true) + carry + otherView.getUint32(offset, true);\n carry = next > 0xffffffff ? 1 : 0;\n view.setUint32(offset, next >>> 0, true);\n }\n }\n\n fingerprint(n: number): Uint8Array {\n const input = new EncodedBuf();\n input.extend(this.#buf);\n input.extend(encodeVarInt(n));\n return sha256(input.unwrap()).subarray(0, FINGERPRINT_SIZE);\n }\n}\n\nfunction compareBytes(a: Uint8Array, b: Uint8Array): number {\n const n = Math.min(a.byteLength, b.byteLength);\n for (let i = 0; i < n; i++) {\n const x = a[i];\n const y = b[i];\n if (x === undefined || y === undefined) {\n break;\n }\n if (x < y) {\n return -1;\n }\n if (x > y) {\n return 1;\n }\n }\n if (a.byteLength > b.byteLength) {\n return 1;\n }\n if (a.byteLength < b.byteLength) {\n return -1;\n }\n return 0;\n}\n\nfunction compareNegItems(a: NegItem, b: NegItem): number {\n if (a.timestamp !== b.timestamp) {\n return a.timestamp - b.timestamp;\n }\n return compareBytes(a.id, b.id);\n}\n\nexport class NegentropyStorageVector {\n readonly #items: NegItem[] = [];\n #sealed = false;\n\n insert(timestamp: number, id: string): void {\n if (this.#sealed) {\n throw new Nip77Error(\"already sealed\");\n }\n if (timestamp === INFINITY) {\n throw new Nip77Error(\"timestamp is reserved infinity\");\n }\n const idb = hexToBytes(assertHex32(id, \"event id\"));\n this.#items.push({ timestamp, id: idb });\n }\n\n seal(): void {\n if (this.#sealed) {\n throw new Nip77Error(\"already sealed\");\n }\n this.#sealed = true;\n this.#items.sort(compareNegItems);\n for (let i = 1; i < this.#items.length; i++) {\n const prev = this.#items[i - 1];\n const item = this.#items[i];\n if (prev !== undefined && item !== undefined && compareNegItems(prev, item) === 0) {\n throw new Nip77Error(\"duplicate item inserted\");\n }\n }\n }\n\n size(): number {\n this.#checkSealed();\n return this.#items.length;\n }\n\n iterate(begin: number, end: number, cb: (item: NegItem, i: number) => boolean): void {\n this.#checkSealed();\n this.#checkBounds(begin, end);\n for (let i = begin; i < end; i++) {\n const item = this.#items[i];\n if (item === undefined || !cb(item, i)) {\n break;\n }\n }\n }\n\n findLowerBound(begin: number, end: number, bound: NegItem): number {\n this.#checkSealed();\n this.#checkBounds(begin, end);\n let first = begin;\n let count = end - begin;\n while (count > 0) {\n const step = Math.floor(count / 2);\n const it = first + step;\n const item = this.#items[it];\n if (item === undefined) {\n break;\n }\n if (compareNegItems(item, bound) < 0) {\n first = it + 1;\n count -= step + 1;\n } else {\n count = step;\n }\n }\n return first;\n }\n\n fingerprint(begin: number, end: number): Uint8Array {\n const acc = new Accumulator();\n this.iterate(begin, end, (item) => {\n acc.add(item.id);\n return true;\n });\n return acc.fingerprint(end - begin);\n }\n\n #checkSealed(): void {\n if (!this.#sealed) {\n throw new Nip77Error(\"not sealed\");\n }\n }\n\n #checkBounds(begin: number, end: number): void {\n if (begin > end || end > this.#items.length) {\n throw new Nip77Error(\"bad range\");\n }\n }\n}\n\nexport function storageFromItems(\n items: ReadonlyArray<{ id: string; created_at: number }>,\n): NegentropyStorageVector {\n const storage = new NegentropyStorageVector();\n const seen = new Set<string>();\n for (const item of items) {\n const id = item.id.toLowerCase();\n if (seen.has(id)) {\n continue;\n }\n seen.add(id);\n storage.insert(item.created_at, id);\n }\n storage.seal();\n return storage;\n}\n\nexport function storageFromEvents(\n events: ReadonlyArray<Pick<Event, \"id\" | \"created_at\">>,\n): NegentropyStorageVector {\n return storageFromItems(events);\n}\n\ntype Bound = { timestamp: number; id: Uint8Array };\n\nexport class Negentropy {\n readonly #storage: NegentropyStorageVector;\n readonly #frameSizeLimit: number;\n #initiator = false;\n #lastTimestampIn = 0;\n #lastTimestampOut = 0;\n\n constructor(storage: NegentropyStorageVector, frameSizeLimit: number = DEFAULT_FRAME_SIZE_LIMIT) {\n if (frameSizeLimit !== 0 && frameSizeLimit < 4096) {\n throw new Nip77Error(\"frameSizeLimit too small\");\n }\n this.#storage = storage;\n this.#frameSizeLimit = frameSizeLimit;\n }\n\n initiate(): string {\n this.#initiator = true;\n const output = new EncodedBuf();\n output.extend(new Uint8Array([PROTOCOL_VERSION]));\n this.#splitRange(0, this.#storage.size(), this.#bound(INFINITY), output);\n return bytesToHex(output.unwrap());\n }\n\n reconcile(queryMsg: string): ReconcileOutcome {\n const have: string[] = [];\n const need: string[] = [];\n const query = new EncodedBuf(hexToBytes(queryMsg));\n\n this.#lastTimestampIn = 0;\n this.#lastTimestampOut = 0;\n\n const fullOutput = new EncodedBuf();\n fullOutput.extend(new Uint8Array([PROTOCOL_VERSION]));\n\n const protocolVersion = query.read();\n if (protocolVersion < 0x60 || protocolVersion > 0x6f) {\n throw new Nip77Error(\"invalid negentropy protocol version byte\");\n }\n if (protocolVersion !== PROTOCOL_VERSION) {\n // The initiator is the client: nobody downgrades on its behalf, so a mismatched\n // version is a hard failure. The responder answers with its highest version so the\n // peer can downgrade (NIP-77).\n if (this.#initiator) {\n throw new Nip77Error(\n `unsupported negentropy protocol version ${protocolVersion - 0x60} (peer), we speak ${PROTOCOL_VERSION - 0x60}`,\n );\n }\n return { have, need, nextMessage: bytesToHex(fullOutput.unwrap()) };\n }\n\n const storageSize = this.#storage.size();\n let prevBound = this.#bound(0);\n let prevIndex = 0;\n let skip = false;\n\n while (query.length > 0) {\n const o = new EncodedBuf();\n\n const currBound = this.#decodeBound(query);\n const mode = decodeVarInt(query);\n const lower = prevIndex;\n const upper = this.#storage.findLowerBound(prevIndex, storageSize, currBound);\n\n if (mode === Mode.Skip) {\n skip = true;\n } else if (mode === Mode.Fingerprint) {\n const theirFingerprint = getBytes(query, FINGERPRINT_SIZE);\n const ourFingerprint = this.#storage.fingerprint(lower, upper);\n if (compareBytes(theirFingerprint, ourFingerprint) === 0) {\n skip = true;\n } else {\n if (skip) {\n skip = false;\n this.#encodeSkipInto(o, prevBound);\n }\n this.#splitRange(lower, upper, currBound, o);\n }\n } else if (mode === Mode.IdList) {\n const numIds = decodeVarInt(query);\n const theirElems = new Map<string, Uint8Array>();\n for (let i = 0; i < numIds; i++) {\n const e = getBytes(query, ID_SIZE);\n theirElems.set(bytesToHex(e), e);\n }\n\n if (this.#initiator) {\n skip = true;\n this.#storage.iterate(lower, upper, (item) => {\n const id = bytesToHex(item.id);\n if (theirElems.has(id)) {\n theirElems.delete(id);\n } else {\n have.push(id);\n }\n return true;\n });\n for (const id of theirElems.keys()) {\n need.push(id);\n }\n } else {\n if (skip) {\n skip = false;\n this.#encodeSkipInto(o, prevBound);\n }\n o.extend(this.#encodeBound(currBound));\n o.extend(encodeVarInt(Mode.IdList));\n const ourIds: Uint8Array[] = [];\n this.#storage.iterate(lower, upper, (item) => {\n ourIds.push(item.id);\n return true;\n });\n o.extend(encodeVarInt(ourIds.length));\n for (const id of ourIds) {\n o.extend(id);\n }\n }\n } else {\n throw new Nip77Error(\"unexpected mode\");\n }\n\n if (this.#exceededFrameSizeLimit(fullOutput.length + o.length)) {\n const remainingFingerprint = this.#storage.fingerprint(upper, storageSize);\n fullOutput.extend(this.#encodeBound(this.#bound(INFINITY)));\n fullOutput.extend(encodeVarInt(Mode.Fingerprint));\n fullOutput.extend(remainingFingerprint);\n break;\n }\n fullOutput.extend(o);\n prevIndex = upper;\n prevBound = currBound;\n }\n\n return {\n have,\n need,\n nextMessage: fullOutput.length === 1 ? undefined : bytesToHex(fullOutput.unwrap()),\n };\n }\n\n #bound(timestamp: number, id?: Uint8Array): Bound {\n return { timestamp, id: id ?? new Uint8Array(0) };\n }\n\n #splitRange(lower: number, upper: number, upperBound: Bound, o: EncodedBuf): void {\n const numElems = upper - lower;\n if (numElems < ID_LIST_THRESHOLD) {\n o.extend(this.#encodeBound(upperBound));\n o.extend(encodeVarInt(Mode.IdList));\n o.extend(encodeVarInt(numElems));\n this.#storage.iterate(lower, upper, (item) => {\n o.extend(item.id);\n return true;\n });\n return;\n }\n\n const itemsPerBucket = Math.floor(numElems / BUCKETS);\n const bucketsWithExtra = numElems % BUCKETS;\n let curr = lower;\n for (let i = 0; i < BUCKETS; i++) {\n const bucketSize = itemsPerBucket + (i < bucketsWithExtra ? 1 : 0);\n const ourFingerprint = this.#storage.fingerprint(curr, curr + bucketSize);\n curr += bucketSize;\n let nextBound: Bound;\n if (curr === upper) {\n nextBound = upperBound;\n } else {\n const at = curr - 1;\n let prevItem: NegItem | undefined;\n let currItem: NegItem | undefined;\n this.#storage.iterate(at, at + 2, (item, index) => {\n if (index === at) {\n prevItem = item;\n } else {\n currItem = item;\n }\n return true;\n });\n if (prevItem === undefined || currItem === undefined) {\n throw new Nip77Error(\"missing bound items\");\n }\n nextBound = this.#minimalBound(prevItem, currItem);\n }\n o.extend(this.#encodeBound(nextBound));\n o.extend(encodeVarInt(Mode.Fingerprint));\n o.extend(ourFingerprint);\n }\n }\n\n #exceededFrameSizeLimit(n: number): boolean {\n if (this.#frameSizeLimit === 0) {\n return false;\n }\n return n > this.#frameSizeLimit - 200;\n }\n\n #decodeTimestampIn(encoded: EncodedBuf): number {\n let timestamp = decodeVarInt(encoded);\n timestamp = timestamp === 0 ? INFINITY : timestamp - 1;\n if (this.#lastTimestampIn === INFINITY || timestamp === INFINITY) {\n this.#lastTimestampIn = INFINITY;\n return INFINITY;\n }\n timestamp += this.#lastTimestampIn;\n this.#lastTimestampIn = timestamp;\n return timestamp;\n }\n\n #decodeBound(encoded: EncodedBuf): Bound {\n const timestamp = this.#decodeTimestampIn(encoded);\n const len = decodeVarInt(encoded);\n if (len > ID_SIZE) {\n throw new Nip77Error(\"bound key too long\");\n }\n return { timestamp, id: getBytes(encoded, len) };\n }\n\n #encodeTimestampOut(timestamp: number): EncodedBuf {\n if (timestamp === INFINITY) {\n this.#lastTimestampOut = INFINITY;\n return encodeVarInt(0);\n }\n const delta = timestamp - this.#lastTimestampOut;\n this.#lastTimestampOut = timestamp;\n return encodeVarInt(delta + 1);\n }\n\n #encodeSkipInto(o: EncodedBuf, bound: Bound): void {\n o.extend(this.#encodeBound(bound));\n o.extend(encodeVarInt(Mode.Skip));\n }\n\n #encodeBound(key: Bound): EncodedBuf {\n const output = new EncodedBuf();\n output.extend(this.#encodeTimestampOut(key.timestamp));\n output.extend(encodeVarInt(key.id.length));\n output.extend(key.id);\n return output;\n }\n\n #minimalBound(prev: NegItem, curr: NegItem): Bound {\n if (curr.timestamp !== prev.timestamp) {\n return this.#bound(curr.timestamp);\n }\n let shared = 0;\n for (let i = 0; i < ID_SIZE; i++) {\n if (curr.id[i] !== prev.id[i]) {\n break;\n }\n shared += 1;\n }\n return this.#bound(curr.timestamp, curr.id.subarray(0, shared + 1));\n }\n}\n\n/** Drive initiate/reconcile until nextMessage is null. Transport-free. */\nexport async function runNegSession(opts: {\n storage: NegentropyStorageVector;\n frameSizeLimit?: number;\n openingSend: (hex: string) => void;\n msgSend: (hex: string) => void;\n next: () => Promise<string>;\n}): Promise<{ have: string[]; need: string[] }> {\n const neg = new Negentropy(opts.storage, opts.frameSizeLimit);\n const have = new Set<string>();\n const need = new Set<string>();\n opts.openingSend(neg.initiate());\n for (let round = 0; round < MAX_NEG_ROUNDS; round++) {\n // oxlint-disable-next-line eslint/no-await-in-loop -- protocol rounds are sequential by definition\n const out = neg.reconcile(await opts.next());\n for (const id of out.have) {\n have.add(id);\n }\n for (const id of out.need) {\n need.add(id);\n }\n if (out.nextMessage === undefined) {\n return { have: [...have], need: [...need] };\n }\n opts.msgSend(out.nextMessage);\n }\n throw new Nip77Error(\"negentropy exceeded max rounds\");\n}\n"],"mappings":"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"}
|
package/dist/nips/nip98.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { n as EventTemplate, t as Event } from "../event-j3BwI5dF.mjs";
|
|
2
|
-
import { a as NostrError } from "../error-
|
|
2
|
+
import { a as NostrError } from "../error-BPqAU5RB.mjs";
|
|
3
3
|
//#region src/nips/nip98.d.ts
|
|
4
4
|
export declare class Nip98Error extends NostrError {
|
|
5
5
|
name: string;
|
package/dist/nips/nip98.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { t as Kind } from "../kind-
|
|
3
|
-
import { h as validateSignedEvent, v as firstTagValue } from "../event-
|
|
4
|
-
import { n as verifyEvent } from "../verifier-
|
|
1
|
+
import { g as utf8Encoder, h as utf8Decoder, i as bytesToHex, p as nowSeconds, x as NostrError } from "../util-DfsyJ9nz.mjs";
|
|
2
|
+
import { t as Kind } from "../kind-I2Tvbe82.mjs";
|
|
3
|
+
import { h as validateSignedEvent, v as firstTagValue } from "../event-DvM1f03b.mjs";
|
|
4
|
+
import { n as verifyEvent } from "../verifier-DEHUy4AD.mjs";
|
|
5
5
|
import { sha256 } from "@noble/hashes/sha2.js";
|
|
6
6
|
import { base64 } from "@scure/base";
|
|
7
7
|
//#region src/nips/nip98.ts
|
|
@@ -43,7 +43,15 @@ async function getToken(url, method, sign, opts) {
|
|
|
43
43
|
tags,
|
|
44
44
|
content: opts?.content ?? ""
|
|
45
45
|
});
|
|
46
|
-
const encoded = base64.encode(utf8Encoder.encode(JSON.stringify(
|
|
46
|
+
const encoded = base64.encode(utf8Encoder.encode(JSON.stringify({
|
|
47
|
+
id: signed.id,
|
|
48
|
+
pubkey: signed.pubkey,
|
|
49
|
+
created_at: signed.created_at,
|
|
50
|
+
kind: signed.kind,
|
|
51
|
+
tags: signed.tags,
|
|
52
|
+
content: signed.content,
|
|
53
|
+
sig: signed.sig
|
|
54
|
+
})));
|
|
47
55
|
return opts?.includeAuthorizationScheme === true ? AUTHORIZATION_SCHEME + encoded : encoded;
|
|
48
56
|
}
|
|
49
57
|
/** Decode a NIP-98 token (with or without the `Nostr ` scheme) into an event. */
|
|
@@ -76,8 +84,11 @@ function validateAuthEvent(event, url, method, opts) {
|
|
|
76
84
|
const maxSkewSec = opts?.maxSkewSec ?? DEFAULT_MAX_SKEW_SEC;
|
|
77
85
|
const now = nowSeconds();
|
|
78
86
|
if (Math.abs(now - event.created_at) > maxSkewSec) return false;
|
|
79
|
-
|
|
80
|
-
const
|
|
87
|
+
const us = event.tags.filter((tag) => tag[0] === "u");
|
|
88
|
+
const methods = event.tags.filter((tag) => tag[0] === "method");
|
|
89
|
+
if (us.length !== 1 || methods.length !== 1) return false;
|
|
90
|
+
if (us[0]?.[1] !== url) return false;
|
|
91
|
+
const m = methods[0]?.[1];
|
|
81
92
|
if (m === void 0 || m.toLowerCase() !== method.toLowerCase()) return false;
|
|
82
93
|
if (opts?.payload !== void 0) {
|
|
83
94
|
if (firstTagValue(event.tags, "payload") !== hashPayload(opts.payload)) return false;
|
package/dist/nips/nip98.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"nip98.mjs","names":[],"sources":["../../src/nips/nip98.ts"],"sourcesContent":["/**\n * NIP-98: HTTP Auth (kind 27235). Token is standard base64 of the signed event JSON, not base64url.\n *\n * @see https://github.com/nostr-protocol/nips/blob/master/98.md\n */\nimport { sha256 } from \"@noble/hashes/sha2.js\";\nimport { base64 } from \"@scure/base\";\n\nimport { NostrError } from \"../core/error.ts\";\nimport type { Event, EventTemplate } from \"../core/event.ts\";\nimport { validateSignedEvent } from \"../core/event.ts\";\nimport { Kind } from \"../core/kind.ts\";\nimport { firstTagValue } from \"../core/tag.ts\";\nimport type { Tag } from \"../core/tag.ts\";\nimport { bytesToHex, nowSeconds, utf8Decoder, utf8Encoder } from \"../core/util.ts\";\nimport { verifyEvent } from \"../core/verifier.ts\";\n\nconst AUTHORIZATION_SCHEME = \"Nostr \";\nconst DEFAULT_MAX_SKEW_SEC = 60;\n\nexport class Nip98Error extends NostrError {\n override name = \"Nip98Error\";\n}\n\n/** SHA-256 of the request body: raw bytes for string/Uint8Array, else JSON.stringify. */\nfunction hashPayload(payload: unknown): string {\n const bytes =\n typeof payload === \"string\"\n ? utf8Encoder.encode(payload)\n : payload instanceof Uint8Array\n ? payload\n : utf8Encoder.encode(JSON.stringify(payload));\n return bytesToHex(sha256(bytes));\n}\n\n/** RFC 4648 standard base64; accept missing `=` (NIP-98 spec example is unpadded). */\nfunction decodeStandardBase64(encoded: string): Uint8Array {\n const rem = encoded.length % 4;\n const padded = rem === 0 ? encoded : encoded + \"=\".repeat(4 - rem);\n return base64.decode(padded);\n}\n\nfunction stripAuthorizationScheme(token: string): string {\n const prefix = /^nostr\\s+/i.exec(token);\n return prefix ? token.slice(prefix[0].length) : token;\n}\n\n/**\n * Sign a kind 27235 auth event and encode it as a NIP-98 token. Uses standard base64\n * (`@scure/base`), not base64url.\n */\nexport async function getToken(\n url: string,\n method: string,\n sign: (template: EventTemplate) => Promise<Event> | Event,\n opts?: {\n includeAuthorizationScheme?: boolean | undefined;\n content?: string | undefined;\n payload?: unknown;\n now?: number | undefined;\n },\n): Promise<string> {\n const tags: Tag[] = [\n [\"u\", url],\n [\"method\", method],\n ];\n if (opts?.payload !== undefined) {\n tags.push([\"payload\", hashPayload(opts.payload)]);\n }\n\n const signed = await sign({\n kind: Kind.HttpAuth,\n created_at: opts?.now ?? nowSeconds(),\n tags,\n content: opts?.content ?? \"\",\n });\n\n const encoded = base64.encode(utf8Encoder.encode(JSON.stringify(signed)));\n return opts?.includeAuthorizationScheme === true ? AUTHORIZATION_SCHEME + encoded : encoded;\n}\n\n/** Decode a NIP-98 token (with or without the `Nostr ` scheme) into an event. */\nexport function unpackEventFromToken(token: string): Event {\n if (token === \"\") {\n throw new Nip98Error(\"missing token\");\n }\n\n const encoded = stripAuthorizationScheme(token);\n\n let json: string;\n try {\n json = utf8Decoder.decode(decodeStandardBase64(encoded));\n } catch (error) {\n throw new Nip98Error(\"invalid token encoding\", { cause: error });\n }\n if (!json.startsWith(\"{\")) {\n throw new Nip98Error(\"invalid token\");\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(json);\n } catch (error) {\n throw new Nip98Error(\"invalid token JSON\", { cause: error });\n }\n if (!validateSignedEvent(parsed)) {\n throw new Nip98Error(\"token is not a signed event\");\n }\n return parsed;\n}\n\n/**\n * True when `event` is a valid NIP-98 auth event for `url` + `method`. Verifies signature, kind\n * 27235, timestamp window, `u`, `method`, and optional `payload` tag.\n */\nexport function validateAuthEvent(\n event: Event,\n url: string,\n method: string,\n opts?: { payload?: unknown; maxSkewSec?: number },\n): boolean {\n if (!verifyEvent(event)) {\n return false;\n }\n if (event.kind !== Kind.HttpAuth) {\n return false;\n }\n\n const maxSkewSec = opts?.maxSkewSec ?? DEFAULT_MAX_SKEW_SEC;\n const now = nowSeconds();\n if (Math.abs(now - event.created_at) > maxSkewSec) {\n return false;\n }\n\n
|
|
1
|
+
{"version":3,"file":"nip98.mjs","names":[],"sources":["../../src/nips/nip98.ts"],"sourcesContent":["/**\n * NIP-98: HTTP Auth (kind 27235). Token is standard base64 of the signed event JSON, not base64url.\n *\n * @see https://github.com/nostr-protocol/nips/blob/master/98.md\n */\nimport { sha256 } from \"@noble/hashes/sha2.js\";\nimport { base64 } from \"@scure/base\";\n\nimport { NostrError } from \"../core/error.ts\";\nimport type { Event, EventTemplate } from \"../core/event.ts\";\nimport { validateSignedEvent } from \"../core/event.ts\";\nimport { Kind } from \"../core/kind.ts\";\nimport { firstTagValue } from \"../core/tag.ts\";\nimport type { Tag } from \"../core/tag.ts\";\nimport { bytesToHex, nowSeconds, utf8Decoder, utf8Encoder } from \"../core/util.ts\";\nimport { verifyEvent } from \"../core/verifier.ts\";\n\nconst AUTHORIZATION_SCHEME = \"Nostr \";\nconst DEFAULT_MAX_SKEW_SEC = 60;\n\nexport class Nip98Error extends NostrError {\n override name = \"Nip98Error\";\n}\n\n/** SHA-256 of the request body: raw bytes for string/Uint8Array, else JSON.stringify. */\nfunction hashPayload(payload: unknown): string {\n const bytes =\n typeof payload === \"string\"\n ? utf8Encoder.encode(payload)\n : payload instanceof Uint8Array\n ? payload\n : utf8Encoder.encode(JSON.stringify(payload));\n return bytesToHex(sha256(bytes));\n}\n\n/** RFC 4648 standard base64; accept missing `=` (NIP-98 spec example is unpadded). */\nfunction decodeStandardBase64(encoded: string): Uint8Array {\n const rem = encoded.length % 4;\n const padded = rem === 0 ? encoded : encoded + \"=\".repeat(4 - rem);\n return base64.decode(padded);\n}\n\nfunction stripAuthorizationScheme(token: string): string {\n const prefix = /^nostr\\s+/i.exec(token);\n return prefix ? token.slice(prefix[0].length) : token;\n}\n\n/**\n * Sign a kind 27235 auth event and encode it as a NIP-98 token. Uses standard base64\n * (`@scure/base`), not base64url.\n */\nexport async function getToken(\n url: string,\n method: string,\n sign: (template: EventTemplate) => Promise<Event> | Event,\n opts?: {\n includeAuthorizationScheme?: boolean | undefined;\n content?: string | undefined;\n payload?: unknown;\n now?: number | undefined;\n },\n): Promise<string> {\n const tags: Tag[] = [\n [\"u\", url],\n [\"method\", method],\n ];\n if (opts?.payload !== undefined) {\n tags.push([\"payload\", hashPayload(opts.payload)]);\n }\n\n const signed = await sign({\n kind: Kind.HttpAuth,\n created_at: opts?.now ?? nowSeconds(),\n tags,\n content: opts?.content ?? \"\",\n });\n\n // Canonical NIP-01 wire field order: the token is deterministic regardless\n // of the signer's object key order, and byte-identical to the token\n // crates/interop builds with `Event::as_json()`. Extra signer-provided\n // properties are dropped.\n const encoded = base64.encode(\n utf8Encoder.encode(\n JSON.stringify({\n id: signed.id,\n pubkey: signed.pubkey,\n created_at: signed.created_at,\n kind: signed.kind,\n tags: signed.tags,\n content: signed.content,\n sig: signed.sig,\n }),\n ),\n );\n return opts?.includeAuthorizationScheme === true ? AUTHORIZATION_SCHEME + encoded : encoded;\n}\n\n/** Decode a NIP-98 token (with or without the `Nostr ` scheme) into an event. */\nexport function unpackEventFromToken(token: string): Event {\n if (token === \"\") {\n throw new Nip98Error(\"missing token\");\n }\n\n const encoded = stripAuthorizationScheme(token);\n\n let json: string;\n try {\n json = utf8Decoder.decode(decodeStandardBase64(encoded));\n } catch (error) {\n throw new Nip98Error(\"invalid token encoding\", { cause: error });\n }\n if (!json.startsWith(\"{\")) {\n throw new Nip98Error(\"invalid token\");\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(json);\n } catch (error) {\n throw new Nip98Error(\"invalid token JSON\", { cause: error });\n }\n if (!validateSignedEvent(parsed)) {\n throw new Nip98Error(\"token is not a signed event\");\n }\n return parsed;\n}\n\n/**\n * True when `event` is a valid NIP-98 auth event for `url` + `method`. Verifies signature, kind\n * 27235, timestamp window, `u`, `method`, and optional `payload` tag.\n */\nexport function validateAuthEvent(\n event: Event,\n url: string,\n method: string,\n opts?: { payload?: unknown; maxSkewSec?: number },\n): boolean {\n if (!verifyEvent(event)) {\n return false;\n }\n if (event.kind !== Kind.HttpAuth) {\n return false;\n }\n\n const maxSkewSec = opts?.maxSkewSec ?? DEFAULT_MAX_SKEW_SEC;\n const now = nowSeconds();\n if (Math.abs(now - event.created_at) > maxSkewSec) {\n return false;\n }\n\n // The `u` tag MUST be exactly the request URL, so a token carrying several `u` (or `method`)\n // tags would be valid for more than one request — reject rather than first-wins.\n const us = event.tags.filter((tag) => tag[0] === \"u\");\n const methods = event.tags.filter((tag) => tag[0] === \"method\");\n if (us.length !== 1 || methods.length !== 1) {\n return false;\n }\n\n if (us[0]?.[1] !== url) {\n return false;\n }\n\n const m = methods[0]?.[1];\n if (m === undefined || m.toLowerCase() !== method.toLowerCase()) {\n return false;\n }\n\n if (opts?.payload !== undefined) {\n const payloadTag = firstTagValue(event.tags, \"payload\");\n if (payloadTag !== hashPayload(opts.payload)) {\n return false;\n }\n }\n\n return true;\n}\n"],"mappings":"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"}
|
|
@@ -1,26 +1,26 @@
|
|
|
1
1
|
import { n as EventTemplate, t as Event } from "./event-j3BwI5dF.mjs";
|
|
2
|
-
import { t as Filter } from "./filter-
|
|
3
|
-
import { o as CountResult, t as EventVerifier } from "./verifier-
|
|
4
|
-
import { a as NegentropyStorageVector } from "./nip77-
|
|
5
|
-
import { t as WebSocketConstructor } from "./websocket-
|
|
2
|
+
import { t as Filter } from "./filter-oYG2zjJ3.mjs";
|
|
3
|
+
import { o as CountResult, t as EventVerifier } from "./verifier-DhAbqVJF.mjs";
|
|
4
|
+
import { a as NegentropyStorageVector } from "./nip77-DhpoL7d8.mjs";
|
|
5
|
+
import { t as WebSocketConstructor } from "./websocket-DaCvJkF9.mjs";
|
|
6
6
|
//#region src/relay/subscription.d.ts
|
|
7
7
|
type SubscriptionHandlers = {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
/** Skip verify +
|
|
8
|
+
onEvent?: ((event: Event) => void) | undefined;
|
|
9
|
+
onEose?: (() => void) | undefined;
|
|
10
|
+
onClose?: ((reason: string) => void) | undefined;
|
|
11
|
+
/** Skip verify + onEvent when true. Evaluated after parse, before verify. */
|
|
12
12
|
alreadyHaveEvent?: ((id: string) => boolean) | undefined;
|
|
13
13
|
/**
|
|
14
14
|
* Fired for every EVENT id this sub sees, including duplicates and alreadyHaveEvent hits, after
|
|
15
15
|
* parse and before the verify skip.
|
|
16
16
|
*/
|
|
17
|
-
|
|
17
|
+
onEventSeen?: ((id: string) => void) | undefined;
|
|
18
18
|
};
|
|
19
19
|
/** Options for {@link Relay.subscribe}: event handlers plus REQ behavior. */
|
|
20
20
|
type SubscribeOptions = SubscriptionHandlers & {
|
|
21
21
|
id?: string | undefined;
|
|
22
22
|
/**
|
|
23
|
-
* If set, fire `
|
|
23
|
+
* If set, fire `onEose` once after this many ms if EOSE has not arrived. The synthesized EOSE
|
|
24
24
|
* also closes a `closeOnEose` subscription — without it a one-shot REQ would hang until a real
|
|
25
25
|
* EOSE that is then ignored.
|
|
26
26
|
*/
|
|
@@ -45,7 +45,6 @@ type RelaySubscription = {
|
|
|
45
45
|
/** AsyncIterable wrapper over a subscription's events until EOSE or close. */
|
|
46
46
|
declare function subscriptionToAsyncIterable(start: (handlers: SubscriptionHandlers) => Closer, opts?: {
|
|
47
47
|
signal?: AbortSignal | undefined;
|
|
48
|
-
includeEose?: boolean | undefined;
|
|
49
48
|
}): AsyncIterable<Event> & Closer;
|
|
50
49
|
//#endregion
|
|
51
50
|
//#region src/relay/fetch.d.ts
|
|
@@ -127,6 +126,11 @@ type RelayEventMap = {
|
|
|
127
126
|
* subscription's filters are dropped silently and are not counted here.
|
|
128
127
|
*/
|
|
129
128
|
invalidevent: undefined;
|
|
129
|
+
/**
|
|
130
|
+
* Fired once when the event verifier throws: the verifier is considered dead and every later
|
|
131
|
+
* EVENT is dropped silently instead of letting the exception escape the socket listener.
|
|
132
|
+
*/
|
|
133
|
+
verifiererror: Error;
|
|
130
134
|
};
|
|
131
135
|
/**
|
|
132
136
|
* Single-relay NIP-01 client. Connection lifecycle, REQ/CLOSE, EVENT publish ACK, AUTH, COUNT,
|
|
@@ -296,9 +300,9 @@ type PoolPublishResult = {
|
|
|
296
300
|
readonly error: Error;
|
|
297
301
|
};
|
|
298
302
|
/** Multi-relay subscribe options: callbacks also receive the normalized relay URL. */
|
|
299
|
-
type PoolSubscribeOptions = Omit<SubscribeOptions, "
|
|
300
|
-
|
|
301
|
-
|
|
303
|
+
type PoolSubscribeOptions = Omit<SubscribeOptions, "onEvent" | "onEventSeen"> & {
|
|
304
|
+
onEvent?: ((event: Event, relayUrl: string) => void) | undefined;
|
|
305
|
+
onEventSeen?: ((id: string, relayUrl: string) => void) | undefined;
|
|
302
306
|
};
|
|
303
307
|
/** Per-relay one-shot fetch outcome: collected events plus how its REQ ended. */
|
|
304
308
|
type PoolFetchResult = {
|
|
@@ -364,7 +368,7 @@ declare class Pool {
|
|
|
364
368
|
timeoutMs?: number | undefined;
|
|
365
369
|
signal?: AbortSignal | undefined;
|
|
366
370
|
/** Every event of every relay batch, including cross-relay duplicates. */
|
|
367
|
-
|
|
371
|
+
onEvent?: ((event: Event, relayUrl: string) => void) | undefined;
|
|
368
372
|
}): Promise<Event[]>;
|
|
369
373
|
/** Publish to all listed relays; returns per-relay outcomes. */
|
|
370
374
|
publish(relays: ReadonlyArray<string>, event: Event, opts?: {
|
|
@@ -392,4 +396,4 @@ declare class Pool {
|
|
|
392
396
|
}
|
|
393
397
|
//#endregion
|
|
394
398
|
export { Closer as _, PoolFetchResult as a, SubscriptionHandlers as b, PoolSubscribeOptions as c, RelayOptions as d, RelayStatus as f, RelayFetchResult as g, RelayFetchEnd as h, PoolEventMap as i, Relay as l, PublishResult as m, Pool as n, PoolOptions as o, RelayStatusName as p, PoolCountResult as r, PoolPublishResult as s, InvalidEventPolicy as t, RelayEventMap as u, RelaySubscription as v, subscriptionToAsyncIterable as x, SubscribeOptions as y };
|
|
395
|
-
//# sourceMappingURL=pool-
|
|
399
|
+
//# sourceMappingURL=pool-MehgIIzm.d.mts.map
|