nostr-tools 2.23.11 → 2.24.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 (141) hide show
  1. package/lib/cjs/abstract-pool.js +11 -21
  2. package/lib/cjs/abstract-pool.js.map +3 -3
  3. package/lib/cjs/abstract-relay.js +1 -1
  4. package/lib/cjs/abstract-relay.js.map +2 -2
  5. package/lib/cjs/core.js +17 -1
  6. package/lib/cjs/core.js.map +3 -3
  7. package/lib/cjs/filter.js.map +1 -1
  8. package/lib/cjs/index.js +124 -123
  9. package/lib/cjs/index.js.map +4 -4
  10. package/lib/cjs/kinds.js +17 -1
  11. package/lib/cjs/kinds.js.map +3 -3
  12. package/lib/cjs/nip04.js.map +1 -1
  13. package/lib/cjs/nip10.js +20 -5
  14. package/lib/cjs/nip10.js.map +3 -3
  15. package/lib/cjs/nip13.js.map +1 -1
  16. package/lib/cjs/nip17.js +30 -23
  17. package/lib/cjs/nip17.js.map +4 -4
  18. package/lib/cjs/nip18.js +21 -14
  19. package/lib/cjs/nip18.js.map +4 -4
  20. package/lib/cjs/nip19.js.map +1 -1
  21. package/lib/cjs/nip21.js.map +1 -1
  22. package/lib/cjs/nip22.js +22 -7
  23. package/lib/cjs/nip22.js.map +3 -3
  24. package/lib/cjs/nip25.js +21 -14
  25. package/lib/cjs/nip25.js.map +4 -4
  26. package/lib/cjs/nip27.js.map +1 -1
  27. package/lib/cjs/nip28.js +21 -14
  28. package/lib/cjs/nip28.js.map +4 -4
  29. package/lib/cjs/nip29.js.map +1 -1
  30. package/lib/cjs/nip30.js.map +2 -2
  31. package/lib/cjs/nip42.js.map +2 -2
  32. package/lib/cjs/nip44.js.map +1 -1
  33. package/lib/cjs/nip45.js +22 -9
  34. package/lib/cjs/nip45.js.map +4 -4
  35. package/lib/cjs/nip46.js +63 -63
  36. package/lib/cjs/nip46.js.map +4 -4
  37. package/lib/cjs/nip47.js +25 -18
  38. package/lib/cjs/nip47.js.map +4 -4
  39. package/lib/cjs/nip57.js +23 -16
  40. package/lib/cjs/nip57.js.map +4 -4
  41. package/lib/cjs/nip58.js.map +2 -2
  42. package/lib/cjs/nip59.js +16 -7
  43. package/lib/cjs/nip59.js.map +3 -3
  44. package/lib/cjs/nip75.js.map +1 -1
  45. package/lib/cjs/nip94.js.map +1 -1
  46. package/lib/cjs/nip98.js +23 -16
  47. package/lib/cjs/nip98.js.map +4 -4
  48. package/lib/cjs/nip99.js.map +2 -2
  49. package/lib/cjs/nipb0.js +38 -17
  50. package/lib/cjs/nipb0.js.map +4 -4
  51. package/lib/cjs/nipb7.js +13 -7
  52. package/lib/cjs/nipb7.js.map +2 -2
  53. package/lib/cjs/pool.js +54 -54
  54. package/lib/cjs/pool.js.map +4 -4
  55. package/lib/cjs/pure.js +21 -14
  56. package/lib/cjs/pure.js.map +4 -4
  57. package/lib/cjs/references.js.map +1 -1
  58. package/lib/cjs/relay.js +43 -36
  59. package/lib/cjs/relay.js.map +4 -4
  60. package/lib/cjs/signer.js +21 -14
  61. package/lib/cjs/signer.js.map +4 -4
  62. package/lib/cjs/utils.js +10 -0
  63. package/lib/cjs/utils.js.map +2 -2
  64. package/lib/cjs/wasm.js +16 -2
  65. package/lib/cjs/wasm.js.map +4 -4
  66. package/lib/esm/abstract-pool.js +11 -21
  67. package/lib/esm/abstract-pool.js.map +3 -3
  68. package/lib/esm/abstract-relay.js +1 -1
  69. package/lib/esm/abstract-relay.js.map +2 -2
  70. package/lib/esm/core.js +15 -1
  71. package/lib/esm/core.js.map +3 -3
  72. package/lib/esm/filter.js.map +1 -1
  73. package/lib/esm/index.js +74 -73
  74. package/lib/esm/index.js.map +4 -4
  75. package/lib/esm/kinds.js +17 -1
  76. package/lib/esm/kinds.js.map +3 -3
  77. package/lib/esm/nip04.js.map +1 -1
  78. package/lib/esm/nip10.js +18 -5
  79. package/lib/esm/nip10.js.map +3 -3
  80. package/lib/esm/nip13.js.map +1 -1
  81. package/lib/esm/nip17.js +15 -8
  82. package/lib/esm/nip17.js.map +4 -4
  83. package/lib/esm/nip18.js +15 -8
  84. package/lib/esm/nip18.js.map +4 -4
  85. package/lib/esm/nip19.js.map +1 -1
  86. package/lib/esm/nip21.js.map +1 -1
  87. package/lib/esm/nip22.js +20 -7
  88. package/lib/esm/nip22.js.map +3 -3
  89. package/lib/esm/nip25.js +15 -8
  90. package/lib/esm/nip25.js.map +4 -4
  91. package/lib/esm/nip27.js.map +1 -1
  92. package/lib/esm/nip28.js +15 -8
  93. package/lib/esm/nip28.js.map +4 -4
  94. package/lib/esm/nip29.js.map +1 -1
  95. package/lib/esm/nip30.js.map +2 -2
  96. package/lib/esm/nip42.js.map +2 -2
  97. package/lib/esm/nip44.js.map +1 -1
  98. package/lib/esm/nip45.js +23 -10
  99. package/lib/esm/nip45.js.map +4 -4
  100. package/lib/esm/nip46.js +47 -47
  101. package/lib/esm/nip46.js.map +4 -4
  102. package/lib/esm/nip47.js +15 -8
  103. package/lib/esm/nip47.js.map +4 -4
  104. package/lib/esm/nip57.js +17 -10
  105. package/lib/esm/nip57.js.map +4 -4
  106. package/lib/esm/nip58.js.map +2 -2
  107. package/lib/esm/nip59.js +10 -1
  108. package/lib/esm/nip59.js.map +3 -3
  109. package/lib/esm/nip75.js.map +1 -1
  110. package/lib/esm/nip94.js.map +1 -1
  111. package/lib/esm/nip98.js +15 -8
  112. package/lib/esm/nip98.js.map +4 -4
  113. package/lib/esm/nip99.js.map +2 -2
  114. package/lib/esm/nipb0.js +39 -18
  115. package/lib/esm/nipb0.js.map +4 -4
  116. package/lib/esm/nipb7.js +13 -7
  117. package/lib/esm/nipb7.js.map +2 -2
  118. package/lib/esm/pool.js +47 -47
  119. package/lib/esm/pool.js.map +4 -4
  120. package/lib/esm/pure.js +15 -8
  121. package/lib/esm/pure.js.map +4 -4
  122. package/lib/esm/references.js.map +1 -1
  123. package/lib/esm/relay.js +37 -30
  124. package/lib/esm/relay.js.map +4 -4
  125. package/lib/esm/signer.js +15 -8
  126. package/lib/esm/signer.js.map +4 -4
  127. package/lib/esm/utils.js +10 -0
  128. package/lib/esm/utils.js.map +2 -2
  129. package/lib/esm/wasm.js +17 -3
  130. package/lib/esm/wasm.js.map +4 -4
  131. package/lib/nostr.bundle.js +73 -70
  132. package/lib/nostr.bundle.js.map +4 -4
  133. package/lib/types/abstract-pool.d.ts +6 -4
  134. package/lib/types/abstract-relay.d.ts +2 -2
  135. package/lib/types/kinds.d.ts +2 -0
  136. package/lib/types/nipb0.d.ts +0 -1
  137. package/lib/types/nipb0.test.d.ts +1 -0
  138. package/lib/types/nipb7.d.ts +0 -1
  139. package/lib/types/utils.d.ts +4 -0
  140. package/package.json +1 -1
  141. package/lib/types/helpers.d.ts +0 -2
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../nip44.ts", "../../utils.ts"],
4
- "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, hexToBytes, randomBytes } from '@noble/hashes/utils.js'\nimport { base64 } from '@scure/base'\n\nimport { utf8Decoder, utf8Encoder } from './utils.ts'\n\nconst minPlaintextSize = 0x0001 // 1b msg => padded to 32b\nconst maxPlaintextSize = 0xffffffff // 4294967295 (2^32-1)\nconst extendedPrefixThreshold = 0x10000 // 65536: lengths below use 2-byte u16 prefix, at or above use 6-byte prefix\n\nexport function getConversationKey(privkeyA: Uint8Array, pubkeyB: string): Uint8Array {\n const sharedX = secp256k1.getSharedSecret(privkeyA, hexToBytes('02' + pubkeyB)).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 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\nfunction calcPaddedLen(len: number): number {\n if (!Number.isSafeInteger(len) || len < 1) throw new Error('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 Error('invalid plaintext size: must be between 1 and 65535 bytes')\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 Error('invalid plaintext size: must be between 65536 and 4294967295 bytes')\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 Error('invalid plaintext size: must be between 1 and 4294967295 bytes')\n const prefix =\n unpaddedLen >= extendedPrefixThreshold\n ? concatBytes(new Uint8Array([0, 0]), writeU32BE(unpaddedLen)) // 6 bytes\n : writeU16BE(unpaddedLen) // 2 bytes\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 // Extended format: 2 zero bytes + 4-byte u32 length\n unpaddedLen = dv.getUint32(2)\n if (unpaddedLen < extendedPrefixThreshold) throw new Error('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 Error('invalid padding')\n return utf8Decoder.decode(unpadded)\n}\n\nfunction hmacAad(key: Uint8Array, message: Uint8Array, aad: Uint8Array): Uint8Array {\n if (aad.length !== 32) throw new Error('AAD associated data must be 32 bytes')\n const combined = concatBytes(aad, message)\n return hmac(sha256, key, combined)\n}\n\n// metadata: always 65b (version: 1b, nonce: 32b, mac: 32b)\n// plaintext: 1b to 0xffffffff\n// padded plaintext (small, <65536): 32b to 0x10000, with 2b prefix -> 34b to 0x10000+2\n// padded plaintext (large, >=65536): 0x10000 to 0x100000000, with 6b prefix -> 0x10006 to 0x100000000+6\n// ciphertext: same as padded plaintext (chacha20 doesn't change length)\n// raw payload (small): 99 (65+34) to 65603 (65+0x10000+2)\n// raw payload (large): 65607 (65+0x10006) to 4294967367 (65+0x100000000+6)\nfunction decodePayload(payload: string): { nonce: Uint8Array; ciphertext: Uint8Array; mac: Uint8Array } {\n if (typeof payload !== 'string') throw new Error('payload must be a valid string')\n const plen = payload.length\n if (plen < 132) throw new Error('invalid payload length: ' + plen)\n if (payload[0] === '#') throw new Error('unknown encryption version')\n let data: Uint8Array\n try {\n data = base64.decode(payload)\n } catch (error) {\n throw new Error('invalid base64: ' + (error as any).message)\n }\n const dlen = data.length\n if (dlen < 99) throw new Error('invalid data length: ' + dlen)\n const vers = data[0]\n if (vers !== 2) throw new Error('unknown encryption version ' + vers)\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(plaintext: string, conversationKey: Uint8Array, nonce: Uint8Array = randomBytes(32)): 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\n/** Callers should validate payload size before calling to prevent DoS from oversized inputs. */\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 Error('invalid MAC')\n const padded = chacha20(chacha_key, chacha_nonce, ciphertext)\n return unpad(padded)\n}\n\nexport const v2 = {\n utils: {\n getConversationKey,\n calcPaddedLen,\n pad,\n unpad,\n },\n encrypt,\n decrypt,\n}\n", "import type { NostrEvent } from './core.ts'\n\nexport const utf8Decoder: TextDecoder = new TextDecoder('utf-8')\nexport const utf8Encoder: TextEncoder = new TextEncoder()\n\nexport { bytesToHex, hexToBytes } from '@noble/hashes/utils.js'\n\nexport function normalizeURL(url: string): string {\n try {\n if (url.indexOf('://') === -1) url = 'wss://' + url\n let p = new URL(url)\n if (p.protocol === 'http:') p.protocol = 'ws:'\n else if (p.protocol === 'https:') p.protocol = 'wss:'\n p.pathname = p.pathname.replace(/\\/+/g, '/')\n if (p.pathname.endsWith('/')) p.pathname = p.pathname.slice(0, -1)\n if ((p.port === '80' && p.protocol === 'ws:') || (p.port === '443' && p.protocol === 'wss:')) p.port = ''\n p.searchParams.sort()\n p.hash = ''\n return p.toString()\n } catch (e) {\n throw new Error(`Invalid URL: ${url}`)\n }\n}\n\nexport function insertEventIntoDescendingList(sortedArray: NostrEvent[], event: NostrEvent): NostrEvent[] {\n const [idx, found] = binarySearch(sortedArray, b => {\n if (event.id === b.id) return 0\n if (event.created_at === b.created_at) return -1\n return b.created_at - event.created_at\n })\n if (!found) {\n sortedArray.splice(idx, 0, event)\n }\n return sortedArray\n}\n\nexport function insertEventIntoAscendingList(sortedArray: NostrEvent[], event: NostrEvent): NostrEvent[] {\n const [idx, found] = binarySearch(sortedArray, b => {\n if (event.id === b.id) return 0\n if (event.created_at === b.created_at) return -1\n return event.created_at - b.created_at\n })\n if (!found) {\n sortedArray.splice(idx, 0, event)\n }\n return sortedArray\n}\n\nexport function binarySearch<T>(arr: T[], compare: (b: T) => number): [number, boolean] {\n let start = 0\n let end = arr.length - 1\n\n while (start <= end) {\n const mid = Math.floor((start + end) / 2)\n const cmp = compare(arr[mid])\n\n if (cmp === 0) {\n return [mid, true]\n }\n\n if (cmp < 0) {\n end = mid - 1\n } else {\n start = mid + 1\n }\n }\n\n return [start, false]\n}\n\nexport function mergeReverseSortedLists(list1: NostrEvent[], list2: NostrEvent[]): NostrEvent[] {\n const result: NostrEvent[] = new Array(list1.length + list2.length)\n result.length = 0\n let i1 = 0\n let i2 = 0\n let sameTimestampIds: string[] = []\n\n while (i1 < list1.length && i2 < list2.length) {\n let next: NostrEvent\n if (list1[i1]?.created_at > list2[i2]?.created_at) {\n next = list1[i1]\n i1++\n } else {\n next = list2[i2]\n i2++\n }\n\n if (result.length > 0 && result[result.length - 1].created_at === next.created_at) {\n if (sameTimestampIds.includes(next.id)) continue\n } else {\n sameTimestampIds.length = 0\n }\n\n result.push(next)\n sameTimestampIds.push(next.id)\n }\n\n while (i1 < list1.length) {\n const next = list1[i1]\n i1++\n\n if (result.length > 0 && result[result.length - 1].created_at === next.created_at) {\n if (sameTimestampIds.includes(next.id)) continue\n } else {\n sameTimestampIds.length = 0\n }\n result.push(next)\n sameTimestampIds.push(next.id)\n }\n\n while (i2 < list2.length) {\n const next = list2[i2]\n i2++\n\n if (result.length > 0 && result[result.length - 1].created_at === next.created_at) {\n if (sameTimestampIds.includes(next.id)) continue\n } else {\n sameTimestampIds.length = 0\n }\n result.push(next)\n sameTimestampIds.push(next.id)\n }\n\n return result\n}\n"],
4
+ "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, hexToBytes, randomBytes } from '@noble/hashes/utils.js'\nimport { base64 } from '@scure/base'\n\nimport { utf8Decoder, utf8Encoder } from './utils.ts'\n\nconst minPlaintextSize = 0x0001 // 1b msg => padded to 32b\nconst maxPlaintextSize = 0xffffffff // 4294967295 (2^32-1)\nconst extendedPrefixThreshold = 0x10000 // 65536: lengths below use 2-byte u16 prefix, at or above use 6-byte prefix\n\nexport function getConversationKey(privkeyA: Uint8Array, pubkeyB: string): Uint8Array {\n const sharedX = secp256k1.getSharedSecret(privkeyA, hexToBytes('02' + pubkeyB)).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 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\nfunction calcPaddedLen(len: number): number {\n if (!Number.isSafeInteger(len) || len < 1) throw new Error('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 Error('invalid plaintext size: must be between 1 and 65535 bytes')\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 Error('invalid plaintext size: must be between 65536 and 4294967295 bytes')\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 Error('invalid plaintext size: must be between 1 and 4294967295 bytes')\n const prefix =\n unpaddedLen >= extendedPrefixThreshold\n ? concatBytes(new Uint8Array([0, 0]), writeU32BE(unpaddedLen)) // 6 bytes\n : writeU16BE(unpaddedLen) // 2 bytes\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 // Extended format: 2 zero bytes + 4-byte u32 length\n unpaddedLen = dv.getUint32(2)\n if (unpaddedLen < extendedPrefixThreshold) throw new Error('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 Error('invalid padding')\n return utf8Decoder.decode(unpadded)\n}\n\nfunction hmacAad(key: Uint8Array, message: Uint8Array, aad: Uint8Array): Uint8Array {\n if (aad.length !== 32) throw new Error('AAD associated data must be 32 bytes')\n const combined = concatBytes(aad, message)\n return hmac(sha256, key, combined)\n}\n\n// metadata: always 65b (version: 1b, nonce: 32b, mac: 32b)\n// plaintext: 1b to 0xffffffff\n// padded plaintext (small, <65536): 32b to 0x10000, with 2b prefix -> 34b to 0x10000+2\n// padded plaintext (large, >=65536): 0x10000 to 0x100000000, with 6b prefix -> 0x10006 to 0x100000000+6\n// ciphertext: same as padded plaintext (chacha20 doesn't change length)\n// raw payload (small): 99 (65+34) to 65603 (65+0x10000+2)\n// raw payload (large): 65607 (65+0x10006) to 4294967367 (65+0x100000000+6)\nfunction decodePayload(payload: string): { nonce: Uint8Array; ciphertext: Uint8Array; mac: Uint8Array } {\n if (typeof payload !== 'string') throw new Error('payload must be a valid string')\n const plen = payload.length\n if (plen < 132) throw new Error('invalid payload length: ' + plen)\n if (payload[0] === '#') throw new Error('unknown encryption version')\n let data: Uint8Array\n try {\n data = base64.decode(payload)\n } catch (error) {\n throw new Error('invalid base64: ' + (error as any).message)\n }\n const dlen = data.length\n if (dlen < 99) throw new Error('invalid data length: ' + dlen)\n const vers = data[0]\n if (vers !== 2) throw new Error('unknown encryption version ' + vers)\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(plaintext: string, conversationKey: Uint8Array, nonce: Uint8Array = randomBytes(32)): 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\n/** Callers should validate payload size before calling to prevent DoS from oversized inputs. */\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 Error('invalid MAC')\n const padded = chacha20(chacha_key, chacha_nonce, ciphertext)\n return unpad(padded)\n}\n\nexport const v2 = {\n utils: {\n getConversationKey,\n calcPaddedLen,\n pad,\n unpad,\n },\n encrypt,\n decrypt,\n}\n", "import type { NostrEvent } from './core.ts'\n\nexport const utf8Decoder: TextDecoder = new TextDecoder('utf-8')\nexport const utf8Encoder: TextEncoder = new TextEncoder()\n\nexport { bytesToHex, hexToBytes } from '@noble/hashes/utils.js'\n\nexport function normalizeURL(url: string): string {\n try {\n if (url.indexOf('://') === -1) url = 'wss://' + url\n let p = new URL(url)\n if (p.protocol === 'http:') p.protocol = 'ws:'\n else if (p.protocol === 'https:') p.protocol = 'wss:'\n p.pathname = p.pathname.replace(/\\/+/g, '/')\n if (p.pathname.endsWith('/')) p.pathname = p.pathname.slice(0, -1)\n if ((p.port === '80' && p.protocol === 'ws:') || (p.port === '443' && p.protocol === 'wss:')) p.port = ''\n p.searchParams.sort()\n p.hash = ''\n return p.toString()\n } catch (e) {\n throw new Error(`Invalid URL: ${url}`)\n }\n}\n\nexport function insertEventIntoDescendingList(sortedArray: NostrEvent[], event: NostrEvent): NostrEvent[] {\n const [idx, found] = binarySearch(sortedArray, b => {\n if (event.id === b.id) return 0\n if (event.created_at === b.created_at) return -1\n return b.created_at - event.created_at\n })\n if (!found) {\n sortedArray.splice(idx, 0, event)\n }\n return sortedArray\n}\n\nexport function insertEventIntoAscendingList(sortedArray: NostrEvent[], event: NostrEvent): NostrEvent[] {\n const [idx, found] = binarySearch(sortedArray, b => {\n if (event.id === b.id) return 0\n if (event.created_at === b.created_at) return -1\n return event.created_at - b.created_at\n })\n if (!found) {\n sortedArray.splice(idx, 0, event)\n }\n return sortedArray\n}\n\nexport function binarySearch<T>(arr: T[], compare: (b: T) => number): [number, boolean] {\n let start = 0\n let end = arr.length - 1\n\n while (start <= end) {\n const mid = Math.floor((start + end) / 2)\n const cmp = compare(arr[mid])\n\n if (cmp === 0) {\n return [mid, true]\n }\n\n if (cmp < 0) {\n end = mid - 1\n } else {\n start = mid + 1\n }\n }\n\n return [start, false]\n}\n\nexport function mergeReverseSortedLists(list1: NostrEvent[], list2: NostrEvent[]): NostrEvent[] {\n const result: NostrEvent[] = new Array(list1.length + list2.length)\n result.length = 0\n let i1 = 0\n let i2 = 0\n let sameTimestampIds: string[] = []\n\n while (i1 < list1.length && i2 < list2.length) {\n let next: NostrEvent\n if (list1[i1]?.created_at > list2[i2]?.created_at) {\n next = list1[i1]\n i1++\n } else {\n next = list2[i2]\n i2++\n }\n\n if (result.length > 0 && result[result.length - 1].created_at === next.created_at) {\n if (sameTimestampIds.includes(next.id)) continue\n } else {\n sameTimestampIds.length = 0\n }\n\n result.push(next)\n sameTimestampIds.push(next.id)\n }\n\n while (i1 < list1.length) {\n const next = list1[i1]\n i1++\n\n if (result.length > 0 && result[result.length - 1].created_at === next.created_at) {\n if (sameTimestampIds.includes(next.id)) continue\n } else {\n sameTimestampIds.length = 0\n }\n result.push(next)\n sameTimestampIds.push(next.id)\n }\n\n while (i2 < list2.length) {\n const next = list2[i2]\n i2++\n\n if (result.length > 0 && result[result.length - 1].created_at === next.created_at) {\n if (sameTimestampIds.includes(next.id)) continue\n } else {\n sameTimestampIds.length = 0\n }\n result.push(next)\n sameTimestampIds.push(next.id)\n }\n\n return result\n}\n\n/**\n * Checks if a string is a 64-char lowercase hex string as most Nostr ids and pubkeys.\n */\nexport function isHex32(input: string): boolean {\n for (let i = 0; i < 64; i++) {\n let cc = input.charCodeAt(i)\n if (isNaN(cc) || cc < 48 || cc > 102 || (cc > 57 && cc < 97)) {\n return false\n }\n }\n return true\n}\n"],
5
5
  "mappings": "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",
6
6
  "names": ["hexToBytes", "hexToBytes"]
7
7
  }
package/lib/esm/nip45.js CHANGED
@@ -1,9 +1,25 @@
1
1
  // nip45.ts
2
2
  import { sha256 } from "@noble/hashes/sha2.js";
3
- import { bytesToHex } from "@noble/hashes/utils.js";
3
+ import { bytesToHex as bytesToHex2 } from "@noble/hashes/utils.js";
4
+
5
+ // utils.ts
6
+ import { bytesToHex, hexToBytes } from "@noble/hashes/utils.js";
7
+ var utf8Decoder = new TextDecoder("utf-8");
8
+ var utf8Encoder = new TextEncoder();
9
+ function isHex32(input) {
10
+ for (let i = 0; i < 64; i++) {
11
+ let cc = input.charCodeAt(i);
12
+ if (isNaN(cc) || cc < 48 || cc > 102 || cc > 57 && cc < 97) {
13
+ return false;
14
+ }
15
+ }
16
+ return true;
17
+ }
18
+
19
+ // nip45.ts
4
20
  var M = 256;
5
21
  var HLL_HEX_LENGTH = M * 2;
6
- var utf8Encoder = new TextEncoder();
22
+ var utf8Encoder2 = new TextEncoder();
7
23
  function getCountManyFilter(target, directive) {
8
24
  switch (directive) {
9
25
  case "reactions":
@@ -24,7 +40,7 @@ function newHll() {
24
40
  return new Uint8Array(M);
25
41
  }
26
42
  function hllDecode(hex) {
27
- if (hex.length !== HLL_HEX_LENGTH || !/^[0-9a-fA-F]+$/.test(hex))
43
+ if (hex.length !== HLL_HEX_LENGTH || !/^[0-9a-f]+$/.test(hex))
28
44
  return void 0;
29
45
  const registers = new Uint8Array(M);
30
46
  for (let i = 0; i < M; i++) {
@@ -43,12 +59,12 @@ function hllEncode(registers) {
43
59
  }
44
60
  function computeOffset(filterFirstTagValue) {
45
61
  let hex = filterFirstTagValue;
46
- if (!isHex64(hex)) {
62
+ if (!isHex32(hex)) {
47
63
  const parts = hex.split(":");
48
- if (parts.length === 3 && isHex64(parts[1])) {
64
+ if (parts.length === 3 && isHex32(parts[1])) {
49
65
  hex = parts[1];
50
66
  } else {
51
- hex = bytesToHex(sha256(utf8Encoder.encode(filterFirstTagValue)));
67
+ hex = bytesToHex2(sha256(utf8Encoder2.encode(filterFirstTagValue)));
52
68
  }
53
69
  }
54
70
  return parseInt(hex[32], 16) + 8;
@@ -66,7 +82,7 @@ function getFilterFirstTagValue(filter) {
66
82
  function feedPubkey(hll, pubkey, offset) {
67
83
  if (offset < 0 || offset > 24)
68
84
  throw new Error(`invalid offset ${offset}`);
69
- if (!isHex64(pubkey))
85
+ if (!isHex32(pubkey))
70
86
  throw new Error("pubkey must be 32-byte hex");
71
87
  if (hll.length === 0)
72
88
  hll = newHll();
@@ -110,9 +126,6 @@ function estimateCount(hll) {
110
126
  return Math.floor(linearCounting(M, v));
111
127
  return Math.floor(estimate);
112
128
  }
113
- function isHex64(value) {
114
- return /^[0-9a-fA-F]{64}$/.test(value);
115
- }
116
129
  function countLeadingZeroBitsAfterOffset(pubkey, offset) {
117
130
  let zeroBits = 0;
118
131
  for (let i = offset + 1; i < offset + 8; i++) {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
- "sources": ["../../nip45.ts"],
4
- "sourcesContent": ["import { sha256 } from '@noble/hashes/sha2.js'\nimport { bytesToHex } from '@noble/hashes/utils.js'\n\nimport type { Event } from './core.ts'\nimport type { Filter } from './filter.ts'\n\nconst M = 256\nconst HLL_HEX_LENGTH = M * 2\nconst utf8Encoder = new TextEncoder()\n\nexport type CountManyDirective = 'reactions' | 'reposts' | 'quotes' | 'replies' | 'comments' | 'followers'\n\nexport function getCountManyFilter(target: string, directive: CountManyDirective): Filter {\n switch (directive) {\n case 'reactions':\n return { '#e': [target], kinds: [7] }\n case 'reposts':\n return { '#e': [target], kinds: [6] }\n case 'quotes':\n return { '#q': [target], kinds: [1, 1111] }\n case 'replies':\n return { '#e': [target], kinds: [1] }\n case 'comments':\n return { '#E': [target], kinds: [1111] }\n case 'followers':\n return { '#p': [target], kinds: [3] }\n }\n}\n\nexport function newHll(): Uint8Array {\n return new Uint8Array(M)\n}\n\nexport function hllDecode(hex: string): Uint8Array | undefined {\n if (hex.length !== HLL_HEX_LENGTH || !/^[0-9a-fA-F]+$/.test(hex)) return undefined\n\n const registers = new Uint8Array(M)\n for (let i = 0; i < M; i++) {\n registers[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16)\n }\n return registers\n}\n\nexport function hllEncode(registers: Uint8Array): string {\n if (registers.length !== M) throw new Error(`invalid number of registers ${registers.length}`)\n\n let hex = ''\n for (let i = 0; i < M; i++) {\n hex += registers[i].toString(16).padStart(2, '0')\n }\n return hex\n}\n\nexport function computeOffset(filterFirstTagValue: string): number {\n let hex = filterFirstTagValue\n\n if (!isHex64(hex)) {\n const parts = hex.split(':')\n if (parts.length === 3 && isHex64(parts[1])) {\n hex = parts[1]\n } else {\n hex = bytesToHex(sha256(utf8Encoder.encode(filterFirstTagValue)))\n }\n }\n\n return parseInt(hex[32], 16) + 8\n}\n\nexport function getFilterFirstTagValue(filter: Filter): string | undefined {\n for (const key in filter) {\n if (key[0] !== '#') continue\n\n const values = filter[key as `#${string}`]\n if (Array.isArray(values) && typeof values[0] === 'string') return values[0]\n }\n\n return undefined\n}\n\nexport function feedPubkey(hll: Uint8Array, pubkey: string, offset: number): Uint8Array {\n if (offset < 0 || offset > 24) throw new Error(`invalid offset ${offset}`)\n if (!isHex64(pubkey)) throw new Error('pubkey must be 32-byte hex')\n\n if (hll.length === 0) hll = newHll()\n if (hll.length !== M) throw new Error(`invalid number of registers ${hll.length}`)\n\n const ri = parseInt(pubkey.slice(offset * 2, offset * 2 + 2), 16)\n const value = countLeadingZeroBitsAfterOffset(pubkey, offset) + 1\n if (value > hll[ri]) hll[ri] = value\n\n return hll\n}\n\nexport function feedEvent(hll: Uint8Array, event: Event, offset: number): Uint8Array {\n return feedPubkey(hll, event.pubkey, offset)\n}\n\nexport function mergeHll(target: Uint8Array, source: Uint8Array): Uint8Array {\n if (target.length === 0) target = newHll()\n if (target.length !== M) throw new Error(`invalid number of registers ${target.length}`)\n if (source.length !== M) throw new Error(`invalid number of registers ${source.length}`)\n\n for (let i = 0; i < M; i++) {\n if (source[i] > target[i]) target[i] = source[i]\n }\n\n return target\n}\n\nexport function estimateCount(hll: Uint8Array): number {\n if (hll.length === 0) return 0\n if (hll.length !== M) throw new Error(`invalid number of registers ${hll.length}`)\n\n const v = countZeros(hll)\n if (v !== 0) {\n const lc = linearCounting(M, v)\n if (lc <= 220) return Math.floor(lc)\n }\n\n const estimate = calculateEstimate(hll)\n if (estimate <= M * 3 && v !== 0) return Math.floor(linearCounting(M, v))\n\n return Math.floor(estimate)\n}\n\nfunction isHex64(value: string): boolean {\n return /^[0-9a-fA-F]{64}$/.test(value)\n}\n\nfunction countLeadingZeroBitsAfterOffset(pubkey: string, offset: number): number {\n let zeroBits = 0\n for (let i = offset + 1; i < offset + 8; i++) {\n const byte = parseInt(pubkey.slice(i * 2, i * 2 + 2), 16)\n if (byte === 0) {\n zeroBits += 8\n continue\n }\n\n let mask = 0x80\n while ((byte & mask) === 0) {\n zeroBits++\n mask >>= 1\n }\n break\n }\n return zeroBits\n}\n\nfunction countZeros(registers: Uint8Array): number {\n let count = 0\n for (let i = 0; i < M; i++) {\n if (registers[i] === 0) count++\n }\n return count\n}\n\nfunction linearCounting(m: number, v: number): number {\n return m * Math.log(m / v)\n}\n\nfunction calculateEstimate(registers: Uint8Array): number {\n let sum = 0\n for (let i = 0; i < M; i++) {\n sum += 1 / 2 ** registers[i]\n }\n return (0.7182725932495458 * M * M) / sum\n}\n"],
5
- "mappings": ";AAAA,SAAS,cAAc;AACvB,SAAS,kBAAkB;AAK3B,IAAM,IAAI;AACV,IAAM,iBAAiB,IAAI;AAC3B,IAAM,cAAc,IAAI,YAAY;AAI7B,SAAS,mBAAmB,QAAgB,WAAuC;AACxF,UAAQ,WAAW;AAAA,IACjB,KAAK;AACH,aAAO,EAAE,MAAM,CAAC,MAAM,GAAG,OAAO,CAAC,CAAC,EAAE;AAAA,IACtC,KAAK;AACH,aAAO,EAAE,MAAM,CAAC,MAAM,GAAG,OAAO,CAAC,CAAC,EAAE;AAAA,IACtC,KAAK;AACH,aAAO,EAAE,MAAM,CAAC,MAAM,GAAG,OAAO,CAAC,GAAG,IAAI,EAAE;AAAA,IAC5C,KAAK;AACH,aAAO,EAAE,MAAM,CAAC,MAAM,GAAG,OAAO,CAAC,CAAC,EAAE;AAAA,IACtC,KAAK;AACH,aAAO,EAAE,MAAM,CAAC,MAAM,GAAG,OAAO,CAAC,IAAI,EAAE;AAAA,IACzC,KAAK;AACH,aAAO,EAAE,MAAM,CAAC,MAAM,GAAG,OAAO,CAAC,CAAC,EAAE;AAAA,EACxC;AACF;AAEO,SAAS,SAAqB;AACnC,SAAO,IAAI,WAAW,CAAC;AACzB;AAEO,SAAS,UAAU,KAAqC;AAC7D,MAAI,IAAI,WAAW,kBAAkB,CAAC,iBAAiB,KAAK,GAAG;AAAG,WAAO;AAEzE,QAAM,YAAY,IAAI,WAAW,CAAC;AAClC,WAAS,IAAI,GAAG,IAAI,GAAG,KAAK;AAC1B,cAAU,KAAK,SAAS,IAAI,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,GAAG,EAAE;AAAA,EACzD;AACA,SAAO;AACT;AAEO,SAAS,UAAU,WAA+B;AACvD,MAAI,UAAU,WAAW;AAAG,UAAM,IAAI,MAAM,+BAA+B,UAAU,QAAQ;AAE7F,MAAI,MAAM;AACV,WAAS,IAAI,GAAG,IAAI,GAAG,KAAK;AAC1B,WAAO,UAAU,GAAG,SAAS,EAAE,EAAE,SAAS,GAAG,GAAG;AAAA,EAClD;AACA,SAAO;AACT;AAEO,SAAS,cAAc,qBAAqC;AACjE,MAAI,MAAM;AAEV,MAAI,CAAC,QAAQ,GAAG,GAAG;AACjB,UAAM,QAAQ,IAAI,MAAM,GAAG;AAC3B,QAAI,MAAM,WAAW,KAAK,QAAQ,MAAM,EAAE,GAAG;AAC3C,YAAM,MAAM;AAAA,IACd,OAAO;AACL,YAAM,WAAW,OAAO,YAAY,OAAO,mBAAmB,CAAC,CAAC;AAAA,IAClE;AAAA,EACF;AAEA,SAAO,SAAS,IAAI,KAAK,EAAE,IAAI;AACjC;AAEO,SAAS,uBAAuB,QAAoC;AACzE,aAAW,OAAO,QAAQ;AACxB,QAAI,IAAI,OAAO;AAAK;AAEpB,UAAM,SAAS,OAAO;AACtB,QAAI,MAAM,QAAQ,MAAM,KAAK,OAAO,OAAO,OAAO;AAAU,aAAO,OAAO;AAAA,EAC5E;AAEA,SAAO;AACT;AAEO,SAAS,WAAW,KAAiB,QAAgB,QAA4B;AACtF,MAAI,SAAS,KAAK,SAAS;AAAI,UAAM,IAAI,MAAM,kBAAkB,QAAQ;AACzE,MAAI,CAAC,QAAQ,MAAM;AAAG,UAAM,IAAI,MAAM,4BAA4B;AAElE,MAAI,IAAI,WAAW;AAAG,UAAM,OAAO;AACnC,MAAI,IAAI,WAAW;AAAG,UAAM,IAAI,MAAM,+BAA+B,IAAI,QAAQ;AAEjF,QAAM,KAAK,SAAS,OAAO,MAAM,SAAS,GAAG,SAAS,IAAI,CAAC,GAAG,EAAE;AAChE,QAAM,QAAQ,gCAAgC,QAAQ,MAAM,IAAI;AAChE,MAAI,QAAQ,IAAI;AAAK,QAAI,MAAM;AAE/B,SAAO;AACT;AAEO,SAAS,UAAU,KAAiB,OAAc,QAA4B;AACnF,SAAO,WAAW,KAAK,MAAM,QAAQ,MAAM;AAC7C;AAEO,SAAS,SAAS,QAAoB,QAAgC;AAC3E,MAAI,OAAO,WAAW;AAAG,aAAS,OAAO;AACzC,MAAI,OAAO,WAAW;AAAG,UAAM,IAAI,MAAM,+BAA+B,OAAO,QAAQ;AACvF,MAAI,OAAO,WAAW;AAAG,UAAM,IAAI,MAAM,+BAA+B,OAAO,QAAQ;AAEvF,WAAS,IAAI,GAAG,IAAI,GAAG,KAAK;AAC1B,QAAI,OAAO,KAAK,OAAO;AAAI,aAAO,KAAK,OAAO;AAAA,EAChD;AAEA,SAAO;AACT;AAEO,SAAS,cAAc,KAAyB;AACrD,MAAI,IAAI,WAAW;AAAG,WAAO;AAC7B,MAAI,IAAI,WAAW;AAAG,UAAM,IAAI,MAAM,+BAA+B,IAAI,QAAQ;AAEjF,QAAM,IAAI,WAAW,GAAG;AACxB,MAAI,MAAM,GAAG;AACX,UAAM,KAAK,eAAe,GAAG,CAAC;AAC9B,QAAI,MAAM;AAAK,aAAO,KAAK,MAAM,EAAE;AAAA,EACrC;AAEA,QAAM,WAAW,kBAAkB,GAAG;AACtC,MAAI,YAAY,IAAI,KAAK,MAAM;AAAG,WAAO,KAAK,MAAM,eAAe,GAAG,CAAC,CAAC;AAExE,SAAO,KAAK,MAAM,QAAQ;AAC5B;AAEA,SAAS,QAAQ,OAAwB;AACvC,SAAO,oBAAoB,KAAK,KAAK;AACvC;AAEA,SAAS,gCAAgC,QAAgB,QAAwB;AAC/E,MAAI,WAAW;AACf,WAAS,IAAI,SAAS,GAAG,IAAI,SAAS,GAAG,KAAK;AAC5C,UAAM,OAAO,SAAS,OAAO,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,GAAG,EAAE;AACxD,QAAI,SAAS,GAAG;AACd,kBAAY;AACZ;AAAA,IACF;AAEA,QAAI,OAAO;AACX,YAAQ,OAAO,UAAU,GAAG;AAC1B;AACA,eAAS;AAAA,IACX;AACA;AAAA,EACF;AACA,SAAO;AACT;AAEA,SAAS,WAAW,WAA+B;AACjD,MAAI,QAAQ;AACZ,WAAS,IAAI,GAAG,IAAI,GAAG,KAAK;AAC1B,QAAI,UAAU,OAAO;AAAG;AAAA,EAC1B;AACA,SAAO;AACT;AAEA,SAAS,eAAe,GAAW,GAAmB;AACpD,SAAO,IAAI,KAAK,IAAI,IAAI,CAAC;AAC3B;AAEA,SAAS,kBAAkB,WAA+B;AACxD,MAAI,MAAM;AACV,WAAS,IAAI,GAAG,IAAI,GAAG,KAAK;AAC1B,WAAO,IAAI,KAAK,UAAU;AAAA,EAC5B;AACA,SAAQ,qBAAqB,IAAI,IAAK;AACxC;",
6
- "names": []
3
+ "sources": ["../../nip45.ts", "../../utils.ts"],
4
+ "sourcesContent": ["import { sha256 } from '@noble/hashes/sha2.js'\nimport { bytesToHex } from '@noble/hashes/utils.js'\n\nimport type { Event } from './core.ts'\nimport type { Filter } from './filter.ts'\nimport { isHex32 } from './utils.ts'\n\nconst M = 256\nconst HLL_HEX_LENGTH = M * 2\nconst utf8Encoder = new TextEncoder()\n\nexport type CountManyDirective = 'reactions' | 'reposts' | 'quotes' | 'replies' | 'comments' | 'followers'\n\nexport function getCountManyFilter(target: string, directive: CountManyDirective): Filter {\n switch (directive) {\n case 'reactions':\n return { '#e': [target], kinds: [7] }\n case 'reposts':\n return { '#e': [target], kinds: [6] }\n case 'quotes':\n return { '#q': [target], kinds: [1, 1111] }\n case 'replies':\n return { '#e': [target], kinds: [1] }\n case 'comments':\n return { '#E': [target], kinds: [1111] }\n case 'followers':\n return { '#p': [target], kinds: [3] }\n }\n}\n\nexport function newHll(): Uint8Array {\n return new Uint8Array(M)\n}\n\nexport function hllDecode(hex: string): Uint8Array | undefined {\n if (hex.length !== HLL_HEX_LENGTH || !/^[0-9a-f]+$/.test(hex)) return undefined\n\n const registers = new Uint8Array(M)\n for (let i = 0; i < M; i++) {\n registers[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16)\n }\n return registers\n}\n\nexport function hllEncode(registers: Uint8Array): string {\n if (registers.length !== M) throw new Error(`invalid number of registers ${registers.length}`)\n\n let hex = ''\n for (let i = 0; i < M; i++) {\n hex += registers[i].toString(16).padStart(2, '0')\n }\n return hex\n}\n\nexport function computeOffset(filterFirstTagValue: string): number {\n let hex = filterFirstTagValue\n\n if (!isHex32(hex)) {\n const parts = hex.split(':')\n if (parts.length === 3 && isHex32(parts[1])) {\n hex = parts[1]\n } else {\n hex = bytesToHex(sha256(utf8Encoder.encode(filterFirstTagValue)))\n }\n }\n\n return parseInt(hex[32], 16) + 8\n}\n\nexport function getFilterFirstTagValue(filter: Filter): string | undefined {\n for (const key in filter) {\n if (key[0] !== '#') continue\n\n const values = filter[key as `#${string}`]\n if (Array.isArray(values) && typeof values[0] === 'string') return values[0]\n }\n\n return undefined\n}\n\nexport function feedPubkey(hll: Uint8Array, pubkey: string, offset: number): Uint8Array {\n if (offset < 0 || offset > 24) throw new Error(`invalid offset ${offset}`)\n if (!isHex32(pubkey)) throw new Error('pubkey must be 32-byte hex')\n\n if (hll.length === 0) hll = newHll()\n if (hll.length !== M) throw new Error(`invalid number of registers ${hll.length}`)\n\n const ri = parseInt(pubkey.slice(offset * 2, offset * 2 + 2), 16)\n const value = countLeadingZeroBitsAfterOffset(pubkey, offset) + 1\n if (value > hll[ri]) hll[ri] = value\n\n return hll\n}\n\nexport function feedEvent(hll: Uint8Array, event: Event, offset: number): Uint8Array {\n return feedPubkey(hll, event.pubkey, offset)\n}\n\nexport function mergeHll(target: Uint8Array, source: Uint8Array): Uint8Array {\n if (target.length === 0) target = newHll()\n if (target.length !== M) throw new Error(`invalid number of registers ${target.length}`)\n if (source.length !== M) throw new Error(`invalid number of registers ${source.length}`)\n\n for (let i = 0; i < M; i++) {\n if (source[i] > target[i]) target[i] = source[i]\n }\n\n return target\n}\n\nexport function estimateCount(hll: Uint8Array): number {\n if (hll.length === 0) return 0\n if (hll.length !== M) throw new Error(`invalid number of registers ${hll.length}`)\n\n const v = countZeros(hll)\n if (v !== 0) {\n const lc = linearCounting(M, v)\n if (lc <= 220) return Math.floor(lc)\n }\n\n const estimate = calculateEstimate(hll)\n if (estimate <= M * 3 && v !== 0) return Math.floor(linearCounting(M, v))\n\n return Math.floor(estimate)\n}\n\nfunction countLeadingZeroBitsAfterOffset(pubkey: string, offset: number): number {\n let zeroBits = 0\n for (let i = offset + 1; i < offset + 8; i++) {\n const byte = parseInt(pubkey.slice(i * 2, i * 2 + 2), 16)\n if (byte === 0) {\n zeroBits += 8\n continue\n }\n\n let mask = 0x80\n while ((byte & mask) === 0) {\n zeroBits++\n mask >>= 1\n }\n break\n }\n return zeroBits\n}\n\nfunction countZeros(registers: Uint8Array): number {\n let count = 0\n for (let i = 0; i < M; i++) {\n if (registers[i] === 0) count++\n }\n return count\n}\n\nfunction linearCounting(m: number, v: number): number {\n return m * Math.log(m / v)\n}\n\nfunction calculateEstimate(registers: Uint8Array): number {\n let sum = 0\n for (let i = 0; i < M; i++) {\n sum += 1 / 2 ** registers[i]\n }\n return (0.7182725932495458 * M * M) / sum\n}\n", "import type { NostrEvent } from './core.ts'\n\nexport const utf8Decoder: TextDecoder = new TextDecoder('utf-8')\nexport const utf8Encoder: TextEncoder = new TextEncoder()\n\nexport { bytesToHex, hexToBytes } from '@noble/hashes/utils.js'\n\nexport function normalizeURL(url: string): string {\n try {\n if (url.indexOf('://') === -1) url = 'wss://' + url\n let p = new URL(url)\n if (p.protocol === 'http:') p.protocol = 'ws:'\n else if (p.protocol === 'https:') p.protocol = 'wss:'\n p.pathname = p.pathname.replace(/\\/+/g, '/')\n if (p.pathname.endsWith('/')) p.pathname = p.pathname.slice(0, -1)\n if ((p.port === '80' && p.protocol === 'ws:') || (p.port === '443' && p.protocol === 'wss:')) p.port = ''\n p.searchParams.sort()\n p.hash = ''\n return p.toString()\n } catch (e) {\n throw new Error(`Invalid URL: ${url}`)\n }\n}\n\nexport function insertEventIntoDescendingList(sortedArray: NostrEvent[], event: NostrEvent): NostrEvent[] {\n const [idx, found] = binarySearch(sortedArray, b => {\n if (event.id === b.id) return 0\n if (event.created_at === b.created_at) return -1\n return b.created_at - event.created_at\n })\n if (!found) {\n sortedArray.splice(idx, 0, event)\n }\n return sortedArray\n}\n\nexport function insertEventIntoAscendingList(sortedArray: NostrEvent[], event: NostrEvent): NostrEvent[] {\n const [idx, found] = binarySearch(sortedArray, b => {\n if (event.id === b.id) return 0\n if (event.created_at === b.created_at) return -1\n return event.created_at - b.created_at\n })\n if (!found) {\n sortedArray.splice(idx, 0, event)\n }\n return sortedArray\n}\n\nexport function binarySearch<T>(arr: T[], compare: (b: T) => number): [number, boolean] {\n let start = 0\n let end = arr.length - 1\n\n while (start <= end) {\n const mid = Math.floor((start + end) / 2)\n const cmp = compare(arr[mid])\n\n if (cmp === 0) {\n return [mid, true]\n }\n\n if (cmp < 0) {\n end = mid - 1\n } else {\n start = mid + 1\n }\n }\n\n return [start, false]\n}\n\nexport function mergeReverseSortedLists(list1: NostrEvent[], list2: NostrEvent[]): NostrEvent[] {\n const result: NostrEvent[] = new Array(list1.length + list2.length)\n result.length = 0\n let i1 = 0\n let i2 = 0\n let sameTimestampIds: string[] = []\n\n while (i1 < list1.length && i2 < list2.length) {\n let next: NostrEvent\n if (list1[i1]?.created_at > list2[i2]?.created_at) {\n next = list1[i1]\n i1++\n } else {\n next = list2[i2]\n i2++\n }\n\n if (result.length > 0 && result[result.length - 1].created_at === next.created_at) {\n if (sameTimestampIds.includes(next.id)) continue\n } else {\n sameTimestampIds.length = 0\n }\n\n result.push(next)\n sameTimestampIds.push(next.id)\n }\n\n while (i1 < list1.length) {\n const next = list1[i1]\n i1++\n\n if (result.length > 0 && result[result.length - 1].created_at === next.created_at) {\n if (sameTimestampIds.includes(next.id)) continue\n } else {\n sameTimestampIds.length = 0\n }\n result.push(next)\n sameTimestampIds.push(next.id)\n }\n\n while (i2 < list2.length) {\n const next = list2[i2]\n i2++\n\n if (result.length > 0 && result[result.length - 1].created_at === next.created_at) {\n if (sameTimestampIds.includes(next.id)) continue\n } else {\n sameTimestampIds.length = 0\n }\n result.push(next)\n sameTimestampIds.push(next.id)\n }\n\n return result\n}\n\n/**\n * Checks if a string is a 64-char lowercase hex string as most Nostr ids and pubkeys.\n */\nexport function isHex32(input: string): boolean {\n for (let i = 0; i < 64; i++) {\n let cc = input.charCodeAt(i)\n if (isNaN(cc) || cc < 48 || cc > 102 || (cc > 57 && cc < 97)) {\n return false\n }\n }\n return true\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["bytesToHex", "utf8Encoder", "bytesToHex"]
7
7
  }
package/lib/esm/nip46.js CHANGED
@@ -2,6 +2,41 @@
2
2
  import { schnorr } from "@noble/curves/secp256k1.js";
3
3
  import { bytesToHex as bytesToHex2, hexToBytes as hexToBytes2 } from "@noble/hashes/utils.js";
4
4
 
5
+ // utils.ts
6
+ import { bytesToHex, hexToBytes } from "@noble/hashes/utils.js";
7
+ var utf8Decoder = new TextDecoder("utf-8");
8
+ var utf8Encoder = new TextEncoder();
9
+ function normalizeURL(url) {
10
+ try {
11
+ if (url.indexOf("://") === -1)
12
+ url = "wss://" + url;
13
+ let p = new URL(url);
14
+ if (p.protocol === "http:")
15
+ p.protocol = "ws:";
16
+ else if (p.protocol === "https:")
17
+ p.protocol = "wss:";
18
+ p.pathname = p.pathname.replace(/\/+/g, "/");
19
+ if (p.pathname.endsWith("/"))
20
+ p.pathname = p.pathname.slice(0, -1);
21
+ if (p.port === "80" && p.protocol === "ws:" || p.port === "443" && p.protocol === "wss:")
22
+ p.port = "";
23
+ p.searchParams.sort();
24
+ p.hash = "";
25
+ return p.toString();
26
+ } catch (e) {
27
+ throw new Error(`Invalid URL: ${url}`);
28
+ }
29
+ }
30
+ function isHex32(input) {
31
+ for (let i2 = 0; i2 < 64; i2++) {
32
+ let cc = input.charCodeAt(i2);
33
+ if (isNaN(cc) || cc < 48 || cc > 102 || cc > 57 && cc < 97) {
34
+ return false;
35
+ }
36
+ }
37
+ return true;
38
+ }
39
+
5
40
  // core.ts
6
41
  var verifiedSymbol = Symbol("verified");
7
42
  var isRecord = (obj) => obj instanceof Object;
@@ -16,7 +51,7 @@ function validateEvent(event) {
16
51
  return false;
17
52
  if (typeof event.pubkey !== "string")
18
53
  return false;
19
- if (!event.pubkey.match(/^[a-f0-9]{64}$/))
54
+ if (!isHex32(event.pubkey))
20
55
  return false;
21
56
  if (!Array.isArray(event.tags))
22
57
  return false;
@@ -34,34 +69,6 @@ function validateEvent(event) {
34
69
 
35
70
  // pure.ts
36
71
  import { sha256 } from "@noble/hashes/sha2.js";
37
-
38
- // utils.ts
39
- import { bytesToHex, hexToBytes } from "@noble/hashes/utils.js";
40
- var utf8Decoder = new TextDecoder("utf-8");
41
- var utf8Encoder = new TextEncoder();
42
- function normalizeURL(url) {
43
- try {
44
- if (url.indexOf("://") === -1)
45
- url = "wss://" + url;
46
- let p = new URL(url);
47
- if (p.protocol === "http:")
48
- p.protocol = "ws:";
49
- else if (p.protocol === "https:")
50
- p.protocol = "wss:";
51
- p.pathname = p.pathname.replace(/\/+/g, "/");
52
- if (p.pathname.endsWith("/"))
53
- p.pathname = p.pathname.slice(0, -1);
54
- if (p.port === "80" && p.protocol === "ws:" || p.port === "443" && p.protocol === "wss:")
55
- p.port = "";
56
- p.searchParams.sort();
57
- p.hash = "";
58
- return p.toString();
59
- } catch (e) {
60
- throw new Error(`Invalid URL: ${url}`);
61
- }
62
- }
63
-
64
- // pure.ts
65
72
  var JS = class {
66
73
  generateSecretKey() {
67
74
  return schnorr.utils.randomSecretKey();
@@ -689,7 +696,7 @@ var AbstractRelay = class {
689
696
  case "EVENT": {
690
697
  const so = this.openSubs.get(data[1]);
691
698
  const event = data[2];
692
- if (this.verifyEvent(event) && matchFilters(so.filters, event)) {
699
+ if (matchFilters(so.filters, event) && this.verifyEvent(event, this.url)) {
693
700
  so.onevent(event);
694
701
  } else {
695
702
  so.oninvalidevent?.(event);
@@ -845,12 +852,6 @@ var Subscription = class {
845
852
  }
846
853
  };
847
854
 
848
- // helpers.ts
849
- var alwaysTrue = (t) => {
850
- t[verifiedSymbol] = true;
851
- return true;
852
- };
853
-
854
855
  // nip45.ts
855
856
  import { sha256 as sha2563 } from "@noble/hashes/sha2.js";
856
857
  import { bytesToHex as bytesToHex3 } from "@noble/hashes/utils.js";
@@ -877,7 +878,7 @@ function newHll() {
877
878
  return new Uint8Array(M);
878
879
  }
879
880
  function hllDecode(hex) {
880
- if (hex.length !== HLL_HEX_LENGTH || !/^[0-9a-fA-F]+$/.test(hex))
881
+ if (hex.length !== HLL_HEX_LENGTH || !/^[0-9a-f]+$/.test(hex))
881
882
  return void 0;
882
883
  const registers = new Uint8Array(M);
883
884
  for (let i2 = 0; i2 < M; i2++) {
@@ -918,7 +919,6 @@ var AbstractSimplePool = class {
918
919
  enableReconnect;
919
920
  idleTimeout = 2e4;
920
921
  automaticallyAuth;
921
- trustedRelayURLs = /* @__PURE__ */ new Set();
922
922
  onRelayConnectionFailure;
923
923
  onRelayConnectionSuccess;
924
924
  allowConnectingToRelay;
@@ -942,7 +942,7 @@ var AbstractSimplePool = class {
942
942
  let relay = this.relays.get(url);
943
943
  if (!relay) {
944
944
  relay = new AbstractRelay(url, {
945
- verifyEvent: this.trustedRelayURLs.has(url) ? alwaysTrue : this.verifyEvent,
945
+ verifyEvent: this.verifyEvent,
946
946
  websocketImplementation: this._WebSocket,
947
947
  enablePing: this.enablePing,
948
948
  enableReconnect: this.enableReconnect,
@@ -1026,11 +1026,11 @@ var AbstractSimplePool = class {
1026
1026
  }
1027
1027
  };
1028
1028
  const closesReceived = [];
1029
- let handleClose = (i2, reason) => {
1029
+ let handleClose = (i2, url, reason) => {
1030
1030
  if (closesReceived[i2])
1031
1031
  return;
1032
1032
  handleEose(i2);
1033
- closesReceived[i2] = reason;
1033
+ closesReceived[i2] = { url, reason };
1034
1034
  if (closesReceived.filter((a) => a).length === groupedRequests.length) {
1035
1035
  params.onclose?.(closesReceived);
1036
1036
  handleClose = () => {
@@ -1048,7 +1048,7 @@ var AbstractSimplePool = class {
1048
1048
  const allOpened = Promise.all(
1049
1049
  groupedRequests.map(async ({ url, filters }, i2) => {
1050
1050
  if (this.allowConnectingToRelay?.(url, ["read", filters]) === false) {
1051
- handleClose(i2, "connection skipped by allowConnectingToRelay");
1051
+ handleClose(i2, url, "connection skipped by allowConnectingToRelay");
1052
1052
  return;
1053
1053
  }
1054
1054
  let relay;
@@ -1059,7 +1059,7 @@ var AbstractSimplePool = class {
1059
1059
  });
1060
1060
  } catch (err) {
1061
1061
  this.onRelayConnectionFailure?.(url);
1062
- handleClose(i2, err?.message || String(err));
1062
+ handleClose(i2, url, err?.message || String(err));
1063
1063
  return;
1064
1064
  }
1065
1065
  this.onRelayConnectionSuccess?.(url);
@@ -1073,17 +1073,17 @@ var AbstractSimplePool = class {
1073
1073
  ...params,
1074
1074
  oneose: () => handleEose(i2),
1075
1075
  onclose: (reason2) => {
1076
- handleClose(i2, reason2);
1076
+ handleClose(i2, url, reason2);
1077
1077
  },
1078
1078
  alreadyHaveEvent: localAlreadyHaveEventHandler,
1079
1079
  eoseTimeout: params.maxWait,
1080
1080
  abort: params.abort
1081
1081
  });
1082
1082
  }).catch((err) => {
1083
- handleClose(i2, `auth was required and attempted, but failed with: ${err}`);
1083
+ handleClose(i2, url, `auth was required and attempted, but failed with: ${err}`);
1084
1084
  });
1085
1085
  } else {
1086
- handleClose(i2, reason);
1086
+ handleClose(i2, url, reason);
1087
1087
  }
1088
1088
  },
1089
1089
  alreadyHaveEvent: localAlreadyHaveEventHandler,
@@ -1111,7 +1111,7 @@ var AbstractSimplePool = class {
1111
1111
  if (subcloser)
1112
1112
  subcloser.close(reason);
1113
1113
  else
1114
- params.onclose?.(relays.map((_) => reason));
1114
+ params.onclose?.(relays.map((url) => ({ url, reason })));
1115
1115
  }
1116
1116
  });
1117
1117
  return subcloser;