libp2r2p 0.8.0 → 0.10.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 (49) hide show
  1. package/README.md +72 -45
  2. package/base16/index.js +6 -3
  3. package/base36/index.js +12 -8
  4. package/base62/index.js +15 -11
  5. package/base64/index.js +18 -2
  6. package/base93/index.js +19 -7
  7. package/content-key/event/index.js +40 -12
  8. package/double-dh/index.js +21 -6
  9. package/ecdh/index.js +12 -1
  10. package/error/index.js +32 -0
  11. package/event/helpers/serialize.js +31 -0
  12. package/event/index.js +116 -0
  13. package/idb/index.js +5 -3
  14. package/idb-queue/index.js +21 -20
  15. package/index.js +10 -1
  16. package/key/index.js +31 -18
  17. package/kind/index.js +244 -0
  18. package/nip04/index.js +47 -0
  19. package/nip05/index.js +61 -0
  20. package/nip19/index.js +176 -43
  21. package/nip44/helpers.js +95 -0
  22. package/nip44/index.js +14 -0
  23. package/nip44-v3/index.js +35 -16
  24. package/nip46/helpers/frame.js +6 -6
  25. package/nip46/helpers/url.js +8 -6
  26. package/nip46/services/bunker-signer.js +7 -6
  27. package/nip46/services/client.js +8 -7
  28. package/nip46/services/server-session.js +8 -7
  29. package/nip46/services/transport.js +9 -8
  30. package/nip96/index.js +285 -0
  31. package/nip98/index.js +56 -0
  32. package/nwt/index.js +241 -0
  33. package/package.json +22 -5
  34. package/private-channel/helpers/chunks.js +7 -6
  35. package/private-channel/helpers/event.js +5 -4
  36. package/private-channel/index.js +63 -61
  37. package/private-channel/services/received-chunks.js +4 -3
  38. package/private-message/index.js +32 -31
  39. package/private-messenger/index.js +23 -22
  40. package/private-messenger/recovery/index.js +15 -14
  41. package/private-messenger/services/channel-state.js +2 -1
  42. package/relay/helpers/hll.js +3 -1
  43. package/relay/services/query.js +3 -3
  44. package/relay/services/relay-connection.js +222 -121
  45. package/relay/services/relay-pool.js +13 -10
  46. package/temporary-storage/index.js +3 -1
  47. package/url/index.js +131 -0
  48. package/web-storage-queue/index.js +8 -6
  49. package/i18n/index.js +0 -235
@@ -1,7 +1,8 @@
1
- import { generateSecretKey, getPublicKey } from 'nostr-tools'
1
+ import { generateSecretKey, getPublicKey } from '../../key/index.js'
2
2
  import { bytesToBase64, base64ToBytes } from '../../base64/index.js'
3
3
  import { bytesToHex } from '../../base16/index.js'
4
- import { verifyIykcProof } from '../../content-key/event/index.js'
4
+ import { isValidIykcProof } from '../../content-key/event/index.js'
5
+ import { ValidationError } from '../../error/index.js'
5
6
  import * as nip44v3 from '../../nip44-v3/index.js'
6
7
  import { JSONL_CHUNK_BYTES } from './chunk-size.js'
7
8
  import { ROUTER_KIND } from '../constants/index.js'
@@ -28,7 +29,7 @@ function rowTempKey (id, index) {
28
29
 
29
30
  function normalizeContentKey ({ receiverPubkey, iykcPubkey = '', iykcProof = '' } = {}) {
30
31
  if (!iykcPubkey) return { iykcPubkey: '', iykcProof: '' }
31
- if (!verifyIykcProof({ receiverPubkey, iykcPubkey, iykcProof })) throw new Error('INVALID_IYKC_PROOF')
32
+ if (!isValidIykcProof({ receiverPubkey, iykcPubkey, iykcProof })) throw new ValidationError('INVALID_IYKC_PROOF')
32
33
  return { iykcPubkey, iykcProof }
33
34
  }
34
35
 
@@ -157,7 +158,7 @@ function setPreparedRow (id, index, row, temporaryStorage) {
157
158
 
158
159
  function readPreparedRow (preparedRows, index) {
159
160
  const row = storageFor(preparedRows.temporaryStorage).getItem(rowTempKey(preparedRows.id, index))
160
- if (typeof row !== 'string') throw new Error('MISSING_PREPARED_ROW')
161
+ if (typeof row !== 'string') throw new ValidationError('MISSING_PREPARED_ROW')
161
162
  return row
162
163
  }
163
164
 
@@ -187,7 +188,7 @@ async function prepareEnvelopeRowsOnce ({ id, senderSigner, receivers, receiverC
187
188
  ciphertext = encrypted[0]
188
189
  const nextContentPubkey = encrypted[1] || ''
189
190
  if (foundOwnContentPubkey && nextContentPubkey !== usedOwnContentPubkey) {
190
- throw new Error('INCONSISTENT_IMKC_PUBKEY')
191
+ throw new ValidationError('INCONSISTENT_IMKC_PUBKEY')
191
192
  }
192
193
  foundOwnContentPubkey = true
193
194
  if (nextContentPubkey) usedOwnContentPubkey = nextContentPubkey
@@ -240,7 +241,7 @@ export function preparedRowIndexesForReceivers (preparedRows, receivers) {
240
241
  for (const receiver of receivers || []) {
241
242
  const pubkey = receiverRecord(receiver, {}).receiverPubkey
242
243
  const index = preparedRows?.receiverRowIndexesByPubkey?.[pubkey]
243
- if (!pubkey || index === undefined) throw new Error('MISSING_PREPARED_RECEIVER')
244
+ if (!pubkey || index === undefined) throw new ValidationError('MISSING_PREPARED_RECEIVER')
244
245
  if (seen.has(index)) continue
245
246
  seen.add(index)
246
247
  indexes.push(index)
@@ -1,4 +1,5 @@
1
1
  import { NYM_CARRIER_KIND, ROUTER_KIND } from '../constants/index.js'
2
+ import { ValidationError } from '../../error/index.js'
2
3
 
3
4
  const encoder = new TextEncoder()
4
5
 
@@ -16,7 +17,7 @@ export function readReceiverTag (event) {
16
17
 
17
18
  export function readSenderTag (event) {
18
19
  const senderPubkey = event.tags?.find(t => t[0] === 'f')?.[1]
19
- if (!senderPubkey) throw new Error('MISSING_SENDER_TAG')
20
+ if (!senderPubkey) throw new ValidationError('MISSING_SENDER_TAG')
20
21
  return senderPubkey
21
22
  }
22
23
 
@@ -41,7 +42,7 @@ export function readChunkTag (event) {
41
42
  const index = Number(tag?.[1])
42
43
  const total = Number(tag?.[2])
43
44
  if (!Number.isInteger(index) || !Number.isInteger(total) || index < 0 || total < 1) {
44
- throw new Error('INVALID_CHUNK_TAG')
45
+ throw new ValidationError('INVALID_CHUNK_TAG')
45
46
  }
46
47
  return { index, total }
47
48
  }
@@ -49,7 +50,7 @@ export function readChunkTag (event) {
49
50
  export function makeRouterEvent ({ pubkey, senderPubkey, imkcPubkey, imkcProof, receiverPubkey, chunkIndex, chunkTotal, content }) {
50
51
  const tags = [['f', senderPubkey]]
51
52
  if (imkcPubkey) {
52
- if (!imkcProof) throw new Error('INVALID_IMKC_PROOF')
53
+ if (!imkcProof) throw new ValidationError('INVALID_IMKC_PROOF')
53
54
  tags.push(['imkc', imkcPubkey, imkcProof])
54
55
  }
55
56
  tags.push(['c', String(chunkIndex), String(chunkTotal)])
@@ -58,7 +59,7 @@ export function makeRouterEvent ({ pubkey, senderPubkey, imkcPubkey, imkcProof,
58
59
  }
59
60
 
60
61
  export function makeNymCarrierEvent ({ innerId, chunkIndex, chunkTotal, content, createdAt = nowSeconds() }) {
61
- if (!innerId) throw new Error('INNER_EVENT_ID_REQUIRED')
62
+ if (!innerId) throw new ValidationError('INNER_EVENT_ID_REQUIRED')
62
63
  return {
63
64
  kind: NYM_CARRIER_KIND,
64
65
  created_at: createdAt,
@@ -1,8 +1,10 @@
1
- import { generateSecretKey, getPublicKey, finalizeEvent, getEventHash, validateEvent, verifyEvent } from 'nostr-tools'
1
+ import { finalizeEvent, getEventHash, isSerializableEvent, isValidEvent } from '../event/index.js'
2
+ import { generateSecretKey, getPublicKey } from '../key/index.js'
2
3
  import { bytesToBase64, base64ToBytes } from '../base64/index.js'
3
4
  import { hexToBytes } from '../base16/index.js'
4
- import { makeContentKeyEventForPubkey, parseContentKeyEvent, verifyContentKeyProof, verifyIykcProof } from '../content-key/event/index.js'
5
+ import { isValidContentKeyProof, isValidIykcProof, makeContentKeyEventForPubkey, parseContentKeyEvent } from '../content-key/event/index.js'
5
6
  import { getIykcProofs } from '../content-key/index.js'
7
+ import { ValidationError } from '../error/index.js'
6
8
  import * as nip44v3 from '../nip44-v3/index.js'
7
9
  import { relayPool } from '../relay/index.js'
8
10
  import { JSONL_CHUNK_BYTES, NYM_CARRIER_CHUNK_CHARS } from './helpers/chunk-size.js'
@@ -43,7 +45,7 @@ function uniq (values) {
43
45
  function normalizeDeletionPubkey (deletionPubkey) {
44
46
  if (deletionPubkey === undefined) return undefined
45
47
  if (typeof deletionPubkey !== 'string' || !HEX_PUBKEY.test(deletionPubkey)) {
46
- throw new Error('INVALID_DELETION_PUBKEY')
48
+ throw new ValidationError('INVALID_DELETION_PUBKEY')
47
49
  }
48
50
  return deletionPubkey.toLowerCase()
49
51
  }
@@ -79,7 +81,7 @@ async function nip44v3DecryptText (signer, peerPubkey, kind, ciphertext) {
79
81
  return base64ToText(await signer.nip44v3Decrypt(peerPubkey, kind, NIP44_V3_SCOPE, ciphertext))
80
82
  }
81
83
 
82
- function storesRecoverySeeds (mode) {
84
+ function doesModeStoreRecoverySeeds (mode) {
83
85
  return mode === 'seeder' || mode === 'watchtower'
84
86
  }
85
87
 
@@ -89,16 +91,16 @@ async function makeImkcProof ({ senderSigner, senderPubkey, imkcPubkey }) {
89
91
  if (
90
92
  event.pubkey !== senderPubkey ||
91
93
  parsed?.iykcPubkey !== imkcPubkey ||
92
- !verifyContentKeyProof({ ownerPubkey: senderPubkey, contentPubkey: imkcPubkey, proof: parsed?.iykcProof })
93
- ) throw new Error('INVALID_IMKC_PROOF')
94
+ !isValidContentKeyProof({ ownerPubkey: senderPubkey, contentPubkey: imkcPubkey, proof: parsed?.iykcProof })
95
+ ) throw new ValidationError('INVALID_IMKC_PROOF')
94
96
  return parsed.iykcProof
95
97
  }
96
98
 
97
99
  async function prepareRoutedMessage ({ senderSigner, imkcSigner, privateChannelSigner = senderSigner, privateChannelReaderPubkey, receivers, event, temporaryStorageArea, _getIykcProofs = getIykcProofs }) {
98
- if (!senderSigner?.getPublicKey) throw new Error('SENDER_SIGNER_REQUIRED')
99
- if (!senderSigner?.nip44EncryptDoubleDH && !senderSigner?.nip44v3Encrypt) throw new Error('SIGNER_NIP44V3_ENCRYPT_UNSUPPORTED')
100
- if (!privateChannelSigner?.getPublicKey || !privateChannelSigner?.nip44v3Encrypt || !privateChannelSigner?.signEvent) throw new Error('PRIVATE_CHANNEL_SIGNER_REQUIRED')
101
- if (!Array.isArray(receivers) || !receivers.length) throw new Error('NO_RECEIVERS')
100
+ if (!senderSigner?.getPublicKey) throw new ValidationError('SENDER_SIGNER_REQUIRED')
101
+ if (!senderSigner?.nip44EncryptDoubleDH && !senderSigner?.nip44v3Encrypt) throw new ValidationError('SIGNER_NIP44V3_ENCRYPT_UNSUPPORTED')
102
+ if (!privateChannelSigner?.getPublicKey || !privateChannelSigner?.nip44v3Encrypt || !privateChannelSigner?.signEvent) throw new ValidationError('PRIVATE_CHANNEL_SIGNER_REQUIRED')
103
+ if (!Array.isArray(receivers) || !receivers.length) throw new ValidationError('NO_RECEIVERS')
102
104
 
103
105
  const senderPubkey = await senderSigner.getPublicKey()
104
106
  const useDoubleDh = typeof senderSigner.nip44EncryptDoubleDH === 'function'
@@ -158,7 +160,7 @@ async function * wrapPreparedEvents ({ privateChannelSigner, receivers, receiver
158
160
  tags: privateBroadcastTags({ deletionPubkey, createdAt, expirationSeconds }),
159
161
  content: await nip44v3EncryptText(privateChannelSigner, context.channelReaderPubkey, PRIVATE_BROADCAST_KIND, JSON.stringify(router))
160
162
  })
161
- if (eventByteLength(outer) > MAX_EVENT_BYTES) throw new Error('EVENT_TOO_LARGE')
163
+ if (eventByteLength(outer) > MAX_EVENT_BYTES) throw new ValidationError('EVENT_TOO_LARGE')
162
164
  yield outer
163
165
  }
164
166
  } finally {
@@ -193,17 +195,17 @@ export async function wrapEvent (options) {
193
195
  }
194
196
 
195
197
  export async function * wrapNymEvents ({ nymSigner, privateChannelSigner, privateChannelReaderPubkey, deletionPubkey, event, expirationSeconds = EXPIRATION_SECONDS }) {
196
- if (!nymSigner?.getPublicKey || !nymSigner?.signEvent) throw new Error('NYM_SIGNER_REQUIRED')
197
- if (!privateChannelSigner?.getPublicKey || !privateChannelSigner?.nip44v3Encrypt || !privateChannelSigner?.signEvent) throw new Error('PRIVATE_CHANNEL_SIGNER_REQUIRED')
198
+ if (!nymSigner?.getPublicKey || !nymSigner?.signEvent) throw new ValidationError('NYM_SIGNER_REQUIRED')
199
+ if (!privateChannelSigner?.getPublicKey || !privateChannelSigner?.nip44v3Encrypt || !privateChannelSigner?.signEvent) throw new ValidationError('PRIVATE_CHANNEL_SIGNER_REQUIRED')
198
200
  const normalizedDeletionPubkey = normalizeDeletionPubkey(deletionPubkey)
199
201
 
200
202
  const nymPubkey = await nymSigner.getPublicKey()
201
203
  const channelPubkey = await privateChannelSigner.getPublicKey()
202
204
  const channelReaderPubkey = privateChannelReaderPubkey || channelPubkey
203
- const wireEvent = isSignedEvent(event)
205
+ const wireEvent = hasEventSignature(event)
204
206
  ? assertValidSignedInnerEvent(event)
205
207
  : wireNymRumor({ ...event, created_at: event?.created_at !== undefined ? event.created_at : nowSeconds() })
206
- const innerEvent = isSignedEvent(wireEvent) ? wireEvent : normalizeNymRumor(wireEvent, nymPubkey)
208
+ const innerEvent = hasEventSignature(wireEvent) ? wireEvent : normalizeNymRumor(wireEvent, nymPubkey)
207
209
  const encoded = bytesToBase64(encoder.encode(JSON.stringify(wireEvent)))
208
210
  const total = Math.max(1, Math.ceil(encoded.length / NYM_CARRIER_CHUNK_CHARS))
209
211
  const carrierCreatedAt = nowSeconds()
@@ -223,7 +225,7 @@ export async function * wrapNymEvents ({ nymSigner, privateChannelSigner, privat
223
225
  tags: privateBroadcastTags({ deletionPubkey: normalizedDeletionPubkey, createdAt, expirationSeconds }),
224
226
  content: await nip44v3EncryptText(privateChannelSigner, channelReaderPubkey, PRIVATE_BROADCAST_KIND, JSON.stringify(carrier))
225
227
  })
226
- if (eventByteLength(outer) > MAX_EVENT_BYTES) throw new Error('EVENT_TOO_LARGE')
228
+ if (eventByteLength(outer) > MAX_EVENT_BYTES) throw new ValidationError('EVENT_TOO_LARGE')
227
229
  yield outer
228
230
  }
229
231
  }
@@ -244,31 +246,31 @@ function joinedRouter (router, content = '') {
244
246
 
245
247
  function parsePayloadEnvelope (line, index = 0) {
246
248
  const record = JSON.parse(line)
247
- if (!Array.isArray(record) || record.length !== 1 || typeof record[0] !== 'string') throw new Error('INVALID_PAYLOAD_ENVELOPE')
249
+ if (!Array.isArray(record) || record.length !== 1 || typeof record[0] !== 'string') throw new ValidationError('INVALID_PAYLOAD_ENVELOPE')
248
250
  return { index, type: 'payload', ciphertext: record[0] }
249
251
  }
250
252
 
251
253
  function parseRecipientEnvelope (line, index = 0) {
252
254
  const record = JSON.parse(line)
253
- if (!Array.isArray(record) || (record.length !== 2 && record.length !== 4)) throw new Error('INVALID_RECIPIENT_ENVELOPE')
255
+ if (!Array.isArray(record) || (record.length !== 2 && record.length !== 4)) throw new ValidationError('INVALID_RECIPIENT_ENVELOPE')
254
256
  const [receiverPubkey, ciphertext, iykcPubkey = '', iykcProof = ''] = record
255
257
  return { index, receiverPubkey, ciphertext, iykcPubkey, iykcProof }
256
258
  }
257
259
 
258
- function isSignedEvent (event) {
260
+ function hasEventSignature (event) {
259
261
  return Object.prototype.hasOwnProperty.call(event || {}, 'sig')
260
262
  }
261
263
 
262
264
  function assertValidSignedInnerEvent (event) {
263
- if (!validateEvent(event) || event.id !== getEventHash(event) || !verifyEvent(event)) {
264
- throw new Error('INVALID_SIGNED_INNER_EVENT')
265
+ if (!isValidEvent(event)) {
266
+ throw new ValidationError('INVALID_SIGNED_INNER_EVENT')
265
267
  }
266
268
  return event
267
269
  }
268
270
 
269
271
  function normalizeNymRumor (event, pubkey) {
270
272
  const normalized = { ...event, pubkey }
271
- if (!validateEvent(normalized)) throw new Error('INVALID_NYM_RUMOR')
273
+ if (!isSerializableEvent(normalized)) throw new ValidationError('INVALID_NYM_RUMOR')
272
274
  return { ...normalized, id: getEventHash(normalized) }
273
275
  }
274
276
 
@@ -282,11 +284,11 @@ function wireNymRumor (event = {}) {
282
284
  }
283
285
 
284
286
  function assertValidNymCarrierEvent (carrier) {
285
- if (!validateEvent(carrier) || carrier.id !== getEventHash(carrier) || !verifyEvent(carrier)) {
286
- throw new Error('INVALID_NYM_CARRIER')
287
+ if (!isValidEvent(carrier)) {
288
+ throw new ValidationError('INVALID_NYM_CARRIER')
287
289
  }
288
- if (carrier.kind !== NYM_CARRIER_KIND) throw new Error('INVALID_NYM_CARRIER_KIND')
289
- if (!readIdTag(carrier)) throw new Error('MISSING_NYM_CARRIER_ID')
290
+ if (carrier.kind !== NYM_CARRIER_KIND) throw new ValidationError('INVALID_NYM_CARRIER_KIND')
291
+ if (!readIdTag(carrier)) throw new ValidationError('MISSING_NYM_CARRIER_ID')
290
292
  readChunkTag(carrier)
291
293
  return carrier
292
294
  }
@@ -296,7 +298,7 @@ function nymCarrierGroupId (carrier) {
296
298
  }
297
299
 
298
300
  function validateNymCarriers (carriers) {
299
- if (!Array.isArray(carriers) || !carriers.length) throw new Error('NYM_CARRIERS_REQUIRED')
301
+ if (!Array.isArray(carriers) || !carriers.length) throw new ValidationError('NYM_CARRIERS_REQUIRED')
300
302
  const chunks = []
301
303
  let nymPubkey = ''
302
304
  let innerId = ''
@@ -312,15 +314,15 @@ function validateNymCarriers (carriers) {
312
314
  total = nextTotal
313
315
  }
314
316
  if (carrier.pubkey !== nymPubkey || nextInnerId !== innerId || nextTotal !== total) {
315
- throw new Error('MISMATCHED_NYM_CARRIER_CHUNKS')
317
+ throw new ValidationError('MISMATCHED_NYM_CARRIER_CHUNKS')
316
318
  }
317
- if (chunks[index] !== undefined) throw new Error('DUPLICATE_NYM_CARRIER_CHUNK')
319
+ if (chunks[index] !== undefined) throw new ValidationError('DUPLICATE_NYM_CARRIER_CHUNK')
318
320
  chunks[index] = carrier.content
319
321
  }
320
322
 
321
- if (chunks.length !== total) throw new Error('MISSING_NYM_CARRIER_CHUNK')
323
+ if (chunks.length !== total) throw new ValidationError('MISSING_NYM_CARRIER_CHUNK')
322
324
  for (let index = 0; index < total; index++) {
323
- if (chunks[index] == null) throw new Error('MISSING_NYM_CARRIER_CHUNK')
325
+ if (chunks[index] == null) throw new ValidationError('MISSING_NYM_CARRIER_CHUNK')
324
326
  }
325
327
  return { nymPubkey, innerId, content: chunks.join('') }
326
328
  }
@@ -331,44 +333,44 @@ export function eventFromNymCarriers (carriers) {
331
333
  try {
332
334
  parsed = JSON.parse(decoder.decode(base64ToBytes(content)))
333
335
  } catch {
334
- throw new Error('INVALID_NYM_CARRIER_PAYLOAD')
336
+ throw new ValidationError('INVALID_NYM_CARRIER_PAYLOAD')
335
337
  }
336
338
 
337
- if (isSignedEvent(parsed)) {
339
+ if (hasEventSignature(parsed)) {
338
340
  const event = assertValidSignedInnerEvent(parsed)
339
- if (event.id !== innerId) throw new Error('INVALID_NYM_CARRIER_INNER_ID')
341
+ if (event.id !== innerId) throw new ValidationError('INVALID_NYM_CARRIER_INNER_ID')
340
342
  return event
341
343
  }
342
344
 
343
345
  const event = normalizeNymRumor(parsed, nymPubkey)
344
- if (event.id !== innerId) throw new Error('INVALID_NYM_CARRIER_INNER_ID')
346
+ if (event.id !== innerId) throw new ValidationError('INVALID_NYM_CARRIER_INNER_ID')
345
347
  return event
346
348
  }
347
349
 
348
350
  function assertValidEnvelopeIykcProof (envelope) {
349
351
  if (!envelope.iykcPubkey) return
350
- if (!verifyIykcProof({
352
+ if (!isValidIykcProof({
351
353
  receiverPubkey: envelope.receiverPubkey,
352
354
  iykcPubkey: envelope.iykcPubkey,
353
355
  iykcProof: envelope.iykcProof
354
- })) throw new Error('INVALID_IYKC_PROOF')
356
+ })) throw new ValidationError('INVALID_IYKC_PROOF')
355
357
  }
356
358
 
357
359
  function assertValidRouterImkcProof ({ router, senderPubkey, imkcPubkey, imkcProof }) {
358
360
  if (!hasImkcTag(router)) return
359
- if (!verifyContentKeyProof({
361
+ if (!isValidContentKeyProof({
360
362
  ownerPubkey: senderPubkey,
361
363
  contentPubkey: imkcPubkey,
362
364
  proof: imkcProof
363
- })) throw new Error('INVALID_IMKC_PROOF')
365
+ })) throw new ValidationError('INVALID_IMKC_PROOF')
364
366
  }
365
367
 
366
368
  function eventFromPayload ({ payloadCiphertext, messageSeckey, senderPubkey }) {
367
- if (!HEX_SECKEY.test(messageSeckey || '')) throw new Error('INVALID_MESSAGE_SECKEY')
369
+ if (!HEX_SECKEY.test(messageSeckey || '')) throw new ValidationError('INVALID_MESSAGE_SECKEY')
368
370
  const messageSecretKey = hexToBytes(messageSeckey)
369
371
  const messagePubkey = getPublicKey(messageSecretKey)
370
372
  const decrypted = JSON.parse(nip44v3.decrypt(messageSecretKey, messagePubkey, ROUTER_KIND, NIP44_V3_SCOPE, payloadCiphertext))
371
- if (isSignedEvent(decrypted)) return assertValidSignedInnerEvent(decrypted)
373
+ if (hasEventSignature(decrypted)) return assertValidSignedInnerEvent(decrypted)
372
374
  const normalized = { ...decrypted, pubkey: senderPubkey }
373
375
  return { ...normalized, id: getEventHash(normalized) }
374
376
  }
@@ -377,7 +379,7 @@ async function unwrapRecipientEnvelope ({ payloadCiphertext, envelope, receiverS
377
379
  if (receiverPubkey && envelope.receiverPubkey !== receiverPubkey) return null
378
380
  let messageSeckey
379
381
  if (envelope.iykcPubkey || imkcPubkey) {
380
- if (!receiverSigner?.nip44DecryptDoubleDH) throw new Error('RECEIVER_DOUBLE_DH_UNSUPPORTED')
382
+ if (!receiverSigner?.nip44DecryptDoubleDH) throw new ValidationError('RECEIVER_DOUBLE_DH_UNSUPPORTED')
381
383
  if (envelope.iykcPubkey) {
382
384
  assertValidEnvelopeIykcProof(envelope)
383
385
  }
@@ -390,7 +392,7 @@ async function unwrapRecipientEnvelope ({ payloadCiphertext, envelope, receiverS
390
392
  envelope.iykcPubkey || ''
391
393
  ))
392
394
  } else {
393
- if (!receiverSigner?.nip44v3Decrypt) throw new Error('RECEIVER_SIGNER_NIP44V3_DECRYPT_UNSUPPORTED')
395
+ if (!receiverSigner?.nip44v3Decrypt) throw new ValidationError('RECEIVER_SIGNER_NIP44V3_DECRYPT_UNSUPPORTED')
394
396
  messageSeckey = base64ToText(await receiverSigner.nip44v3Decrypt(senderPubkey, ROUTER_KIND, rowScope, envelope.ciphertext))
395
397
  }
396
398
  return eventFromPayload({ payloadCiphertext, messageSeckey, senderPubkey })
@@ -398,12 +400,12 @@ async function unwrapRecipientEnvelope ({ payloadCiphertext, envelope, receiverS
398
400
 
399
401
  export async function unwrapEvent ({ receiverSigner, privateChannelSigner = receiverSigner, privateChannelReaderSigner = privateChannelSigner, privateChannelReaderPubkey, event, receiverPubkey }) {
400
402
  if (!event || event.kind !== PRIVATE_BROADCAST_KIND) return null
401
- if (!receiverSigner?.nip44DecryptDoubleDH && !receiverSigner?.nip44v3Decrypt) throw new Error('RECEIVER_SIGNER_NIP44V3_DECRYPT_UNSUPPORTED')
403
+ if (!receiverSigner?.nip44DecryptDoubleDH && !receiverSigner?.nip44v3Decrypt) throw new ValidationError('RECEIVER_SIGNER_NIP44V3_DECRYPT_UNSUPPORTED')
402
404
  const channelReaderSigner = privateChannelReaderSigner || privateChannelSigner
403
- if (!channelReaderSigner?.nip44v3Decrypt) throw new Error('PRIVATE_CHANNEL_READER_REQUIRED')
405
+ if (!channelReaderSigner?.nip44v3Decrypt) throw new ValidationError('PRIVATE_CHANNEL_READER_REQUIRED')
404
406
 
405
407
  const channelPubkey = event.pubkey || await privateChannelSigner?.getPublicKey?.()
406
- if (!channelPubkey) throw new Error('PRIVATE_CHANNEL_PUBKEY_REQUIRED')
408
+ if (!channelPubkey) throw new ValidationError('PRIVATE_CHANNEL_PUBKEY_REQUIRED')
407
409
  const router = await decryptRouter({
408
410
  content: event.content,
409
411
  channelPubkey,
@@ -411,14 +413,14 @@ export async function unwrapEvent ({ receiverSigner, privateChannelSigner = rece
411
413
  channelReaderSigner,
412
414
  channelReaderPubkey: privateChannelReaderPubkey
413
415
  })
414
- if (router.kind !== ROUTER_KIND) throw new Error('INVALID_ROUTER_KIND')
416
+ if (router.kind !== ROUTER_KIND) throw new ValidationError('INVALID_ROUTER_KIND')
415
417
  if (receiverPubkey && readReceiverTag(router) && readReceiverTag(router) !== receiverPubkey) return null
416
418
 
417
419
  const senderPubkey = readSenderTag(router)
418
420
  const imkcPubkey = readImkcTag(router)
419
421
  assertValidRouterImkcProof({ router, senderPubkey, imkcPubkey, imkcProof: readImkcProof(router) })
420
422
  const lines = decodeChunkLines(router.content)
421
- if (!lines.length) throw new Error('MISSING_PAYLOAD_ENVELOPE')
423
+ if (!lines.length) throw new ValidationError('MISSING_PAYLOAD_ENVELOPE')
422
424
  const payload = parsePayloadEnvelope(lines[0], 0)
423
425
  for (let index = 1; index < lines.length; index++) {
424
426
  const event = await unwrapRecipientEnvelope({
@@ -443,7 +445,7 @@ function relayReceiverEntries (relayToReceivers) {
443
445
  if (!relayToReceivers) return []
444
446
  if (relayToReceivers instanceof Map) return [...relayToReceivers.entries()]
445
447
  if (typeof relayToReceivers === 'object') return Object.entries(relayToReceivers)
446
- throw new Error('INVALID_RELAY_RECEIVERS')
448
+ throw new ValidationError('INVALID_RELAY_RECEIVERS')
447
449
  }
448
450
 
449
451
  function groupedRelayReceivers ({ relayToReceivers, receivers }) {
@@ -467,7 +469,7 @@ function groupedRelayReceivers ({ relayToReceivers, receivers }) {
467
469
  const pubkeys = uniq(Array.isArray(value) ? value : [value])
468
470
  if (!pubkeys.length) continue
469
471
  for (const pubkey of pubkeys) {
470
- if (!wanted.has(pubkey)) throw new Error('RELAY_RECEIVER_NOT_REQUESTED')
472
+ if (!wanted.has(pubkey)) throw new ValidationError('RELAY_RECEIVER_NOT_REQUESTED')
471
473
  covered.add(pubkey)
472
474
  }
473
475
  const key = [...pubkeys].sort().join(',')
@@ -481,9 +483,9 @@ function groupedRelayReceivers ({ relayToReceivers, receivers }) {
481
483
  groupsByKey.get(key).relays.push(relay)
482
484
  }
483
485
 
484
- if (!groupsByKey.size) throw new Error('NO_RELAYS')
486
+ if (!groupsByKey.size) throw new ValidationError('NO_RELAYS')
485
487
  for (const pubkey of orderedPubkeys) {
486
- if (!covered.has(pubkey)) throw new Error('RELAY_RECEIVER_MISSING')
488
+ if (!covered.has(pubkey)) throw new ValidationError('RELAY_RECEIVER_MISSING')
487
489
  }
488
490
 
489
491
  return [...groupsByKey.values()].map(group => ({
@@ -554,7 +556,7 @@ function readSignerFromMap (signersByPubkey, pubkey) {
554
556
 
555
557
  async function decryptRouter ({ content, channelPubkey, channelSigner, channelReaderSigner, channelReaderPubkey }) {
556
558
  const signer = channelReaderSigner || channelSigner
557
- if (!signer?.nip44v3Decrypt) throw new Error('PRIVATE_CHANNEL_READER_REQUIRED')
559
+ if (!signer?.nip44v3Decrypt) throw new ValidationError('PRIVATE_CHANNEL_READER_REQUIRED')
558
560
 
559
561
  const readerPubkey = channelReaderPubkey || channelPubkey
560
562
  const signerPubkey = await signer.getPublicKey?.()
@@ -697,7 +699,7 @@ function createProcessor ({
697
699
  const channelReaderPubkey = readValueFromMap(privateChannelReaderPubkeysByPubkey, channelPubkey) || privateChannelReaderPubkey || channelPubkey
698
700
  const channelMode = readValueFromMap(modeByPubkey, channelPubkey) || mode
699
701
  const channelReceivedChunkTtlMs = readValueFromMap(receivedChunkTtlMsByPubkey, channelPubkey) ?? receivedChunkTtlMs
700
- if (!channelPubkey) throw new Error('PRIVATE_CHANNEL_PUBKEY_REQUIRED')
702
+ if (!channelPubkey) throw new ValidationError('PRIVATE_CHANNEL_PUBKEY_REQUIRED')
701
703
 
702
704
  const decrypted = await decryptRouter({
703
705
  content: outer.content,
@@ -734,7 +736,7 @@ function createProcessor ({
734
736
 
735
737
  const carriers = (await receivedChunks.readChunkContents(groupKey)).map(raw => JSON.parse(raw))
736
738
  const event = eventFromNymCarriers(carriers)
737
- const shouldSeed = storesRecoverySeeds(channelMode)
739
+ const shouldSeed = doesModeStoreRecoverySeeds(channelMode)
738
740
  if (shouldSeed) {
739
741
  await onSeedEvent?.({
740
742
  recordType: 'nymCarrier_v1',
@@ -782,7 +784,7 @@ function createProcessor ({
782
784
  missing: status.missing
783
785
  })
784
786
 
785
- const shouldSeed = storesRecoverySeeds(channelMode)
787
+ const shouldSeed = doesModeStoreRecoverySeeds(channelMode)
786
788
  const sentByReceiver = receiverPubkey && senderPubkey === receiverPubkey
787
789
  // Recovery seeders and own-sent messages need the full recipient list;
788
790
  // regular leechers can stop as soon as their envelope is decrypted.
@@ -796,7 +798,7 @@ function createProcessor ({
796
798
  helpers.rememberPayloadCiphertext(groupMeta, parsePayloadEnvelope(line, rowIndex).ciphertext)
797
799
  return
798
800
  }
799
- if (!groupMeta.payloadCiphertext) throw new Error('MISSING_PAYLOAD_ENVELOPE')
801
+ if (!groupMeta.payloadCiphertext) throw new ValidationError('MISSING_PAYLOAD_ENVELOPE')
800
802
  const envelope = parseRecipientEnvelope(line, rowIndex)
801
803
  helpers.rememberReceiverPubkey(groupMeta, envelope.receiverPubkey)
802
804
 
@@ -893,7 +895,7 @@ function shouldIgnoreGroupError (err) {
893
895
  }
894
896
 
895
897
  export async function fetch ({ receiverSigner, iykcSigner, privateChannelSigner = receiverSigner, privateChannelSignersByPubkey, privateChannelReaderSigner = privateChannelSigner, privateChannelReaderSignersByPubkey, privateChannelReaderPubkey, privateChannelReaderPubkeysByPubkey, privateChannelPubkey, privateChannelPubkeys, receiverPubkey, relays, onChunk, onEvent, onNymEvent, onSeedEvent, onContentKeyUsage, onError, since, until, limit, mode = 'leecher', modeByPubkey, receivedChunkTtlMs = DEFAULT_RECEIVED_CHUNK_TTL_MS, receivedChunkTtlMsByPubkey, receivedChunkMaxBytes = DEFAULT_RECEIVED_CHUNK_MAX_BYTES, receivedChunkIndexedDB = globalThis.indexedDB, ignoredGroupTtlMs = DEFAULT_IGNORED_GROUP_TTL_MS, ignoredGroupMaxEntries = DEFAULT_IGNORED_GROUP_MAX_ENTRIES, _getEvents = getEvents }) {
896
- if (!relays?.length) throw new Error('NO_RELAYS')
898
+ if (!relays?.length) throw new ValidationError('NO_RELAYS')
897
899
  const authors = privateChannelPubkeyList({ privateChannelPubkey, privateChannelPubkeys })
898
900
  const filter = { kinds: [PRIVATE_BROADCAST_KIND] }
899
901
  if (authors.length) filter.authors = authors
@@ -916,9 +918,9 @@ export async function fetch ({ receiverSigner, iykcSigner, privateChannelSigner
916
918
  }
917
919
 
918
920
  export function subscribe ({ receiverSigner, iykcSigner, privateChannelSigner = receiverSigner, privateChannelSignersByPubkey, privateChannelReaderSigner = privateChannelSigner, privateChannelReaderSignersByPubkey, privateChannelReaderPubkey, privateChannelReaderPubkeysByPubkey, privateChannelPubkey, privateChannelPubkeys, receiverPubkey, relays, onChunk, onEvent, onNymEvent, onSeedEvent, onContentKeyUsage, onError, since = nowSeconds() - 5, limit, liveOnly = false, mode = 'leecher', modeByPubkey, receivedChunkTtlMs = DEFAULT_RECEIVED_CHUNK_TTL_MS, receivedChunkTtlMsByPubkey, receivedChunkMaxBytes = DEFAULT_RECEIVED_CHUNK_MAX_BYTES, receivedChunkIndexedDB = globalThis.indexedDB, ignoredGroupTtlMs = DEFAULT_IGNORED_GROUP_TTL_MS, ignoredGroupMaxEntries = DEFAULT_IGNORED_GROUP_MAX_ENTRIES, _liveEventsGenerator = getLiveEventsGenerator, _eventsFeedGenerator = getEventsFeedGenerator }) {
919
- if (!relays?.length) throw new Error('NO_RELAYS')
920
- if (receiverSigner && !receiverSigner?.nip44DecryptDoubleDH && !receiverSigner?.nip44v3Decrypt) throw new Error('RECEIVER_SIGNER_NIP44V3_DECRYPT_UNSUPPORTED')
921
- if (!privateChannelReaderSigner && !privateChannelReaderSignersByPubkey && !privateChannelSigner && !privateChannelSignersByPubkey) throw new Error('PRIVATE_CHANNEL_READER_REQUIRED')
921
+ if (!relays?.length) throw new ValidationError('NO_RELAYS')
922
+ if (receiverSigner && !receiverSigner?.nip44DecryptDoubleDH && !receiverSigner?.nip44v3Decrypt) throw new ValidationError('RECEIVER_SIGNER_NIP44V3_DECRYPT_UNSUPPORTED')
923
+ if (!privateChannelReaderSigner && !privateChannelReaderSignersByPubkey && !privateChannelSigner && !privateChannelSignersByPubkey) throw new ValidationError('PRIVATE_CHANNEL_READER_REQUIRED')
922
924
 
923
925
  const authors = privateChannelPubkeyList({ privateChannelPubkey, privateChannelPubkeys })
924
926
  const filter = { kinds: [PRIVATE_BROADCAST_KIND], since }
@@ -1,4 +1,5 @@
1
1
  import { bytesToBase64 } from '../../base64/index.js'
2
+ import { ValidationError } from '../../error/index.js'
2
3
  import { run } from '../../idb/index.js'
3
4
 
4
5
  export const DEFAULT_RECEIVED_CHUNK_TTL_MS = 60 * 60 * 1000 // 1 hour
@@ -106,7 +107,7 @@ function normalizeBytes (value) {
106
107
  if (value instanceof Uint8Array) return new Uint8Array(value)
107
108
  if (value instanceof ArrayBuffer) return new Uint8Array(value)
108
109
  if (ArrayBuffer.isView(value)) return new Uint8Array(value.buffer, value.byteOffset, value.byteLength)
109
- throw new Error('RECEIVED_CHUNK_BYTES_REQUIRED')
110
+ throw new ValidationError('RECEIVED_CHUNK_BYTES_REQUIRED')
110
111
  }
111
112
 
112
113
  function uniq (values) {
@@ -362,9 +363,9 @@ export function createReceivedChunkStore ({
362
363
  }
363
364
 
364
365
  async function put ({ channelPubkey, routerPubkey, index, total, contentBytes, ttlMs }) {
365
- if (!channelPubkey || !routerPubkey) throw new Error('RECEIVED_CHUNK_GROUP_REQUIRED')
366
+ if (!channelPubkey || !routerPubkey) throw new ValidationError('RECEIVED_CHUNK_GROUP_REQUIRED')
366
367
  if (!Number.isSafeInteger(index) || !Number.isSafeInteger(total) || index < 0 || total < 1 || index >= total) {
367
- throw new Error('INVALID_RECEIVED_CHUNK_INDEX')
368
+ throw new ValidationError('INVALID_RECEIVED_CHUNK_INDEX')
368
369
  }
369
370
  const bytes = normalizeBytes(contentBytes)
370
371
  await ready()