@qntx/nostr 0.0.1

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 (146) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +25 -0
  3. package/dist/builder-BphJa-Y-.d.mts +67 -0
  4. package/dist/builder-q0VsvnYE.mjs +158 -0
  5. package/dist/builder-q0VsvnYE.mjs.map +1 -0
  6. package/dist/client-Bd8B1mYm.mjs +835 -0
  7. package/dist/client-Bd8B1mYm.mjs.map +1 -0
  8. package/dist/client.d.mts +2 -0
  9. package/dist/client.mjs +2 -0
  10. package/dist/core.d.mts +8 -0
  11. package/dist/core.mjs +9 -0
  12. package/dist/error-B49rbDzT.d.mts +7 -0
  13. package/dist/error-CwBxle6N.mjs +13 -0
  14. package/dist/error-CwBxle6N.mjs.map +1 -0
  15. package/dist/error-DZEs5OuZ.mjs +24 -0
  16. package/dist/error-DZEs5OuZ.mjs.map +1 -0
  17. package/dist/error-WEFS-R-4.d.mts +20 -0
  18. package/dist/event-B5D2jxt_.d.mts +108 -0
  19. package/dist/event-CzoQEFMF.mjs +188 -0
  20. package/dist/event-CzoQEFMF.mjs.map +1 -0
  21. package/dist/filter-BxwjEkZp.mjs +113 -0
  22. package/dist/filter-BxwjEkZp.mjs.map +1 -0
  23. package/dist/filter-DUEKP3q1.d.mts +40 -0
  24. package/dist/gossip-3xkRRCm_.mjs +220 -0
  25. package/dist/gossip-3xkRRCm_.mjs.map +1 -0
  26. package/dist/gossip-C5RVB1UB.d.mts +71 -0
  27. package/dist/gossip.d.mts +3 -0
  28. package/dist/gossip.mjs +2 -0
  29. package/dist/http-6vevbBlu.d.mts +22 -0
  30. package/dist/http-D2rhokZo.mjs +24 -0
  31. package/dist/http-D2rhokZo.mjs.map +1 -0
  32. package/dist/index-BYMnhRIC.d.mts +161 -0
  33. package/dist/index-C0C4e-Gn.d.mts +55 -0
  34. package/dist/index-CaxrMD1A.d.mts +1 -0
  35. package/dist/index-DaRyjo7O.d.mts +77 -0
  36. package/dist/index-DgOugrpF.d.mts +275 -0
  37. package/dist/index-EwL6fHLo.d.mts +267 -0
  38. package/dist/index-k2YhCgsh.d.mts +57 -0
  39. package/dist/index.d.mts +22 -0
  40. package/dist/index.mjs +23 -0
  41. package/dist/key-BiGxGLhg.d.mts +37 -0
  42. package/dist/key-Dn-aX4x4.mjs +130 -0
  43. package/dist/key-Dn-aX4x4.mjs.map +1 -0
  44. package/dist/kind-BLl4cLOc.mjs +59 -0
  45. package/dist/kind-BLl4cLOc.mjs.map +1 -0
  46. package/dist/loaders-BNQszDAn.mjs +767 -0
  47. package/dist/loaders-BNQszDAn.mjs.map +1 -0
  48. package/dist/loaders.d.mts +3 -0
  49. package/dist/loaders.mjs +2 -0
  50. package/dist/memory-BCmhZPek.mjs +461 -0
  51. package/dist/memory-BCmhZPek.mjs.map +1 -0
  52. package/dist/message-C_Uj-Y-c.mjs +179 -0
  53. package/dist/message-C_Uj-Y-c.mjs.map +1 -0
  54. package/dist/message-DJC-Jz6T.d.mts +32 -0
  55. package/dist/nip17-BWq-Kfq5.d.mts +38 -0
  56. package/dist/nip19-D7905oIv.d.mts +63 -0
  57. package/dist/nip46-C_DoxiS_.d.mts +51 -0
  58. package/dist/nip59-DljW_sYh.d.mts +76 -0
  59. package/dist/nip65-Di5hZRuV.d.mts +21 -0
  60. package/dist/nip77-ByxlgoQO.d.mts +55 -0
  61. package/dist/nips/blossom.d.mts +93 -0
  62. package/dist/nips/blossom.mjs +325 -0
  63. package/dist/nips/blossom.mjs.map +1 -0
  64. package/dist/nips/nip04.d.mts +8 -0
  65. package/dist/nips/nip04.mjs +41 -0
  66. package/dist/nips/nip04.mjs.map +1 -0
  67. package/dist/nips/nip05.d.mts +89 -0
  68. package/dist/nips/nip05.mjs +147 -0
  69. package/dist/nips/nip05.mjs.map +1 -0
  70. package/dist/nips/nip10.d.mts +43 -0
  71. package/dist/nips/nip10.mjs +194 -0
  72. package/dist/nips/nip10.mjs.map +1 -0
  73. package/dist/nips/nip11.d.mts +43 -0
  74. package/dist/nips/nip11.mjs +99 -0
  75. package/dist/nips/nip11.mjs.map +1 -0
  76. package/dist/nips/nip13.d.mts +23 -0
  77. package/dist/nips/nip13.mjs +88 -0
  78. package/dist/nips/nip13.mjs.map +1 -0
  79. package/dist/nips/nip17.d.mts +2 -0
  80. package/dist/nips/nip17.mjs +138 -0
  81. package/dist/nips/nip17.mjs.map +1 -0
  82. package/dist/nips/nip19.d.mts +2 -0
  83. package/dist/nips/nip19.mjs +174 -0
  84. package/dist/nips/nip19.mjs.map +1 -0
  85. package/dist/nips/nip21.d.mts +17 -0
  86. package/dist/nips/nip21.mjs +29 -0
  87. package/dist/nips/nip21.mjs.map +1 -0
  88. package/dist/nips/nip27.d.mts +42 -0
  89. package/dist/nips/nip27.mjs +175 -0
  90. package/dist/nips/nip27.mjs.map +1 -0
  91. package/dist/nips/nip42.d.mts +12 -0
  92. package/dist/nips/nip42.mjs +22 -0
  93. package/dist/nips/nip42.mjs.map +1 -0
  94. package/dist/nips/nip44.d.mts +12 -0
  95. package/dist/nips/nip44.mjs +125 -0
  96. package/dist/nips/nip44.mjs.map +1 -0
  97. package/dist/nips/nip46.d.mts +2 -0
  98. package/dist/nips/nip46.mjs +132 -0
  99. package/dist/nips/nip46.mjs.map +1 -0
  100. package/dist/nips/nip49.d.mts +12 -0
  101. package/dist/nips/nip49.mjs +80 -0
  102. package/dist/nips/nip49.mjs.map +1 -0
  103. package/dist/nips/nip51.d.mts +88 -0
  104. package/dist/nips/nip51.mjs +246 -0
  105. package/dist/nips/nip51.mjs.map +1 -0
  106. package/dist/nips/nip57.d.mts +43 -0
  107. package/dist/nips/nip57.mjs +230 -0
  108. package/dist/nips/nip57.mjs.map +1 -0
  109. package/dist/nips/nip59.d.mts +2 -0
  110. package/dist/nips/nip59.mjs +171 -0
  111. package/dist/nips/nip59.mjs.map +1 -0
  112. package/dist/nips/nip65.d.mts +2 -0
  113. package/dist/nips/nip65.mjs +76 -0
  114. package/dist/nips/nip65.mjs.map +1 -0
  115. package/dist/nips/nip77.d.mts +2 -0
  116. package/dist/nips/nip77.mjs +421 -0
  117. package/dist/nips/nip77.mjs.map +1 -0
  118. package/dist/nips/nip96.d.mts +31 -0
  119. package/dist/nips/nip96.mjs +95 -0
  120. package/dist/nips/nip96.mjs.map +1 -0
  121. package/dist/nips/nip98.d.mts +28 -0
  122. package/dist/nips/nip98.mjs +91 -0
  123. package/dist/nips/nip98.mjs.map +1 -0
  124. package/dist/nostr_crypto_wasm_bg-hi5aXi5P.mjs +4 -0
  125. package/dist/nostr_crypto_wasm_bg.wasm +0 -0
  126. package/dist/pool-C_j1OmBB.mjs +1881 -0
  127. package/dist/pool-C_j1OmBB.mjs.map +1 -0
  128. package/dist/pool-DhRi7l-b.d.mts +297 -0
  129. package/dist/relay.d.mts +4 -0
  130. package/dist/relay.mjs +2 -0
  131. package/dist/signer-CZ6K6FUc.mjs +473 -0
  132. package/dist/signer-CZ6K6FUc.mjs.map +1 -0
  133. package/dist/signer.d.mts +3 -0
  134. package/dist/signer.mjs +2 -0
  135. package/dist/storage-DOy0iLRE.mjs +870 -0
  136. package/dist/storage-DOy0iLRE.mjs.map +1 -0
  137. package/dist/storage.d.mts +4 -0
  138. package/dist/storage.mjs +4 -0
  139. package/dist/types-BzyADqnw.d.mts +19 -0
  140. package/dist/types-Dn24TPFs.d.mts +43 -0
  141. package/dist/util-6tfLQ9JF.mjs +73 -0
  142. package/dist/util-6tfLQ9JF.mjs.map +1 -0
  143. package/dist/wasm.d.mts +19 -0
  144. package/dist/wasm.mjs +237 -0
  145. package/dist/wasm.mjs.map +1 -0
  146. package/package.json +204 -0
@@ -0,0 +1,175 @@
1
+ import { decode } from "./nip19.mjs";
2
+ //#region src/nips/nip27.ts
3
+ const noCharacter = /\W/m;
4
+ const noURLCharacter = /[^\w/] |[^\w/]$|$|,| /m;
5
+ const MAX_HASHTAG_LENGTH = 42;
6
+ /**
7
+ * Parse note content (or a full event, to pick up emoji tags) into blocks.
8
+ * Generator — consume with `for…of` or `[...parseContent(s)]`.
9
+ */
10
+ function* parseContent(content) {
11
+ let emojis = [];
12
+ let text;
13
+ if (typeof content === "string") text = content;
14
+ else {
15
+ for (const tag of content.tags) if (tag[0] === "emoji" && tag[1] && tag[2]) emojis.push({
16
+ type: "emoji",
17
+ shortcode: tag[1],
18
+ url: tag[2]
19
+ });
20
+ text = content.content;
21
+ }
22
+ const max = text.length;
23
+ let prevIndex = 0;
24
+ let index = 0;
25
+ mainloop: while (index < max) {
26
+ const u = text.indexOf(":", index);
27
+ const h = text.indexOf("#", index);
28
+ if (u === -1 && h === -1) break;
29
+ if (u === -1 || h >= 0 && h < u) {
30
+ if (h === 0 || h > 0 && text[h - 1].match(noCharacter)) {
31
+ const m = text.slice(h + 1, h + MAX_HASHTAG_LENGTH).match(noCharacter);
32
+ const end = m ? h + 1 + (m.index ?? 0) : max;
33
+ if (prevIndex !== h) yield {
34
+ type: "text",
35
+ text: text.slice(prevIndex, h)
36
+ };
37
+ yield {
38
+ type: "hashtag",
39
+ value: text.slice(h + 1, end)
40
+ };
41
+ index = end;
42
+ prevIndex = index;
43
+ continue mainloop;
44
+ }
45
+ index = h + 1;
46
+ continue mainloop;
47
+ }
48
+ if (u >= 5 && text.slice(u - 5, u) === "nostr") {
49
+ const m = text.slice(u + 60).match(noCharacter);
50
+ const end = m ? u + 60 + (m.index ?? 0) : max;
51
+ try {
52
+ const { data, type } = decode(text.slice(u + 1, end));
53
+ let pointer;
54
+ switch (type) {
55
+ case "npub":
56
+ pointer = { pubkey: data };
57
+ break;
58
+ case "note":
59
+ pointer = { id: data };
60
+ break;
61
+ case "nprofile":
62
+ case "nevent":
63
+ case "naddr":
64
+ pointer = data;
65
+ break;
66
+ case "nsec":
67
+ index = end + 1;
68
+ continue mainloop;
69
+ default:
70
+ index = u + 1;
71
+ continue mainloop;
72
+ }
73
+ if (prevIndex !== u - 5) yield {
74
+ type: "text",
75
+ text: text.slice(prevIndex, u - 5)
76
+ };
77
+ yield {
78
+ type: "reference",
79
+ pointer
80
+ };
81
+ index = end;
82
+ prevIndex = index;
83
+ continue mainloop;
84
+ } catch {
85
+ index = u + 1;
86
+ continue mainloop;
87
+ }
88
+ }
89
+ if (u >= 5 && text.slice(u - 5, u) === "https" || u >= 4 && text.slice(u - 4, u) === "http") {
90
+ const m = text.slice(u + 4).match(noURLCharacter);
91
+ const end = m ? u + 4 + (m.index ?? 0) : max;
92
+ const prefixLen = text[u - 1] === "s" ? 5 : 4;
93
+ try {
94
+ const url = new URL(text.slice(u - prefixLen, end));
95
+ if (!url.hostname.includes(".")) throw new Error("invalid url");
96
+ if (prevIndex !== u - prefixLen) yield {
97
+ type: "text",
98
+ text: text.slice(prevIndex, u - prefixLen)
99
+ };
100
+ const path = url.pathname;
101
+ if (/\.(png|jpe?g|gif|webp|heic|svg)$/i.test(path)) yield {
102
+ type: "image",
103
+ url: url.toString()
104
+ };
105
+ else if (/\.(mp4|avi|webm|mkv|mov)$/i.test(path)) yield {
106
+ type: "video",
107
+ url: url.toString()
108
+ };
109
+ else if (/\.(mp3|aac|ogg|opus|wav|flac)$/i.test(path)) yield {
110
+ type: "audio",
111
+ url: url.toString()
112
+ };
113
+ else yield {
114
+ type: "url",
115
+ url: url.toString()
116
+ };
117
+ index = end;
118
+ prevIndex = index;
119
+ continue mainloop;
120
+ } catch {
121
+ index = end + 1;
122
+ continue mainloop;
123
+ }
124
+ }
125
+ if (u >= 3 && text.slice(u - 3, u) === "wss" || u >= 2 && text.slice(u - 2, u) === "ws") {
126
+ const m = text.slice(u + 4).match(noURLCharacter);
127
+ const end = m ? u + 4 + (m.index ?? 0) : max;
128
+ const prefixLen = text[u - 1] === "s" ? 3 : 2;
129
+ try {
130
+ const url = new URL(text.slice(u - prefixLen, end));
131
+ if (!url.hostname.includes(".")) throw new Error("invalid ws url");
132
+ if (prevIndex !== u - prefixLen) yield {
133
+ type: "text",
134
+ text: text.slice(prevIndex, u - prefixLen)
135
+ };
136
+ yield {
137
+ type: "relay",
138
+ url: url.toString()
139
+ };
140
+ index = end;
141
+ prevIndex = index;
142
+ continue mainloop;
143
+ } catch {
144
+ index = end + 1;
145
+ continue mainloop;
146
+ }
147
+ }
148
+ for (const emoji of emojis) {
149
+ const endColon = u + emoji.shortcode.length + 1;
150
+ if (text[endColon] === ":" && text.slice(u + 1, endColon) === emoji.shortcode) {
151
+ if (prevIndex !== u) yield {
152
+ type: "text",
153
+ text: text.slice(prevIndex, u)
154
+ };
155
+ yield emoji;
156
+ index = endColon + 1;
157
+ prevIndex = index;
158
+ continue mainloop;
159
+ }
160
+ }
161
+ index = u + 1;
162
+ }
163
+ if (prevIndex !== max) yield {
164
+ type: "text",
165
+ text: text.slice(prevIndex)
166
+ };
167
+ }
168
+ /** Collect all content blocks into an array. */
169
+ function parseContentBlocks(content) {
170
+ return [...parseContent(content)];
171
+ }
172
+ //#endregion
173
+ export { parseContent, parseContentBlocks };
174
+
175
+ //# sourceMappingURL=nip27.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nip27.mjs","names":[],"sources":["../../src/nips/nip27.ts"],"sourcesContent":["/**\n * NIP-27: Text Note References\n * Tokenizes note content into text, nostr: references, URLs, hashtags, media, relays, emoji.\n * @see https://github.com/nostr-protocol/nips/blob/master/27.md\n */\nimport type { Event } from \"../core/event.ts\";\nimport { decode, type AddressPointer, type EventPointer, type ProfilePointer } from \"./nip19.ts\";\n\nexport type ContentBlock =\n | { type: \"text\"; text: string }\n | { type: \"reference\"; pointer: ProfilePointer | AddressPointer | EventPointer }\n | { type: \"url\"; url: string }\n | { type: \"relay\"; url: string }\n | { type: \"image\"; url: string }\n | { type: \"video\"; url: string }\n | { type: \"audio\"; url: string }\n | { type: \"emoji\"; shortcode: string; url: string }\n | { type: \"hashtag\"; value: string };\n\nconst noCharacter = /\\W/m;\nconst noURLCharacter = /[^\\w/] |[^\\w/]$|$|,| /m;\nconst MAX_HASHTAG_LENGTH = 42;\n\n/**\n * Parse note content (or a full event, to pick up emoji tags) into blocks.\n * Generator — consume with `for…of` or `[...parseContent(s)]`.\n */\nexport function* parseContent(\n content: string | Pick<Event, \"content\" | \"tags\">,\n): Iterable<ContentBlock> {\n let emojis: Array<{ type: \"emoji\"; shortcode: string; url: string }> = [];\n let text: string;\n\n if (typeof content === \"string\") {\n text = content;\n } else {\n for (const tag of content.tags) {\n if (tag[0] === \"emoji\" && tag[1] && tag[2]) {\n emojis.push({ type: \"emoji\", shortcode: tag[1], url: tag[2] });\n }\n }\n text = content.content;\n }\n\n const max = text.length;\n let prevIndex = 0;\n let index = 0;\n\n mainloop: while (index < max) {\n const u = text.indexOf(\":\", index);\n const h = text.indexOf(\"#\", index);\n if (u === -1 && h === -1) break;\n\n if (u === -1 || (h >= 0 && h < u)) {\n // hashtag\n if (h === 0 || (h > 0 && text[h - 1]!.match(noCharacter))) {\n const m = text.slice(h + 1, h + MAX_HASHTAG_LENGTH).match(noCharacter);\n const end = m ? h + 1 + (m.index ?? 0) : max;\n if (prevIndex !== h) yield { type: \"text\", text: text.slice(prevIndex, h) };\n yield { type: \"hashtag\", value: text.slice(h + 1, end) };\n index = end;\n prevIndex = index;\n continue mainloop;\n }\n index = h + 1;\n continue mainloop;\n }\n\n // nostr: references\n if (u >= 5 && text.slice(u - 5, u) === \"nostr\") {\n const m = text.slice(u + 60).match(noCharacter);\n const end = m ? u + 60 + (m.index ?? 0) : max;\n try {\n const { data, type } = decode(text.slice(u + 1, end));\n let pointer: ProfilePointer | AddressPointer | EventPointer | undefined;\n switch (type) {\n case \"npub\":\n pointer = { pubkey: data };\n break;\n case \"note\":\n pointer = { id: data };\n break;\n case \"nprofile\":\n case \"nevent\":\n case \"naddr\":\n pointer = data as ProfilePointer | EventPointer | AddressPointer;\n break;\n case \"nsec\":\n index = end + 1;\n continue mainloop;\n default:\n index = u + 1;\n continue mainloop;\n }\n if (prevIndex !== u - 5) yield { type: \"text\", text: text.slice(prevIndex, u - 5) };\n yield { type: \"reference\", pointer };\n index = end;\n prevIndex = index;\n continue mainloop;\n } catch {\n index = u + 1;\n continue mainloop;\n }\n }\n\n // http(s) URLs\n if (\n (u >= 5 && text.slice(u - 5, u) === \"https\") ||\n (u >= 4 && text.slice(u - 4, u) === \"http\")\n ) {\n const m = text.slice(u + 4).match(noURLCharacter);\n const end = m ? u + 4 + (m.index ?? 0) : max;\n const prefixLen = text[u - 1] === \"s\" ? 5 : 4;\n try {\n const url = new URL(text.slice(u - prefixLen, end));\n if (!url.hostname.includes(\".\")) throw new Error(\"invalid url\");\n if (prevIndex !== u - prefixLen) {\n yield { type: \"text\", text: text.slice(prevIndex, u - prefixLen) };\n }\n const path = url.pathname;\n if (/\\.(png|jpe?g|gif|webp|heic|svg)$/i.test(path)) {\n yield { type: \"image\", url: url.toString() };\n } else if (/\\.(mp4|avi|webm|mkv|mov)$/i.test(path)) {\n yield { type: \"video\", url: url.toString() };\n } else if (/\\.(mp3|aac|ogg|opus|wav|flac)$/i.test(path)) {\n yield { type: \"audio\", url: url.toString() };\n } else {\n yield { type: \"url\", url: url.toString() };\n }\n index = end;\n prevIndex = index;\n continue mainloop;\n } catch {\n index = end + 1;\n continue mainloop;\n }\n }\n\n // ws(s) relays\n if ((u >= 3 && text.slice(u - 3, u) === \"wss\") || (u >= 2 && text.slice(u - 2, u) === \"ws\")) {\n const m = text.slice(u + 4).match(noURLCharacter);\n const end = m ? u + 4 + (m.index ?? 0) : max;\n const prefixLen = text[u - 1] === \"s\" ? 3 : 2;\n try {\n const url = new URL(text.slice(u - prefixLen, end));\n if (!url.hostname.includes(\".\")) throw new Error(\"invalid ws url\");\n if (prevIndex !== u - prefixLen) {\n yield { type: \"text\", text: text.slice(prevIndex, u - prefixLen) };\n }\n yield { type: \"relay\", url: url.toString() };\n index = end;\n prevIndex = index;\n continue mainloop;\n } catch {\n index = end + 1;\n continue mainloop;\n }\n }\n\n // custom emoji shortcodes :code:\n for (const emoji of emojis) {\n const endColon = u + emoji.shortcode.length + 1;\n if (text[endColon] === \":\" && text.slice(u + 1, endColon) === emoji.shortcode) {\n if (prevIndex !== u) yield { type: \"text\", text: text.slice(prevIndex, u) };\n yield emoji;\n index = endColon + 1;\n prevIndex = index;\n continue mainloop;\n }\n }\n\n index = u + 1;\n }\n\n if (prevIndex !== max) {\n yield { type: \"text\", text: text.slice(prevIndex) };\n }\n}\n\n/** Collect all content blocks into an array. */\nexport function parseContentBlocks(\n content: string | Pick<Event, \"content\" | \"tags\">,\n): ContentBlock[] {\n return [...parseContent(content)];\n}\n"],"mappings":";;AAmBA,MAAM,cAAc;AACpB,MAAM,iBAAiB;AACvB,MAAM,qBAAqB;;;;;AAM3B,UAAiB,aACf,SACwB;CACxB,IAAI,SAAmE,CAAC;CACxE,IAAI;CAEJ,IAAI,OAAO,YAAY,UACrB,OAAO;MACF;EACL,KAAK,MAAM,OAAO,QAAQ,MACxB,IAAI,IAAI,OAAO,WAAW,IAAI,MAAM,IAAI,IACtC,OAAO,KAAK;GAAE,MAAM;GAAS,WAAW,IAAI;GAAI,KAAK,IAAI;EAAG,CAAC;EAGjE,OAAO,QAAQ;CACjB;CAEA,MAAM,MAAM,KAAK;CACjB,IAAI,YAAY;CAChB,IAAI,QAAQ;CAEZ,UAAU,OAAO,QAAQ,KAAK;EAC5B,MAAM,IAAI,KAAK,QAAQ,KAAK,KAAK;EACjC,MAAM,IAAI,KAAK,QAAQ,KAAK,KAAK;EACjC,IAAI,MAAM,MAAM,MAAM,IAAI;EAE1B,IAAI,MAAM,MAAO,KAAK,KAAK,IAAI,GAAI;GAEjC,IAAI,MAAM,KAAM,IAAI,KAAK,KAAK,IAAI,EAAE,CAAE,MAAM,WAAW,GAAI;IACzD,MAAM,IAAI,KAAK,MAAM,IAAI,GAAG,IAAI,kBAAkB,CAAC,CAAC,MAAM,WAAW;IACrE,MAAM,MAAM,IAAI,IAAI,KAAK,EAAE,SAAS,KAAK;IACzC,IAAI,cAAc,GAAG,MAAM;KAAE,MAAM;KAAQ,MAAM,KAAK,MAAM,WAAW,CAAC;IAAE;IAC1E,MAAM;KAAE,MAAM;KAAW,OAAO,KAAK,MAAM,IAAI,GAAG,GAAG;IAAE;IACvD,QAAQ;IACR,YAAY;IACZ,SAAS;GACX;GACA,QAAQ,IAAI;GACZ,SAAS;EACX;EAGA,IAAI,KAAK,KAAK,KAAK,MAAM,IAAI,GAAG,CAAC,MAAM,SAAS;GAC9C,MAAM,IAAI,KAAK,MAAM,IAAI,EAAE,CAAC,CAAC,MAAM,WAAW;GAC9C,MAAM,MAAM,IAAI,IAAI,MAAM,EAAE,SAAS,KAAK;GAC1C,IAAI;IACF,MAAM,EAAE,MAAM,SAAS,OAAO,KAAK,MAAM,IAAI,GAAG,GAAG,CAAC;IACpD,IAAI;IACJ,QAAQ,MAAR;KACE,KAAK;MACH,UAAU,EAAE,QAAQ,KAAK;MACzB;KACF,KAAK;MACH,UAAU,EAAE,IAAI,KAAK;MACrB;KACF,KAAK;KACL,KAAK;KACL,KAAK;MACH,UAAU;MACV;KACF,KAAK;MACH,QAAQ,MAAM;MACd,SAAS;KACX;MACE,QAAQ,IAAI;MACZ,SAAS;IACb;IACA,IAAI,cAAc,IAAI,GAAG,MAAM;KAAE,MAAM;KAAQ,MAAM,KAAK,MAAM,WAAW,IAAI,CAAC;IAAE;IAClF,MAAM;KAAE,MAAM;KAAa;IAAQ;IACnC,QAAQ;IACR,YAAY;IACZ,SAAS;GACX,QAAQ;IACN,QAAQ,IAAI;IACZ,SAAS;GACX;EACF;EAGA,IACG,KAAK,KAAK,KAAK,MAAM,IAAI,GAAG,CAAC,MAAM,WACnC,KAAK,KAAK,KAAK,MAAM,IAAI,GAAG,CAAC,MAAM,QACpC;GACA,MAAM,IAAI,KAAK,MAAM,IAAI,CAAC,CAAC,CAAC,MAAM,cAAc;GAChD,MAAM,MAAM,IAAI,IAAI,KAAK,EAAE,SAAS,KAAK;GACzC,MAAM,YAAY,KAAK,IAAI,OAAO,MAAM,IAAI;GAC5C,IAAI;IACF,MAAM,MAAM,IAAI,IAAI,KAAK,MAAM,IAAI,WAAW,GAAG,CAAC;IAClD,IAAI,CAAC,IAAI,SAAS,SAAS,GAAG,GAAG,MAAM,IAAI,MAAM,aAAa;IAC9D,IAAI,cAAc,IAAI,WACpB,MAAM;KAAE,MAAM;KAAQ,MAAM,KAAK,MAAM,WAAW,IAAI,SAAS;IAAE;IAEnE,MAAM,OAAO,IAAI;IACjB,IAAI,oCAAoC,KAAK,IAAI,GAC/C,MAAM;KAAE,MAAM;KAAS,KAAK,IAAI,SAAS;IAAE;SACtC,IAAI,6BAA6B,KAAK,IAAI,GAC/C,MAAM;KAAE,MAAM;KAAS,KAAK,IAAI,SAAS;IAAE;SACtC,IAAI,kCAAkC,KAAK,IAAI,GACpD,MAAM;KAAE,MAAM;KAAS,KAAK,IAAI,SAAS;IAAE;SAE3C,MAAM;KAAE,MAAM;KAAO,KAAK,IAAI,SAAS;IAAE;IAE3C,QAAQ;IACR,YAAY;IACZ,SAAS;GACX,QAAQ;IACN,QAAQ,MAAM;IACd,SAAS;GACX;EACF;EAGA,IAAK,KAAK,KAAK,KAAK,MAAM,IAAI,GAAG,CAAC,MAAM,SAAW,KAAK,KAAK,KAAK,MAAM,IAAI,GAAG,CAAC,MAAM,MAAO;GAC3F,MAAM,IAAI,KAAK,MAAM,IAAI,CAAC,CAAC,CAAC,MAAM,cAAc;GAChD,MAAM,MAAM,IAAI,IAAI,KAAK,EAAE,SAAS,KAAK;GACzC,MAAM,YAAY,KAAK,IAAI,OAAO,MAAM,IAAI;GAC5C,IAAI;IACF,MAAM,MAAM,IAAI,IAAI,KAAK,MAAM,IAAI,WAAW,GAAG,CAAC;IAClD,IAAI,CAAC,IAAI,SAAS,SAAS,GAAG,GAAG,MAAM,IAAI,MAAM,gBAAgB;IACjE,IAAI,cAAc,IAAI,WACpB,MAAM;KAAE,MAAM;KAAQ,MAAM,KAAK,MAAM,WAAW,IAAI,SAAS;IAAE;IAEnE,MAAM;KAAE,MAAM;KAAS,KAAK,IAAI,SAAS;IAAE;IAC3C,QAAQ;IACR,YAAY;IACZ,SAAS;GACX,QAAQ;IACN,QAAQ,MAAM;IACd,SAAS;GACX;EACF;EAGA,KAAK,MAAM,SAAS,QAAQ;GAC1B,MAAM,WAAW,IAAI,MAAM,UAAU,SAAS;GAC9C,IAAI,KAAK,cAAc,OAAO,KAAK,MAAM,IAAI,GAAG,QAAQ,MAAM,MAAM,WAAW;IAC7E,IAAI,cAAc,GAAG,MAAM;KAAE,MAAM;KAAQ,MAAM,KAAK,MAAM,WAAW,CAAC;IAAE;IAC1E,MAAM;IACN,QAAQ,WAAW;IACnB,YAAY;IACZ,SAAS;GACX;EACF;EAEA,QAAQ,IAAI;CACd;CAEA,IAAI,cAAc,KAChB,MAAM;EAAE,MAAM;EAAQ,MAAM,KAAK,MAAM,SAAS;CAAE;AAEtD;;AAGA,SAAgB,mBACd,SACgB;CAChB,OAAO,CAAC,GAAG,aAAa,OAAO,CAAC;AAClC"}
@@ -0,0 +1,12 @@
1
+ import { n as EventTemplate } from "../event-B5D2jxt_.mjs";
2
+ //#region src/nips/nip42.d.ts
3
+ /**
4
+ * Build an unsigned NIP-42 AUTH event template for the given relay challenge.
5
+ * Caller must sign with their `NostrSigner` / keys.
6
+ */
7
+ declare function makeAuthEvent(relayURL: string, challenge: string): EventTemplate;
8
+ /** True when a CLOSED reason requests NIP-42 authentication. */
9
+ declare function isAuthRequired(reason: string): boolean;
10
+ //#endregion
11
+ export { isAuthRequired, makeAuthEvent };
12
+ //# sourceMappingURL=nip42.d.mts.map
@@ -0,0 +1,22 @@
1
+ import { t as Kind } from "../kind-BLl4cLOc.mjs";
2
+ //#region src/nips/nip42.ts
3
+ /**
4
+ * Build an unsigned NIP-42 AUTH event template for the given relay challenge.
5
+ * Caller must sign with their `NostrSigner` / keys.
6
+ */
7
+ function makeAuthEvent(relayURL, challenge) {
8
+ return {
9
+ kind: Kind.ClientAuth,
10
+ created_at: Math.floor(Date.now() / 1e3),
11
+ tags: [["relay", relayURL], ["challenge", challenge]],
12
+ content: ""
13
+ };
14
+ }
15
+ /** True when a CLOSED reason requests NIP-42 authentication. */
16
+ function isAuthRequired(reason) {
17
+ return reason.startsWith("auth-required:");
18
+ }
19
+ //#endregion
20
+ export { isAuthRequired, makeAuthEvent };
21
+
22
+ //# sourceMappingURL=nip42.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nip42.mjs","names":[],"sources":["../../src/nips/nip42.ts"],"sourcesContent":["import type { EventTemplate } from \"../core/event.ts\";\nimport { Kind } from \"../core/kind.ts\";\n\n/**\n * Build an unsigned NIP-42 AUTH event template for the given relay challenge.\n * Caller must sign with their `NostrSigner` / keys.\n */\nexport function makeAuthEvent(relayURL: string, challenge: string): EventTemplate {\n return {\n kind: Kind.ClientAuth,\n created_at: Math.floor(Date.now() / 1000),\n tags: [\n [\"relay\", relayURL],\n [\"challenge\", challenge],\n ],\n content: \"\",\n };\n}\n\n/** True when a CLOSED reason requests NIP-42 authentication. */\nexport function isAuthRequired(reason: string): boolean {\n return reason.startsWith(\"auth-required:\");\n}\n"],"mappings":";;;;;;AAOA,SAAgB,cAAc,UAAkB,WAAkC;CAChF,OAAO;EACL,MAAM,KAAK;EACX,YAAY,KAAK,MAAM,KAAK,IAAI,IAAI,GAAI;EACxC,MAAM,CACJ,CAAC,SAAS,QAAQ,GAClB,CAAC,aAAa,SAAS,CACzB;EACA,SAAS;CACX;AACF;;AAGA,SAAgB,eAAe,QAAyB;CACtD,OAAO,OAAO,WAAW,gBAAgB;AAC3C"}
@@ -0,0 +1,12 @@
1
+ //#region src/nips/nip44.d.ts
2
+ declare function getConversationKey(privkeyA: Uint8Array, pubkeyB: string): Uint8Array;
3
+ declare function calcPaddedLen(len: number): number;
4
+ declare function encrypt(plaintext: string, conversationKey: Uint8Array, nonce?: Uint8Array): string;
5
+ declare function decrypt(payload: string, conversationKey: Uint8Array): string;
6
+ /** Convenience: derive conversation key from secret/public pair and encrypt. */
7
+ declare function encryptToPubkey(plaintext: string, secretKey: Uint8Array, peerPubkey: string): string;
8
+ /** Convenience: decrypt a payload from a peer pubkey. */
9
+ declare function decryptFromPubkey(payload: string, secretKey: Uint8Array, peerPubkey: string): string;
10
+ //#endregion
11
+ export { calcPaddedLen, decrypt, decryptFromPubkey, encrypt, encryptToPubkey, getConversationKey };
12
+ //# sourceMappingURL=nip44.d.mts.map
@@ -0,0 +1,125 @@
1
+ import { c as utf8Decoder, i as hexToBytes, l as utf8Encoder, n as assertSecretKeyBytes, t as assertHex32 } from "../util-6tfLQ9JF.mjs";
2
+ import { t as CryptoError } from "../error-DZEs5OuZ.mjs";
3
+ import { sha256 } from "@noble/hashes/sha2.js";
4
+ import { secp256k1 } from "@noble/curves/secp256k1.js";
5
+ import { concatBytes, randomBytes } from "@noble/hashes/utils.js";
6
+ import { base64 } from "@scure/base";
7
+ import { chacha20 } from "@noble/ciphers/chacha.js";
8
+ import { equalBytes } from "@noble/ciphers/utils.js";
9
+ import { expand, extract } from "@noble/hashes/hkdf.js";
10
+ import { hmac } from "@noble/hashes/hmac.js";
11
+ //#region src/nips/nip44.ts
12
+ const minPlaintextSize = 1;
13
+ const maxPlaintextSize = 4294967295;
14
+ const extendedPrefixThreshold = 65536;
15
+ function assert32(bytes, label) {
16
+ if (bytes.length !== 32) throw new CryptoError(`${label} must be 32 bytes`);
17
+ }
18
+ function getConversationKey(privkeyA, pubkeyB) {
19
+ assertSecretKeyBytes(privkeyA);
20
+ assertHex32(pubkeyB, "public key");
21
+ const sharedX = secp256k1.getSharedSecret(privkeyA, hexToBytes("02" + pubkeyB.toLowerCase())).subarray(1, 33);
22
+ return extract(sha256, sharedX, utf8Encoder.encode("nip44-v2"));
23
+ }
24
+ function getMessageKeys(conversationKey, nonce) {
25
+ assert32(conversationKey, "conversation_key");
26
+ assert32(nonce, "nonce");
27
+ const keys = expand(sha256, conversationKey, nonce, 76);
28
+ return {
29
+ chacha_key: keys.subarray(0, 32),
30
+ chacha_nonce: keys.subarray(32, 44),
31
+ hmac_key: keys.subarray(44, 76)
32
+ };
33
+ }
34
+ function calcPaddedLen(len) {
35
+ if (!Number.isSafeInteger(len) || len < 1) throw new CryptoError("expected positive integer");
36
+ if (len <= 32) return 32;
37
+ const nextPower = 2 ** (Math.floor(Math.log2(len - 1)) + 1);
38
+ const chunk = nextPower <= 256 ? 32 : nextPower / 8;
39
+ return chunk * (Math.floor((len - 1) / chunk) + 1);
40
+ }
41
+ function writeU16BE(num) {
42
+ if (!Number.isSafeInteger(num) || num < minPlaintextSize || num > 65535) throw new CryptoError("invalid plaintext size: must be between 1 and 65535 bytes");
43
+ const arr = /* @__PURE__ */ new Uint8Array(2);
44
+ new DataView(arr.buffer).setUint16(0, num, false);
45
+ return arr;
46
+ }
47
+ function writeU32BE(num) {
48
+ if (!Number.isSafeInteger(num) || num < extendedPrefixThreshold || num > maxPlaintextSize) throw new CryptoError("invalid plaintext size: must be between 65536 and 4294967295 bytes");
49
+ const arr = /* @__PURE__ */ new Uint8Array(4);
50
+ new DataView(arr.buffer).setUint32(0, num, false);
51
+ return arr;
52
+ }
53
+ function pad(plaintext) {
54
+ const unpadded = utf8Encoder.encode(plaintext);
55
+ const unpaddedLen = unpadded.length;
56
+ if (unpaddedLen < minPlaintextSize || unpaddedLen > maxPlaintextSize) throw new CryptoError("invalid plaintext size: must be between 1 and 4294967295 bytes");
57
+ const prefix = unpaddedLen >= extendedPrefixThreshold ? concatBytes(new Uint8Array([0, 0]), writeU32BE(unpaddedLen)) : writeU16BE(unpaddedLen);
58
+ const suffix = new Uint8Array(calcPaddedLen(unpaddedLen) - unpaddedLen);
59
+ return concatBytes(prefix, unpadded, suffix);
60
+ }
61
+ function unpad(padded) {
62
+ const dv = new DataView(padded.buffer, padded.byteOffset, padded.byteLength);
63
+ const firstTwo = dv.getUint16(0);
64
+ let unpaddedLen;
65
+ let prefixLen;
66
+ if (firstTwo === 0) {
67
+ unpaddedLen = dv.getUint32(2);
68
+ if (unpaddedLen < extendedPrefixThreshold) throw new CryptoError("invalid padding");
69
+ prefixLen = 6;
70
+ } else {
71
+ unpaddedLen = firstTwo;
72
+ prefixLen = 2;
73
+ }
74
+ const unpadded = padded.subarray(prefixLen, prefixLen + unpaddedLen);
75
+ if (unpaddedLen < minPlaintextSize || unpaddedLen > maxPlaintextSize || unpadded.length !== unpaddedLen || padded.length !== prefixLen + calcPaddedLen(unpaddedLen)) throw new CryptoError("invalid padding");
76
+ return utf8Decoder.decode(unpadded);
77
+ }
78
+ function hmacAad(key, message, aad) {
79
+ if (aad.length !== 32) throw new CryptoError("AAD associated data must be 32 bytes");
80
+ return hmac(sha256, key, concatBytes(aad, message));
81
+ }
82
+ function decodePayload(payload) {
83
+ if (typeof payload !== "string") throw new CryptoError("payload must be a valid string");
84
+ if (payload.length < 132) throw new CryptoError("invalid payload length: " + payload.length);
85
+ if (payload[0] === "#") throw new CryptoError("unknown encryption version");
86
+ let data;
87
+ try {
88
+ data = base64.decode(payload);
89
+ } catch (error) {
90
+ throw new CryptoError("invalid base64: " + (error instanceof Error ? error.message : "decode failed"));
91
+ }
92
+ if (data.length < 99) throw new CryptoError("invalid data length: " + data.length);
93
+ if (data[0] !== 2) throw new CryptoError("unknown encryption version " + data[0]);
94
+ return {
95
+ nonce: data.subarray(1, 33),
96
+ ciphertext: data.subarray(33, -32),
97
+ mac: data.subarray(-32)
98
+ };
99
+ }
100
+ function encrypt(plaintext, conversationKey, nonce = randomBytes(32)) {
101
+ const { chacha_key, chacha_nonce, hmac_key } = getMessageKeys(conversationKey, nonce);
102
+ const padded = pad(plaintext);
103
+ const ciphertext = chacha20(chacha_key, chacha_nonce, padded);
104
+ const mac = hmacAad(hmac_key, ciphertext, nonce);
105
+ return base64.encode(concatBytes(new Uint8Array([2]), nonce, ciphertext, mac));
106
+ }
107
+ function decrypt(payload, conversationKey) {
108
+ const { nonce, ciphertext, mac } = decodePayload(payload);
109
+ const { chacha_key, chacha_nonce, hmac_key } = getMessageKeys(conversationKey, nonce);
110
+ const calculatedMac = hmacAad(hmac_key, ciphertext, nonce);
111
+ if (!equalBytes(calculatedMac, mac)) throw new CryptoError("invalid MAC");
112
+ return unpad(chacha20(chacha_key, chacha_nonce, ciphertext));
113
+ }
114
+ /** Convenience: derive conversation key from secret/public pair and encrypt. */
115
+ function encryptToPubkey(plaintext, secretKey, peerPubkey) {
116
+ return encrypt(plaintext, getConversationKey(secretKey, peerPubkey));
117
+ }
118
+ /** Convenience: decrypt a payload from a peer pubkey. */
119
+ function decryptFromPubkey(payload, secretKey, peerPubkey) {
120
+ return decrypt(payload, getConversationKey(secretKey, peerPubkey));
121
+ }
122
+ //#endregion
123
+ export { calcPaddedLen, decrypt, decryptFromPubkey, encrypt, encryptToPubkey, getConversationKey };
124
+
125
+ //# sourceMappingURL=nip44.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nip44.mjs","names":["hkdf_extract","hkdf_expand"],"sources":["../../src/nips/nip44.ts"],"sourcesContent":["import { chacha20 } from \"@noble/ciphers/chacha.js\";\nimport { equalBytes } from \"@noble/ciphers/utils.js\";\nimport { secp256k1 } from \"@noble/curves/secp256k1.js\";\nimport { extract as hkdf_extract, expand as hkdf_expand } from \"@noble/hashes/hkdf.js\";\nimport { hmac } from \"@noble/hashes/hmac.js\";\nimport { sha256 } from \"@noble/hashes/sha2.js\";\nimport { concatBytes, randomBytes } from \"@noble/hashes/utils.js\";\nimport { base64 } from \"@scure/base\";\nimport { CryptoError } from \"../core/error.ts\";\nimport {\n assertHex32,\n assertSecretKeyBytes,\n hexToBytes,\n utf8Decoder,\n utf8Encoder,\n} from \"../core/util.ts\";\n\nconst minPlaintextSize = 0x0001;\nconst maxPlaintextSize = 0xffffffff;\nconst extendedPrefixThreshold = 0x10000;\n\nfunction assert32(bytes: Uint8Array, label: string): void {\n if (bytes.length !== 32) throw new CryptoError(`${label} must be 32 bytes`);\n}\n\nexport function getConversationKey(privkeyA: Uint8Array, pubkeyB: string): Uint8Array {\n assertSecretKeyBytes(privkeyA);\n assertHex32(pubkeyB, \"public key\");\n const sharedX = secp256k1\n .getSharedSecret(privkeyA, hexToBytes(\"02\" + pubkeyB.toLowerCase()))\n .subarray(1, 33);\n return hkdf_extract(sha256, sharedX, utf8Encoder.encode(\"nip44-v2\"));\n}\n\nfunction getMessageKeys(\n conversationKey: Uint8Array,\n nonce: Uint8Array,\n): { chacha_key: Uint8Array; chacha_nonce: Uint8Array; hmac_key: Uint8Array } {\n assert32(conversationKey, \"conversation_key\");\n assert32(nonce, \"nonce\");\n const keys = hkdf_expand(sha256, conversationKey, nonce, 76);\n return {\n chacha_key: keys.subarray(0, 32),\n chacha_nonce: keys.subarray(32, 44),\n hmac_key: keys.subarray(44, 76),\n };\n}\n\nexport function calcPaddedLen(len: number): number {\n if (!Number.isSafeInteger(len) || len < 1) throw new CryptoError(\"expected positive integer\");\n if (len <= 32) return 32;\n const nextPower = 2 ** (Math.floor(Math.log2(len - 1)) + 1);\n const chunk = nextPower <= 256 ? 32 : nextPower / 8;\n return chunk * (Math.floor((len - 1) / chunk) + 1);\n}\n\nfunction writeU16BE(num: number): Uint8Array {\n if (!Number.isSafeInteger(num) || num < minPlaintextSize || num > 0xffff) {\n throw new CryptoError(\"invalid plaintext size: must be between 1 and 65535 bytes\");\n }\n const arr = new Uint8Array(2);\n new DataView(arr.buffer).setUint16(0, num, false);\n return arr;\n}\n\nfunction writeU32BE(num: number): Uint8Array {\n if (!Number.isSafeInteger(num) || num < extendedPrefixThreshold || num > maxPlaintextSize) {\n throw new CryptoError(\"invalid plaintext size: must be between 65536 and 4294967295 bytes\");\n }\n const arr = new Uint8Array(4);\n new DataView(arr.buffer).setUint32(0, num, false);\n return arr;\n}\n\nfunction pad(plaintext: string): Uint8Array {\n const unpadded = utf8Encoder.encode(plaintext);\n const unpaddedLen = unpadded.length;\n if (unpaddedLen < minPlaintextSize || unpaddedLen > maxPlaintextSize) {\n throw new CryptoError(\"invalid plaintext size: must be between 1 and 4294967295 bytes\");\n }\n const prefix =\n unpaddedLen >= extendedPrefixThreshold\n ? concatBytes(new Uint8Array([0, 0]), writeU32BE(unpaddedLen))\n : writeU16BE(unpaddedLen);\n const suffix = new Uint8Array(calcPaddedLen(unpaddedLen) - unpaddedLen);\n return concatBytes(prefix, unpadded, suffix);\n}\n\nfunction unpad(padded: Uint8Array): string {\n const dv = new DataView(padded.buffer, padded.byteOffset, padded.byteLength);\n const firstTwo = dv.getUint16(0);\n let unpaddedLen: number;\n let prefixLen: number;\n if (firstTwo === 0) {\n unpaddedLen = dv.getUint32(2);\n if (unpaddedLen < extendedPrefixThreshold) throw new CryptoError(\"invalid padding\");\n prefixLen = 6;\n } else {\n unpaddedLen = firstTwo;\n prefixLen = 2;\n }\n const unpadded = padded.subarray(prefixLen, prefixLen + unpaddedLen);\n if (\n unpaddedLen < minPlaintextSize ||\n unpaddedLen > maxPlaintextSize ||\n unpadded.length !== unpaddedLen ||\n padded.length !== prefixLen + calcPaddedLen(unpaddedLen)\n ) {\n throw new CryptoError(\"invalid padding\");\n }\n return utf8Decoder.decode(unpadded);\n}\n\nfunction hmacAad(key: Uint8Array, message: Uint8Array, aad: Uint8Array): Uint8Array {\n if (aad.length !== 32) throw new CryptoError(\"AAD associated data must be 32 bytes\");\n return hmac(sha256, key, concatBytes(aad, message));\n}\n\nfunction decodePayload(payload: string): {\n nonce: Uint8Array;\n ciphertext: Uint8Array;\n mac: Uint8Array;\n} {\n if (typeof payload !== \"string\") throw new CryptoError(\"payload must be a valid string\");\n if (payload.length < 132) throw new CryptoError(\"invalid payload length: \" + payload.length);\n if (payload[0] === \"#\") throw new CryptoError(\"unknown encryption version\");\n let data: Uint8Array;\n try {\n data = base64.decode(payload);\n } catch (error) {\n throw new CryptoError(\n \"invalid base64: \" + (error instanceof Error ? error.message : \"decode failed\"),\n );\n }\n if (data.length < 99) throw new CryptoError(\"invalid data length: \" + data.length);\n if (data[0] !== 2) throw new CryptoError(\"unknown encryption version \" + data[0]);\n return {\n nonce: data.subarray(1, 33),\n ciphertext: data.subarray(33, -32),\n mac: data.subarray(-32),\n };\n}\n\nexport function encrypt(\n plaintext: string,\n conversationKey: Uint8Array,\n nonce: Uint8Array = randomBytes(32),\n): string {\n const { chacha_key, chacha_nonce, hmac_key } = getMessageKeys(conversationKey, nonce);\n const padded = pad(plaintext);\n const ciphertext = chacha20(chacha_key, chacha_nonce, padded);\n const mac = hmacAad(hmac_key, ciphertext, nonce);\n return base64.encode(concatBytes(new Uint8Array([2]), nonce, ciphertext, mac));\n}\n\nexport function decrypt(payload: string, conversationKey: Uint8Array): string {\n const { nonce, ciphertext, mac } = decodePayload(payload);\n const { chacha_key, chacha_nonce, hmac_key } = getMessageKeys(conversationKey, nonce);\n const calculatedMac = hmacAad(hmac_key, ciphertext, nonce);\n if (!equalBytes(calculatedMac, mac)) throw new CryptoError(\"invalid MAC\");\n const padded = chacha20(chacha_key, chacha_nonce, ciphertext);\n return unpad(padded);\n}\n\n/** Convenience: derive conversation key from secret/public pair and encrypt. */\nexport function encryptToPubkey(\n plaintext: string,\n secretKey: Uint8Array,\n peerPubkey: string,\n): string {\n return encrypt(plaintext, getConversationKey(secretKey, peerPubkey));\n}\n\n/** Convenience: decrypt a payload from a peer pubkey. */\nexport function decryptFromPubkey(\n payload: string,\n secretKey: Uint8Array,\n peerPubkey: string,\n): string {\n return decrypt(payload, getConversationKey(secretKey, peerPubkey));\n}\n"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import { a as Nip46Request, c as createNostrConnectURI, d as encodeNip46Request, f as encodeNip46Response, h as toBunkerURL, i as Nip46Error, l as decodeNip46Request, m as parseNostrConnectURI, n as BunkerPointer, o as Nip46Response, p as parseBunkerURL, r as ClientMetadata, s as NostrConnectParams, t as BUNKER_REGEX, u as decodeNip46Response } from "../nip46-C_DoxiS_.mjs";
2
+ export { BUNKER_REGEX, BunkerPointer, ClientMetadata, Nip46Error, Nip46Request, Nip46Response, NostrConnectParams, createNostrConnectURI, decodeNip46Request, decodeNip46Response, encodeNip46Request, encodeNip46Response, parseBunkerURL, parseNostrConnectURI, toBunkerURL };
@@ -0,0 +1,132 @@
1
+ import { a as isHex32, t as assertHex32 } from "../util-6tfLQ9JF.mjs";
2
+ import { a as NostrError } from "../error-DZEs5OuZ.mjs";
3
+ //#region src/nips/nip46.ts
4
+ /**
5
+ * NIP-46 (Nostr Connect) protocol helpers: bunker URI, nostrconnect URI, RPC JSON.
6
+ * Transport/signing live in {@link Nip46Signer}.
7
+ *
8
+ * @see https://github.com/nostr-protocol/nips/blob/master/46.md
9
+ */
10
+ const BUNKER_REGEX = /^bunker:\/\/([0-9a-fA-F]{64})\??([?/\w:.=&%-]*)$/;
11
+ var Nip46Error = class extends NostrError {
12
+ constructor(message, options) {
13
+ super(message, options);
14
+ }
15
+ };
16
+ /** Encode a bunker pointer as `bunker://…`. */
17
+ function toBunkerURL(pointer) {
18
+ const url = new URL(`bunker://${pointer.pubkey.toLowerCase()}`);
19
+ for (const relay of pointer.relays) url.searchParams.append("relay", relay);
20
+ if (pointer.secret) url.searchParams.set("secret", pointer.secret);
21
+ return url.toString();
22
+ }
23
+ /**
24
+ * Parse a `bunker://` URL into a pointer.
25
+ * Returns null when the input is not a bunker URL (including NIP-05 identifiers).
26
+ */
27
+ function parseBunkerURL(input) {
28
+ const match = input.trim().match(BUNKER_REGEX);
29
+ if (!match?.[1]) return null;
30
+ try {
31
+ const pubkey = assertHex32(match[1], "bunker pubkey");
32
+ const qs = new URLSearchParams(match[2] ?? "");
33
+ return {
34
+ pubkey,
35
+ relays: qs.getAll("relay"),
36
+ secret: qs.get("secret")
37
+ };
38
+ } catch {
39
+ return null;
40
+ }
41
+ }
42
+ /** Build a client-initiated `nostrconnect://` URI. */
43
+ function createNostrConnectURI(params) {
44
+ if (!isHex32(params.clientPubkey)) throw new Nip46Error("invalid client pubkey");
45
+ if (!params.secret) throw new Nip46Error("nostrconnect secret is required");
46
+ if (params.relays.length === 0) throw new Nip46Error("at least one relay is required");
47
+ const query = new URLSearchParams();
48
+ for (const relay of params.relays) query.append("relay", relay);
49
+ query.set("secret", params.secret);
50
+ if (params.perms && params.perms.length > 0) query.set("perms", params.perms.join(","));
51
+ if (params.name) query.set("name", params.name);
52
+ if (params.url) query.set("url", params.url);
53
+ if (params.image) query.set("image", params.image);
54
+ return `nostrconnect://${params.clientPubkey.toLowerCase()}?${query.toString()}`;
55
+ }
56
+ /** Parse a `nostrconnect://` URI. */
57
+ function parseNostrConnectURI(uri) {
58
+ let url;
59
+ try {
60
+ url = new URL(uri);
61
+ } catch (cause) {
62
+ throw new Nip46Error(`invalid nostrconnect URI: ${uri}`, { cause: cause instanceof Error ? cause : void 0 });
63
+ }
64
+ if (url.protocol !== "nostrconnect:") throw new Nip46Error(`expected nostrconnect: scheme, got ${url.protocol}`);
65
+ const clientPubkey = url.hostname || url.pathname.replace(/^\/*/, "");
66
+ if (!isHex32(clientPubkey)) throw new Nip46Error("invalid client pubkey in nostrconnect URI");
67
+ const secret = url.searchParams.get("secret");
68
+ if (!secret) throw new Nip46Error("missing secret in nostrconnect URI");
69
+ const relays = url.searchParams.getAll("relay");
70
+ if (relays.length === 0) throw new Nip46Error("missing relays in nostrconnect URI");
71
+ const permsRaw = url.searchParams.get("perms");
72
+ return {
73
+ clientPubkey: clientPubkey.toLowerCase(),
74
+ relays,
75
+ secret,
76
+ perms: permsRaw ? permsRaw.split(",").filter(Boolean) : void 0,
77
+ name: url.searchParams.get("name") ?? void 0,
78
+ url: url.searchParams.get("url") ?? void 0,
79
+ image: url.searchParams.get("image") ?? void 0
80
+ };
81
+ }
82
+ function encodeNip46Request(req) {
83
+ return JSON.stringify({
84
+ id: req.id,
85
+ method: req.method,
86
+ params: req.params
87
+ });
88
+ }
89
+ function decodeNip46Request(json) {
90
+ let data;
91
+ try {
92
+ data = JSON.parse(json);
93
+ } catch (cause) {
94
+ throw new Nip46Error("invalid NIP-46 request JSON", { cause: cause instanceof Error ? cause : void 0 });
95
+ }
96
+ if (!isRecord(data) || typeof data.id !== "string" || typeof data.method !== "string") throw new Nip46Error("invalid NIP-46 request shape");
97
+ if (!Array.isArray(data.params) || !data.params.every((p) => typeof p === "string")) throw new Nip46Error("invalid NIP-46 request params");
98
+ return {
99
+ id: data.id,
100
+ method: data.method,
101
+ params: data.params
102
+ };
103
+ }
104
+ function encodeNip46Response(res) {
105
+ const body = { id: res.id };
106
+ if (res.result !== void 0) body.result = res.result;
107
+ if (res.error !== void 0) body.error = res.error;
108
+ return JSON.stringify(body);
109
+ }
110
+ function decodeNip46Response(json) {
111
+ let data;
112
+ try {
113
+ data = JSON.parse(json);
114
+ } catch (cause) {
115
+ throw new Nip46Error("invalid NIP-46 response JSON", { cause: cause instanceof Error ? cause : void 0 });
116
+ }
117
+ if (!isRecord(data) || typeof data.id !== "string") throw new Nip46Error("invalid NIP-46 response shape");
118
+ if (data.result !== void 0 && typeof data.result !== "string") throw new Nip46Error("invalid NIP-46 response result");
119
+ if (data.error !== void 0 && typeof data.error !== "string") throw new Nip46Error("invalid NIP-46 response error");
120
+ return {
121
+ id: data.id,
122
+ result: data.result,
123
+ error: data.error
124
+ };
125
+ }
126
+ function isRecord(value) {
127
+ return typeof value === "object" && value !== null && !Array.isArray(value);
128
+ }
129
+ //#endregion
130
+ export { BUNKER_REGEX, Nip46Error, createNostrConnectURI, decodeNip46Request, decodeNip46Response, encodeNip46Request, encodeNip46Response, parseBunkerURL, parseNostrConnectURI, toBunkerURL };
131
+
132
+ //# sourceMappingURL=nip46.mjs.map
@@ -0,0 +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 { assertHex32, isHex32 } from \"../core/util.ts\";\nimport { NostrError } from \"../core/error.ts\";\n\nexport const BUNKER_REGEX = /^bunker:\\/\\/([0-9a-fA-F]{64})\\??([?/\\w:.=&%-]*)$/;\n\nexport type BunkerPointer = {\n /** Remote signer / bunker public key (hex). */\n pubkey: string;\n relays: string[];\n secret: string | null;\n};\n\nexport type ClientMetadata = {\n name?: string;\n url?: string;\n image?: string;\n};\n\nexport type NostrConnectParams = {\n clientPubkey: string;\n relays: string[];\n secret: string;\n perms?: string[];\n} & ClientMetadata;\n\nexport type Nip46Request = {\n id: string;\n method: string;\n params: string[];\n};\n\nexport type Nip46Response = {\n id: string;\n result?: string;\n error?: string;\n};\n\nexport class Nip46Error extends NostrError {\n constructor(message: string, options?: ErrorOptions) {\n super(message, options);\n }\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) {\n url.searchParams.set(\"secret\", pointer.secret);\n }\n return url.toString();\n}\n\n/**\n * Parse a `bunker://` URL into a pointer.\n * Returns null when the input is not a bunker URL (including NIP-05 identifiers).\n */\nexport function parseBunkerURL(input: string): BunkerPointer | null {\n const match = input.trim().match(BUNKER_REGEX);\n if (!match?.[1]) return null;\n try {\n const pubkey = assertHex32(match[1], \"bunker pubkey\");\n const qs = new URLSearchParams(match[2] ?? \"\");\n return {\n pubkey,\n relays: qs.getAll(\"relay\"),\n secret: qs.get(\"secret\"),\n };\n } catch {\n return null;\n }\n}\n\n/** Build a client-initiated `nostrconnect://` URI. */\nexport function createNostrConnectURI(params: NostrConnectParams): string {\n if (!isHex32(params.clientPubkey)) {\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 && params.perms.length > 0) {\n query.set(\"perms\", params.perms.join(\",\"));\n }\n if (params.name) query.set(\"name\", params.name);\n if (params.url) query.set(\"url\", params.url);\n if (params.image) query.set(\"image\", params.image);\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 (cause) {\n throw new Nip46Error(`invalid nostrconnect URI: ${uri}`, {\n cause: cause instanceof Error ? cause : undefined,\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)) {\n throw new Nip46Error(\"invalid client pubkey in nostrconnect URI\");\n }\n const secret = url.searchParams.get(\"secret\");\n if (!secret) throw new Nip46Error(\"missing secret in nostrconnect URI\");\n const relays = url.searchParams.getAll(\"relay\");\n if (relays.length === 0) throw new Nip46Error(\"missing relays in nostrconnect URI\");\n const permsRaw = url.searchParams.get(\"perms\");\n return {\n clientPubkey: clientPubkey.toLowerCase(),\n relays,\n secret,\n perms: permsRaw ? permsRaw.split(\",\").filter(Boolean) : undefined,\n name: url.searchParams.get(\"name\") ?? undefined,\n url: url.searchParams.get(\"url\") ?? undefined,\n image: url.searchParams.get(\"image\") ?? undefined,\n };\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 (cause) {\n throw new Nip46Error(\"invalid NIP-46 request JSON\", {\n cause: cause instanceof Error ? cause : undefined,\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 as string[] };\n}\n\nexport function encodeNip46Response(res: Nip46Response): string {\n const body: Record<string, string> = { id: res.id };\n if (res.result !== undefined) body.result = res.result;\n if (res.error !== undefined) body.error = res.error;\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 (cause) {\n throw new Nip46Error(\"invalid NIP-46 response JSON\", {\n cause: cause instanceof Error ? cause : undefined,\n });\n }\n if (!isRecord(data) || typeof data.id !== \"string\") {\n throw new Nip46Error(\"invalid NIP-46 response shape\");\n }\n if (data.result !== undefined && typeof data.result !== \"string\") {\n throw new Nip46Error(\"invalid NIP-46 response result\");\n }\n if (data.error !== undefined && typeof data.error !== \"string\") {\n throw new Nip46Error(\"invalid NIP-46 response error\");\n }\n return {\n id: data.id,\n result: data.result as string | undefined,\n error: data.error as string | undefined,\n };\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n"],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ import { a as NostrError } from "../error-WEFS-R-4.mjs";
2
+ //#region src/nips/nip49.d.ts
3
+ type Ncryptsec = `ncryptsec1${string}`;
4
+ type KeySecurityByte = 0x00 | 0x01 | 0x02;
5
+ declare class Nip49Error extends NostrError {}
6
+ /** Encrypt a 32-byte secret key to an `ncryptsec` bech32 string. */
7
+ declare function encrypt(secretKey: Uint8Array, password: string, logn?: number, ksb?: KeySecurityByte): Ncryptsec;
8
+ /** Decrypt an `ncryptsec` bech32 string to a 32-byte secret key. */
9
+ declare function decrypt(ncryptsec: string, password: string): Uint8Array;
10
+ //#endregion
11
+ export { KeySecurityByte, Ncryptsec, Nip49Error, decrypt, encrypt };
12
+ //# sourceMappingURL=nip49.d.mts.map
@@ -0,0 +1,80 @@
1
+ import { n as assertSecretKeyBytes } from "../util-6tfLQ9JF.mjs";
2
+ import { a as NostrError } from "../error-DZEs5OuZ.mjs";
3
+ import { Bech32MaxSize, encodeBytes } from "./nip19.mjs";
4
+ import { concatBytes, randomBytes } from "@noble/hashes/utils.js";
5
+ import { bech32 } from "@scure/base";
6
+ import { xchacha20poly1305 } from "@noble/ciphers/chacha.js";
7
+ import { scrypt } from "@noble/hashes/scrypt.js";
8
+ //#region src/nips/nip49.ts
9
+ /**
10
+ * NIP-49: Private Key Encryption (`ncryptsec`).
11
+ *
12
+ * @see https://github.com/nostr-protocol/nips/blob/master/49.md
13
+ */
14
+ const VERSION = 2;
15
+ const SALT_LEN = 16;
16
+ const NONCE_LEN = 24;
17
+ const PAYLOAD_LEN = 91;
18
+ const LOGN_MIN = 1;
19
+ const LOGN_MAX = 22;
20
+ const SCRYPT_R = 8;
21
+ const SCRYPT_P = 1;
22
+ var Nip49Error = class extends NostrError {};
23
+ function assertLogn(logn) {
24
+ if (!Number.isInteger(logn) || logn < LOGN_MIN || logn > LOGN_MAX) throw new Nip49Error(`invalid logn ${logn}, expected integer ${LOGN_MIN}..${LOGN_MAX}`);
25
+ }
26
+ function deriveKey(password, salt, logn) {
27
+ assertLogn(logn);
28
+ const N = 2 ** logn;
29
+ return scrypt(password.normalize("NFKC"), salt, {
30
+ N,
31
+ r: SCRYPT_R,
32
+ p: SCRYPT_P,
33
+ dkLen: 32,
34
+ maxmem: 1024 * (N + SCRYPT_P + 1)
35
+ });
36
+ }
37
+ /** Encrypt a 32-byte secret key to an `ncryptsec` bech32 string. */
38
+ function encrypt(secretKey, password, logn = 16, ksb = 2) {
39
+ assertSecretKeyBytes(secretKey);
40
+ const salt = randomBytes(SALT_LEN);
41
+ const key = deriveKey(password, salt, logn);
42
+ const nonce = randomBytes(NONCE_LEN);
43
+ const aad = Uint8Array.from([ksb]);
44
+ const ciphertext = xchacha20poly1305(key, nonce, aad).encrypt(secretKey);
45
+ const bytes = concatBytes(Uint8Array.from([VERSION]), Uint8Array.from([logn]), salt, nonce, aad, ciphertext);
46
+ return encodeBytes("ncryptsec", bytes);
47
+ }
48
+ /** Decrypt an `ncryptsec` bech32 string to a 32-byte secret key. */
49
+ function decrypt(ncryptsec, password) {
50
+ let prefix;
51
+ let b;
52
+ try {
53
+ const decoded = bech32.decode(ncryptsec, Bech32MaxSize);
54
+ prefix = decoded.prefix;
55
+ b = new Uint8Array(bech32.fromWords(decoded.words));
56
+ } catch (cause) {
57
+ throw new Nip49Error("invalid ncryptsec", { cause: cause instanceof Error ? cause : void 0 });
58
+ }
59
+ if (prefix !== "ncryptsec") throw new Nip49Error(`invalid prefix ${prefix}, expected 'ncryptsec'`);
60
+ if (b.length !== PAYLOAD_LEN) throw new Nip49Error("invalid ncryptsec length");
61
+ const version = b[0];
62
+ if (version !== VERSION) throw new Nip49Error(`invalid version ${version}, expected 0x02`);
63
+ const logn = b[1];
64
+ const salt = b.subarray(2, 18);
65
+ const nonce = b.subarray(18, 42);
66
+ const ksb = b[42];
67
+ const aad = Uint8Array.from([ksb]);
68
+ const ciphertext = b.subarray(43);
69
+ try {
70
+ const key = deriveKey(password, salt, logn);
71
+ return xchacha20poly1305(key, nonce, aad).decrypt(ciphertext);
72
+ } catch (cause) {
73
+ if (cause instanceof Nip49Error) throw cause;
74
+ throw new Nip49Error("failed to decrypt", { cause: cause instanceof Error ? cause : void 0 });
75
+ }
76
+ }
77
+ //#endregion
78
+ export { Nip49Error, decrypt, encrypt };
79
+
80
+ //# sourceMappingURL=nip49.mjs.map