@qntx/nostr 0.12.1 → 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.
Files changed (159) hide show
  1. package/dist/{builder-BrmLOVj_.d.mts → builder-C72nJITG.d.mts} +2 -2
  2. package/dist/{builder-Cf2N12CG.mjs → builder-CGgM_QHT.mjs} +5 -5
  3. package/dist/{builder-Cf2N12CG.mjs.map → builder-CGgM_QHT.mjs.map} +1 -1
  4. package/dist/{client-B3_vToDX.mjs → client-COkEihj_.mjs} +87 -79
  5. package/dist/client-COkEihj_.mjs.map +1 -0
  6. package/dist/client.d.mts +1 -1
  7. package/dist/client.mjs +1 -1
  8. package/dist/core.d.mts +7 -7
  9. package/dist/core.mjs +9 -9
  10. package/dist/{emitter-DkCY6NkU.mjs → emitter-u4kJqQDk.mjs} +2 -2
  11. package/dist/{emitter-DkCY6NkU.mjs.map → emitter-u4kJqQDk.mjs.map} +1 -1
  12. package/dist/{error-BquXDmIN.d.mts → error-BPqAU5RB.d.mts} +2 -6
  13. package/dist/{error-BD_0QFKT.d.mts → error-BjSUsx6M.d.mts} +2 -2
  14. package/dist/{error-D3VNRVhL.mjs → error-Dyb89ahb.mjs} +2 -2
  15. package/dist/{error-D3VNRVhL.mjs.map → error-Dyb89ahb.mjs.map} +1 -1
  16. package/dist/{error-DH4guike.mjs → error-QyECUDIp.mjs} +2 -2
  17. package/dist/{error-DH4guike.mjs.map → error-QyECUDIp.mjs.map} +1 -1
  18. package/dist/{event-B3ua2_oL.mjs → event-DvM1f03b.mjs} +7 -3
  19. package/dist/event-DvM1f03b.mjs.map +1 -0
  20. package/dist/{filter-B16P33dz.mjs → filter-3jOh4uLN.mjs} +50 -20
  21. package/dist/filter-3jOh4uLN.mjs.map +1 -0
  22. package/dist/{filter-PG7r79rt.d.mts → filter-oYG2zjJ3.d.mts} +6 -2
  23. package/dist/{gossip-u0iBlZ1U.d.mts → gossip-BvCItMVZ.d.mts} +3 -3
  24. package/dist/{gossip-K_JIexQs.mjs → gossip-CRyhK6E6.mjs} +4 -4
  25. package/dist/{gossip-K_JIexQs.mjs.map → gossip-CRyhK6E6.mjs.map} +1 -1
  26. package/dist/gossip.d.mts +1 -1
  27. package/dist/gossip.mjs +1 -1
  28. package/dist/http-CuazNRIP.mjs.map +1 -1
  29. package/dist/{index-DD0Ywpay.d.mts → index-B2JlP2Jf.d.mts} +9 -9
  30. package/dist/{index-yq9xkONo.d.mts → index-DCobP2Jj.d.mts} +10 -10
  31. package/dist/{index-Hp26bgYG.d.mts → index-Dpj_MUxb.d.mts} +8 -5
  32. package/dist/{index-rKcmI6ex.d.mts → index-DzHmhHqD.d.mts} +3 -3
  33. package/dist/{index-SwMknboJ.d.mts → index-YID-zPDF.d.mts} +7 -7
  34. package/dist/{index-DxQO1eWA.d.mts → index-muydfuYD.d.mts} +21 -21
  35. package/dist/index.d.mts +19 -20
  36. package/dist/index.mjs +20 -20
  37. package/dist/{key-B218_SoL.mjs → key-D_oEutQ6.mjs} +5 -7
  38. package/dist/{key-B218_SoL.mjs.map → key-D_oEutQ6.mjs.map} +1 -1
  39. package/dist/{key-CwMVo14x.d.mts → key-DojW2QZl.d.mts} +3 -5
  40. package/dist/{kind-D8fENiiL.mjs → kind-I2Tvbe82.mjs} +6 -5
  41. package/dist/kind-I2Tvbe82.mjs.map +1 -0
  42. package/dist/{loaders-Xdbx-K61.mjs → loaders-BRh2riV9.mjs} +17 -16
  43. package/dist/loaders-BRh2riV9.mjs.map +1 -0
  44. package/dist/loaders.d.mts +2 -2
  45. package/dist/loaders.mjs +1 -1
  46. package/dist/{memory-VyPnOBU3.mjs → memory-D_GdEQYG.mjs} +2 -2
  47. package/dist/memory-D_GdEQYG.mjs.map +1 -0
  48. package/dist/memory-index-3GSrPIWj.mjs +671 -0
  49. package/dist/memory-index-3GSrPIWj.mjs.map +1 -0
  50. package/dist/{message-pYfVKxPn.mjs → message-BMfreCQc.mjs} +6 -6
  51. package/dist/message-BMfreCQc.mjs.map +1 -0
  52. package/dist/{nip17-BM4hJYBq.d.mts → nip17-BZvFQLcs.d.mts} +4 -4
  53. package/dist/{nip19-DpPN17bY.d.mts → nip19-D4RKn6GC.d.mts} +2 -2
  54. package/dist/{nip46-BHaxC9TZ.d.mts → nip46-i8L8_nIm.d.mts} +2 -2
  55. package/dist/{nip51-D_bu8Hph.d.mts → nip51-C_kTYcud.d.mts} +2 -2
  56. package/dist/{nip59-CGWfoItR.d.mts → nip59-DfJ6Moeu.d.mts} +2 -2
  57. package/dist/{nip65-DaWMg943.d.mts → nip65-CPNE0SV0.d.mts} +2 -2
  58. package/dist/{nip77-wHA_nEV5.d.mts → nip77-DhpoL7d8.d.mts} +2 -2
  59. package/dist/nips/blossom.d.mts +2 -2
  60. package/dist/nips/blossom.mjs +3 -3
  61. package/dist/nips/blossom.mjs.map +1 -1
  62. package/dist/nips/nip04.d.mts +9 -12
  63. package/dist/nips/nip04.mjs +38 -45
  64. package/dist/nips/nip04.mjs.map +1 -1
  65. package/dist/nips/nip05.d.mts +6 -3
  66. package/dist/nips/nip05.mjs +6 -3
  67. package/dist/nips/nip05.mjs.map +1 -1
  68. package/dist/nips/nip10.d.mts +2 -2
  69. package/dist/nips/nip10.mjs +4 -4
  70. package/dist/nips/nip11.d.mts +1 -1
  71. package/dist/nips/nip11.mjs +1 -1
  72. package/dist/nips/nip13.d.mts +1 -1
  73. package/dist/nips/nip13.mjs +1 -1
  74. package/dist/nips/nip13.mjs.map +1 -1
  75. package/dist/nips/nip17.d.mts +1 -1
  76. package/dist/nips/nip17.mjs +4 -4
  77. package/dist/nips/nip17.mjs.map +1 -1
  78. package/dist/nips/nip19.d.mts +1 -1
  79. package/dist/nips/nip19.mjs +2 -2
  80. package/dist/nips/nip19.mjs.map +1 -1
  81. package/dist/nips/nip21.d.mts +6 -3
  82. package/dist/nips/nip21.mjs +8 -5
  83. package/dist/nips/nip21.mjs.map +1 -1
  84. package/dist/nips/nip27.d.mts +1 -1
  85. package/dist/nips/nip27.mjs +15 -9
  86. package/dist/nips/nip27.mjs.map +1 -1
  87. package/dist/nips/nip42.mjs +2 -2
  88. package/dist/nips/nip44.mjs +1 -1
  89. package/dist/nips/nip46.d.mts +1 -1
  90. package/dist/nips/nip46.mjs +9 -7
  91. package/dist/nips/nip46.mjs.map +1 -1
  92. package/dist/nips/nip49.d.mts +1 -1
  93. package/dist/nips/nip49.mjs +1 -1
  94. package/dist/nips/nip51.d.mts +1 -1
  95. package/dist/nips/nip51.mjs +5 -5
  96. package/dist/nips/nip51.mjs.map +1 -1
  97. package/dist/nips/nip57.mjs +6 -5
  98. package/dist/nips/nip57.mjs.map +1 -1
  99. package/dist/nips/nip59.d.mts +1 -1
  100. package/dist/nips/nip59.mjs +5 -5
  101. package/dist/nips/nip59.mjs.map +1 -1
  102. package/dist/nips/nip65.d.mts +1 -1
  103. package/dist/nips/nip65.mjs +3 -3
  104. package/dist/nips/nip77.d.mts +1 -1
  105. package/dist/nips/nip77.mjs +1 -1
  106. package/dist/nips/nip77.mjs.map +1 -1
  107. package/dist/nips/nip98.d.mts +1 -1
  108. package/dist/nips/nip98.mjs +9 -6
  109. package/dist/nips/nip98.mjs.map +1 -1
  110. package/dist/{pool-DUuYJ0x4.d.mts → pool-MehgIIzm.d.mts} +20 -16
  111. package/dist/{pool-THuuOW4S.mjs → pool-lMT_mvzn.mjs} +25 -25
  112. package/dist/pool-lMT_mvzn.mjs.map +1 -0
  113. package/dist/{put-DRmV1wHD.mjs → put-BEM5j8xK.mjs} +5 -5
  114. package/dist/{put-DRmV1wHD.mjs.map → put-BEM5j8xK.mjs.map} +1 -1
  115. package/dist/{reactive-jyKrA9ej.d.mts → reactive-B7lPFgTS.d.mts} +3 -3
  116. package/dist/{reactive--pDpLt2H.mjs → reactive-DFHY-f00.mjs} +24 -9
  117. package/dist/reactive-DFHY-f00.mjs.map +1 -0
  118. package/dist/{relay-BGPqiGKF.mjs → relay-DIR77xQ2.mjs} +102 -89
  119. package/dist/relay-DIR77xQ2.mjs.map +1 -0
  120. package/dist/relay.d.mts +5 -5
  121. package/dist/relay.mjs +3 -3
  122. package/dist/{report-CHld4uWj.mjs → report-D2BM5dgn.mjs} +2 -2
  123. package/dist/{report-CHld4uWj.mjs.map → report-D2BM5dgn.mjs.map} +1 -1
  124. package/dist/{signer-CNTIeYH4.mjs → signer-DJcmC4nA.mjs} +17 -17
  125. package/dist/signer-DJcmC4nA.mjs.map +1 -0
  126. package/dist/signer.d.mts +1 -1
  127. package/dist/signer.mjs +2 -2
  128. package/dist/{storage-B5Wdt_Mn.mjs → storage-n2BE1bdY.mjs} +108 -179
  129. package/dist/storage-n2BE1bdY.mjs.map +1 -0
  130. package/dist/storage.d.mts +3 -3
  131. package/dist/storage.mjs +4 -4
  132. package/dist/store.d.mts +1 -1
  133. package/dist/store.mjs +1 -1
  134. package/dist/testing.d.mts +4 -4
  135. package/dist/testing.mjs +35 -10
  136. package/dist/testing.mjs.map +1 -1
  137. package/dist/{types-CqhnT-Mx.d.mts → types-DiVVyf1e.d.mts} +2 -2
  138. package/dist/{util-DDpPkiED.mjs → util-DfsyJ9nz.mjs} +30 -10
  139. package/dist/util-DfsyJ9nz.mjs.map +1 -0
  140. package/dist/{verifier-CjH1vnP2.mjs → verifier-DEHUy4AD.mjs} +3 -3
  141. package/dist/{verifier-CjH1vnP2.mjs.map → verifier-DEHUy4AD.mjs.map} +1 -1
  142. package/dist/{verifier-Cl24c9ph.d.mts → verifier-DhAbqVJF.d.mts} +2 -2
  143. package/dist/{websocket-CmeqvAkp.d.mts → websocket-DaCvJkF9.d.mts} +2 -5
  144. package/package.json +4 -3
  145. package/dist/client-B3_vToDX.mjs.map +0 -1
  146. package/dist/event-B3ua2_oL.mjs.map +0 -1
  147. package/dist/filter-B16P33dz.mjs.map +0 -1
  148. package/dist/kind-D8fENiiL.mjs.map +0 -1
  149. package/dist/loaders-Xdbx-K61.mjs.map +0 -1
  150. package/dist/memory-VyPnOBU3.mjs.map +0 -1
  151. package/dist/memory-index-Bhwe0ZdG.mjs +0 -374
  152. package/dist/memory-index-Bhwe0ZdG.mjs.map +0 -1
  153. package/dist/message-pYfVKxPn.mjs.map +0 -1
  154. package/dist/pool-THuuOW4S.mjs.map +0 -1
  155. package/dist/reactive--pDpLt2H.mjs.map +0 -1
  156. package/dist/relay-BGPqiGKF.mjs.map +0 -1
  157. package/dist/signer-CNTIeYH4.mjs.map +0 -1
  158. package/dist/storage-B5Wdt_Mn.mjs.map +0 -1
  159. package/dist/util-DDpPkiED.mjs.map +0 -1
@@ -1,4 +1,4 @@
1
- import { h as CryptoError, m as utf8Encoder, n as assertHex32, o as hexToBytes$1, p as utf8Decoder, r as assertSecretKeyBytes } from "../util-DDpPkiED.mjs";
1
+ import { _ as CryptoError, g as utf8Encoder, h as utf8Decoder, n as assertHex32, r as assertSecretKeyBytes, s as hexToBytes$1 } from "../util-DfsyJ9nz.mjs";
2
2
  import { concatBytes, randomBytes } from "@noble/hashes/utils.js";
3
3
  import { sha256 } from "@noble/hashes/sha2.js";
4
4
  import { secp256k1 } from "@noble/curves/secp256k1.js";
@@ -1,2 +1,2 @@
1
- import { a as Nip46Response, c as decodeNip46Request, d as encodeNip46Response, f as parseBunkerURL, i as Nip46Request, l as decodeNip46Response, m as toBunkerURL, n as ClientMetadata, o as NostrConnectParams, p as parseNostrConnectURI, r as Nip46Error, s as createNostrConnectURI, t as BunkerPointer, u as encodeNip46Request } from "../nip46-BHaxC9TZ.mjs";
1
+ import { a as Nip46Response, c as decodeNip46Request, d as encodeNip46Response, f as parseBunkerURL, i as Nip46Request, l as decodeNip46Response, m as toBunkerURL, n as ClientMetadata, o as NostrConnectParams, p as parseNostrConnectURI, r as Nip46Error, s as createNostrConnectURI, t as BunkerPointer, u as encodeNip46Request } from "../nip46-i8L8_nIm.mjs";
2
2
  export { BunkerPointer, ClientMetadata, Nip46Error, Nip46Request, Nip46Response, NostrConnectParams, createNostrConnectURI, decodeNip46Request, decodeNip46Response, encodeNip46Request, encodeNip46Response, parseBunkerURL, parseNostrConnectURI, toBunkerURL };
@@ -1,4 +1,4 @@
1
- import { l as isRecord, s as isHex32, y as NostrError } from "../util-DDpPkiED.mjs";
1
+ import { a as containsLoneSurrogate, c as isHex32, u as isRecord, x as NostrError } from "../util-DfsyJ9nz.mjs";
2
2
  //#region src/nips/nip46.ts
3
3
  /**
4
4
  * NIP-46 (Nostr Connect) protocol helpers: bunker URI, nostrconnect URI, RPC JSON.
@@ -55,8 +55,8 @@ function parseNostrConnectURI(uri) {
55
55
  let url;
56
56
  try {
57
57
  url = new URL(uri);
58
- } catch (error) {
59
- throw new Nip46Error(`invalid nostrconnect URI: ${uri}`, { cause: error });
58
+ } catch {
59
+ throw new Nip46Error("invalid nostrconnect URI");
60
60
  }
61
61
  if (url.protocol !== "nostrconnect:") throw new Nip46Error(`expected nostrconnect: scheme, got ${url.protocol}`);
62
62
  const clientPubkey = url.hostname || url.pathname.replace(/^\/*/, "");
@@ -91,9 +91,10 @@ function decodeNip46Request(json) {
91
91
  let data;
92
92
  try {
93
93
  data = JSON.parse(json);
94
- } catch (error) {
95
- throw new Nip46Error("invalid NIP-46 request JSON", { cause: error });
94
+ } catch {
95
+ throw new Nip46Error("invalid NIP-46 request JSON");
96
96
  }
97
+ if (containsLoneSurrogate(data)) throw new Nip46Error("invalid NIP-46 request JSON");
97
98
  if (!isRecord(data) || typeof data["id"] !== "string" || typeof data["method"] !== "string") throw new Nip46Error("invalid NIP-46 request shape");
98
99
  if (!Array.isArray(data["params"]) || !data["params"].every((p) => typeof p === "string")) throw new Nip46Error("invalid NIP-46 request params");
99
100
  return {
@@ -112,9 +113,10 @@ function decodeNip46Response(json) {
112
113
  let data;
113
114
  try {
114
115
  data = JSON.parse(json);
115
- } catch (error) {
116
- throw new Nip46Error("invalid NIP-46 response JSON", { cause: error });
116
+ } catch {
117
+ throw new Nip46Error("invalid NIP-46 response JSON");
117
118
  }
119
+ if (containsLoneSurrogate(data)) throw new Nip46Error("invalid NIP-46 response JSON");
118
120
  if (!isRecord(data) || typeof data["id"] !== "string") throw new Nip46Error("invalid NIP-46 response shape");
119
121
  const result = data["result"] === null ? void 0 : data["result"];
120
122
  const error = data["error"] === null ? void 0 : data["error"];
@@ -1 +1 @@
1
- {"version":3,"file":"nip46.mjs","names":[],"sources":["../../src/nips/nip46.ts"],"sourcesContent":["/**\n * NIP-46 (Nostr Connect) protocol helpers: bunker URI, nostrconnect URI, RPC JSON.\n * Transport/signing live in {@link Nip46Signer}.\n *\n * @see https://github.com/nostr-protocol/nips/blob/master/46.md\n */\nimport { NostrError } from \"../core/error.ts\";\nimport type { Mutable } from \"../core/util.ts\";\nimport { isHex32, isRecord } from \"../core/util.ts\";\n\nexport type BunkerPointer = {\n /** Remote signer / bunker public key (hex). */\n readonly pubkey: string;\n readonly relays: ReadonlyArray<string>;\n readonly secret?: string | undefined;\n};\n\nexport type ClientMetadata = {\n readonly name?: string | undefined;\n readonly url?: string | undefined;\n readonly image?: string | undefined;\n};\n\nexport type NostrConnectParams = {\n readonly clientPubkey: string;\n readonly relays: ReadonlyArray<string>;\n readonly secret: string;\n readonly perms?: ReadonlyArray<string> | undefined;\n} & ClientMetadata;\n\nexport type Nip46Request = {\n readonly id: string;\n readonly method: string;\n readonly params: ReadonlyArray<string>;\n};\n\nexport type Nip46Response = {\n readonly id: string;\n readonly result?: string | undefined;\n readonly error?: string | undefined;\n};\n\nexport class Nip46Error extends NostrError {\n override name = \"Nip46Error\";\n}\n\n/** Encode a bunker pointer as `bunker://…`. */\nexport function toBunkerURL(pointer: BunkerPointer): string {\n const url = new URL(`bunker://${pointer.pubkey.toLowerCase()}`);\n for (const relay of pointer.relays) {\n url.searchParams.append(\"relay\", relay);\n }\n if (pointer.secret !== undefined && pointer.secret !== \"\") {\n url.searchParams.set(\"secret\", pointer.secret);\n }\n return url.toString();\n}\n\n/**\n * Parse a `bunker://` URL into a pointer. Returns undefined when the input is not a bunker URL\n * (including NIP-05 identifiers).\n */\nexport function parseBunkerURL(input: string): BunkerPointer | undefined {\n let url: URL;\n try {\n url = new URL(input.trim());\n } catch {\n return undefined;\n }\n if (url.protocol !== \"bunker:\") {\n return undefined;\n }\n const pubkey = url.hostname || url.pathname.replace(/^\\/*/, \"\");\n if (!isHex32(pubkey.toLowerCase())) {\n return undefined;\n }\n return {\n pubkey: pubkey.toLowerCase(),\n relays: url.searchParams.getAll(\"relay\"),\n secret: url.searchParams.get(\"secret\") ?? undefined,\n };\n}\n\n/** Build a client-initiated `nostrconnect://` URI. */\nexport function createNostrConnectURI(params: NostrConnectParams): string {\n if (!isHex32(params.clientPubkey.toLowerCase())) {\n throw new Nip46Error(\"invalid client pubkey\");\n }\n if (!params.secret) {\n throw new Nip46Error(\"nostrconnect secret is required\");\n }\n if (params.relays.length === 0) {\n throw new Nip46Error(\"at least one relay is required\");\n }\n\n const query = new URLSearchParams();\n for (const relay of params.relays) {\n query.append(\"relay\", relay);\n }\n query.set(\"secret\", params.secret);\n if (params.perms !== undefined && params.perms.length > 0) {\n query.set(\"perms\", params.perms.join(\",\"));\n }\n if (params.name !== undefined && params.name !== \"\") {\n query.set(\"name\", params.name);\n }\n if (params.url !== undefined && params.url !== \"\") {\n query.set(\"url\", params.url);\n }\n if (params.image !== undefined && params.image !== \"\") {\n query.set(\"image\", params.image);\n }\n\n return `nostrconnect://${params.clientPubkey.toLowerCase()}?${query.toString()}`;\n}\n\n/** Parse a `nostrconnect://` URI. */\nexport function parseNostrConnectURI(uri: string): NostrConnectParams {\n let url: URL;\n try {\n url = new URL(uri);\n } catch (error) {\n throw new Nip46Error(`invalid nostrconnect URI: ${uri}`, {\n cause: error,\n });\n }\n if (url.protocol !== \"nostrconnect:\") {\n throw new Nip46Error(`expected nostrconnect: scheme, got ${url.protocol}`);\n }\n const clientPubkey = url.hostname || url.pathname.replace(/^\\/*/, \"\");\n if (!isHex32(clientPubkey.toLowerCase())) {\n throw new Nip46Error(\"invalid client pubkey in nostrconnect URI\");\n }\n const secret = url.searchParams.get(\"secret\");\n if (secret === null || secret === \"\") {\n throw new Nip46Error(\"missing secret in nostrconnect URI\");\n }\n const relays = url.searchParams.getAll(\"relay\");\n if (relays.length === 0) {\n throw new Nip46Error(\"missing relays in nostrconnect URI\");\n }\n const params: Mutable<NostrConnectParams> = {\n clientPubkey: clientPubkey.toLowerCase(),\n relays,\n secret,\n };\n const permsRaw = url.searchParams.get(\"perms\");\n if (permsRaw !== null && permsRaw !== \"\") {\n params.perms = permsRaw.split(\",\").filter((p) => p !== \"\");\n }\n const name = url.searchParams.get(\"name\");\n if (name !== null) {\n params.name = name;\n }\n const metadataUrl = url.searchParams.get(\"url\");\n if (metadataUrl !== null) {\n params.url = metadataUrl;\n }\n const image = url.searchParams.get(\"image\");\n if (image !== null) {\n params.image = image;\n }\n return params;\n}\n\nexport function encodeNip46Request(req: Nip46Request): string {\n return JSON.stringify({ id: req.id, method: req.method, params: req.params });\n}\n\nexport function decodeNip46Request(json: string): Nip46Request {\n let data: unknown;\n try {\n data = JSON.parse(json);\n } catch (error) {\n throw new Nip46Error(\"invalid NIP-46 request JSON\", {\n cause: error,\n });\n }\n if (!isRecord(data) || typeof data[\"id\"] !== \"string\" || typeof data[\"method\"] !== \"string\") {\n throw new Nip46Error(\"invalid NIP-46 request shape\");\n }\n if (!Array.isArray(data[\"params\"]) || !data[\"params\"].every((p) => typeof p === \"string\")) {\n throw new Nip46Error(\"invalid NIP-46 request params\");\n }\n return { id: data[\"id\"], method: data[\"method\"], params: data[\"params\"] };\n}\n\nexport function encodeNip46Response(res: Nip46Response): string {\n const body: Record<string, string> = { id: res.id };\n if (res.result !== undefined) {\n body[\"result\"] = res.result;\n }\n if (res.error !== undefined) {\n body[\"error\"] = res.error;\n }\n return JSON.stringify(body);\n}\n\nexport function decodeNip46Response(json: string): Nip46Response {\n let data: unknown;\n try {\n data = JSON.parse(json);\n } catch (error) {\n throw new Nip46Error(\"invalid NIP-46 response JSON\", {\n cause: error,\n });\n }\n if (!isRecord(data) || typeof data[\"id\"] !== \"string\") {\n throw new Nip46Error(\"invalid NIP-46 response shape\");\n }\n // Some remote signers send explicit nulls for absent fields.\n const result = data[\"result\"] === null ? undefined : data[\"result\"];\n const error = data[\"error\"] === null ? undefined : data[\"error\"];\n if (result !== undefined && typeof result !== \"string\") {\n throw new Nip46Error(\"invalid NIP-46 response result\");\n }\n if (error !== undefined && typeof error !== \"string\") {\n throw new Nip46Error(\"invalid NIP-46 response error\");\n }\n const response: Mutable<Nip46Response> = { id: data[\"id\"] };\n if (result !== undefined) {\n response.result = result;\n }\n if (error !== undefined) {\n response.error = error;\n }\n return response;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"nip46.mjs","names":[],"sources":["../../src/nips/nip46.ts"],"sourcesContent":["/**\n * NIP-46 (Nostr Connect) protocol helpers: bunker URI, nostrconnect URI, RPC JSON.\n * Transport/signing live in {@link Nip46Signer}.\n *\n * @see https://github.com/nostr-protocol/nips/blob/master/46.md\n */\nimport { NostrError } from \"../core/error.ts\";\nimport type { Mutable } from \"../core/util.ts\";\nimport { containsLoneSurrogate, isHex32, isRecord } from \"../core/util.ts\";\n\nexport type BunkerPointer = {\n /** Remote signer / bunker public key (hex). */\n readonly pubkey: string;\n readonly relays: ReadonlyArray<string>;\n readonly secret?: string | undefined;\n};\n\nexport type ClientMetadata = {\n readonly name?: string | undefined;\n readonly url?: string | undefined;\n readonly image?: string | undefined;\n};\n\nexport type NostrConnectParams = {\n readonly clientPubkey: string;\n readonly relays: ReadonlyArray<string>;\n readonly secret: string;\n readonly perms?: ReadonlyArray<string> | undefined;\n} & ClientMetadata;\n\nexport type Nip46Request = {\n readonly id: string;\n readonly method: string;\n readonly params: ReadonlyArray<string>;\n};\n\nexport type Nip46Response = {\n readonly id: string;\n readonly result?: string | undefined;\n readonly error?: string | undefined;\n};\n\nexport class Nip46Error extends NostrError {\n override name = \"Nip46Error\";\n}\n\n/** Encode a bunker pointer as `bunker://…`. */\nexport function toBunkerURL(pointer: BunkerPointer): string {\n const url = new URL(`bunker://${pointer.pubkey.toLowerCase()}`);\n for (const relay of pointer.relays) {\n url.searchParams.append(\"relay\", relay);\n }\n if (pointer.secret !== undefined && pointer.secret !== \"\") {\n url.searchParams.set(\"secret\", pointer.secret);\n }\n return url.toString();\n}\n\n/**\n * Parse a `bunker://` URL into a pointer. Returns undefined when the input is not a bunker URL\n * (including NIP-05 identifiers).\n */\nexport function parseBunkerURL(input: string): BunkerPointer | undefined {\n let url: URL;\n try {\n url = new URL(input.trim());\n } catch {\n return undefined;\n }\n if (url.protocol !== \"bunker:\") {\n return undefined;\n }\n const pubkey = url.hostname || url.pathname.replace(/^\\/*/, \"\");\n if (!isHex32(pubkey.toLowerCase())) {\n return undefined;\n }\n return {\n pubkey: pubkey.toLowerCase(),\n relays: url.searchParams.getAll(\"relay\"),\n secret: url.searchParams.get(\"secret\") ?? undefined,\n };\n}\n\n/** Build a client-initiated `nostrconnect://` URI. */\nexport function createNostrConnectURI(params: NostrConnectParams): string {\n if (!isHex32(params.clientPubkey.toLowerCase())) {\n throw new Nip46Error(\"invalid client pubkey\");\n }\n if (!params.secret) {\n throw new Nip46Error(\"nostrconnect secret is required\");\n }\n if (params.relays.length === 0) {\n throw new Nip46Error(\"at least one relay is required\");\n }\n\n const query = new URLSearchParams();\n for (const relay of params.relays) {\n query.append(\"relay\", relay);\n }\n query.set(\"secret\", params.secret);\n if (params.perms !== undefined && params.perms.length > 0) {\n query.set(\"perms\", params.perms.join(\",\"));\n }\n if (params.name !== undefined && params.name !== \"\") {\n query.set(\"name\", params.name);\n }\n if (params.url !== undefined && params.url !== \"\") {\n query.set(\"url\", params.url);\n }\n if (params.image !== undefined && params.image !== \"\") {\n query.set(\"image\", params.image);\n }\n\n return `nostrconnect://${params.clientPubkey.toLowerCase()}?${query.toString()}`;\n}\n\n/** Parse a `nostrconnect://` URI. */\nexport function parseNostrConnectURI(uri: string): NostrConnectParams {\n let url: URL;\n try {\n url = new URL(uri);\n } catch {\n // No `cause`: Node's URL TypeError carries the rejected input, which may\n // hold the handshake `secret` — the URI is never echoed (N11).\n throw new Nip46Error(\"invalid nostrconnect URI\");\n }\n if (url.protocol !== \"nostrconnect:\") {\n throw new Nip46Error(`expected nostrconnect: scheme, got ${url.protocol}`);\n }\n const clientPubkey = url.hostname || url.pathname.replace(/^\\/*/, \"\");\n if (!isHex32(clientPubkey.toLowerCase())) {\n throw new Nip46Error(\"invalid client pubkey in nostrconnect URI\");\n }\n const secret = url.searchParams.get(\"secret\");\n if (secret === null || secret === \"\") {\n throw new Nip46Error(\"missing secret in nostrconnect URI\");\n }\n const relays = url.searchParams.getAll(\"relay\");\n if (relays.length === 0) {\n throw new Nip46Error(\"missing relays in nostrconnect URI\");\n }\n const params: Mutable<NostrConnectParams> = {\n clientPubkey: clientPubkey.toLowerCase(),\n relays,\n secret,\n };\n const permsRaw = url.searchParams.get(\"perms\");\n if (permsRaw !== null && permsRaw !== \"\") {\n params.perms = permsRaw.split(\",\").filter((p) => p !== \"\");\n }\n const name = url.searchParams.get(\"name\");\n if (name !== null) {\n params.name = name;\n }\n const metadataUrl = url.searchParams.get(\"url\");\n if (metadataUrl !== null) {\n params.url = metadataUrl;\n }\n const image = url.searchParams.get(\"image\");\n if (image !== null) {\n params.image = image;\n }\n return params;\n}\n\nexport function encodeNip46Request(req: Nip46Request): string {\n return JSON.stringify({ id: req.id, method: req.method, params: req.params });\n}\n\nexport function decodeNip46Request(json: string): Nip46Request {\n let data: unknown;\n try {\n data = JSON.parse(json);\n } catch {\n // No `cause`: some runtimes embed an input excerpt in the SyntaxError and\n // RPC payloads may carry the handshake `secret` (N11).\n throw new Nip46Error(\"invalid NIP-46 request JSON\");\n }\n if (containsLoneSurrogate(data)) {\n throw new Nip46Error(\"invalid NIP-46 request JSON\");\n }\n if (!isRecord(data) || typeof data[\"id\"] !== \"string\" || typeof data[\"method\"] !== \"string\") {\n throw new Nip46Error(\"invalid NIP-46 request shape\");\n }\n if (!Array.isArray(data[\"params\"]) || !data[\"params\"].every((p) => typeof p === \"string\")) {\n throw new Nip46Error(\"invalid NIP-46 request params\");\n }\n return { id: data[\"id\"], method: data[\"method\"], params: data[\"params\"] };\n}\n\nexport function encodeNip46Response(res: Nip46Response): string {\n const body: Record<string, string> = { id: res.id };\n if (res.result !== undefined) {\n body[\"result\"] = res.result;\n }\n if (res.error !== undefined) {\n body[\"error\"] = res.error;\n }\n return JSON.stringify(body);\n}\n\nexport function decodeNip46Response(json: string): Nip46Response {\n let data: unknown;\n try {\n data = JSON.parse(json);\n } catch {\n // No `cause`: same N11 reasoning as the request decoder.\n throw new Nip46Error(\"invalid NIP-46 response JSON\");\n }\n if (containsLoneSurrogate(data)) {\n throw new Nip46Error(\"invalid NIP-46 response JSON\");\n }\n if (!isRecord(data) || typeof data[\"id\"] !== \"string\") {\n throw new Nip46Error(\"invalid NIP-46 response shape\");\n }\n // Some remote signers send explicit nulls for absent fields.\n const result = data[\"result\"] === null ? undefined : data[\"result\"];\n const error = data[\"error\"] === null ? undefined : data[\"error\"];\n if (result !== undefined && typeof result !== \"string\") {\n throw new Nip46Error(\"invalid NIP-46 response result\");\n }\n if (error !== undefined && typeof error !== \"string\") {\n throw new Nip46Error(\"invalid NIP-46 response error\");\n }\n const response: Mutable<Nip46Response> = { id: data[\"id\"] };\n if (result !== undefined) {\n response.result = result;\n }\n if (error !== undefined) {\n response.error = error;\n }\n return response;\n}\n"],"mappings":";;;;;;;;AA0CA,IAAa,aAAb,cAAgC,WAAW;CACzC,OAAgB;AAClB;;AAGA,SAAgB,YAAY,SAAgC;CAC1D,MAAM,MAAM,IAAI,IAAI,YAAY,QAAQ,OAAO,YAAY,GAAG;CAC9D,KAAK,MAAM,SAAS,QAAQ,QAC1B,IAAI,aAAa,OAAO,SAAS,KAAK;CAExC,IAAI,QAAQ,WAAW,KAAA,KAAa,QAAQ,WAAW,IACrD,IAAI,aAAa,IAAI,UAAU,QAAQ,MAAM;CAE/C,OAAO,IAAI,SAAS;AACtB;;;;;AAMA,SAAgB,eAAe,OAA0C;CACvE,IAAI;CACJ,IAAI;EACF,MAAM,IAAI,IAAI,MAAM,KAAK,CAAC;CAC5B,QAAQ;EACN;CACF;CACA,IAAI,IAAI,aAAa,WACnB;CAEF,MAAM,SAAS,IAAI,YAAY,IAAI,SAAS,QAAQ,QAAQ,EAAE;CAC9D,IAAI,CAAC,QAAQ,OAAO,YAAY,CAAC,GAC/B;CAEF,OAAO;EACL,QAAQ,OAAO,YAAY;EAC3B,QAAQ,IAAI,aAAa,OAAO,OAAO;EACvC,QAAQ,IAAI,aAAa,IAAI,QAAQ,KAAK,KAAA;CAC5C;AACF;;AAGA,SAAgB,sBAAsB,QAAoC;CACxE,IAAI,CAAC,QAAQ,OAAO,aAAa,YAAY,CAAC,GAC5C,MAAM,IAAI,WAAW,uBAAuB;CAE9C,IAAI,CAAC,OAAO,QACV,MAAM,IAAI,WAAW,iCAAiC;CAExD,IAAI,OAAO,OAAO,WAAW,GAC3B,MAAM,IAAI,WAAW,gCAAgC;CAGvD,MAAM,QAAQ,IAAI,gBAAgB;CAClC,KAAK,MAAM,SAAS,OAAO,QACzB,MAAM,OAAO,SAAS,KAAK;CAE7B,MAAM,IAAI,UAAU,OAAO,MAAM;CACjC,IAAI,OAAO,UAAU,KAAA,KAAa,OAAO,MAAM,SAAS,GACtD,MAAM,IAAI,SAAS,OAAO,MAAM,KAAK,GAAG,CAAC;CAE3C,IAAI,OAAO,SAAS,KAAA,KAAa,OAAO,SAAS,IAC/C,MAAM,IAAI,QAAQ,OAAO,IAAI;CAE/B,IAAI,OAAO,QAAQ,KAAA,KAAa,OAAO,QAAQ,IAC7C,MAAM,IAAI,OAAO,OAAO,GAAG;CAE7B,IAAI,OAAO,UAAU,KAAA,KAAa,OAAO,UAAU,IACjD,MAAM,IAAI,SAAS,OAAO,KAAK;CAGjC,OAAO,kBAAkB,OAAO,aAAa,YAAY,EAAE,GAAG,MAAM,SAAS;AAC/E;;AAGA,SAAgB,qBAAqB,KAAiC;CACpE,IAAI;CACJ,IAAI;EACF,MAAM,IAAI,IAAI,GAAG;CACnB,QAAQ;EAGN,MAAM,IAAI,WAAW,0BAA0B;CACjD;CACA,IAAI,IAAI,aAAa,iBACnB,MAAM,IAAI,WAAW,sCAAsC,IAAI,UAAU;CAE3E,MAAM,eAAe,IAAI,YAAY,IAAI,SAAS,QAAQ,QAAQ,EAAE;CACpE,IAAI,CAAC,QAAQ,aAAa,YAAY,CAAC,GACrC,MAAM,IAAI,WAAW,2CAA2C;CAElE,MAAM,SAAS,IAAI,aAAa,IAAI,QAAQ;CAC5C,IAAI,WAAW,QAAQ,WAAW,IAChC,MAAM,IAAI,WAAW,oCAAoC;CAE3D,MAAM,SAAS,IAAI,aAAa,OAAO,OAAO;CAC9C,IAAI,OAAO,WAAW,GACpB,MAAM,IAAI,WAAW,oCAAoC;CAE3D,MAAM,SAAsC;EAC1C,cAAc,aAAa,YAAY;EACvC;EACA;CACF;CACA,MAAM,WAAW,IAAI,aAAa,IAAI,OAAO;CAC7C,IAAI,aAAa,QAAQ,aAAa,IACpC,OAAO,QAAQ,SAAS,MAAM,GAAG,CAAC,CAAC,QAAQ,MAAM,MAAM,EAAE;CAE3D,MAAM,OAAO,IAAI,aAAa,IAAI,MAAM;CACxC,IAAI,SAAS,MACX,OAAO,OAAO;CAEhB,MAAM,cAAc,IAAI,aAAa,IAAI,KAAK;CAC9C,IAAI,gBAAgB,MAClB,OAAO,MAAM;CAEf,MAAM,QAAQ,IAAI,aAAa,IAAI,OAAO;CAC1C,IAAI,UAAU,MACZ,OAAO,QAAQ;CAEjB,OAAO;AACT;AAEA,SAAgB,mBAAmB,KAA2B;CAC5D,OAAO,KAAK,UAAU;EAAE,IAAI,IAAI;EAAI,QAAQ,IAAI;EAAQ,QAAQ,IAAI;CAAO,CAAC;AAC9E;AAEA,SAAgB,mBAAmB,MAA4B;CAC7D,IAAI;CACJ,IAAI;EACF,OAAO,KAAK,MAAM,IAAI;CACxB,QAAQ;EAGN,MAAM,IAAI,WAAW,6BAA6B;CACpD;CACA,IAAI,sBAAsB,IAAI,GAC5B,MAAM,IAAI,WAAW,6BAA6B;CAEpD,IAAI,CAAC,SAAS,IAAI,KAAK,OAAO,KAAK,UAAU,YAAY,OAAO,KAAK,cAAc,UACjF,MAAM,IAAI,WAAW,8BAA8B;CAErD,IAAI,CAAC,MAAM,QAAQ,KAAK,SAAS,KAAK,CAAC,KAAK,SAAS,CAAC,OAAO,MAAM,OAAO,MAAM,QAAQ,GACtF,MAAM,IAAI,WAAW,+BAA+B;CAEtD,OAAO;EAAE,IAAI,KAAK;EAAO,QAAQ,KAAK;EAAW,QAAQ,KAAK;CAAU;AAC1E;AAEA,SAAgB,oBAAoB,KAA4B;CAC9D,MAAM,OAA+B,EAAE,IAAI,IAAI,GAAG;CAClD,IAAI,IAAI,WAAW,KAAA,GACjB,KAAK,YAAY,IAAI;CAEvB,IAAI,IAAI,UAAU,KAAA,GAChB,KAAK,WAAW,IAAI;CAEtB,OAAO,KAAK,UAAU,IAAI;AAC5B;AAEA,SAAgB,oBAAoB,MAA6B;CAC/D,IAAI;CACJ,IAAI;EACF,OAAO,KAAK,MAAM,IAAI;CACxB,QAAQ;EAEN,MAAM,IAAI,WAAW,8BAA8B;CACrD;CACA,IAAI,sBAAsB,IAAI,GAC5B,MAAM,IAAI,WAAW,8BAA8B;CAErD,IAAI,CAAC,SAAS,IAAI,KAAK,OAAO,KAAK,UAAU,UAC3C,MAAM,IAAI,WAAW,+BAA+B;CAGtD,MAAM,SAAS,KAAK,cAAc,OAAO,KAAA,IAAY,KAAK;CAC1D,MAAM,QAAQ,KAAK,aAAa,OAAO,KAAA,IAAY,KAAK;CACxD,IAAI,WAAW,KAAA,KAAa,OAAO,WAAW,UAC5C,MAAM,IAAI,WAAW,gCAAgC;CAEvD,IAAI,UAAU,KAAA,KAAa,OAAO,UAAU,UAC1C,MAAM,IAAI,WAAW,+BAA+B;CAEtD,MAAM,WAAmC,EAAE,IAAI,KAAK,MAAM;CAC1D,IAAI,WAAW,KAAA,GACb,SAAS,SAAS;CAEpB,IAAI,UAAU,KAAA,GACZ,SAAS,QAAQ;CAEnB,OAAO;AACT"}
@@ -1,4 +1,4 @@
1
- import { a as NostrError } from "../error-BquXDmIN.mjs";
1
+ import { a as NostrError } from "../error-BPqAU5RB.mjs";
2
2
  //#region src/nips/nip49.d.ts
3
3
  export type Ncryptsec = `ncryptsec1${string}`;
4
4
  export type KeySecurityByte = 0x00 | 0x01 | 0x02;
@@ -1,4 +1,4 @@
1
- import { m as utf8Encoder, r as assertSecretKeyBytes, y as NostrError } from "../util-DDpPkiED.mjs";
1
+ import { g as utf8Encoder, r as assertSecretKeyBytes, x as NostrError } from "../util-DfsyJ9nz.mjs";
2
2
  import { Bech32MaxSize, encodeBytes } from "./nip19.mjs";
3
3
  import { concatBytes, randomBytes } from "@noble/hashes/utils.js";
4
4
  import { secp256k1 } from "@noble/curves/secp256k1.js";
@@ -1,2 +1,2 @@
1
- import { a as encryptPrivateTags, c as parseEmojiSet, d as parseMuteList, f as parseMuteListPrivate, g as pinListEventBuilder, h as parseUserEmojiList, i as decryptPrivateTags, l as parseFavoriteRelays, m as parseRelaySet, n as Nip51Crypto, o as muteListEventBuilder, p as parsePinList, r as bookmarkListEventBuilder, s as parseBookmarkList, t as MuteItem, u as parseFollowPack } from "../nip51-D_bu8Hph.mjs";
1
+ import { a as encryptPrivateTags, c as parseEmojiSet, d as parseMuteList, f as parseMuteListPrivate, g as pinListEventBuilder, h as parseUserEmojiList, i as decryptPrivateTags, l as parseFavoriteRelays, m as parseRelaySet, n as Nip51Crypto, o as muteListEventBuilder, p as parsePinList, r as bookmarkListEventBuilder, s as parseBookmarkList, t as MuteItem, u as parseFollowPack } from "../nip51-C_kTYcud.mjs";
2
2
  export { MuteItem, Nip51Crypto, bookmarkListEventBuilder, decryptPrivateTags, encryptPrivateTags, muteListEventBuilder, parseBookmarkList, parseEmojiSet, parseFavoriteRelays, parseFollowPack, parseMuteList, parseMuteListPrivate, parsePinList, parseRelaySet, parseUserEmojiList, pinListEventBuilder };
@@ -1,7 +1,7 @@
1
- import { g as EventValidationError, n as assertHex32, s as isHex32, u as normalizeRelayUrls } from "../util-DDpPkiED.mjs";
2
- import { t as Kind } from "../kind-D8fENiiL.mjs";
3
- import { b as getDTag, g as Tag, v as firstTagValue, x as isTag } from "../event-B3ua2_oL.mjs";
4
- import { t as EventBuilder } from "../builder-Cf2N12CG.mjs";
1
+ import { a as containsLoneSurrogate, c as isHex32, d as normalizeRelayUrls, n as assertHex32, v as EventValidationError } from "../util-DfsyJ9nz.mjs";
2
+ import { t as Kind } from "../kind-I2Tvbe82.mjs";
3
+ import { b as getDTag, g as Tag, v as firstTagValue, x as isTag } from "../event-DvM1f03b.mjs";
4
+ import { t as EventBuilder } from "../builder-CGgM_QHT.mjs";
5
5
  //#region src/nips/nip51.ts
6
6
  /**
7
7
  * NIP-51: Lists Public tags plus NIP-44 private `.content` (author encrypts to self). Crypto is
@@ -117,7 +117,7 @@ async function decryptPrivateTags(crypto, event) {
117
117
  } catch (error) {
118
118
  throw new EventValidationError("invalid NIP-51 private tags", { cause: error });
119
119
  }
120
- if (!Array.isArray(parsed) || !parsed.every(isTag)) throw new EventValidationError("invalid NIP-51 private tags");
120
+ if (containsLoneSurrogate(parsed) || !Array.isArray(parsed) || !parsed.every(isTag)) throw new EventValidationError("invalid NIP-51 private tags");
121
121
  return parsed;
122
122
  }
123
123
  /** Public mute items from tags; private mute items from NIP-44 `.content`. */
@@ -1 +1 @@
1
- {"version":3,"file":"nip51.mjs","names":[],"sources":["../../src/nips/nip51.ts"],"sourcesContent":["/**\n * NIP-51: Lists Public tags plus NIP-44 private `.content` (author encrypts to self). Crypto is\n * injected; this module does not import signer. No NIP-04 sniff. Kind 10063 Blossom servers live in\n * blossom.ts.\n *\n * @see https://github.com/nostr-protocol/nips/blob/master/51.md\n */\nimport { EventBuilder } from \"../core/builder.ts\";\nimport { EventValidationError } from \"../core/error.ts\";\nimport type { Event } from \"../core/event.ts\";\nimport { Kind } from \"../core/kind.ts\";\nimport { firstTagValue, getDTag, isTag, Tag } from \"../core/tag.ts\";\nimport { assertHex32, isHex32, normalizeRelayUrls } from \"../core/util.ts\";\n\n/**\n * Structural crypto used by NIP-51 private tags. Satisfied by NostrSigner when\n * nip44Encrypt/nip44Decrypt are present. This module must not import src/signer/.\n */\nexport type Nip51Crypto = {\n getPublicKey: () => Promise<string>;\n nip44Encrypt: (peer: string, plaintext: string) => Promise<string>;\n nip44Decrypt: (peer: string, payload: string) => Promise<string>;\n};\n\nexport type MuteItem =\n | { readonly type: \"pubkey\"; readonly value: string }\n | { readonly type: \"event\"; readonly value: string }\n | { readonly type: \"hashtag\"; readonly value: string }\n | { readonly type: \"word\"; readonly value: string };\n\nfunction requireKind(event: Pick<Event, \"kind\">, kind: number): void {\n if (event.kind !== kind) {\n throw new EventValidationError(`expected kind ${kind}, got ${event.kind}`);\n }\n}\n\nfunction collectRelays(tags: Event[\"tags\"]): string[] {\n const urls: string[] = [];\n for (const tag of tags) {\n const value = tag.at(1);\n if (tag[0] === \"relay\" && value !== undefined) {\n urls.push(value);\n }\n }\n return normalizeRelayUrls(urls);\n}\n\nfunction collectEmoji(tags: Event[\"tags\"]): Array<{ shortcode: string; url: string }> {\n const out: Array<{ shortcode: string; url: string }> = [];\n for (const tag of tags) {\n if (tag[0] !== \"emoji\") {\n continue;\n }\n const shortcode = tag.at(1);\n const url = tag.at(2);\n if (shortcode === undefined || shortcode === \"\" || url === undefined || url === \"\") {\n continue;\n }\n out.push({ shortcode, url });\n }\n return out;\n}\n\nfunction identifier(tags: Event[\"tags\"]): string {\n return getDTag(tags) ?? \"\";\n}\n\n/** NIP-51 a-tag: `30002:<64-hex-pubkey>:<d>` with `d` possibly containing `:`. */\nfunction isRelaySetAddress(value: string): boolean {\n const kindSep = value.indexOf(\":\");\n if (kindSep === -1) {\n return false;\n }\n if (value.slice(0, kindSep) !== String(Kind.RelaySets)) {\n return false;\n }\n const pkSep = value.indexOf(\":\", kindSep + 1);\n if (pkSep === -1) {\n return false;\n }\n const pubkey = value.slice(kindSep + 1, pkSep);\n const d = value.slice(pkSep + 1);\n return isHex32(pubkey.toLowerCase()) && d.length > 0;\n}\n\n/** Parse kind:10000 mute list public tags (`p` / `e` / `t` / `word`). */\nexport function parseMuteList(event: Pick<Event, \"kind\" | \"tags\">): MuteItem[] {\n requireKind(event, Kind.MuteList);\n const items: MuteItem[] = [];\n for (const tag of event.tags) {\n const value = tag.at(1);\n if (value === undefined || value === \"\") {\n continue;\n }\n switch (tag[0]) {\n case \"p\":\n if (isHex32(value.toLowerCase())) {\n items.push({ type: \"pubkey\", value: value.toLowerCase() });\n }\n break;\n case \"e\":\n if (isHex32(value.toLowerCase())) {\n items.push({ type: \"event\", value: value.toLowerCase() });\n }\n break;\n case \"t\":\n // NIP-51 does not require hashtag case-folding.\n items.push({ type: \"hashtag\", value });\n break;\n case \"word\":\n items.push({ type: \"word\", value: value.toLowerCase() });\n break;\n default:\n break;\n }\n }\n return items;\n}\n\n/** Build an unsigned kind:10000 EventBuilder from public mute items. */\nexport function muteListEventBuilder(items: ReadonlyArray<MuteItem>): EventBuilder {\n const b = new EventBuilder(Kind.MuteList, \"\");\n for (const item of items) {\n switch (item.type) {\n case \"pubkey\":\n b.tag(Tag.p(assertHex32(item.value, \"public key\")));\n break;\n case \"event\":\n b.tag(Tag.e(assertHex32(item.value, \"event id\")));\n break;\n case \"hashtag\":\n if (item.value) {\n b.tag(Tag.t(item.value));\n }\n break;\n case \"word\":\n if (item.value) {\n b.tag([\"word\", item.value.toLowerCase()]);\n }\n break;\n }\n }\n return b;\n}\n\n/** NIP-44 ciphertext of tags, conversation peer = author's own pubkey. */\nexport async function encryptPrivateTags(\n crypto: Nip51Crypto,\n tags: Event[\"tags\"],\n): Promise<string> {\n const self = await crypto.getPublicKey();\n return crypto.nip44Encrypt(self, JSON.stringify(tags));\n}\n\n/** Decrypt NIP-51 `.content`. Empty content is no private tags, not ciphertext. */\nexport async function decryptPrivateTags(\n crypto: Nip51Crypto,\n event: Pick<Event, \"pubkey\" | \"content\">,\n): Promise<Event[\"tags\"]> {\n // NIP-44 rejects empty plaintext; empty `.content` means no private items.\n if (event.content === \"\") {\n return [];\n }\n const self = await crypto.getPublicKey();\n if (self.toLowerCase() !== event.pubkey.toLowerCase()) {\n throw new EventValidationError(\"NIP-51 private content is only for the author\");\n }\n const plaintext = await crypto.nip44Decrypt(self, event.content);\n let parsed: unknown;\n try {\n parsed = JSON.parse(plaintext);\n } catch (error) {\n throw new EventValidationError(\"invalid NIP-51 private tags\", { cause: error });\n }\n if (!Array.isArray(parsed) || !parsed.every(isTag)) {\n throw new EventValidationError(\"invalid NIP-51 private tags\");\n }\n return parsed;\n}\n\n/** Public mute items from tags; private mute items from NIP-44 `.content`. */\nexport async function parseMuteListPrivate(\n crypto: Nip51Crypto,\n event: Pick<Event, \"kind\" | \"tags\" | \"pubkey\" | \"content\">,\n): Promise<{ public: MuteItem[]; private: MuteItem[] }> {\n const pub = parseMuteList(event);\n const decrypted = await decryptPrivateTags(crypto, event);\n return {\n public: pub,\n private: parseMuteList({ kind: event.kind, tags: decrypted }),\n };\n}\n\n/** Parse kind:10001 pin list public `e` tags. */\nexport function parsePinList(event: Pick<Event, \"kind\" | \"tags\">): string[] {\n requireKind(event, Kind.PinList);\n const ids: string[] = [];\n for (const tag of event.tags) {\n const value = tag.at(1);\n if (tag[0] !== \"e\" || value === undefined || !isHex32(value.toLowerCase())) {\n continue;\n }\n ids.push(value.toLowerCase());\n }\n return ids;\n}\n\n/** Build an unsigned kind:10001 EventBuilder from event ids. */\nexport function pinListEventBuilder(ids: ReadonlyArray<string>): EventBuilder {\n const b = new EventBuilder(Kind.PinList, \"\");\n for (const id of ids) {\n b.tag(Tag.e(assertHex32(id, \"event id\")));\n }\n return b;\n}\n\n/** Parse kind:10003 bookmark list public `e` and `a` tags. */\nexport function parseBookmarkList(event: Pick<Event, \"kind\" | \"tags\">): {\n e: string[];\n a: string[];\n} {\n requireKind(event, Kind.BookmarkList);\n const e: string[] = [];\n const a: string[] = [];\n for (const tag of event.tags) {\n const value = tag.at(1);\n if (value === undefined || value === \"\") {\n continue;\n }\n if (tag[0] === \"e\") {\n if (isHex32(value.toLowerCase())) {\n e.push(value.toLowerCase());\n }\n } else if (tag[0] === \"a\") {\n a.push(value);\n }\n }\n return { e, a };\n}\n\n/** Build an unsigned kind:10003 EventBuilder from public bookmark pointers. */\nexport function bookmarkListEventBuilder(items: {\n e?: ReadonlyArray<string>;\n a?: ReadonlyArray<string>;\n}): EventBuilder {\n const b = new EventBuilder(Kind.BookmarkList, \"\");\n if (items.e) {\n for (const id of items.e) {\n b.tag(Tag.e(assertHex32(id, \"event id\")));\n }\n }\n if (items.a) {\n for (const coord of items.a) {\n if (coord) {\n b.tag(Tag.a(coord));\n }\n }\n }\n return b;\n}\n\n/** Parse kind:10030 user emoji list public `emoji` and `a` tags. */\nexport function parseUserEmojiList(event: Pick<Event, \"kind\" | \"tags\">): {\n emoji: Array<{ shortcode: string; url: string }>;\n sets: string[];\n} {\n requireKind(event, Kind.UserEmojiList);\n const sets: string[] = [];\n for (const tag of event.tags) {\n const value = tag.at(1);\n if (tag[0] === \"a\" && value !== undefined && value !== \"\") {\n sets.push(value);\n }\n }\n return { emoji: collectEmoji(event.tags), sets };\n}\n\n/** Parse kind:30002 relay set public `d` and `relay` tags. */\nexport function parseRelaySet(event: Pick<Event, \"kind\" | \"tags\">): {\n d: string;\n relays: string[];\n} {\n requireKind(event, Kind.RelaySets);\n return { d: identifier(event.tags), relays: collectRelays(event.tags) };\n}\n\n/** Parse kind:10012 favorite relays public `relay` tags and kind:30002 `a` tags. */\nexport function parseFavoriteRelays(event: Pick<Event, \"kind\" | \"tags\">): {\n relays: string[];\n sets: string[];\n} {\n requireKind(event, Kind.FavoriteRelays);\n const sets: string[] = [];\n for (const tag of event.tags) {\n const value = tag.at(1);\n if (tag[0] !== \"a\" || value === undefined || !isRelaySetAddress(value)) {\n continue;\n }\n sets.push(value);\n }\n return { relays: collectRelays(event.tags), sets };\n}\n\n/** Parse kind:30030 emoji set public `d` / `title` / `emoji` tags. */\nexport function parseEmojiSet(event: Pick<Event, \"kind\" | \"tags\">): {\n d: string;\n title?: string;\n emoji: Array<{ shortcode: string; url: string }>;\n} {\n requireKind(event, Kind.EmojiSet);\n const result: {\n d: string;\n title?: string;\n emoji: Array<{ shortcode: string; url: string }>;\n } = { d: identifier(event.tags), emoji: collectEmoji(event.tags) };\n const title = firstTagValue(event.tags, \"title\");\n // empty title is treated as absent\n if (title !== undefined && title !== \"\") {\n result.title = title;\n }\n return result;\n}\n\n/** Parse kind:39089 starter pack (follow pack) public `d` and `p` tags. */\nexport function parseFollowPack(event: Pick<Event, \"kind\" | \"tags\">): {\n d: string;\n pubkeys: string[];\n} {\n requireKind(event, Kind.StarterPack);\n const pubkeys: string[] = [];\n for (const tag of event.tags) {\n const value = tag.at(1);\n if (tag[0] !== \"p\" || value === undefined || !isHex32(value.toLowerCase())) {\n continue;\n }\n pubkeys.push(value.toLowerCase());\n }\n return { d: identifier(event.tags), pubkeys };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"nip51.mjs","names":[],"sources":["../../src/nips/nip51.ts"],"sourcesContent":["/**\n * NIP-51: Lists Public tags plus NIP-44 private `.content` (author encrypts to self). Crypto is\n * injected; this module does not import signer. No NIP-04 sniff. Kind 10063 Blossom servers live in\n * blossom.ts.\n *\n * @see https://github.com/nostr-protocol/nips/blob/master/51.md\n */\nimport { EventBuilder } from \"../core/builder.ts\";\nimport { EventValidationError } from \"../core/error.ts\";\nimport type { Event } from \"../core/event.ts\";\nimport { Kind } from \"../core/kind.ts\";\nimport { firstTagValue, getDTag, isTag, Tag } from \"../core/tag.ts\";\nimport { assertHex32, containsLoneSurrogate, isHex32, normalizeRelayUrls } from \"../core/util.ts\";\n\n/**\n * Structural crypto used by NIP-51 private tags. Satisfied by NostrSigner when\n * nip44Encrypt/nip44Decrypt are present. This module must not import src/signer/.\n */\nexport type Nip51Crypto = {\n getPublicKey: () => Promise<string>;\n nip44Encrypt: (peer: string, plaintext: string) => Promise<string>;\n nip44Decrypt: (peer: string, payload: string) => Promise<string>;\n};\n\nexport type MuteItem =\n | { readonly type: \"pubkey\"; readonly value: string }\n | { readonly type: \"event\"; readonly value: string }\n | { readonly type: \"hashtag\"; readonly value: string }\n | { readonly type: \"word\"; readonly value: string };\n\nfunction requireKind(event: Pick<Event, \"kind\">, kind: number): void {\n if (event.kind !== kind) {\n throw new EventValidationError(`expected kind ${kind}, got ${event.kind}`);\n }\n}\n\nfunction collectRelays(tags: Event[\"tags\"]): string[] {\n const urls: string[] = [];\n for (const tag of tags) {\n const value = tag.at(1);\n if (tag[0] === \"relay\" && value !== undefined) {\n urls.push(value);\n }\n }\n return normalizeRelayUrls(urls);\n}\n\nfunction collectEmoji(tags: Event[\"tags\"]): Array<{ shortcode: string; url: string }> {\n const out: Array<{ shortcode: string; url: string }> = [];\n for (const tag of tags) {\n if (tag[0] !== \"emoji\") {\n continue;\n }\n const shortcode = tag.at(1);\n const url = tag.at(2);\n if (shortcode === undefined || shortcode === \"\" || url === undefined || url === \"\") {\n continue;\n }\n out.push({ shortcode, url });\n }\n return out;\n}\n\nfunction identifier(tags: Event[\"tags\"]): string {\n return getDTag(tags) ?? \"\";\n}\n\n/** NIP-51 a-tag: `30002:<64-hex-pubkey>:<d>` with `d` possibly containing `:`. */\nfunction isRelaySetAddress(value: string): boolean {\n const kindSep = value.indexOf(\":\");\n if (kindSep === -1) {\n return false;\n }\n if (value.slice(0, kindSep) !== String(Kind.RelaySets)) {\n return false;\n }\n const pkSep = value.indexOf(\":\", kindSep + 1);\n if (pkSep === -1) {\n return false;\n }\n const pubkey = value.slice(kindSep + 1, pkSep);\n const d = value.slice(pkSep + 1);\n return isHex32(pubkey.toLowerCase()) && d.length > 0;\n}\n\n/** Parse kind:10000 mute list public tags (`p` / `e` / `t` / `word`). */\nexport function parseMuteList(event: Pick<Event, \"kind\" | \"tags\">): MuteItem[] {\n requireKind(event, Kind.MuteList);\n const items: MuteItem[] = [];\n for (const tag of event.tags) {\n const value = tag.at(1);\n if (value === undefined || value === \"\") {\n continue;\n }\n switch (tag[0]) {\n case \"p\":\n if (isHex32(value.toLowerCase())) {\n items.push({ type: \"pubkey\", value: value.toLowerCase() });\n }\n break;\n case \"e\":\n if (isHex32(value.toLowerCase())) {\n items.push({ type: \"event\", value: value.toLowerCase() });\n }\n break;\n case \"t\":\n // NIP-51 does not require hashtag case-folding.\n items.push({ type: \"hashtag\", value });\n break;\n case \"word\":\n items.push({ type: \"word\", value: value.toLowerCase() });\n break;\n default:\n break;\n }\n }\n return items;\n}\n\n/** Build an unsigned kind:10000 EventBuilder from public mute items. */\nexport function muteListEventBuilder(items: ReadonlyArray<MuteItem>): EventBuilder {\n const b = new EventBuilder(Kind.MuteList, \"\");\n for (const item of items) {\n switch (item.type) {\n case \"pubkey\":\n b.tag(Tag.p(assertHex32(item.value, \"public key\")));\n break;\n case \"event\":\n b.tag(Tag.e(assertHex32(item.value, \"event id\")));\n break;\n case \"hashtag\":\n if (item.value) {\n b.tag(Tag.t(item.value));\n }\n break;\n case \"word\":\n if (item.value) {\n b.tag([\"word\", item.value.toLowerCase()]);\n }\n break;\n }\n }\n return b;\n}\n\n/** NIP-44 ciphertext of tags, conversation peer = author's own pubkey. */\nexport async function encryptPrivateTags(\n crypto: Nip51Crypto,\n tags: Event[\"tags\"],\n): Promise<string> {\n const self = await crypto.getPublicKey();\n return crypto.nip44Encrypt(self, JSON.stringify(tags));\n}\n\n/** Decrypt NIP-51 `.content`. Empty content is no private tags, not ciphertext. */\nexport async function decryptPrivateTags(\n crypto: Nip51Crypto,\n event: Pick<Event, \"pubkey\" | \"content\">,\n): Promise<Event[\"tags\"]> {\n // NIP-44 rejects empty plaintext; empty `.content` means no private items.\n if (event.content === \"\") {\n return [];\n }\n const self = await crypto.getPublicKey();\n if (self.toLowerCase() !== event.pubkey.toLowerCase()) {\n throw new EventValidationError(\"NIP-51 private content is only for the author\");\n }\n const plaintext = await crypto.nip44Decrypt(self, event.content);\n let parsed: unknown;\n try {\n parsed = JSON.parse(plaintext);\n } catch (error) {\n throw new EventValidationError(\"invalid NIP-51 private tags\", { cause: error });\n }\n // Scan the parsed value before the shape check: `JSON.parse` accepts lone\n // surrogates via `\\ud800`-style escapes while serde_json rejects them, so\n // both sides must agree on the invalid-tags error (N10).\n if (containsLoneSurrogate(parsed) || !Array.isArray(parsed) || !parsed.every(isTag)) {\n throw new EventValidationError(\"invalid NIP-51 private tags\");\n }\n return parsed;\n}\n\n/** Public mute items from tags; private mute items from NIP-44 `.content`. */\nexport async function parseMuteListPrivate(\n crypto: Nip51Crypto,\n event: Pick<Event, \"kind\" | \"tags\" | \"pubkey\" | \"content\">,\n): Promise<{ public: MuteItem[]; private: MuteItem[] }> {\n const pub = parseMuteList(event);\n const decrypted = await decryptPrivateTags(crypto, event);\n return {\n public: pub,\n private: parseMuteList({ kind: event.kind, tags: decrypted }),\n };\n}\n\n/** Parse kind:10001 pin list public `e` tags. */\nexport function parsePinList(event: Pick<Event, \"kind\" | \"tags\">): string[] {\n requireKind(event, Kind.PinList);\n const ids: string[] = [];\n for (const tag of event.tags) {\n const value = tag.at(1);\n if (tag[0] !== \"e\" || value === undefined || !isHex32(value.toLowerCase())) {\n continue;\n }\n ids.push(value.toLowerCase());\n }\n return ids;\n}\n\n/** Build an unsigned kind:10001 EventBuilder from event ids. */\nexport function pinListEventBuilder(ids: ReadonlyArray<string>): EventBuilder {\n const b = new EventBuilder(Kind.PinList, \"\");\n for (const id of ids) {\n b.tag(Tag.e(assertHex32(id, \"event id\")));\n }\n return b;\n}\n\n/** Parse kind:10003 bookmark list public `e` and `a` tags. */\nexport function parseBookmarkList(event: Pick<Event, \"kind\" | \"tags\">): {\n e: string[];\n a: string[];\n} {\n requireKind(event, Kind.BookmarkList);\n const e: string[] = [];\n const a: string[] = [];\n for (const tag of event.tags) {\n const value = tag.at(1);\n if (value === undefined || value === \"\") {\n continue;\n }\n if (tag[0] === \"e\") {\n if (isHex32(value.toLowerCase())) {\n e.push(value.toLowerCase());\n }\n } else if (tag[0] === \"a\") {\n a.push(value);\n }\n }\n return { e, a };\n}\n\n/** Build an unsigned kind:10003 EventBuilder from public bookmark pointers. */\nexport function bookmarkListEventBuilder(items: {\n e?: ReadonlyArray<string>;\n a?: ReadonlyArray<string>;\n}): EventBuilder {\n const b = new EventBuilder(Kind.BookmarkList, \"\");\n if (items.e) {\n for (const id of items.e) {\n b.tag(Tag.e(assertHex32(id, \"event id\")));\n }\n }\n if (items.a) {\n for (const coord of items.a) {\n if (coord) {\n b.tag(Tag.a(coord));\n }\n }\n }\n return b;\n}\n\n/** Parse kind:10030 user emoji list public `emoji` and `a` tags. */\nexport function parseUserEmojiList(event: Pick<Event, \"kind\" | \"tags\">): {\n emoji: Array<{ shortcode: string; url: string }>;\n sets: string[];\n} {\n requireKind(event, Kind.UserEmojiList);\n const sets: string[] = [];\n for (const tag of event.tags) {\n const value = tag.at(1);\n if (tag[0] === \"a\" && value !== undefined && value !== \"\") {\n sets.push(value);\n }\n }\n return { emoji: collectEmoji(event.tags), sets };\n}\n\n/** Parse kind:30002 relay set public `d` and `relay` tags. */\nexport function parseRelaySet(event: Pick<Event, \"kind\" | \"tags\">): {\n d: string;\n relays: string[];\n} {\n requireKind(event, Kind.RelaySets);\n return { d: identifier(event.tags), relays: collectRelays(event.tags) };\n}\n\n/** Parse kind:10012 favorite relays public `relay` tags and kind:30002 `a` tags. */\nexport function parseFavoriteRelays(event: Pick<Event, \"kind\" | \"tags\">): {\n relays: string[];\n sets: string[];\n} {\n requireKind(event, Kind.FavoriteRelays);\n const sets: string[] = [];\n for (const tag of event.tags) {\n const value = tag.at(1);\n if (tag[0] !== \"a\" || value === undefined || !isRelaySetAddress(value)) {\n continue;\n }\n sets.push(value);\n }\n return { relays: collectRelays(event.tags), sets };\n}\n\n/** Parse kind:30030 emoji set public `d` / `title` / `emoji` tags. */\nexport function parseEmojiSet(event: Pick<Event, \"kind\" | \"tags\">): {\n d: string;\n title?: string;\n emoji: Array<{ shortcode: string; url: string }>;\n} {\n requireKind(event, Kind.EmojiSet);\n const result: {\n d: string;\n title?: string;\n emoji: Array<{ shortcode: string; url: string }>;\n } = { d: identifier(event.tags), emoji: collectEmoji(event.tags) };\n const title = firstTagValue(event.tags, \"title\");\n // empty title is treated as absent\n if (title !== undefined && title !== \"\") {\n result.title = title;\n }\n return result;\n}\n\n/** Parse kind:39089 starter pack (follow pack) public `d` and `p` tags. */\nexport function parseFollowPack(event: Pick<Event, \"kind\" | \"tags\">): {\n d: string;\n pubkeys: string[];\n} {\n requireKind(event, Kind.StarterPack);\n const pubkeys: string[] = [];\n for (const tag of event.tags) {\n const value = tag.at(1);\n if (tag[0] !== \"p\" || value === undefined || !isHex32(value.toLowerCase())) {\n continue;\n }\n pubkeys.push(value.toLowerCase());\n }\n return { d: identifier(event.tags), pubkeys };\n}\n"],"mappings":"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"}
@@ -1,7 +1,7 @@
1
- import { f as nowSeconds, g as EventValidationError, m as utf8Encoder, o as hexToBytes } from "../util-DDpPkiED.mjs";
2
- import { r as isAddressableKind, t as Kind } from "../kind-D8fENiiL.mjs";
3
- import { S as parseEventAddress, _ as eventAddress, b as getDTag, g as Tag, h as validateSignedEvent, v as firstTagValue } from "../event-B3ua2_oL.mjs";
4
- import { n as verifyEvent } from "../verifier-CjH1vnP2.mjs";
1
+ import { g as utf8Encoder, p as nowSeconds, s as hexToBytes, v as EventValidationError } from "../util-DfsyJ9nz.mjs";
2
+ import { r as isAddressableKind, t as Kind } from "../kind-I2Tvbe82.mjs";
3
+ import { S as parseEventAddress, _ as eventAddress, b as getDTag, g as Tag, 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 { bech32 } from "@scure/base";
7
7
  import { equalBytes } from "@noble/ciphers/utils.js";
@@ -142,7 +142,8 @@ function parseBolt11(pr) {
142
142
  if (type === BOLT11_TAG_EXPIRY) {
143
143
  if (!sawExpiry) {
144
144
  sawExpiry = true;
145
- fields.expiry = wordsToInt(data);
145
+ const expiry = wordsToInt(data);
146
+ if (Number.isSafeInteger(expiry)) fields.expiry = expiry;
146
147
  }
147
148
  continue;
148
149
  }
@@ -1 +1 @@
1
- {"version":3,"file":"nip57.mjs","names":[],"sources":["../../src/nips/nip57.ts"],"sourcesContent":["/**\n * NIP-57 Lightning Zaps — zap request template (kind 9734) and receipt validation (kind 9735). Does\n * not fetch LNURL. Receipt checks never throw.\n *\n * @see https://github.com/nostr-protocol/nips/blob/master/57.md\n */\nimport { equalBytes } from \"@noble/ciphers/utils.js\";\nimport { sha256 } from \"@noble/hashes/sha2.js\";\nimport { bech32 } from \"@scure/base\";\n\nimport { EventValidationError } from \"../core/error.ts\";\nimport { validateSignedEvent } from \"../core/event.ts\";\nimport type { Event, EventTemplate } from \"../core/event.ts\";\nimport { isAddressableKind, Kind } from \"../core/kind.ts\";\nimport { eventAddress, firstTagValue, getDTag, parseEventAddress, Tag } from \"../core/tag.ts\";\nimport type { Mutable } from \"../core/util.ts\";\nimport { hexToBytes, nowSeconds, utf8Encoder } from \"../core/util.ts\";\nimport { verifyEvent } from \"../core/verifier.ts\";\n\nexport type ProfileZapRequest = {\n readonly pubkey: string;\n /** Amount in millisats. */\n readonly amount: number;\n readonly relays: ReadonlyArray<string>;\n readonly comment?: string | undefined;\n readonly lnurl?: string | undefined;\n};\n\nexport type EventZapRequest = {\n readonly event: Event;\n /** Amount in millisats. */\n readonly amount: number;\n readonly relays: ReadonlyArray<string>;\n readonly comment?: string | undefined;\n readonly lnurl?: string | undefined;\n};\n\nexport function makeZapRequest(params: ProfileZapRequest | EventZapRequest): EventTemplate {\n if (!Number.isSafeInteger(params.amount) || params.amount <= 0) {\n throw new EventValidationError(\"zap amount must be a positive integer (msats)\");\n }\n if (params.relays.length === 0) {\n throw new EventValidationError(\"relays tag requires one or more URLs\");\n }\n\n const recipient = \"event\" in params ? params.event.pubkey : params.pubkey;\n const tags: Tag[] = [\n Tag.p(recipient),\n [\"amount\", params.amount.toString()],\n [\"relays\", ...params.relays],\n ];\n\n if (\"event\" in params) {\n const { event } = params;\n tags.push(Tag.e(event.id));\n if (isAddressableKind(event.kind)) {\n const d = getDTag(event.tags);\n if (d === undefined) {\n throw new EventValidationError(\"d tag not found\");\n }\n const addr = eventAddress(event);\n if (addr !== undefined) {\n tags.push([\"a\", addr]);\n }\n }\n tags.push([\"k\", event.kind.toString()]);\n }\n\n if (params.lnurl !== undefined && params.lnurl !== \"\") {\n tags.push([\"lnurl\", params.lnurl]);\n }\n\n return {\n kind: Kind.ZapRequest,\n created_at: nowSeconds(),\n content: params.comment ?? \"\",\n tags,\n };\n}\n\nexport type ZapReceiptContext = {\n readonly nostrPubkey: string;\n readonly lnurl?: string | undefined;\n};\n\nexport type ZapReceiptValidation =\n | {\n readonly valid: true;\n readonly request: Event;\n readonly amountMsats: number | undefined;\n }\n | { readonly valid: false; readonly reason: string };\n\nexport type Bolt11Fields = {\n readonly amountMsats?: number | undefined;\n /** The `d` tagged field, UTF-8 decoded; omitted on invalid UTF-8. */\n readonly description?: string | undefined;\n readonly descriptionHash?: Uint8Array | undefined;\n readonly paymentHash?: Uint8Array | undefined;\n /** Invoice creation time (unix seconds). */\n readonly timestamp: number;\n /** `x` tagged field — seconds after `timestamp` until expiry. Defaults to 3600 when absent. */\n readonly expiry: number;\n};\n\nfunction fail(reason: string): ZapReceiptValidation {\n return { valid: false, reason };\n}\n\nconst MSATS_PER_BTC = 100_000_000_000;\nconst MSATS_PER_MILLI = 100_000_000;\nconst MSATS_PER_MICRO = 100_000;\nconst MSATS_PER_NANO = 100;\n\n/** Amount is digits after `ln` + currency letters, optional m/u/n/p. Non-integer pico is omitted. */\nfunction amountMsatsFromHrp(hrp: string): number | undefined {\n if (!hrp.startsWith(\"ln\")) {\n return undefined;\n }\n let i = 2;\n while (i < hrp.length) {\n const c = hrp.codePointAt(i);\n if (c === undefined || c < 97 || c > 122) {\n break;\n }\n i++;\n }\n if (i === hrp.length) {\n return undefined;\n }\n const rest = hrp.slice(i);\n const m = /^([0-9]+)([munp])?$/.exec(rest);\n if (!m) {\n return undefined;\n }\n const n = Number(m[1]);\n if (!Number.isSafeInteger(n)) {\n return undefined;\n }\n const mul = m.at(2);\n const factor =\n mul === undefined\n ? MSATS_PER_BTC\n : mul === \"m\"\n ? MSATS_PER_MILLI\n : mul === \"u\"\n ? MSATS_PER_MICRO\n : mul === \"n\"\n ? MSATS_PER_NANO\n : 0;\n if (factor !== 0) {\n const msats = n * factor;\n return Number.isSafeInteger(msats) ? msats : undefined;\n }\n // p: 0.1 msat; drop amounts that are not whole millisats\n if (n % 10 !== 0) {\n return undefined;\n }\n return n / 10;\n}\n\nfunction parseMsatsTag(value: string): number | undefined {\n if (!/^[0-9]+$/.test(value)) {\n return undefined;\n }\n const n = Number(value);\n return Number.isSafeInteger(n) ? n : undefined;\n}\n\nfunction countTags(tags: ReadonlyArray<Tag>, name: string): number {\n let n = 0;\n for (const tag of tags) {\n if (tag[0] === name) {\n n++;\n }\n }\n return n;\n}\n\nfunction hasHexTagValue(tags: ReadonlyArray<Tag>, name: string, value: string): boolean {\n const needle = value.toLowerCase();\n for (const tag of tags) {\n if (tag[0] === name && tag[1] !== undefined && tag[1].toLowerCase() === needle) {\n return true;\n }\n }\n return false;\n}\n\nfunction hasAddressTag(tags: ReadonlyArray<Tag>, value: string): boolean {\n const want = parseEventAddress(value);\n if (!want) {\n return false;\n }\n for (const tag of tags) {\n if (tag[0] !== \"a\" || tag[1] === undefined) {\n continue;\n }\n const got = parseEventAddress(tag[1]);\n if (\n got &&\n got.kind === want.kind &&\n got.pubkey === want.pubkey &&\n got.identifier === want.identifier\n ) {\n return true;\n }\n }\n return false;\n}\n\nconst BOLT11_TIMESTAMP_WORDS = 7;\nconst BOLT11_SIGNATURE_WORDS = 104;\nconst BOLT11_HASH_WORDS = 52;\nconst HASH_BYTES = 32;\nconst BOLT11_TAG_PAYMENT_HASH = 1;\nconst BOLT11_TAG_EXPIRY = 6;\nconst BOLT11_TAG_DESCRIPTION = 13;\nconst BOLT11_TAG_DESCRIPTION_HASH = 23;\nconst DEFAULT_EXPIRY_SECONDS = 3600;\n\nconst utf8DecoderFatal = new TextDecoder(\"utf-8\", { fatal: true });\n\n/** Big-endian 5-bit words as a JS number (safe below 2^53). */\nfunction wordsToInt(words: ReadonlyArray<number>): number {\n let value = 0;\n for (const word of words) {\n value = value * 32 + word;\n }\n return value;\n}\n\n/** Decode a BOLT11 invoice. Never throws. Requires a 32-byte payment hash (type 1). */\nexport function parseBolt11(pr: string): Bolt11Fields | undefined {\n try {\n // Invoices exceed bech32's default 90-char limit.\n const { prefix, words } = bech32.decode(pr.toLowerCase(), false);\n if (!prefix.startsWith(\"ln\")) {\n return undefined;\n }\n // timestamp (7) + tagged fields + secp256k1 signature (104)\n if (words.length < BOLT11_TIMESTAMP_WORDS + BOLT11_SIGNATURE_WORDS) {\n return undefined;\n }\n const fields: Mutable<Bolt11Fields> = {\n timestamp: wordsToInt(words.slice(0, BOLT11_TIMESTAMP_WORDS)),\n expiry: DEFAULT_EXPIRY_SECONDS,\n };\n let sawExpiry = false;\n const amountMsats = amountMsatsFromHrp(prefix);\n if (amountMsats !== undefined) {\n fields.amountMsats = amountMsats;\n }\n const tlvEnd = words.length - BOLT11_SIGNATURE_WORDS;\n let i = BOLT11_TIMESTAMP_WORDS;\n while (i + 3 <= tlvEnd) {\n const type = words[i];\n const lenHigh = words[i + 1];\n const lenLow = words[i + 2];\n if (type === undefined || lenHigh === undefined || lenLow === undefined) {\n break;\n }\n const dataLen = lenHigh * 32 + lenLow;\n i += 3;\n if (i + dataLen > tlvEnd) {\n break;\n }\n const data = words.slice(i, i + dataLen);\n i += dataLen;\n if (type === BOLT11_TAG_EXPIRY) {\n if (!sawExpiry) {\n sawExpiry = true;\n fields.expiry = wordsToInt(data);\n }\n continue;\n }\n if (type === BOLT11_TAG_DESCRIPTION) {\n const bytes = bech32.fromWordsUnsafe(data);\n if (bytes !== undefined) {\n try {\n fields.description ??= utf8DecoderFatal.decode(bytes);\n } catch {\n // Invalid UTF-8 — drop the field, not the invoice.\n }\n }\n continue;\n }\n if (\n (type !== BOLT11_TAG_PAYMENT_HASH && type !== BOLT11_TAG_DESCRIPTION_HASH) ||\n dataLen !== BOLT11_HASH_WORDS\n ) {\n continue;\n }\n const bytes = bech32.fromWordsUnsafe(data);\n if (!bytes || bytes.length !== HASH_BYTES) {\n continue;\n }\n if (type === BOLT11_TAG_PAYMENT_HASH) {\n fields.paymentHash ??= bytes;\n } else {\n fields.descriptionHash ??= bytes;\n }\n }\n if (!fields.paymentHash) {\n return undefined;\n }\n return fields;\n } catch {\n return undefined;\n }\n}\n\n/** Parse the `description` tag of a kind 9735 receipt as a signed kind 9734. Never throws. */\nexport function parseZapRequestFromReceipt(receipt: Event): Event | undefined {\n try {\n const raw = firstTagValue(receipt.tags, \"description\");\n if (raw === undefined) {\n return undefined;\n }\n const parsed: unknown = JSON.parse(raw);\n if (!validateSignedEvent(parsed) || parsed.kind !== Kind.ZapRequest) {\n return undefined;\n }\n if (!verifyEvent(parsed)) {\n return undefined;\n }\n return parsed;\n } catch {\n return undefined;\n }\n}\n\n/** Validate a kind 9735 zap receipt. Never throws. */\nexport function validateZapReceipt(receipt: Event, ctx: ZapReceiptContext): ZapReceiptValidation {\n try {\n if (receipt.kind !== Kind.Zap || !verifyEvent(receipt)) {\n return fail(\"invalid receipt\");\n }\n if (receipt.pubkey.toLowerCase() !== ctx.nostrPubkey.toLowerCase()) {\n return fail(\"pubkey mismatch\");\n }\n\n const descriptionRaw = firstTagValue(receipt.tags, \"description\");\n const request = parseZapRequestFromReceipt(receipt);\n if (descriptionRaw === undefined || !request) {\n return fail(\"invalid zap request\");\n }\n if (countTags(request.tags, \"p\") !== 1) {\n return fail(\"invalid p count\");\n }\n if (countTags(request.tags, \"e\") > 1) {\n return fail(\"too many e tags\");\n }\n const relaysValue = firstTagValue(request.tags, \"relays\");\n if (relaysValue === undefined || relaysValue === \"\") {\n return fail(\"missing relays\");\n }\n\n for (const tag of request.tags) {\n if (tag[0] !== \"a\") {\n continue;\n }\n if (tag[1] === undefined) {\n return fail(\"invalid a\");\n }\n const parsed = parseEventAddress(tag[1]);\n if (!parsed || !isAddressableKind(parsed.kind)) {\n return fail(\"invalid a\");\n }\n }\n\n // Request P is the LNURL provider (receipt pubkey), not the zap sender.\n const requestPCount = countTags(request.tags, \"P\");\n if (requestPCount > 1) {\n return fail(\"too many P tags\");\n }\n if (requestPCount === 1) {\n const requestP = firstTagValue(request.tags, \"P\");\n if (requestP === undefined || requestP.toLowerCase() !== receipt.pubkey.toLowerCase()) {\n return fail(\"request P mismatch\");\n }\n }\n\n const bolt11Tag = firstTagValue(receipt.tags, \"bolt11\");\n if (bolt11Tag === undefined) {\n return fail(\"missing bolt11\");\n }\n const bolt11 = parseBolt11(bolt11Tag);\n if (!bolt11 || !bolt11.descriptionHash) {\n return fail(\"invalid bolt11\");\n }\n\n const requestAmount = firstTagValue(request.tags, \"amount\");\n if (requestAmount !== undefined) {\n const msats = parseMsatsTag(requestAmount);\n if (msats === undefined || bolt11.amountMsats !== msats) {\n return fail(\"amount mismatch\");\n }\n }\n\n // Hash the tag payload, not JSON.stringify(parsed) (key order may differ).\n const digest = sha256(utf8Encoder.encode(descriptionRaw));\n if (!equalBytes(digest, bolt11.descriptionHash)) {\n return fail(\"description hash mismatch\");\n }\n\n const requestLnurl = firstTagValue(request.tags, \"lnurl\");\n if (\n requestLnurl !== undefined &&\n ctx.lnurl !== undefined &&\n requestLnurl.toLowerCase() !== ctx.lnurl.toLowerCase()\n ) {\n return fail(\"lnurl mismatch\");\n }\n\n const preimageHex = firstTagValue(receipt.tags, \"preimage\");\n if (preimageHex !== undefined && bolt11.paymentHash) {\n let preimage: Uint8Array;\n try {\n preimage = hexToBytes(preimageHex);\n } catch {\n return fail(\"preimage mismatch\");\n }\n if (!equalBytes(sha256(preimage), bolt11.paymentHash)) {\n return fail(\"preimage mismatch\");\n }\n }\n\n const recipient = firstTagValue(request.tags, \"p\");\n if (recipient === undefined || !hasHexTagValue(receipt.tags, \"p\", recipient)) {\n return fail(\"missing p\");\n }\n const requestE = firstTagValue(request.tags, \"e\");\n if (requestE !== undefined && !hasHexTagValue(receipt.tags, \"e\", requestE)) {\n return fail(\"missing e\");\n }\n for (const tag of request.tags) {\n if (tag[0] !== \"a\" || tag[1] === undefined) {\n continue;\n }\n if (!hasAddressTag(receipt.tags, tag[1])) {\n return fail(\"missing a\");\n }\n }\n\n // Receipt P is the zap sender (request pubkey). Do not copy request tag P.\n for (const tag of receipt.tags) {\n if (tag[0] !== \"P\") {\n continue;\n }\n if (tag[1] === undefined || tag[1].toLowerCase() !== request.pubkey.toLowerCase()) {\n return fail(\"receipt P mismatch\");\n }\n }\n\n const amountMsats =\n bolt11.amountMsats ??\n (requestAmount === undefined ? undefined : parseMsatsTag(requestAmount));\n return { valid: true, request, amountMsats };\n } catch {\n return fail(\"invalid receipt\");\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"nip57.mjs","names":[],"sources":["../../src/nips/nip57.ts"],"sourcesContent":["/**\n * NIP-57 Lightning Zaps — zap request template (kind 9734) and receipt validation (kind 9735). Does\n * not fetch LNURL. Receipt checks never throw.\n *\n * @see https://github.com/nostr-protocol/nips/blob/master/57.md\n */\nimport { equalBytes } from \"@noble/ciphers/utils.js\";\nimport { sha256 } from \"@noble/hashes/sha2.js\";\nimport { bech32 } from \"@scure/base\";\n\nimport { EventValidationError } from \"../core/error.ts\";\nimport { validateSignedEvent } from \"../core/event.ts\";\nimport type { Event, EventTemplate } from \"../core/event.ts\";\nimport { isAddressableKind, Kind } from \"../core/kind.ts\";\nimport { eventAddress, firstTagValue, getDTag, parseEventAddress, Tag } from \"../core/tag.ts\";\nimport type { Mutable } from \"../core/util.ts\";\nimport { hexToBytes, nowSeconds, utf8Encoder } from \"../core/util.ts\";\nimport { verifyEvent } from \"../core/verifier.ts\";\n\nexport type ProfileZapRequest = {\n readonly pubkey: string;\n /** Amount in millisats. */\n readonly amount: number;\n readonly relays: ReadonlyArray<string>;\n readonly comment?: string | undefined;\n readonly lnurl?: string | undefined;\n};\n\nexport type EventZapRequest = {\n readonly event: Event;\n /** Amount in millisats. */\n readonly amount: number;\n readonly relays: ReadonlyArray<string>;\n readonly comment?: string | undefined;\n readonly lnurl?: string | undefined;\n};\n\nexport function makeZapRequest(params: ProfileZapRequest | EventZapRequest): EventTemplate {\n if (!Number.isSafeInteger(params.amount) || params.amount <= 0) {\n throw new EventValidationError(\"zap amount must be a positive integer (msats)\");\n }\n if (params.relays.length === 0) {\n throw new EventValidationError(\"relays tag requires one or more URLs\");\n }\n\n const recipient = \"event\" in params ? params.event.pubkey : params.pubkey;\n const tags: Tag[] = [\n Tag.p(recipient),\n [\"amount\", params.amount.toString()],\n [\"relays\", ...params.relays],\n ];\n\n if (\"event\" in params) {\n const { event } = params;\n tags.push(Tag.e(event.id));\n if (isAddressableKind(event.kind)) {\n const d = getDTag(event.tags);\n if (d === undefined) {\n throw new EventValidationError(\"d tag not found\");\n }\n const addr = eventAddress(event);\n if (addr !== undefined) {\n tags.push([\"a\", addr]);\n }\n }\n tags.push([\"k\", event.kind.toString()]);\n }\n\n if (params.lnurl !== undefined && params.lnurl !== \"\") {\n tags.push([\"lnurl\", params.lnurl]);\n }\n\n return {\n kind: Kind.ZapRequest,\n created_at: nowSeconds(),\n content: params.comment ?? \"\",\n tags,\n };\n}\n\nexport type ZapReceiptContext = {\n readonly nostrPubkey: string;\n readonly lnurl?: string | undefined;\n};\n\nexport type ZapReceiptValidation =\n | {\n readonly valid: true;\n readonly request: Event;\n readonly amountMsats: number | undefined;\n }\n | { readonly valid: false; readonly reason: string };\n\nexport type Bolt11Fields = {\n readonly amountMsats?: number | undefined;\n /** The `d` tagged field, UTF-8 decoded; omitted on invalid UTF-8. */\n readonly description?: string | undefined;\n readonly descriptionHash?: Uint8Array | undefined;\n readonly paymentHash?: Uint8Array | undefined;\n /** Invoice creation time (unix seconds). */\n readonly timestamp: number;\n /** `x` tagged field — seconds after `timestamp` until expiry. Defaults to 3600 when absent. */\n readonly expiry: number;\n};\n\nfunction fail(reason: string): ZapReceiptValidation {\n return { valid: false, reason };\n}\n\nconst MSATS_PER_BTC = 100_000_000_000;\nconst MSATS_PER_MILLI = 100_000_000;\nconst MSATS_PER_MICRO = 100_000;\nconst MSATS_PER_NANO = 100;\n\n/** Amount is digits after `ln` + currency letters, optional m/u/n/p. Non-integer pico is omitted. */\nfunction amountMsatsFromHrp(hrp: string): number | undefined {\n if (!hrp.startsWith(\"ln\")) {\n return undefined;\n }\n let i = 2;\n while (i < hrp.length) {\n const c = hrp.codePointAt(i);\n if (c === undefined || c < 97 || c > 122) {\n break;\n }\n i++;\n }\n if (i === hrp.length) {\n return undefined;\n }\n const rest = hrp.slice(i);\n const m = /^([0-9]+)([munp])?$/.exec(rest);\n if (!m) {\n return undefined;\n }\n const n = Number(m[1]);\n if (!Number.isSafeInteger(n)) {\n return undefined;\n }\n const mul = m.at(2);\n const factor =\n mul === undefined\n ? MSATS_PER_BTC\n : mul === \"m\"\n ? MSATS_PER_MILLI\n : mul === \"u\"\n ? MSATS_PER_MICRO\n : mul === \"n\"\n ? MSATS_PER_NANO\n : 0;\n if (factor !== 0) {\n const msats = n * factor;\n return Number.isSafeInteger(msats) ? msats : undefined;\n }\n // p: 0.1 msat; drop amounts that are not whole millisats\n if (n % 10 !== 0) {\n return undefined;\n }\n return n / 10;\n}\n\nfunction parseMsatsTag(value: string): number | undefined {\n if (!/^[0-9]+$/.test(value)) {\n return undefined;\n }\n const n = Number(value);\n return Number.isSafeInteger(n) ? n : undefined;\n}\n\nfunction countTags(tags: ReadonlyArray<Tag>, name: string): number {\n let n = 0;\n for (const tag of tags) {\n if (tag[0] === name) {\n n++;\n }\n }\n return n;\n}\n\nfunction hasHexTagValue(tags: ReadonlyArray<Tag>, name: string, value: string): boolean {\n const needle = value.toLowerCase();\n for (const tag of tags) {\n if (tag[0] === name && tag[1] !== undefined && tag[1].toLowerCase() === needle) {\n return true;\n }\n }\n return false;\n}\n\nfunction hasAddressTag(tags: ReadonlyArray<Tag>, value: string): boolean {\n const want = parseEventAddress(value);\n if (!want) {\n return false;\n }\n for (const tag of tags) {\n if (tag[0] !== \"a\" || tag[1] === undefined) {\n continue;\n }\n const got = parseEventAddress(tag[1]);\n if (\n got &&\n got.kind === want.kind &&\n got.pubkey === want.pubkey &&\n got.identifier === want.identifier\n ) {\n return true;\n }\n }\n return false;\n}\n\nconst BOLT11_TIMESTAMP_WORDS = 7;\nconst BOLT11_SIGNATURE_WORDS = 104;\nconst BOLT11_HASH_WORDS = 52;\nconst HASH_BYTES = 32;\nconst BOLT11_TAG_PAYMENT_HASH = 1;\nconst BOLT11_TAG_EXPIRY = 6;\nconst BOLT11_TAG_DESCRIPTION = 13;\nconst BOLT11_TAG_DESCRIPTION_HASH = 23;\nconst DEFAULT_EXPIRY_SECONDS = 3600;\n\nconst utf8DecoderFatal = new TextDecoder(\"utf-8\", { fatal: true });\n\n/** Big-endian 5-bit words as a JS number (safe below 2^53). */\nfunction wordsToInt(words: ReadonlyArray<number>): number {\n let value = 0;\n for (const word of words) {\n value = value * 32 + word;\n }\n return value;\n}\n\n/** Decode a BOLT11 invoice. Never throws. Requires a 32-byte payment hash (type 1). */\nexport function parseBolt11(pr: string): Bolt11Fields | undefined {\n try {\n // Invoices exceed bech32's default 90-char limit.\n const { prefix, words } = bech32.decode(pr.toLowerCase(), false);\n if (!prefix.startsWith(\"ln\")) {\n return undefined;\n }\n // timestamp (7) + tagged fields + secp256k1 signature (104)\n if (words.length < BOLT11_TIMESTAMP_WORDS + BOLT11_SIGNATURE_WORDS) {\n return undefined;\n }\n const fields: Mutable<Bolt11Fields> = {\n timestamp: wordsToInt(words.slice(0, BOLT11_TIMESTAMP_WORDS)),\n expiry: DEFAULT_EXPIRY_SECONDS,\n };\n let sawExpiry = false;\n const amountMsats = amountMsatsFromHrp(prefix);\n if (amountMsats !== undefined) {\n fields.amountMsats = amountMsats;\n }\n const tlvEnd = words.length - BOLT11_SIGNATURE_WORDS;\n let i = BOLT11_TIMESTAMP_WORDS;\n while (i + 3 <= tlvEnd) {\n const type = words[i];\n const lenHigh = words[i + 1];\n const lenLow = words[i + 2];\n if (type === undefined || lenHigh === undefined || lenLow === undefined) {\n break;\n }\n const dataLen = lenHigh * 32 + lenLow;\n i += 3;\n if (i + dataLen > tlvEnd) {\n break;\n }\n const data = words.slice(i, i + dataLen);\n i += dataLen;\n if (type === BOLT11_TAG_EXPIRY) {\n if (!sawExpiry) {\n sawExpiry = true;\n // An `x` beyond 2^53-1 would lose precision as a JS number —\n // ignore the field and keep the 3600s default (N8).\n const expiry = wordsToInt(data);\n if (Number.isSafeInteger(expiry)) {\n fields.expiry = expiry;\n }\n }\n continue;\n }\n if (type === BOLT11_TAG_DESCRIPTION) {\n const bytes = bech32.fromWordsUnsafe(data);\n if (bytes !== undefined) {\n try {\n fields.description ??= utf8DecoderFatal.decode(bytes);\n } catch {\n // Invalid UTF-8 — drop the field, not the invoice.\n }\n }\n continue;\n }\n if (\n (type !== BOLT11_TAG_PAYMENT_HASH && type !== BOLT11_TAG_DESCRIPTION_HASH) ||\n dataLen !== BOLT11_HASH_WORDS\n ) {\n continue;\n }\n const bytes = bech32.fromWordsUnsafe(data);\n if (!bytes || bytes.length !== HASH_BYTES) {\n continue;\n }\n if (type === BOLT11_TAG_PAYMENT_HASH) {\n fields.paymentHash ??= bytes;\n } else {\n fields.descriptionHash ??= bytes;\n }\n }\n if (!fields.paymentHash) {\n return undefined;\n }\n return fields;\n } catch {\n return undefined;\n }\n}\n\n/** Parse the `description` tag of a kind 9735 receipt as a signed kind 9734. Never throws. */\nexport function parseZapRequestFromReceipt(receipt: Event): Event | undefined {\n try {\n const raw = firstTagValue(receipt.tags, \"description\");\n if (raw === undefined) {\n return undefined;\n }\n const parsed: unknown = JSON.parse(raw);\n if (!validateSignedEvent(parsed) || parsed.kind !== Kind.ZapRequest) {\n return undefined;\n }\n if (!verifyEvent(parsed)) {\n return undefined;\n }\n return parsed;\n } catch {\n return undefined;\n }\n}\n\n/** Validate a kind 9735 zap receipt. Never throws. */\nexport function validateZapReceipt(receipt: Event, ctx: ZapReceiptContext): ZapReceiptValidation {\n try {\n if (receipt.kind !== Kind.Zap || !verifyEvent(receipt)) {\n return fail(\"invalid receipt\");\n }\n if (receipt.pubkey.toLowerCase() !== ctx.nostrPubkey.toLowerCase()) {\n return fail(\"pubkey mismatch\");\n }\n\n const descriptionRaw = firstTagValue(receipt.tags, \"description\");\n const request = parseZapRequestFromReceipt(receipt);\n if (descriptionRaw === undefined || !request) {\n return fail(\"invalid zap request\");\n }\n if (countTags(request.tags, \"p\") !== 1) {\n return fail(\"invalid p count\");\n }\n if (countTags(request.tags, \"e\") > 1) {\n return fail(\"too many e tags\");\n }\n const relaysValue = firstTagValue(request.tags, \"relays\");\n if (relaysValue === undefined || relaysValue === \"\") {\n return fail(\"missing relays\");\n }\n\n for (const tag of request.tags) {\n if (tag[0] !== \"a\") {\n continue;\n }\n if (tag[1] === undefined) {\n return fail(\"invalid a\");\n }\n const parsed = parseEventAddress(tag[1]);\n if (!parsed || !isAddressableKind(parsed.kind)) {\n return fail(\"invalid a\");\n }\n }\n\n // Request P is the LNURL provider (receipt pubkey), not the zap sender.\n const requestPCount = countTags(request.tags, \"P\");\n if (requestPCount > 1) {\n return fail(\"too many P tags\");\n }\n if (requestPCount === 1) {\n const requestP = firstTagValue(request.tags, \"P\");\n if (requestP === undefined || requestP.toLowerCase() !== receipt.pubkey.toLowerCase()) {\n return fail(\"request P mismatch\");\n }\n }\n\n const bolt11Tag = firstTagValue(receipt.tags, \"bolt11\");\n if (bolt11Tag === undefined) {\n return fail(\"missing bolt11\");\n }\n const bolt11 = parseBolt11(bolt11Tag);\n if (!bolt11 || !bolt11.descriptionHash) {\n return fail(\"invalid bolt11\");\n }\n\n const requestAmount = firstTagValue(request.tags, \"amount\");\n if (requestAmount !== undefined) {\n const msats = parseMsatsTag(requestAmount);\n if (msats === undefined || bolt11.amountMsats !== msats) {\n return fail(\"amount mismatch\");\n }\n }\n\n // Hash the tag payload, not JSON.stringify(parsed) (key order may differ).\n const digest = sha256(utf8Encoder.encode(descriptionRaw));\n if (!equalBytes(digest, bolt11.descriptionHash)) {\n return fail(\"description hash mismatch\");\n }\n\n const requestLnurl = firstTagValue(request.tags, \"lnurl\");\n if (\n requestLnurl !== undefined &&\n ctx.lnurl !== undefined &&\n requestLnurl.toLowerCase() !== ctx.lnurl.toLowerCase()\n ) {\n return fail(\"lnurl mismatch\");\n }\n\n const preimageHex = firstTagValue(receipt.tags, \"preimage\");\n if (preimageHex !== undefined && bolt11.paymentHash) {\n let preimage: Uint8Array;\n try {\n preimage = hexToBytes(preimageHex);\n } catch {\n return fail(\"preimage mismatch\");\n }\n if (!equalBytes(sha256(preimage), bolt11.paymentHash)) {\n return fail(\"preimage mismatch\");\n }\n }\n\n const recipient = firstTagValue(request.tags, \"p\");\n if (recipient === undefined || !hasHexTagValue(receipt.tags, \"p\", recipient)) {\n return fail(\"missing p\");\n }\n const requestE = firstTagValue(request.tags, \"e\");\n if (requestE !== undefined && !hasHexTagValue(receipt.tags, \"e\", requestE)) {\n return fail(\"missing e\");\n }\n for (const tag of request.tags) {\n if (tag[0] !== \"a\" || tag[1] === undefined) {\n continue;\n }\n if (!hasAddressTag(receipt.tags, tag[1])) {\n return fail(\"missing a\");\n }\n }\n\n // Receipt P is the zap sender (request pubkey). Do not copy request tag P.\n for (const tag of receipt.tags) {\n if (tag[0] !== \"P\") {\n continue;\n }\n if (tag[1] === undefined || tag[1].toLowerCase() !== request.pubkey.toLowerCase()) {\n return fail(\"receipt P mismatch\");\n }\n }\n\n const amountMsats =\n bolt11.amountMsats ??\n (requestAmount === undefined ? undefined : parseMsatsTag(requestAmount));\n return { valid: true, request, amountMsats };\n } catch {\n return fail(\"invalid receipt\");\n }\n}\n"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- import { _ as requireNip59Crypto, a as Rumor, b as wrap, c as TimestampRandomize, d as createRumor, f as createSeal, g as requireNip44Decryptor, h as randomPastTimestamp, i as Nip59Error, l as WrapOptions, m as isGiftWrapKind, n as Nip44Decryptor, o as SealOptions, p as eventToJson, r as Nip59Crypto, s as TWO_DAYS_SECS, t as GiftWrapTimestamps, u as createGiftWrap, v as rumorToJson, y as unwrap } from "../nip59-CGWfoItR.mjs";
1
+ import { _ as requireNip59Crypto, a as Rumor, b as wrap, c as TimestampRandomize, d as createRumor, f as createSeal, g as requireNip44Decryptor, h as randomPastTimestamp, i as Nip59Error, l as WrapOptions, m as isGiftWrapKind, n as Nip44Decryptor, o as SealOptions, p as eventToJson, r as Nip59Crypto, s as TWO_DAYS_SECS, t as GiftWrapTimestamps, u as createGiftWrap, v as rumorToJson, y as unwrap } from "../nip59-DfJ6Moeu.mjs";
2
2
  export { GiftWrapTimestamps, Nip44Decryptor, Nip59Crypto, Nip59Error, Rumor, SealOptions, TWO_DAYS_SECS, TimestampRandomize, WrapOptions, createGiftWrap, createRumor, createSeal, eventToJson, isGiftWrapKind, randomPastTimestamp, requireNip44Decryptor, requireNip59Crypto, rumorToJson, unwrap, wrap };
@@ -1,8 +1,8 @@
1
- import { f as nowSeconds, l as isRecord, n as assertHex32, y as NostrError } from "../util-DDpPkiED.mjs";
2
- import { t as Kind } from "../kind-D8fENiiL.mjs";
3
- import { g as Tag, h as validateSignedEvent, m as validateEvent, n as getEventHash } from "../event-B3ua2_oL.mjs";
4
- import { n as SecretKey, o as signEvent, t as Keys } from "../key-B218_SoL.mjs";
5
- import { n as verifyEvent } from "../verifier-CjH1vnP2.mjs";
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
6
  import { encrypt, getConversationKey } from "./nip44.mjs";
7
7
  import { randomBytes } from "@noble/hashes/utils.js";
8
8
  import { secp256k1 } from "@noble/curves/secp256k1.js";
@@ -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 { 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 // the shared stream matches nk-nips: 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, like\n // nk-core's `SecretKey::generate_with_rng`. Stream order: key → offset → nonce → aux. The\n // candidates, the ephemeral secret key, and the conversation key are wiped 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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"}
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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"}
@@ -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-DaWMg943.mjs";
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 };
@@ -1,6 +1,6 @@
1
- import { d as normalizeURL, g as EventValidationError } from "../util-DDpPkiED.mjs";
2
- import { t as Kind } from "../kind-D8fENiiL.mjs";
3
- import { t as EventBuilder } from "../builder-Cf2N12CG.mjs";
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) {
@@ -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-wHA_nEV5.mjs";
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 };
@@ -1,4 +1,4 @@
1
- import { i as bytesToHex, n as assertHex32, o as hexToBytes, y as NostrError } from "../util-DDpPkiED.mjs";
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
  /**