@unicitylabs/nostr-js-sdk 0.1.0 → 0.2.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 (78) hide show
  1. package/README.md +86 -5
  2. package/dist/browser/index.js +1650 -75
  3. package/dist/browser/index.js.map +1 -1
  4. package/dist/browser/index.min.js +7 -6
  5. package/dist/browser/index.min.js.map +1 -1
  6. package/dist/browser/index.umd.js +1659 -80
  7. package/dist/browser/index.umd.js.map +1 -1
  8. package/dist/browser/index.umd.min.js +7 -6
  9. package/dist/browser/index.umd.min.js.map +1 -1
  10. package/dist/cjs/NostrKeyManager.js +57 -0
  11. package/dist/cjs/NostrKeyManager.js.map +1 -1
  12. package/dist/cjs/client/NostrClient.js +46 -0
  13. package/dist/cjs/client/NostrClient.js.map +1 -1
  14. package/dist/cjs/crypto/index.js +4 -2
  15. package/dist/cjs/crypto/index.js.map +1 -1
  16. package/dist/cjs/crypto/nip04.js +14 -3
  17. package/dist/cjs/crypto/nip04.js.map +1 -1
  18. package/dist/cjs/crypto/nip44.js +297 -0
  19. package/dist/cjs/crypto/nip44.js.map +1 -0
  20. package/dist/cjs/index.js +27 -1
  21. package/dist/cjs/index.js.map +1 -1
  22. package/dist/cjs/messaging/index.js +24 -0
  23. package/dist/cjs/messaging/index.js.map +1 -0
  24. package/dist/cjs/messaging/nip17.js +267 -0
  25. package/dist/cjs/messaging/nip17.js.map +1 -0
  26. package/dist/cjs/messaging/types.js +20 -0
  27. package/dist/cjs/messaging/types.js.map +1 -0
  28. package/dist/cjs/nametag/NametagUtils.js +1 -1
  29. package/dist/cjs/nametag/NametagUtils.js.map +1 -1
  30. package/dist/cjs/payment/PaymentRequestProtocol.js +2 -0
  31. package/dist/cjs/payment/PaymentRequestProtocol.js.map +1 -1
  32. package/dist/cjs/protocol/EventKinds.js +13 -1
  33. package/dist/cjs/protocol/EventKinds.js.map +1 -1
  34. package/dist/esm/NostrKeyManager.js +57 -0
  35. package/dist/esm/NostrKeyManager.js.map +1 -1
  36. package/dist/esm/client/NostrClient.js +46 -0
  37. package/dist/esm/client/NostrClient.js.map +1 -1
  38. package/dist/esm/crypto/index.js +3 -1
  39. package/dist/esm/crypto/index.js.map +1 -1
  40. package/dist/esm/crypto/nip04.js +14 -3
  41. package/dist/esm/crypto/nip04.js.map +1 -1
  42. package/dist/esm/crypto/nip44.js +283 -0
  43. package/dist/esm/crypto/nip44.js.map +1 -0
  44. package/dist/esm/index.js +4 -1
  45. package/dist/esm/index.js.map +1 -1
  46. package/dist/esm/messaging/index.js +8 -0
  47. package/dist/esm/messaging/index.js.map +1 -0
  48. package/dist/esm/messaging/nip17.js +229 -0
  49. package/dist/esm/messaging/nip17.js.map +1 -0
  50. package/dist/esm/messaging/types.js +16 -0
  51. package/dist/esm/messaging/types.js.map +1 -0
  52. package/dist/esm/nametag/NametagUtils.js +1 -1
  53. package/dist/esm/nametag/NametagUtils.js.map +1 -1
  54. package/dist/esm/payment/PaymentRequestProtocol.js +2 -0
  55. package/dist/esm/payment/PaymentRequestProtocol.js.map +1 -1
  56. package/dist/esm/protocol/EventKinds.js +12 -0
  57. package/dist/esm/protocol/EventKinds.js.map +1 -1
  58. package/dist/types/NostrKeyManager.d.ts +36 -0
  59. package/dist/types/NostrKeyManager.d.ts.map +1 -1
  60. package/dist/types/client/NostrClient.d.ts +31 -0
  61. package/dist/types/client/NostrClient.d.ts.map +1 -1
  62. package/dist/types/crypto/index.d.ts +1 -1
  63. package/dist/types/crypto/index.d.ts.map +1 -1
  64. package/dist/types/crypto/nip04.d.ts.map +1 -1
  65. package/dist/types/crypto/nip44.d.ts +78 -0
  66. package/dist/types/crypto/nip44.d.ts.map +1 -0
  67. package/dist/types/index.d.ts +4 -1
  68. package/dist/types/index.d.ts.map +1 -1
  69. package/dist/types/messaging/index.d.ts +8 -0
  70. package/dist/types/messaging/index.d.ts.map +1 -0
  71. package/dist/types/messaging/nip17.d.ts +42 -0
  72. package/dist/types/messaging/nip17.d.ts.map +1 -0
  73. package/dist/types/messaging/types.d.ts +59 -0
  74. package/dist/types/messaging/types.d.ts.map +1 -0
  75. package/dist/types/payment/PaymentRequestProtocol.d.ts.map +1 -1
  76. package/dist/types/protocol/EventKinds.d.ts +6 -0
  77. package/dist/types/protocol/EventKinds.d.ts.map +1 -1
  78. package/package.json +2 -1
@@ -18,17 +18,17 @@
18
18
  // Makes the utils un-importable in browsers without a bundler.
19
19
  // Once node.js 18 is deprecated (2025-04-30), we can just drop the import.
20
20
  /** Checks if something is Uint8Array. Be careful: nodejs Buffer will return true. */
21
- function isBytes(a) {
21
+ function isBytes$1(a) {
22
22
  return a instanceof Uint8Array || (ArrayBuffer.isView(a) && a.constructor.name === 'Uint8Array');
23
23
  }
24
24
  /** Asserts something is positive integer. */
25
- function anumber(n) {
25
+ function anumber$1(n) {
26
26
  if (!Number.isSafeInteger(n) || n < 0)
27
27
  throw new Error('positive integer expected, got ' + n);
28
28
  }
29
29
  /** Asserts something is Uint8Array. */
30
- function abytes(b, ...lengths) {
31
- if (!isBytes(b))
30
+ function abytes$1(b, ...lengths) {
31
+ if (!isBytes$1(b))
32
32
  throw new Error('Uint8Array expected');
33
33
  if (lengths.length > 0 && !lengths.includes(b.length))
34
34
  throw new Error('Uint8Array expected of length ' + lengths + ', got length=' + b.length);
@@ -37,32 +37,32 @@
37
37
  function ahash(h) {
38
38
  if (typeof h !== 'function' || typeof h.create !== 'function')
39
39
  throw new Error('Hash should be wrapped by utils.createHasher');
40
- anumber(h.outputLen);
41
- anumber(h.blockLen);
40
+ anumber$1(h.outputLen);
41
+ anumber$1(h.blockLen);
42
42
  }
43
43
  /** Asserts a hash instance has not been destroyed / finished */
44
- function aexists(instance, checkFinished = true) {
44
+ function aexists$1(instance, checkFinished = true) {
45
45
  if (instance.destroyed)
46
46
  throw new Error('Hash instance has been destroyed');
47
47
  if (checkFinished && instance.finished)
48
48
  throw new Error('Hash#digest() has already been called');
49
49
  }
50
50
  /** Asserts output is properly-sized byte array */
51
- function aoutput(out, instance) {
52
- abytes(out);
51
+ function aoutput$1(out, instance) {
52
+ abytes$1(out);
53
53
  const min = instance.outputLen;
54
54
  if (out.length < min) {
55
55
  throw new Error('digestInto() expects output buffer of length at least ' + min);
56
56
  }
57
57
  }
58
58
  /** Zeroize a byte array. Warning: JS provides no guarantees. */
59
- function clean(...arrays) {
59
+ function clean$1(...arrays) {
60
60
  for (let i = 0; i < arrays.length; i++) {
61
61
  arrays[i].fill(0);
62
62
  }
63
63
  }
64
64
  /** Create DataView of an array for easy byte-level manipulation. */
65
- function createView(arr) {
65
+ function createView$1(arr) {
66
66
  return new DataView(arr.buffer, arr.byteOffset, arr.byteLength);
67
67
  }
68
68
  /** The rotate right (circular right shift) operation for uint32 */
@@ -80,7 +80,7 @@
80
80
  * @example bytesToHex(Uint8Array.from([0xca, 0xfe, 0x01, 0x23])) // 'cafe0123'
81
81
  */
82
82
  function bytesToHex(bytes) {
83
- abytes(bytes);
83
+ abytes$1(bytes);
84
84
  // @ts-ignore
85
85
  if (hasHexBuiltin)
86
86
  return bytes.toHex();
@@ -132,7 +132,7 @@
132
132
  * Converts string to bytes using UTF8 encoding.
133
133
  * @example utf8ToBytes('abc') // Uint8Array.from([97, 98, 99])
134
134
  */
135
- function utf8ToBytes(str) {
135
+ function utf8ToBytes$1(str) {
136
136
  if (typeof str !== 'string')
137
137
  throw new Error('string expected');
138
138
  return new Uint8Array(new TextEncoder().encode(str)); // https://bugzil.la/1681809
@@ -142,10 +142,10 @@
142
142
  * Warning: when Uint8Array is passed, it would NOT get copied.
143
143
  * Keep in mind for future mutable operations.
144
144
  */
145
- function toBytes(data) {
145
+ function toBytes$1(data) {
146
146
  if (typeof data === 'string')
147
- data = utf8ToBytes(data);
148
- abytes(data);
147
+ data = utf8ToBytes$1(data);
148
+ abytes$1(data);
149
149
  return data;
150
150
  }
151
151
  /** Copies several Uint8Arrays into one. */
@@ -153,7 +153,7 @@
153
153
  let sum = 0;
154
154
  for (let i = 0; i < arrays.length; i++) {
155
155
  const a = arrays[i];
156
- abytes(a);
156
+ abytes$1(a);
157
157
  sum += a.length;
158
158
  }
159
159
  const res = new Uint8Array(sum);
@@ -169,7 +169,7 @@
169
169
  }
170
170
  /** Wraps hash function, creating an interface on top of it */
171
171
  function createHasher(hashCons) {
172
- const hashC = (msg) => hashCons().update(toBytes(msg)).digest();
172
+ const hashC = (msg) => hashCons().update(toBytes$1(msg)).digest();
173
173
  const tmp = hashCons();
174
174
  hashC.outputLen = tmp.outputLen;
175
175
  hashC.blockLen = tmp.blockLen;
@@ -191,22 +191,22 @@
191
191
  var utils = /*#__PURE__*/Object.freeze({
192
192
  __proto__: null,
193
193
  Hash: Hash,
194
- abytes: abytes,
195
- aexists: aexists,
194
+ abytes: abytes$1,
195
+ aexists: aexists$1,
196
196
  ahash: ahash,
197
- anumber: anumber,
198
- aoutput: aoutput,
197
+ anumber: anumber$1,
198
+ aoutput: aoutput$1,
199
199
  bytesToHex: bytesToHex,
200
- clean: clean,
200
+ clean: clean$1,
201
201
  concatBytes: concatBytes,
202
202
  createHasher: createHasher,
203
- createView: createView,
203
+ createView: createView$1,
204
204
  hexToBytes: hexToBytes,
205
- isBytes: isBytes,
205
+ isBytes: isBytes$1,
206
206
  randomBytes: randomBytes,
207
207
  rotr: rotr,
208
- toBytes: toBytes,
209
- utf8ToBytes: utf8ToBytes
208
+ toBytes: toBytes$1,
209
+ utf8ToBytes: utf8ToBytes$1
210
210
  });
211
211
 
212
212
  /**
@@ -417,7 +417,7 @@
417
417
  * @module
418
418
  */
419
419
  /** Polyfill for Safari 14. https://caniuse.com/mdn-javascript_builtins_dataview_setbiguint64 */
420
- function setBigUint64(view, byteOffset, value, isLE) {
420
+ function setBigUint64$1(view, byteOffset, value, isLE) {
421
421
  if (typeof view.setBigUint64 === 'function')
422
422
  return view.setBigUint64(byteOffset, value, isLE);
423
423
  const _32n = BigInt(32);
@@ -453,19 +453,19 @@
453
453
  this.padOffset = padOffset;
454
454
  this.isLE = isLE;
455
455
  this.buffer = new Uint8Array(blockLen);
456
- this.view = createView(this.buffer);
456
+ this.view = createView$1(this.buffer);
457
457
  }
458
458
  update(data) {
459
- aexists(this);
460
- data = toBytes(data);
461
- abytes(data);
459
+ aexists$1(this);
460
+ data = toBytes$1(data);
461
+ abytes$1(data);
462
462
  const { view, buffer, blockLen } = this;
463
463
  const len = data.length;
464
464
  for (let pos = 0; pos < len;) {
465
465
  const take = Math.min(blockLen - this.pos, len - pos);
466
466
  // Fast path: we have at least one block in input, cast it to view and process
467
467
  if (take === blockLen) {
468
- const dataView = createView(data);
468
+ const dataView = createView$1(data);
469
469
  for (; blockLen <= len - pos; pos += blockLen)
470
470
  this.process(dataView, pos);
471
471
  continue;
@@ -483,8 +483,8 @@
483
483
  return this;
484
484
  }
485
485
  digestInto(out) {
486
- aexists(this);
487
- aoutput(out, this);
486
+ aexists$1(this);
487
+ aoutput$1(out, this);
488
488
  this.finished = true;
489
489
  // Padding
490
490
  // We can avoid allocation of buffer for padding completely if it
@@ -493,7 +493,7 @@
493
493
  let { pos } = this;
494
494
  // append the bit '1' to the message
495
495
  buffer[pos++] = 0b10000000;
496
- clean(this.buffer.subarray(pos));
496
+ clean$1(this.buffer.subarray(pos));
497
497
  // we have less than padOffset left in buffer, so we cannot put length in
498
498
  // current block, need process it and pad again
499
499
  if (this.padOffset > blockLen - pos) {
@@ -506,9 +506,9 @@
506
506
  // Note: sha512 requires length to be 128bit integer, but length in JS will overflow before that
507
507
  // You need to write around 2 exabytes (u64_max / 8 / (1024**6)) for this to happen.
508
508
  // So we just write lowest 64 bits of that value.
509
- setBigUint64(view, blockLen - 8, BigInt(this.length * 8), isLE);
509
+ setBigUint64$1(view, blockLen - 8, BigInt(this.length * 8), isLE);
510
510
  this.process(view, 0);
511
- const oview = createView(out);
511
+ const oview = createView$1(out);
512
512
  const len = this.outputLen;
513
513
  // NOTE: we do division by 4 later, which should be fused in single op with modulo by JIT
514
514
  if (len % 4)
@@ -644,11 +644,11 @@
644
644
  this.set(A, B, C, D, E, F, G, H);
645
645
  }
646
646
  roundClean() {
647
- clean(SHA256_W);
647
+ clean$1(SHA256_W);
648
648
  }
649
649
  destroy() {
650
650
  this.set(0, 0, 0, 0, 0, 0, 0, 0);
651
- clean(this.buffer);
651
+ clean$1(this.buffer);
652
652
  }
653
653
  }
654
654
  /**
@@ -670,7 +670,7 @@
670
670
  this.finished = false;
671
671
  this.destroyed = false;
672
672
  ahash(hash);
673
- const key = toBytes(_key);
673
+ const key = toBytes$1(_key);
674
674
  this.iHash = hash.create();
675
675
  if (typeof this.iHash.update !== 'function')
676
676
  throw new Error('Expected instance of class which extends utils.Hash');
@@ -689,16 +689,16 @@
689
689
  for (let i = 0; i < pad.length; i++)
690
690
  pad[i] ^= 0x36 ^ 0x5c;
691
691
  this.oHash.update(pad);
692
- clean(pad);
692
+ clean$1(pad);
693
693
  }
694
694
  update(buf) {
695
- aexists(this);
695
+ aexists$1(this);
696
696
  this.iHash.update(buf);
697
697
  return this;
698
698
  }
699
699
  digestInto(out) {
700
- aexists(this);
701
- abytes(out, this.outputLen);
700
+ aexists$1(this);
701
+ abytes$1(out, this.outputLen);
702
702
  this.finished = true;
703
703
  this.iHash.digestInto(out);
704
704
  this.oHash.update(out);
@@ -763,7 +763,7 @@
763
763
  // tmp name until v2
764
764
  /** Asserts something is Uint8Array. */
765
765
  function _abytes2(value, length, title = '') {
766
- const bytes = isBytes(value);
766
+ const bytes = isBytes$1(value);
767
767
  const len = value?.length;
768
768
  const needsLen = length !== undefined;
769
769
  if (!bytes || (needsLen && len !== length)) {
@@ -789,7 +789,7 @@
789
789
  return hexToNumber(bytesToHex(bytes));
790
790
  }
791
791
  function bytesToNumberLE(bytes) {
792
- abytes(bytes);
792
+ abytes$1(bytes);
793
793
  return hexToNumber(bytesToHex(Uint8Array.from(bytes).reverse()));
794
794
  }
795
795
  function numberToBytesBE(n, len) {
@@ -817,7 +817,7 @@
817
817
  throw new Error(title + ' must be hex string or Uint8Array, cause: ' + e);
818
818
  }
819
819
  }
820
- else if (isBytes(hex)) {
820
+ else if (isBytes$1(hex)) {
821
821
  // Uint8Array.from() instead of hash.slice() because node.js Buffer
822
822
  // is instance of Uint8Array, and its slice() creates **mutable** copy
823
823
  res = Uint8Array.from(hex);
@@ -1258,7 +1258,7 @@
1258
1258
  function nLength(n, nBitLength) {
1259
1259
  // Bit size, byte size of CURVE.n
1260
1260
  if (nBitLength !== undefined)
1261
- anumber(nBitLength);
1261
+ anumber$1(nBitLength);
1262
1262
  const _nBitLength = nBitLength !== undefined ? nBitLength : n.toString(2).length;
1263
1263
  const nByteLength = Math.ceil(_nBitLength / 8);
1264
1264
  return { nBitLength: _nBitLength, nByteLength };
@@ -2897,7 +2897,7 @@
2897
2897
  function tryParsingSig(sg) {
2898
2898
  // Try to deduce format
2899
2899
  let sig = undefined;
2900
- const isHex = typeof sg === 'string' || isBytes(sg);
2900
+ const isHex = typeof sg === 'string' || isBytes$1(sg);
2901
2901
  const isObj = !isHex &&
2902
2902
  sg !== null &&
2903
2903
  typeof sg === 'object' &&
@@ -3140,7 +3140,7 @@
3140
3140
  function taggedHash(tag, ...messages) {
3141
3141
  let tagP = TAGGED_HASH_PREFIXES[tag];
3142
3142
  if (tagP === undefined) {
3143
- const tagH = sha256$1(utf8ToBytes(tag));
3143
+ const tagH = sha256$1(utf8ToBytes$1(tag));
3144
3144
  tagP = concatBytes(tagH, tagH);
3145
3145
  TAGGED_HASH_PREFIXES[tag] = tagP;
3146
3146
  }
@@ -3414,6 +3414,17 @@
3414
3414
  * AES-256-CBC encryption with ECDH key agreement and optional GZIP compression.
3415
3415
  * Works in both Node.js and browser environments.
3416
3416
  */
3417
+ /**
3418
+ * Get the Web Crypto API (works in both Node.js and browser)
3419
+ */
3420
+ async function getWebCrypto() {
3421
+ if (typeof globalThis.crypto?.subtle !== 'undefined') {
3422
+ return globalThis.crypto;
3423
+ }
3424
+ // Node.js environment - import webcrypto
3425
+ const nodeCrypto = await import('crypto');
3426
+ return nodeCrypto.webcrypto;
3427
+ }
3417
3428
  /** Compression threshold in bytes */
3418
3429
  const COMPRESSION_THRESHOLD = 1024;
3419
3430
  /** Prefix for compressed messages */
@@ -3421,7 +3432,7 @@
3421
3432
  /**
3422
3433
  * Convert a Uint8Array to base64 string (browser and Node.js compatible)
3423
3434
  */
3424
- function toBase64(bytes) {
3435
+ function toBase64$1(bytes) {
3425
3436
  if (typeof Buffer !== 'undefined') {
3426
3437
  return Buffer.from(bytes).toString('base64');
3427
3438
  }
@@ -3435,7 +3446,7 @@
3435
3446
  /**
3436
3447
  * Convert a base64 string to Uint8Array (browser and Node.js compatible)
3437
3448
  */
3438
- function fromBase64(base64) {
3449
+ function fromBase64$1(base64) {
3439
3450
  if (typeof Buffer !== 'undefined') {
3440
3451
  return new Uint8Array(Buffer.from(base64, 'base64'));
3441
3452
  }
@@ -3530,7 +3541,7 @@
3530
3541
  * Import an AES-256-CBC key for encryption/decryption
3531
3542
  */
3532
3543
  async function importKey(keyBytes) {
3533
- const crypto = globalThis.crypto;
3544
+ const crypto = await getWebCrypto();
3534
3545
  return crypto.subtle.importKey('raw', toBufferSource(keyBytes), { name: 'AES-CBC' }, false, [
3535
3546
  'encrypt',
3536
3547
  'decrypt',
@@ -3540,7 +3551,7 @@
3540
3551
  * AES-256-CBC encrypt
3541
3552
  */
3542
3553
  async function aesEncrypt(plaintext, key, iv) {
3543
- const crypto = globalThis.crypto;
3554
+ const crypto = await getWebCrypto();
3544
3555
  const cryptoKey = await importKey(key);
3545
3556
  const ciphertext = await crypto.subtle.encrypt({ name: 'AES-CBC', iv: toBufferSource(iv) }, cryptoKey, toBufferSource(plaintext));
3546
3557
  return new Uint8Array(ciphertext);
@@ -3549,7 +3560,7 @@
3549
3560
  * AES-256-CBC decrypt
3550
3561
  */
3551
3562
  async function aesDecrypt(ciphertext, key, iv) {
3552
- const crypto = globalThis.crypto;
3563
+ const crypto = await getWebCrypto();
3553
3564
  const cryptoKey = await importKey(key);
3554
3565
  const plaintext = await crypto.subtle.decrypt({ name: 'AES-CBC', iv: toBufferSource(iv) }, cryptoKey, toBufferSource(ciphertext));
3555
3566
  return new Uint8Array(plaintext);
@@ -3600,7 +3611,7 @@
3600
3611
  * @param theirPublicKey 32-byte x-only public key
3601
3612
  * @returns Encrypted content string
3602
3613
  */
3603
- async function encrypt(message, myPrivateKey, theirPublicKey) {
3614
+ async function encrypt$1(message, myPrivateKey, theirPublicKey) {
3604
3615
  const encoder = new TextEncoder();
3605
3616
  let plaintext = encoder.encode(message);
3606
3617
  // Check if compression is needed
@@ -3621,8 +3632,8 @@
3621
3632
  // Encrypt
3622
3633
  const ciphertext = await aesEncrypt(plaintextToEncrypt, sharedSecret, iv);
3623
3634
  // Format output
3624
- const ciphertextBase64 = toBase64(ciphertext);
3625
- const ivBase64 = toBase64(iv);
3635
+ const ciphertextBase64 = toBase64$1(ciphertext);
3636
+ const ivBase64 = toBase64$1(iv);
3626
3637
  if (useCompression) {
3627
3638
  return `${COMPRESSION_PREFIX}${ciphertextBase64}?iv=${ivBase64}`;
3628
3639
  }
@@ -3635,10 +3646,10 @@
3635
3646
  * @param theirPublicKeyHex Hex-encoded public key
3636
3647
  * @returns Encrypted content string
3637
3648
  */
3638
- async function encryptHex(message, myPrivateKeyHex, theirPublicKeyHex) {
3649
+ async function encryptHex$1(message, myPrivateKeyHex, theirPublicKeyHex) {
3639
3650
  const myPrivateKey = hexToBytes(myPrivateKeyHex);
3640
3651
  const theirPublicKey = hexToBytes(theirPublicKeyHex);
3641
- return encrypt(message, myPrivateKey, theirPublicKey);
3652
+ return encrypt$1(message, myPrivateKey, theirPublicKey);
3642
3653
  }
3643
3654
  /**
3644
3655
  * Decrypt a NIP-04 encrypted message.
@@ -3648,7 +3659,7 @@
3648
3659
  * @param theirPublicKey 32-byte x-only public key
3649
3660
  * @returns Decrypted message
3650
3661
  */
3651
- async function decrypt(encryptedContent, myPrivateKey, theirPublicKey) {
3662
+ async function decrypt$1(encryptedContent, myPrivateKey, theirPublicKey) {
3652
3663
  // Check for compression prefix
3653
3664
  let content = encryptedContent;
3654
3665
  let isCompressed = false;
@@ -3663,8 +3674,8 @@
3663
3674
  }
3664
3675
  const ciphertextBase64 = parts[0];
3665
3676
  const ivBase64 = parts[1];
3666
- const ciphertext = fromBase64(ciphertextBase64);
3667
- const iv = fromBase64(ivBase64);
3677
+ const ciphertext = fromBase64$1(ciphertextBase64);
3678
+ const iv = fromBase64$1(ivBase64);
3668
3679
  if (iv.length !== 16) {
3669
3680
  throw new Error('Invalid IV length');
3670
3681
  }
@@ -3686,20 +3697,1221 @@
3686
3697
  * @param theirPublicKeyHex Hex-encoded public key
3687
3698
  * @returns Decrypted message
3688
3699
  */
3689
- async function decryptHex(encryptedContent, myPrivateKeyHex, theirPublicKeyHex) {
3700
+ async function decryptHex$1(encryptedContent, myPrivateKeyHex, theirPublicKeyHex) {
3690
3701
  const myPrivateKey = hexToBytes(myPrivateKeyHex);
3691
3702
  const theirPublicKey = hexToBytes(theirPublicKeyHex);
3692
- return decrypt(encryptedContent, myPrivateKey, theirPublicKey);
3703
+ return decrypt$1(encryptedContent, myPrivateKey, theirPublicKey);
3693
3704
  }
3694
3705
 
3695
3706
  var nip04 = /*#__PURE__*/Object.freeze({
3696
3707
  __proto__: null,
3697
- decrypt: decrypt,
3698
- decryptHex: decryptHex,
3708
+ decrypt: decrypt$1,
3709
+ decryptHex: decryptHex$1,
3699
3710
  deriveSharedSecret: deriveSharedSecret,
3700
3711
  deriveSharedSecretHex: deriveSharedSecretHex,
3712
+ encrypt: encrypt$1,
3713
+ encryptHex: encryptHex$1
3714
+ });
3715
+
3716
+ /**
3717
+ * Utilities for hex, bytes, CSPRNG.
3718
+ * @module
3719
+ */
3720
+ /*! noble-ciphers - MIT License (c) 2023 Paul Miller (paulmillr.com) */
3721
+ /** Checks if something is Uint8Array. Be careful: nodejs Buffer will return true. */
3722
+ function isBytes(a) {
3723
+ return a instanceof Uint8Array || (ArrayBuffer.isView(a) && a.constructor.name === 'Uint8Array');
3724
+ }
3725
+ /** Asserts something is boolean. */
3726
+ function abool(b) {
3727
+ if (typeof b !== 'boolean')
3728
+ throw new Error(`boolean expected, not ${b}`);
3729
+ }
3730
+ /** Asserts something is positive integer. */
3731
+ function anumber(n) {
3732
+ if (!Number.isSafeInteger(n) || n < 0)
3733
+ throw new Error('positive integer expected, got ' + n);
3734
+ }
3735
+ /** Asserts something is Uint8Array. */
3736
+ function abytes(b, ...lengths) {
3737
+ if (!isBytes(b))
3738
+ throw new Error('Uint8Array expected');
3739
+ if (lengths.length > 0 && !lengths.includes(b.length))
3740
+ throw new Error('Uint8Array expected of length ' + lengths + ', got length=' + b.length);
3741
+ }
3742
+ /** Asserts a hash instance has not been destroyed / finished */
3743
+ function aexists(instance, checkFinished = true) {
3744
+ if (instance.destroyed)
3745
+ throw new Error('Hash instance has been destroyed');
3746
+ if (checkFinished && instance.finished)
3747
+ throw new Error('Hash#digest() has already been called');
3748
+ }
3749
+ /** Asserts output is properly-sized byte array */
3750
+ function aoutput(out, instance) {
3751
+ abytes(out);
3752
+ const min = instance.outputLen;
3753
+ if (out.length < min) {
3754
+ throw new Error('digestInto() expects output buffer of length at least ' + min);
3755
+ }
3756
+ }
3757
+ /** Cast u8 / u16 / u32 to u32. */
3758
+ function u32(arr) {
3759
+ return new Uint32Array(arr.buffer, arr.byteOffset, Math.floor(arr.byteLength / 4));
3760
+ }
3761
+ /** Zeroize a byte array. Warning: JS provides no guarantees. */
3762
+ function clean(...arrays) {
3763
+ for (let i = 0; i < arrays.length; i++) {
3764
+ arrays[i].fill(0);
3765
+ }
3766
+ }
3767
+ /** Create DataView of an array for easy byte-level manipulation. */
3768
+ function createView(arr) {
3769
+ return new DataView(arr.buffer, arr.byteOffset, arr.byteLength);
3770
+ }
3771
+ /** Is current platform little-endian? Most are. Big-Endian platform: IBM */
3772
+ const isLE = /* @__PURE__ */ (() => new Uint8Array(new Uint32Array([0x11223344]).buffer)[0] === 0x44)();
3773
+ /**
3774
+ * Converts string to bytes using UTF8 encoding.
3775
+ * @example utf8ToBytes('abc') // new Uint8Array([97, 98, 99])
3776
+ */
3777
+ function utf8ToBytes(str) {
3778
+ if (typeof str !== 'string')
3779
+ throw new Error('string expected');
3780
+ return new Uint8Array(new TextEncoder().encode(str)); // https://bugzil.la/1681809
3781
+ }
3782
+ /**
3783
+ * Normalizes (non-hex) string or Uint8Array to Uint8Array.
3784
+ * Warning: when Uint8Array is passed, it would NOT get copied.
3785
+ * Keep in mind for future mutable operations.
3786
+ */
3787
+ function toBytes(data) {
3788
+ if (typeof data === 'string')
3789
+ data = utf8ToBytes(data);
3790
+ else if (isBytes(data))
3791
+ data = copyBytes(data);
3792
+ else
3793
+ throw new Error('Uint8Array expected, got ' + typeof data);
3794
+ return data;
3795
+ }
3796
+ function checkOpts(defaults, opts) {
3797
+ if (opts == null || typeof opts !== 'object')
3798
+ throw new Error('options must be defined');
3799
+ const merged = Object.assign(defaults, opts);
3800
+ return merged;
3801
+ }
3802
+ /** Compares 2 uint8array-s in kinda constant time. */
3803
+ function equalBytes(a, b) {
3804
+ if (a.length !== b.length)
3805
+ return false;
3806
+ let diff = 0;
3807
+ for (let i = 0; i < a.length; i++)
3808
+ diff |= a[i] ^ b[i];
3809
+ return diff === 0;
3810
+ }
3811
+ /**
3812
+ * Wraps a cipher: validates args, ensures encrypt() can only be called once.
3813
+ * @__NO_SIDE_EFFECTS__
3814
+ */
3815
+ const wrapCipher = (params, constructor) => {
3816
+ function wrappedCipher(key, ...args) {
3817
+ // Validate key
3818
+ abytes(key);
3819
+ // Big-Endian hardware is rare. Just in case someone still decides to run ciphers:
3820
+ if (!isLE)
3821
+ throw new Error('Non little-endian hardware is not yet supported');
3822
+ // Validate nonce if nonceLength is present
3823
+ if (params.nonceLength !== undefined) {
3824
+ const nonce = args[0];
3825
+ if (!nonce)
3826
+ throw new Error('nonce / iv required');
3827
+ if (params.varSizeNonce)
3828
+ abytes(nonce);
3829
+ else
3830
+ abytes(nonce, params.nonceLength);
3831
+ }
3832
+ // Validate AAD if tagLength present
3833
+ const tagl = params.tagLength;
3834
+ if (tagl && args[1] !== undefined) {
3835
+ abytes(args[1]);
3836
+ }
3837
+ const cipher = constructor(key, ...args);
3838
+ const checkOutput = (fnLength, output) => {
3839
+ if (output !== undefined) {
3840
+ if (fnLength !== 2)
3841
+ throw new Error('cipher output not supported');
3842
+ abytes(output);
3843
+ }
3844
+ };
3845
+ // Create wrapped cipher with validation and single-use encryption
3846
+ let called = false;
3847
+ const wrCipher = {
3848
+ encrypt(data, output) {
3849
+ if (called)
3850
+ throw new Error('cannot encrypt() twice with same key + nonce');
3851
+ called = true;
3852
+ abytes(data);
3853
+ checkOutput(cipher.encrypt.length, output);
3854
+ return cipher.encrypt(data, output);
3855
+ },
3856
+ decrypt(data, output) {
3857
+ abytes(data);
3858
+ if (tagl && data.length < tagl)
3859
+ throw new Error('invalid ciphertext length: smaller than tagLength=' + tagl);
3860
+ checkOutput(cipher.decrypt.length, output);
3861
+ return cipher.decrypt(data, output);
3862
+ },
3863
+ };
3864
+ return wrCipher;
3865
+ }
3866
+ Object.assign(wrappedCipher, params);
3867
+ return wrappedCipher;
3868
+ };
3869
+ /**
3870
+ * By default, returns u8a of length.
3871
+ * When out is available, it checks it for validity and uses it.
3872
+ */
3873
+ function getOutput(expectedLength, out, onlyAligned = true) {
3874
+ if (out === undefined)
3875
+ return new Uint8Array(expectedLength);
3876
+ if (out.length !== expectedLength)
3877
+ throw new Error('invalid output length, expected ' + expectedLength + ', got: ' + out.length);
3878
+ if (onlyAligned && !isAligned32$1(out))
3879
+ throw new Error('invalid output, must be aligned');
3880
+ return out;
3881
+ }
3882
+ /** Polyfill for Safari 14. */
3883
+ function setBigUint64(view, byteOffset, value, isLE) {
3884
+ if (typeof view.setBigUint64 === 'function')
3885
+ return view.setBigUint64(byteOffset, value, isLE);
3886
+ const _32n = BigInt(32);
3887
+ const _u32_max = BigInt(0xffffffff);
3888
+ const wh = Number((value >> _32n) & _u32_max);
3889
+ const wl = Number(value & _u32_max);
3890
+ const h = 4 ;
3891
+ const l = 0 ;
3892
+ view.setUint32(byteOffset + h, wh, isLE);
3893
+ view.setUint32(byteOffset + l, wl, isLE);
3894
+ }
3895
+ function u64Lengths(dataLength, aadLength, isLE) {
3896
+ abool(isLE);
3897
+ const num = new Uint8Array(16);
3898
+ const view = createView(num);
3899
+ setBigUint64(view, 0, BigInt(aadLength), isLE);
3900
+ setBigUint64(view, 8, BigInt(dataLength), isLE);
3901
+ return num;
3902
+ }
3903
+ // Is byte array aligned to 4 byte offset (u32)?
3904
+ function isAligned32$1(bytes) {
3905
+ return bytes.byteOffset % 4 === 0;
3906
+ }
3907
+ // copy bytes to new u8a (aligned). Because Buffer.slice is broken.
3908
+ function copyBytes(bytes) {
3909
+ return Uint8Array.from(bytes);
3910
+ }
3911
+
3912
+ /**
3913
+ * Basic utils for ARX (add-rotate-xor) salsa and chacha ciphers.
3914
+
3915
+ RFC8439 requires multi-step cipher stream, where
3916
+ authKey starts with counter: 0, actual msg with counter: 1.
3917
+
3918
+ For this, we need a way to re-use nonce / counter:
3919
+
3920
+ const counter = new Uint8Array(4);
3921
+ chacha(..., counter, ...); // counter is now 1
3922
+ chacha(..., counter, ...); // counter is now 2
3923
+
3924
+ This is complicated:
3925
+
3926
+ - 32-bit counters are enough, no need for 64-bit: max ArrayBuffer size in JS is 4GB
3927
+ - Original papers don't allow mutating counters
3928
+ - Counter overflow is undefined [^1]
3929
+ - Idea A: allow providing (nonce | counter) instead of just nonce, re-use it
3930
+ - Caveat: Cannot be re-used through all cases:
3931
+ - * chacha has (counter | nonce)
3932
+ - * xchacha has (nonce16 | counter | nonce16)
3933
+ - Idea B: separate nonce / counter and provide separate API for counter re-use
3934
+ - Caveat: there are different counter sizes depending on an algorithm.
3935
+ - salsa & chacha also differ in structures of key & sigma:
3936
+ salsa20: s[0] | k(4) | s[1] | nonce(2) | ctr(2) | s[2] | k(4) | s[3]
3937
+ chacha: s(4) | k(8) | ctr(1) | nonce(3)
3938
+ chacha20orig: s(4) | k(8) | ctr(2) | nonce(2)
3939
+ - Idea C: helper method such as `setSalsaState(key, nonce, sigma, data)`
3940
+ - Caveat: we can't re-use counter array
3941
+
3942
+ xchacha [^2] uses the subkey and remaining 8 byte nonce with ChaCha20 as normal
3943
+ (prefixed by 4 NUL bytes, since [RFC8439] specifies a 12-byte nonce).
3944
+
3945
+ [^1]: https://mailarchive.ietf.org/arch/msg/cfrg/gsOnTJzcbgG6OqD8Sc0GO5aR_tU/
3946
+ [^2]: https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-xchacha#appendix-A.2
3947
+
3948
+ * @module
3949
+ */
3950
+ // prettier-ignore
3951
+ // We can't make top-level var depend on utils.utf8ToBytes
3952
+ // because it's not present in all envs. Creating a similar fn here
3953
+ const _utf8ToBytes = (str) => Uint8Array.from(str.split('').map((c) => c.charCodeAt(0)));
3954
+ const sigma16 = _utf8ToBytes('expand 16-byte k');
3955
+ const sigma32 = _utf8ToBytes('expand 32-byte k');
3956
+ const sigma16_32 = u32(sigma16);
3957
+ const sigma32_32 = u32(sigma32);
3958
+ function rotl(a, b) {
3959
+ return (a << b) | (a >>> (32 - b));
3960
+ }
3961
+ // Is byte array aligned to 4 byte offset (u32)?
3962
+ function isAligned32(b) {
3963
+ return b.byteOffset % 4 === 0;
3964
+ }
3965
+ // Salsa and Chacha block length is always 512-bit
3966
+ const BLOCK_LEN = 64;
3967
+ const BLOCK_LEN32 = 16;
3968
+ // new Uint32Array([2**32]) // => Uint32Array(1) [ 0 ]
3969
+ // new Uint32Array([2**32-1]) // => Uint32Array(1) [ 4294967295 ]
3970
+ const MAX_COUNTER = 2 ** 32 - 1;
3971
+ const U32_EMPTY = new Uint32Array();
3972
+ function runCipher(core, sigma, key, nonce, data, output, counter, rounds) {
3973
+ const len = data.length;
3974
+ const block = new Uint8Array(BLOCK_LEN);
3975
+ const b32 = u32(block);
3976
+ // Make sure that buffers aligned to 4 bytes
3977
+ const isAligned = isAligned32(data) && isAligned32(output);
3978
+ const d32 = isAligned ? u32(data) : U32_EMPTY;
3979
+ const o32 = isAligned ? u32(output) : U32_EMPTY;
3980
+ for (let pos = 0; pos < len; counter++) {
3981
+ core(sigma, key, nonce, b32, counter, rounds);
3982
+ if (counter >= MAX_COUNTER)
3983
+ throw new Error('arx: counter overflow');
3984
+ const take = Math.min(BLOCK_LEN, len - pos);
3985
+ // aligned to 4 bytes
3986
+ if (isAligned && take === BLOCK_LEN) {
3987
+ const pos32 = pos / 4;
3988
+ if (pos % 4 !== 0)
3989
+ throw new Error('arx: invalid block position');
3990
+ for (let j = 0, posj; j < BLOCK_LEN32; j++) {
3991
+ posj = pos32 + j;
3992
+ o32[posj] = d32[posj] ^ b32[j];
3993
+ }
3994
+ pos += BLOCK_LEN;
3995
+ continue;
3996
+ }
3997
+ for (let j = 0, posj; j < take; j++) {
3998
+ posj = pos + j;
3999
+ output[posj] = data[posj] ^ block[j];
4000
+ }
4001
+ pos += take;
4002
+ }
4003
+ }
4004
+ /** Creates ARX-like (ChaCha, Salsa) cipher stream from core function. */
4005
+ function createCipher(core, opts) {
4006
+ const { allowShortKeys, extendNonceFn, counterLength, counterRight, rounds } = checkOpts({ allowShortKeys: false, counterLength: 8, counterRight: false, rounds: 20 }, opts);
4007
+ if (typeof core !== 'function')
4008
+ throw new Error('core must be a function');
4009
+ anumber(counterLength);
4010
+ anumber(rounds);
4011
+ abool(counterRight);
4012
+ abool(allowShortKeys);
4013
+ return (key, nonce, data, output, counter = 0) => {
4014
+ abytes(key);
4015
+ abytes(nonce);
4016
+ abytes(data);
4017
+ const len = data.length;
4018
+ if (output === undefined)
4019
+ output = new Uint8Array(len);
4020
+ abytes(output);
4021
+ anumber(counter);
4022
+ if (counter < 0 || counter >= MAX_COUNTER)
4023
+ throw new Error('arx: counter overflow');
4024
+ if (output.length < len)
4025
+ throw new Error(`arx: output (${output.length}) is shorter than data (${len})`);
4026
+ const toClean = [];
4027
+ // Key & sigma
4028
+ // key=16 -> sigma16, k=key|key
4029
+ // key=32 -> sigma32, k=key
4030
+ let l = key.length;
4031
+ let k;
4032
+ let sigma;
4033
+ if (l === 32) {
4034
+ toClean.push((k = copyBytes(key)));
4035
+ sigma = sigma32_32;
4036
+ }
4037
+ else if (l === 16 && allowShortKeys) {
4038
+ k = new Uint8Array(32);
4039
+ k.set(key);
4040
+ k.set(key, 16);
4041
+ sigma = sigma16_32;
4042
+ toClean.push(k);
4043
+ }
4044
+ else {
4045
+ throw new Error(`arx: invalid 32-byte key, got length=${l}`);
4046
+ }
4047
+ // Nonce
4048
+ // salsa20: 8 (8-byte counter)
4049
+ // chacha20orig: 8 (8-byte counter)
4050
+ // chacha20: 12 (4-byte counter)
4051
+ // xsalsa20: 24 (16 -> hsalsa, 8 -> old nonce)
4052
+ // xchacha20: 24 (16 -> hchacha, 8 -> old nonce)
4053
+ // Align nonce to 4 bytes
4054
+ if (!isAligned32(nonce))
4055
+ toClean.push((nonce = copyBytes(nonce)));
4056
+ const k32 = u32(k);
4057
+ // hsalsa & hchacha: handle extended nonce
4058
+ if (extendNonceFn) {
4059
+ if (nonce.length !== 24)
4060
+ throw new Error(`arx: extended nonce must be 24 bytes`);
4061
+ extendNonceFn(sigma, k32, u32(nonce.subarray(0, 16)), k32);
4062
+ nonce = nonce.subarray(16);
4063
+ }
4064
+ // Handle nonce counter
4065
+ const nonceNcLen = 16 - counterLength;
4066
+ if (nonceNcLen !== nonce.length)
4067
+ throw new Error(`arx: nonce must be ${nonceNcLen} or 16 bytes`);
4068
+ // Pad counter when nonce is 64 bit
4069
+ if (nonceNcLen !== 12) {
4070
+ const nc = new Uint8Array(12);
4071
+ nc.set(nonce, counterRight ? 0 : 12 - nonce.length);
4072
+ nonce = nc;
4073
+ toClean.push(nonce);
4074
+ }
4075
+ const n32 = u32(nonce);
4076
+ runCipher(core, sigma, k32, n32, data, output, counter, rounds);
4077
+ clean(...toClean);
4078
+ return output;
4079
+ };
4080
+ }
4081
+
4082
+ /**
4083
+ * Poly1305 ([PDF](https://cr.yp.to/mac/poly1305-20050329.pdf),
4084
+ * [wiki](https://en.wikipedia.org/wiki/Poly1305))
4085
+ * is a fast and parallel secret-key message-authentication code suitable for
4086
+ * a wide variety of applications. It was standardized in
4087
+ * [RFC 8439](https://datatracker.ietf.org/doc/html/rfc8439) and is now used in TLS 1.3.
4088
+ *
4089
+ * Polynomial MACs are not perfect for every situation:
4090
+ * they lack Random Key Robustness: the MAC can be forged, and can't be used in PAKE schemes.
4091
+ * See [invisible salamanders attack](https://keymaterial.net/2020/09/07/invisible-salamanders-in-aes-gcm-siv/).
4092
+ * To combat invisible salamanders, `hash(key)` can be included in ciphertext,
4093
+ * however, this would violate ciphertext indistinguishability:
4094
+ * an attacker would know which key was used - so `HKDF(key, i)`
4095
+ * could be used instead.
4096
+ *
4097
+ * Check out [original website](https://cr.yp.to/mac.html).
4098
+ * @module
4099
+ */
4100
+ // Based on Public Domain poly1305-donna https://github.com/floodyberry/poly1305-donna
4101
+ const u8to16 = (a, i) => (a[i++] & 0xff) | ((a[i++] & 0xff) << 8);
4102
+ class Poly1305 {
4103
+ constructor(key) {
4104
+ this.blockLen = 16;
4105
+ this.outputLen = 16;
4106
+ this.buffer = new Uint8Array(16);
4107
+ this.r = new Uint16Array(10);
4108
+ this.h = new Uint16Array(10);
4109
+ this.pad = new Uint16Array(8);
4110
+ this.pos = 0;
4111
+ this.finished = false;
4112
+ key = toBytes(key);
4113
+ abytes(key, 32);
4114
+ const t0 = u8to16(key, 0);
4115
+ const t1 = u8to16(key, 2);
4116
+ const t2 = u8to16(key, 4);
4117
+ const t3 = u8to16(key, 6);
4118
+ const t4 = u8to16(key, 8);
4119
+ const t5 = u8to16(key, 10);
4120
+ const t6 = u8to16(key, 12);
4121
+ const t7 = u8to16(key, 14);
4122
+ // https://github.com/floodyberry/poly1305-donna/blob/e6ad6e091d30d7f4ec2d4f978be1fcfcbce72781/poly1305-donna-16.h#L47
4123
+ this.r[0] = t0 & 0x1fff;
4124
+ this.r[1] = ((t0 >>> 13) | (t1 << 3)) & 0x1fff;
4125
+ this.r[2] = ((t1 >>> 10) | (t2 << 6)) & 0x1f03;
4126
+ this.r[3] = ((t2 >>> 7) | (t3 << 9)) & 0x1fff;
4127
+ this.r[4] = ((t3 >>> 4) | (t4 << 12)) & 0x00ff;
4128
+ this.r[5] = (t4 >>> 1) & 0x1ffe;
4129
+ this.r[6] = ((t4 >>> 14) | (t5 << 2)) & 0x1fff;
4130
+ this.r[7] = ((t5 >>> 11) | (t6 << 5)) & 0x1f81;
4131
+ this.r[8] = ((t6 >>> 8) | (t7 << 8)) & 0x1fff;
4132
+ this.r[9] = (t7 >>> 5) & 0x007f;
4133
+ for (let i = 0; i < 8; i++)
4134
+ this.pad[i] = u8to16(key, 16 + 2 * i);
4135
+ }
4136
+ process(data, offset, isLast = false) {
4137
+ const hibit = isLast ? 0 : 1 << 11;
4138
+ const { h, r } = this;
4139
+ const r0 = r[0];
4140
+ const r1 = r[1];
4141
+ const r2 = r[2];
4142
+ const r3 = r[3];
4143
+ const r4 = r[4];
4144
+ const r5 = r[5];
4145
+ const r6 = r[6];
4146
+ const r7 = r[7];
4147
+ const r8 = r[8];
4148
+ const r9 = r[9];
4149
+ const t0 = u8to16(data, offset + 0);
4150
+ const t1 = u8to16(data, offset + 2);
4151
+ const t2 = u8to16(data, offset + 4);
4152
+ const t3 = u8to16(data, offset + 6);
4153
+ const t4 = u8to16(data, offset + 8);
4154
+ const t5 = u8to16(data, offset + 10);
4155
+ const t6 = u8to16(data, offset + 12);
4156
+ const t7 = u8to16(data, offset + 14);
4157
+ let h0 = h[0] + (t0 & 0x1fff);
4158
+ let h1 = h[1] + (((t0 >>> 13) | (t1 << 3)) & 0x1fff);
4159
+ let h2 = h[2] + (((t1 >>> 10) | (t2 << 6)) & 0x1fff);
4160
+ let h3 = h[3] + (((t2 >>> 7) | (t3 << 9)) & 0x1fff);
4161
+ let h4 = h[4] + (((t3 >>> 4) | (t4 << 12)) & 0x1fff);
4162
+ let h5 = h[5] + ((t4 >>> 1) & 0x1fff);
4163
+ let h6 = h[6] + (((t4 >>> 14) | (t5 << 2)) & 0x1fff);
4164
+ let h7 = h[7] + (((t5 >>> 11) | (t6 << 5)) & 0x1fff);
4165
+ let h8 = h[8] + (((t6 >>> 8) | (t7 << 8)) & 0x1fff);
4166
+ let h9 = h[9] + ((t7 >>> 5) | hibit);
4167
+ let c = 0;
4168
+ let d0 = c + h0 * r0 + h1 * (5 * r9) + h2 * (5 * r8) + h3 * (5 * r7) + h4 * (5 * r6);
4169
+ c = d0 >>> 13;
4170
+ d0 &= 0x1fff;
4171
+ d0 += h5 * (5 * r5) + h6 * (5 * r4) + h7 * (5 * r3) + h8 * (5 * r2) + h9 * (5 * r1);
4172
+ c += d0 >>> 13;
4173
+ d0 &= 0x1fff;
4174
+ let d1 = c + h0 * r1 + h1 * r0 + h2 * (5 * r9) + h3 * (5 * r8) + h4 * (5 * r7);
4175
+ c = d1 >>> 13;
4176
+ d1 &= 0x1fff;
4177
+ d1 += h5 * (5 * r6) + h6 * (5 * r5) + h7 * (5 * r4) + h8 * (5 * r3) + h9 * (5 * r2);
4178
+ c += d1 >>> 13;
4179
+ d1 &= 0x1fff;
4180
+ let d2 = c + h0 * r2 + h1 * r1 + h2 * r0 + h3 * (5 * r9) + h4 * (5 * r8);
4181
+ c = d2 >>> 13;
4182
+ d2 &= 0x1fff;
4183
+ d2 += h5 * (5 * r7) + h6 * (5 * r6) + h7 * (5 * r5) + h8 * (5 * r4) + h9 * (5 * r3);
4184
+ c += d2 >>> 13;
4185
+ d2 &= 0x1fff;
4186
+ let d3 = c + h0 * r3 + h1 * r2 + h2 * r1 + h3 * r0 + h4 * (5 * r9);
4187
+ c = d3 >>> 13;
4188
+ d3 &= 0x1fff;
4189
+ d3 += h5 * (5 * r8) + h6 * (5 * r7) + h7 * (5 * r6) + h8 * (5 * r5) + h9 * (5 * r4);
4190
+ c += d3 >>> 13;
4191
+ d3 &= 0x1fff;
4192
+ let d4 = c + h0 * r4 + h1 * r3 + h2 * r2 + h3 * r1 + h4 * r0;
4193
+ c = d4 >>> 13;
4194
+ d4 &= 0x1fff;
4195
+ d4 += h5 * (5 * r9) + h6 * (5 * r8) + h7 * (5 * r7) + h8 * (5 * r6) + h9 * (5 * r5);
4196
+ c += d4 >>> 13;
4197
+ d4 &= 0x1fff;
4198
+ let d5 = c + h0 * r5 + h1 * r4 + h2 * r3 + h3 * r2 + h4 * r1;
4199
+ c = d5 >>> 13;
4200
+ d5 &= 0x1fff;
4201
+ d5 += h5 * r0 + h6 * (5 * r9) + h7 * (5 * r8) + h8 * (5 * r7) + h9 * (5 * r6);
4202
+ c += d5 >>> 13;
4203
+ d5 &= 0x1fff;
4204
+ let d6 = c + h0 * r6 + h1 * r5 + h2 * r4 + h3 * r3 + h4 * r2;
4205
+ c = d6 >>> 13;
4206
+ d6 &= 0x1fff;
4207
+ d6 += h5 * r1 + h6 * r0 + h7 * (5 * r9) + h8 * (5 * r8) + h9 * (5 * r7);
4208
+ c += d6 >>> 13;
4209
+ d6 &= 0x1fff;
4210
+ let d7 = c + h0 * r7 + h1 * r6 + h2 * r5 + h3 * r4 + h4 * r3;
4211
+ c = d7 >>> 13;
4212
+ d7 &= 0x1fff;
4213
+ d7 += h5 * r2 + h6 * r1 + h7 * r0 + h8 * (5 * r9) + h9 * (5 * r8);
4214
+ c += d7 >>> 13;
4215
+ d7 &= 0x1fff;
4216
+ let d8 = c + h0 * r8 + h1 * r7 + h2 * r6 + h3 * r5 + h4 * r4;
4217
+ c = d8 >>> 13;
4218
+ d8 &= 0x1fff;
4219
+ d8 += h5 * r3 + h6 * r2 + h7 * r1 + h8 * r0 + h9 * (5 * r9);
4220
+ c += d8 >>> 13;
4221
+ d8 &= 0x1fff;
4222
+ let d9 = c + h0 * r9 + h1 * r8 + h2 * r7 + h3 * r6 + h4 * r5;
4223
+ c = d9 >>> 13;
4224
+ d9 &= 0x1fff;
4225
+ d9 += h5 * r4 + h6 * r3 + h7 * r2 + h8 * r1 + h9 * r0;
4226
+ c += d9 >>> 13;
4227
+ d9 &= 0x1fff;
4228
+ c = ((c << 2) + c) | 0;
4229
+ c = (c + d0) | 0;
4230
+ d0 = c & 0x1fff;
4231
+ c = c >>> 13;
4232
+ d1 += c;
4233
+ h[0] = d0;
4234
+ h[1] = d1;
4235
+ h[2] = d2;
4236
+ h[3] = d3;
4237
+ h[4] = d4;
4238
+ h[5] = d5;
4239
+ h[6] = d6;
4240
+ h[7] = d7;
4241
+ h[8] = d8;
4242
+ h[9] = d9;
4243
+ }
4244
+ finalize() {
4245
+ const { h, pad } = this;
4246
+ const g = new Uint16Array(10);
4247
+ let c = h[1] >>> 13;
4248
+ h[1] &= 0x1fff;
4249
+ for (let i = 2; i < 10; i++) {
4250
+ h[i] += c;
4251
+ c = h[i] >>> 13;
4252
+ h[i] &= 0x1fff;
4253
+ }
4254
+ h[0] += c * 5;
4255
+ c = h[0] >>> 13;
4256
+ h[0] &= 0x1fff;
4257
+ h[1] += c;
4258
+ c = h[1] >>> 13;
4259
+ h[1] &= 0x1fff;
4260
+ h[2] += c;
4261
+ g[0] = h[0] + 5;
4262
+ c = g[0] >>> 13;
4263
+ g[0] &= 0x1fff;
4264
+ for (let i = 1; i < 10; i++) {
4265
+ g[i] = h[i] + c;
4266
+ c = g[i] >>> 13;
4267
+ g[i] &= 0x1fff;
4268
+ }
4269
+ g[9] -= 1 << 13;
4270
+ let mask = (c ^ 1) - 1;
4271
+ for (let i = 0; i < 10; i++)
4272
+ g[i] &= mask;
4273
+ mask = ~mask;
4274
+ for (let i = 0; i < 10; i++)
4275
+ h[i] = (h[i] & mask) | g[i];
4276
+ h[0] = (h[0] | (h[1] << 13)) & 0xffff;
4277
+ h[1] = ((h[1] >>> 3) | (h[2] << 10)) & 0xffff;
4278
+ h[2] = ((h[2] >>> 6) | (h[3] << 7)) & 0xffff;
4279
+ h[3] = ((h[3] >>> 9) | (h[4] << 4)) & 0xffff;
4280
+ h[4] = ((h[4] >>> 12) | (h[5] << 1) | (h[6] << 14)) & 0xffff;
4281
+ h[5] = ((h[6] >>> 2) | (h[7] << 11)) & 0xffff;
4282
+ h[6] = ((h[7] >>> 5) | (h[8] << 8)) & 0xffff;
4283
+ h[7] = ((h[8] >>> 8) | (h[9] << 5)) & 0xffff;
4284
+ let f = h[0] + pad[0];
4285
+ h[0] = f & 0xffff;
4286
+ for (let i = 1; i < 8; i++) {
4287
+ f = (((h[i] + pad[i]) | 0) + (f >>> 16)) | 0;
4288
+ h[i] = f & 0xffff;
4289
+ }
4290
+ clean(g);
4291
+ }
4292
+ update(data) {
4293
+ aexists(this);
4294
+ data = toBytes(data);
4295
+ abytes(data);
4296
+ const { buffer, blockLen } = this;
4297
+ const len = data.length;
4298
+ for (let pos = 0; pos < len;) {
4299
+ const take = Math.min(blockLen - this.pos, len - pos);
4300
+ // Fast path: we have at least one block in input
4301
+ if (take === blockLen) {
4302
+ for (; blockLen <= len - pos; pos += blockLen)
4303
+ this.process(data, pos);
4304
+ continue;
4305
+ }
4306
+ buffer.set(data.subarray(pos, pos + take), this.pos);
4307
+ this.pos += take;
4308
+ pos += take;
4309
+ if (this.pos === blockLen) {
4310
+ this.process(buffer, 0, false);
4311
+ this.pos = 0;
4312
+ }
4313
+ }
4314
+ return this;
4315
+ }
4316
+ destroy() {
4317
+ clean(this.h, this.r, this.buffer, this.pad);
4318
+ }
4319
+ digestInto(out) {
4320
+ aexists(this);
4321
+ aoutput(out, this);
4322
+ this.finished = true;
4323
+ const { buffer, h } = this;
4324
+ let { pos } = this;
4325
+ if (pos) {
4326
+ buffer[pos++] = 1;
4327
+ for (; pos < 16; pos++)
4328
+ buffer[pos] = 0;
4329
+ this.process(buffer, 0, true);
4330
+ }
4331
+ this.finalize();
4332
+ let opos = 0;
4333
+ for (let i = 0; i < 8; i++) {
4334
+ out[opos++] = h[i] >>> 0;
4335
+ out[opos++] = h[i] >>> 8;
4336
+ }
4337
+ return out;
4338
+ }
4339
+ digest() {
4340
+ const { buffer, outputLen } = this;
4341
+ this.digestInto(buffer);
4342
+ const res = buffer.slice(0, outputLen);
4343
+ this.destroy();
4344
+ return res;
4345
+ }
4346
+ }
4347
+ function wrapConstructorWithKey(hashCons) {
4348
+ const hashC = (msg, key) => hashCons(key).update(toBytes(msg)).digest();
4349
+ const tmp = hashCons(new Uint8Array(32));
4350
+ hashC.outputLen = tmp.outputLen;
4351
+ hashC.blockLen = tmp.blockLen;
4352
+ hashC.create = (key) => hashCons(key);
4353
+ return hashC;
4354
+ }
4355
+ /** Poly1305 MAC from RFC 8439. */
4356
+ const poly1305 = wrapConstructorWithKey((key) => new Poly1305(key));
4357
+
4358
+ /**
4359
+ * [ChaCha20](https://cr.yp.to/chacha.html) stream cipher, released
4360
+ * in 2008. Developed after Salsa20, ChaCha aims to increase diffusion per round.
4361
+ * It was standardized in [RFC 8439](https://datatracker.ietf.org/doc/html/rfc8439) and
4362
+ * is now used in TLS 1.3.
4363
+ *
4364
+ * [XChaCha20](https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-xchacha)
4365
+ * extended-nonce variant is also provided. Similar to XSalsa, it's safe to use with
4366
+ * randomly-generated nonces.
4367
+ *
4368
+ * Check out [PDF](http://cr.yp.to/chacha/chacha-20080128.pdf) and
4369
+ * [wiki](https://en.wikipedia.org/wiki/Salsa20).
4370
+ * @module
4371
+ */
4372
+ /**
4373
+ * ChaCha core function.
4374
+ */
4375
+ // prettier-ignore
4376
+ function chachaCore(s, k, n, out, cnt, rounds = 20) {
4377
+ let y00 = s[0], y01 = s[1], y02 = s[2], y03 = s[3], // "expa" "nd 3" "2-by" "te k"
4378
+ y04 = k[0], y05 = k[1], y06 = k[2], y07 = k[3], // Key Key Key Key
4379
+ y08 = k[4], y09 = k[5], y10 = k[6], y11 = k[7], // Key Key Key Key
4380
+ y12 = cnt, y13 = n[0], y14 = n[1], y15 = n[2]; // Counter Counter Nonce Nonce
4381
+ // Save state to temporary variables
4382
+ let x00 = y00, x01 = y01, x02 = y02, x03 = y03, x04 = y04, x05 = y05, x06 = y06, x07 = y07, x08 = y08, x09 = y09, x10 = y10, x11 = y11, x12 = y12, x13 = y13, x14 = y14, x15 = y15;
4383
+ for (let r = 0; r < rounds; r += 2) {
4384
+ x00 = (x00 + x04) | 0;
4385
+ x12 = rotl(x12 ^ x00, 16);
4386
+ x08 = (x08 + x12) | 0;
4387
+ x04 = rotl(x04 ^ x08, 12);
4388
+ x00 = (x00 + x04) | 0;
4389
+ x12 = rotl(x12 ^ x00, 8);
4390
+ x08 = (x08 + x12) | 0;
4391
+ x04 = rotl(x04 ^ x08, 7);
4392
+ x01 = (x01 + x05) | 0;
4393
+ x13 = rotl(x13 ^ x01, 16);
4394
+ x09 = (x09 + x13) | 0;
4395
+ x05 = rotl(x05 ^ x09, 12);
4396
+ x01 = (x01 + x05) | 0;
4397
+ x13 = rotl(x13 ^ x01, 8);
4398
+ x09 = (x09 + x13) | 0;
4399
+ x05 = rotl(x05 ^ x09, 7);
4400
+ x02 = (x02 + x06) | 0;
4401
+ x14 = rotl(x14 ^ x02, 16);
4402
+ x10 = (x10 + x14) | 0;
4403
+ x06 = rotl(x06 ^ x10, 12);
4404
+ x02 = (x02 + x06) | 0;
4405
+ x14 = rotl(x14 ^ x02, 8);
4406
+ x10 = (x10 + x14) | 0;
4407
+ x06 = rotl(x06 ^ x10, 7);
4408
+ x03 = (x03 + x07) | 0;
4409
+ x15 = rotl(x15 ^ x03, 16);
4410
+ x11 = (x11 + x15) | 0;
4411
+ x07 = rotl(x07 ^ x11, 12);
4412
+ x03 = (x03 + x07) | 0;
4413
+ x15 = rotl(x15 ^ x03, 8);
4414
+ x11 = (x11 + x15) | 0;
4415
+ x07 = rotl(x07 ^ x11, 7);
4416
+ x00 = (x00 + x05) | 0;
4417
+ x15 = rotl(x15 ^ x00, 16);
4418
+ x10 = (x10 + x15) | 0;
4419
+ x05 = rotl(x05 ^ x10, 12);
4420
+ x00 = (x00 + x05) | 0;
4421
+ x15 = rotl(x15 ^ x00, 8);
4422
+ x10 = (x10 + x15) | 0;
4423
+ x05 = rotl(x05 ^ x10, 7);
4424
+ x01 = (x01 + x06) | 0;
4425
+ x12 = rotl(x12 ^ x01, 16);
4426
+ x11 = (x11 + x12) | 0;
4427
+ x06 = rotl(x06 ^ x11, 12);
4428
+ x01 = (x01 + x06) | 0;
4429
+ x12 = rotl(x12 ^ x01, 8);
4430
+ x11 = (x11 + x12) | 0;
4431
+ x06 = rotl(x06 ^ x11, 7);
4432
+ x02 = (x02 + x07) | 0;
4433
+ x13 = rotl(x13 ^ x02, 16);
4434
+ x08 = (x08 + x13) | 0;
4435
+ x07 = rotl(x07 ^ x08, 12);
4436
+ x02 = (x02 + x07) | 0;
4437
+ x13 = rotl(x13 ^ x02, 8);
4438
+ x08 = (x08 + x13) | 0;
4439
+ x07 = rotl(x07 ^ x08, 7);
4440
+ x03 = (x03 + x04) | 0;
4441
+ x14 = rotl(x14 ^ x03, 16);
4442
+ x09 = (x09 + x14) | 0;
4443
+ x04 = rotl(x04 ^ x09, 12);
4444
+ x03 = (x03 + x04) | 0;
4445
+ x14 = rotl(x14 ^ x03, 8);
4446
+ x09 = (x09 + x14) | 0;
4447
+ x04 = rotl(x04 ^ x09, 7);
4448
+ }
4449
+ // Write output
4450
+ let oi = 0;
4451
+ out[oi++] = (y00 + x00) | 0;
4452
+ out[oi++] = (y01 + x01) | 0;
4453
+ out[oi++] = (y02 + x02) | 0;
4454
+ out[oi++] = (y03 + x03) | 0;
4455
+ out[oi++] = (y04 + x04) | 0;
4456
+ out[oi++] = (y05 + x05) | 0;
4457
+ out[oi++] = (y06 + x06) | 0;
4458
+ out[oi++] = (y07 + x07) | 0;
4459
+ out[oi++] = (y08 + x08) | 0;
4460
+ out[oi++] = (y09 + x09) | 0;
4461
+ out[oi++] = (y10 + x10) | 0;
4462
+ out[oi++] = (y11 + x11) | 0;
4463
+ out[oi++] = (y12 + x12) | 0;
4464
+ out[oi++] = (y13 + x13) | 0;
4465
+ out[oi++] = (y14 + x14) | 0;
4466
+ out[oi++] = (y15 + x15) | 0;
4467
+ }
4468
+ /**
4469
+ * ChaCha stream cipher. Conforms to RFC 8439 (IETF, TLS). 12-byte nonce, 4-byte counter.
4470
+ * With 12-byte nonce, it's not safe to use fill it with random (CSPRNG), due to collision chance.
4471
+ */
4472
+ const chacha20 = /* @__PURE__ */ createCipher(chachaCore, {
4473
+ counterRight: false,
4474
+ counterLength: 4,
4475
+ allowShortKeys: false,
4476
+ });
4477
+ const ZEROS16 = /* @__PURE__ */ new Uint8Array(16);
4478
+ // Pad to digest size with zeros
4479
+ const updatePadded = (h, msg) => {
4480
+ h.update(msg);
4481
+ const left = msg.length % 16;
4482
+ if (left)
4483
+ h.update(ZEROS16.subarray(left));
4484
+ };
4485
+ const ZEROS32 = /* @__PURE__ */ new Uint8Array(32);
4486
+ function computeTag(fn, key, nonce, data, AAD) {
4487
+ const authKey = fn(key, nonce, ZEROS32);
4488
+ const h = poly1305.create(authKey);
4489
+ if (AAD)
4490
+ updatePadded(h, AAD);
4491
+ updatePadded(h, data);
4492
+ const num = u64Lengths(data.length, AAD ? AAD.length : 0, true);
4493
+ h.update(num);
4494
+ const res = h.digest();
4495
+ clean(authKey, num);
4496
+ return res;
4497
+ }
4498
+ /**
4499
+ * AEAD algorithm from RFC 8439.
4500
+ * Salsa20 and chacha (RFC 8439) use poly1305 differently.
4501
+ * We could have composed them similar to:
4502
+ * https://github.com/paulmillr/scure-base/blob/b266c73dde977b1dd7ef40ef7a23cc15aab526b3/index.ts#L250
4503
+ * But it's hard because of authKey:
4504
+ * In salsa20, authKey changes position in salsa stream.
4505
+ * In chacha, authKey can't be computed inside computeTag, it modifies the counter.
4506
+ */
4507
+ const _poly1305_aead = (xorStream) => (key, nonce, AAD) => {
4508
+ const tagLength = 16;
4509
+ return {
4510
+ encrypt(plaintext, output) {
4511
+ const plength = plaintext.length;
4512
+ output = getOutput(plength + tagLength, output, false);
4513
+ output.set(plaintext);
4514
+ const oPlain = output.subarray(0, -tagLength);
4515
+ xorStream(key, nonce, oPlain, oPlain, 1);
4516
+ const tag = computeTag(xorStream, key, nonce, oPlain, AAD);
4517
+ output.set(tag, plength); // append tag
4518
+ clean(tag);
4519
+ return output;
4520
+ },
4521
+ decrypt(ciphertext, output) {
4522
+ output = getOutput(ciphertext.length - tagLength, output, false);
4523
+ const data = ciphertext.subarray(0, -tagLength);
4524
+ const passedTag = ciphertext.subarray(-tagLength);
4525
+ const tag = computeTag(xorStream, key, nonce, data, AAD);
4526
+ if (!equalBytes(passedTag, tag))
4527
+ throw new Error('invalid tag');
4528
+ output.set(ciphertext.subarray(0, -tagLength));
4529
+ xorStream(key, nonce, output, output, 1); // start stream with i=1
4530
+ clean(tag);
4531
+ return output;
4532
+ },
4533
+ };
4534
+ };
4535
+ /**
4536
+ * ChaCha20-Poly1305 from RFC 8439.
4537
+ *
4538
+ * Unsafe to use random nonces under the same key, due to collision chance.
4539
+ * Prefer XChaCha instead.
4540
+ */
4541
+ const chacha20poly1305 = /* @__PURE__ */ wrapCipher({ blockSize: 64, nonceLength: 12, tagLength: 16 }, _poly1305_aead(chacha20));
4542
+
4543
+ /**
4544
+ * HKDF (RFC 5869): extract + expand in one step.
4545
+ * See https://soatok.blog/2021/11/17/understanding-hkdf/.
4546
+ * @module
4547
+ */
4548
+ /**
4549
+ * HKDF-extract from spec. Less important part. `HKDF-Extract(IKM, salt) -> PRK`
4550
+ * Arguments position differs from spec (IKM is first one, since it is not optional)
4551
+ * @param hash - hash function that would be used (e.g. sha256)
4552
+ * @param ikm - input keying material, the initial key
4553
+ * @param salt - optional salt value (a non-secret random value)
4554
+ */
4555
+ function extract(hash, ikm, salt) {
4556
+ ahash(hash);
4557
+ // NOTE: some libraries treat zero-length array as 'not provided';
4558
+ // we don't, since we have undefined as 'not provided'
4559
+ // https://github.com/RustCrypto/KDFs/issues/15
4560
+ if (salt === undefined)
4561
+ salt = new Uint8Array(hash.outputLen);
4562
+ return hmac(hash, toBytes$1(salt), toBytes$1(ikm));
4563
+ }
4564
+ const HKDF_COUNTER = /* @__PURE__ */ Uint8Array.from([0]);
4565
+ const EMPTY_BUFFER = /* @__PURE__ */ Uint8Array.of();
4566
+ /**
4567
+ * HKDF-expand from the spec. The most important part. `HKDF-Expand(PRK, info, L) -> OKM`
4568
+ * @param hash - hash function that would be used (e.g. sha256)
4569
+ * @param prk - a pseudorandom key of at least HashLen octets (usually, the output from the extract step)
4570
+ * @param info - optional context and application specific information (can be a zero-length string)
4571
+ * @param length - length of output keying material in bytes
4572
+ */
4573
+ function expand(hash, prk, info, length = 32) {
4574
+ ahash(hash);
4575
+ anumber$1(length);
4576
+ const olen = hash.outputLen;
4577
+ if (length > 255 * olen)
4578
+ throw new Error('Length should be <= 255*HashLen');
4579
+ const blocks = Math.ceil(length / olen);
4580
+ if (info === undefined)
4581
+ info = EMPTY_BUFFER;
4582
+ // first L(ength) octets of T
4583
+ const okm = new Uint8Array(blocks * olen);
4584
+ // Re-use HMAC instance between blocks
4585
+ const HMAC = hmac.create(hash, prk);
4586
+ const HMACTmp = HMAC._cloneInto();
4587
+ const T = new Uint8Array(HMAC.outputLen);
4588
+ for (let counter = 0; counter < blocks; counter++) {
4589
+ HKDF_COUNTER[0] = counter + 1;
4590
+ // T(0) = empty string (zero length)
4591
+ // T(N) = HMAC-Hash(PRK, T(N-1) | info | N)
4592
+ HMACTmp.update(counter === 0 ? EMPTY_BUFFER : T)
4593
+ .update(info)
4594
+ .update(HKDF_COUNTER)
4595
+ .digestInto(T);
4596
+ okm.set(T, olen * counter);
4597
+ HMAC._cloneInto(HMACTmp);
4598
+ }
4599
+ HMAC.destroy();
4600
+ HMACTmp.destroy();
4601
+ clean$1(T, HKDF_COUNTER);
4602
+ return okm.slice(0, length);
4603
+ }
4604
+ /**
4605
+ * HKDF (RFC 5869): derive keys from an initial input.
4606
+ * Combines hkdf_extract + hkdf_expand in one step
4607
+ * @param hash - hash function that would be used (e.g. sha256)
4608
+ * @param ikm - input keying material, the initial key
4609
+ * @param salt - optional salt value (a non-secret random value)
4610
+ * @param info - optional context and application specific information (can be a zero-length string)
4611
+ * @param length - length of output keying material in bytes
4612
+ * @example
4613
+ * import { hkdf } from '@noble/hashes/hkdf';
4614
+ * import { sha256 } from '@noble/hashes/sha2';
4615
+ * import { randomBytes } from '@noble/hashes/utils';
4616
+ * const inputKey = randomBytes(32);
4617
+ * const salt = randomBytes(32);
4618
+ * const info = 'application-key';
4619
+ * const hk1 = hkdf(sha256, inputKey, salt, info, 32);
4620
+ */
4621
+ const hkdf = (hash, ikm, salt, info, length) => expand(hash, extract(hash, ikm, salt), info, length);
4622
+
4623
+ /**
4624
+ * NIP-44 Encryption implementation.
4625
+ * ChaCha20-Poly1305 AEAD encryption with HKDF key derivation.
4626
+ * Works in both Node.js and browser environments.
4627
+ * See: https://github.com/nostr-protocol/nips/blob/master/44.md
4628
+ */
4629
+ /** NIP-44 version byte */
4630
+ const VERSION = 0x02;
4631
+ /** Nonce size for XChaCha20 (24 bytes) */
4632
+ const NONCE_SIZE = 24;
4633
+ /** MAC size for Poly1305 (16 bytes) */
4634
+ const MAC_SIZE = 16;
4635
+ /** Minimum padded length */
4636
+ const MIN_PADDED_LEN = 32;
4637
+ /** Maximum message length */
4638
+ const MAX_MESSAGE_LEN = 65535;
4639
+ /** HKDF salt for conversation key derivation */
4640
+ const HKDF_SALT = new TextEncoder().encode('nip44-v2');
4641
+ /**
4642
+ * Derive conversation key using ECDH + HKDF.
4643
+ * NIP-44 uses sorted public keys as salt for HKDF.
4644
+ *
4645
+ * @param myPrivateKey 32-byte private key
4646
+ * @param theirPublicKey 32-byte x-only public key
4647
+ * @returns 32-byte conversation key
4648
+ */
4649
+ function deriveConversationKey(myPrivateKey, theirPublicKey) {
4650
+ if (myPrivateKey.length !== 32) {
4651
+ throw new Error('Private key must be 32 bytes');
4652
+ }
4653
+ if (theirPublicKey.length !== 32) {
4654
+ throw new Error('Public key must be 32 bytes');
4655
+ }
4656
+ // Get shared X coordinate via ECDH
4657
+ const sharedX = computeSharedX(myPrivateKey, theirPublicKey);
4658
+ // Get my public key
4659
+ const myPublicKey = secp256k1.getPublicKey(myPrivateKey, true).slice(1); // Remove prefix
4660
+ // Create salt from sorted public keys
4661
+ const salt = createSortedKeysSalt(myPublicKey, theirPublicKey);
4662
+ // Use HKDF to derive conversation key
4663
+ return hkdf(sha256, sharedX, salt, HKDF_SALT, 32);
4664
+ }
4665
+ /**
4666
+ * Derive conversation key from hex-encoded keys.
4667
+ */
4668
+ function deriveConversationKeyHex(myPrivateKeyHex, theirPublicKeyHex) {
4669
+ const myPrivateKey = hexToBytes(myPrivateKeyHex);
4670
+ const theirPublicKey = hexToBytes(theirPublicKeyHex);
4671
+ return bytesToHex(deriveConversationKey(myPrivateKey, theirPublicKey));
4672
+ }
4673
+ /**
4674
+ * Compute ECDH shared X coordinate.
4675
+ */
4676
+ function computeSharedX(myPrivateKey, theirPublicKey) {
4677
+ // Reconstruct full public key (add 02 prefix for even y)
4678
+ const fullPublicKey = new Uint8Array(33);
4679
+ fullPublicKey[0] = 0x02;
4680
+ fullPublicKey.set(theirPublicKey, 1);
4681
+ // Compute shared point
4682
+ const sharedPoint = secp256k1.getSharedSecret(myPrivateKey, fullPublicKey);
4683
+ // Extract X coordinate (skip 04 prefix, take first 32 bytes)
4684
+ return sharedPoint.slice(1, 33);
4685
+ }
4686
+ /**
4687
+ * Create salt from lexicographically sorted public keys.
4688
+ */
4689
+ function createSortedKeysSalt(pk1, pk2) {
4690
+ const cmp = compareBytes(pk1, pk2);
4691
+ if (cmp <= 0) {
4692
+ return concatBytes(pk1, pk2);
4693
+ }
4694
+ else {
4695
+ return concatBytes(pk2, pk1);
4696
+ }
4697
+ }
4698
+ /**
4699
+ * Compare two byte arrays lexicographically.
4700
+ */
4701
+ function compareBytes(a, b) {
4702
+ const len = Math.min(a.length, b.length);
4703
+ for (let i = 0; i < len; i++) {
4704
+ const diff = a[i] - b[i];
4705
+ if (diff !== 0)
4706
+ return diff;
4707
+ }
4708
+ return a.length - b.length;
4709
+ }
4710
+ /**
4711
+ * Calculate padded length according to NIP-44 spec.
4712
+ * Uses power-of-2 chunk padding to hide message length.
4713
+ */
4714
+ function calcPaddedLen(unpaddedLen) {
4715
+ if (unpaddedLen <= 0) {
4716
+ throw new Error('Message too short');
4717
+ }
4718
+ if (unpaddedLen > MAX_MESSAGE_LEN) {
4719
+ throw new Error('Message too long');
4720
+ }
4721
+ if (unpaddedLen <= 32) {
4722
+ return 32;
4723
+ }
4724
+ // Find next power of 2
4725
+ const nextPow2 = 1 << Math.ceil(Math.log2(unpaddedLen));
4726
+ const chunk = Math.max(32, nextPow2 >> 3);
4727
+ return Math.ceil(unpaddedLen / chunk) * chunk;
4728
+ }
4729
+ /**
4730
+ * Pad message according to NIP-44 spec.
4731
+ * Format: length(2 bytes big-endian) || message || padding
4732
+ */
4733
+ function pad(message) {
4734
+ const len = message.length;
4735
+ if (len < 1) {
4736
+ throw new Error('Message too short');
4737
+ }
4738
+ if (len > MAX_MESSAGE_LEN) {
4739
+ throw new Error('Message too long');
4740
+ }
4741
+ const paddedLen = calcPaddedLen(len);
4742
+ const result = new Uint8Array(2 + paddedLen);
4743
+ // Big-endian length prefix
4744
+ result[0] = (len >> 8) & 0xff;
4745
+ result[1] = len & 0xff;
4746
+ // Copy message
4747
+ result.set(message, 2);
4748
+ // Remaining bytes are already zero (padding)
4749
+ return result;
4750
+ }
4751
+ /**
4752
+ * Unpad message according to NIP-44 spec.
4753
+ */
4754
+ function unpad(padded) {
4755
+ if (padded.length < 2 + MIN_PADDED_LEN) {
4756
+ throw new Error('Padded message too short');
4757
+ }
4758
+ // Read big-endian length prefix
4759
+ const len = (padded[0] << 8) | padded[1];
4760
+ if (len < 1 || len > MAX_MESSAGE_LEN) {
4761
+ throw new Error(`Invalid message length: ${len}`);
4762
+ }
4763
+ const expectedPaddedLen = calcPaddedLen(len);
4764
+ if (padded.length !== 2 + expectedPaddedLen) {
4765
+ throw new Error('Invalid padding');
4766
+ }
4767
+ return padded.slice(2, 2 + len);
4768
+ }
4769
+ /**
4770
+ * Encrypt a message using NIP-44.
4771
+ *
4772
+ * @param message Plaintext message
4773
+ * @param myPrivateKey Sender's 32-byte private key
4774
+ * @param theirPublicKey Recipient's 32-byte x-only public key
4775
+ * @returns Base64-encoded encrypted payload
4776
+ */
4777
+ function encrypt(message, myPrivateKey, theirPublicKey) {
4778
+ const conversationKey = deriveConversationKey(myPrivateKey, theirPublicKey);
4779
+ return encryptWithKey(message, conversationKey);
4780
+ }
4781
+ /**
4782
+ * Encrypt a message using a pre-derived conversation key.
4783
+ *
4784
+ * @param message Plaintext message
4785
+ * @param conversationKey 32-byte conversation key
4786
+ * @returns Base64-encoded encrypted payload
4787
+ */
4788
+ function encryptWithKey(message, conversationKey) {
4789
+ const encoder = new TextEncoder();
4790
+ const messageBytes = encoder.encode(message);
4791
+ if (messageBytes.length > MAX_MESSAGE_LEN) {
4792
+ throw new Error(`Message too long (max ${MAX_MESSAGE_LEN} bytes)`);
4793
+ }
4794
+ // Pad the message
4795
+ const padded = pad(messageBytes);
4796
+ // Generate random nonce (24 bytes for XChaCha20)
4797
+ const nonce = randomBytes(NONCE_SIZE);
4798
+ // Derive message keys using HKDF
4799
+ const messageKey = hkdf(sha256, conversationKey, nonce, new Uint8Array(0), 76);
4800
+ const chachaKey = messageKey.slice(0, 32);
4801
+ const chachaNonce = messageKey.slice(32, 44);
4802
+ // Encrypt with ChaCha20-Poly1305
4803
+ const cipher = chacha20poly1305(chachaKey, chachaNonce);
4804
+ const ciphertext = cipher.encrypt(padded);
4805
+ // Assemble payload: version(1) || nonce(24) || ciphertext+mac
4806
+ const payload = new Uint8Array(1 + NONCE_SIZE + ciphertext.length);
4807
+ payload[0] = VERSION;
4808
+ payload.set(nonce, 1);
4809
+ payload.set(ciphertext, 1 + NONCE_SIZE);
4810
+ return toBase64(payload);
4811
+ }
4812
+ /**
4813
+ * Decrypt a NIP-44 encrypted message.
4814
+ *
4815
+ * @param encryptedContent Base64-encoded encrypted payload
4816
+ * @param myPrivateKey Recipient's 32-byte private key
4817
+ * @param theirPublicKey Sender's 32-byte x-only public key
4818
+ * @returns Decrypted plaintext message
4819
+ */
4820
+ function decrypt(encryptedContent, myPrivateKey, theirPublicKey) {
4821
+ const conversationKey = deriveConversationKey(myPrivateKey, theirPublicKey);
4822
+ return decryptWithKey(encryptedContent, conversationKey);
4823
+ }
4824
+ /**
4825
+ * Decrypt a message using a pre-derived conversation key.
4826
+ *
4827
+ * @param encryptedContent Base64-encoded encrypted payload
4828
+ * @param conversationKey 32-byte conversation key
4829
+ * @returns Decrypted plaintext message
4830
+ */
4831
+ function decryptWithKey(encryptedContent, conversationKey) {
4832
+ const payload = fromBase64(encryptedContent);
4833
+ if (payload.length < 1 + NONCE_SIZE + MIN_PADDED_LEN + MAC_SIZE) {
4834
+ throw new Error('Payload too short');
4835
+ }
4836
+ // Check version
4837
+ if (payload[0] !== VERSION) {
4838
+ throw new Error(`Unsupported NIP-44 version: ${payload[0]}`);
4839
+ }
4840
+ // Extract components
4841
+ const nonce = payload.slice(1, 1 + NONCE_SIZE);
4842
+ const ciphertext = payload.slice(1 + NONCE_SIZE);
4843
+ // Derive message keys
4844
+ const messageKey = hkdf(sha256, conversationKey, nonce, new Uint8Array(0), 76);
4845
+ const chachaKey = messageKey.slice(0, 32);
4846
+ const chachaNonce = messageKey.slice(32, 44);
4847
+ // Decrypt with ChaCha20-Poly1305
4848
+ const cipher = chacha20poly1305(chachaKey, chachaNonce);
4849
+ const padded = cipher.decrypt(ciphertext);
4850
+ // Unpad
4851
+ const messageBytes = unpad(padded);
4852
+ const decoder = new TextDecoder();
4853
+ return decoder.decode(messageBytes);
4854
+ }
4855
+ /**
4856
+ * Encrypt a message using hex-encoded keys.
4857
+ */
4858
+ function encryptHex(message, myPrivateKeyHex, theirPublicKeyHex) {
4859
+ const myPrivateKey = hexToBytes(myPrivateKeyHex);
4860
+ const theirPublicKey = hexToBytes(theirPublicKeyHex);
4861
+ return encrypt(message, myPrivateKey, theirPublicKey);
4862
+ }
4863
+ /**
4864
+ * Decrypt a message using hex-encoded keys.
4865
+ */
4866
+ function decryptHex(encryptedContent, myPrivateKeyHex, theirPublicKeyHex) {
4867
+ const myPrivateKey = hexToBytes(myPrivateKeyHex);
4868
+ const theirPublicKey = hexToBytes(theirPublicKeyHex);
4869
+ return decrypt(encryptedContent, myPrivateKey, theirPublicKey);
4870
+ }
4871
+ /**
4872
+ * Convert a Uint8Array to base64 string (browser and Node.js compatible).
4873
+ */
4874
+ function toBase64(bytes) {
4875
+ if (typeof Buffer !== 'undefined') {
4876
+ return Buffer.from(bytes).toString('base64');
4877
+ }
4878
+ // Browser environment
4879
+ let binary = '';
4880
+ for (let i = 0; i < bytes.length; i++) {
4881
+ binary += String.fromCharCode(bytes[i]);
4882
+ }
4883
+ return btoa(binary);
4884
+ }
4885
+ /**
4886
+ * Convert a base64 string to Uint8Array (browser and Node.js compatible).
4887
+ */
4888
+ function fromBase64(base64) {
4889
+ if (typeof Buffer !== 'undefined') {
4890
+ return new Uint8Array(Buffer.from(base64, 'base64'));
4891
+ }
4892
+ // Browser environment
4893
+ const binary = atob(base64);
4894
+ const bytes = new Uint8Array(binary.length);
4895
+ for (let i = 0; i < binary.length; i++) {
4896
+ bytes[i] = binary.charCodeAt(i);
4897
+ }
4898
+ return bytes;
4899
+ }
4900
+
4901
+ var nip44 = /*#__PURE__*/Object.freeze({
4902
+ __proto__: null,
4903
+ VERSION: VERSION,
4904
+ calcPaddedLen: calcPaddedLen,
4905
+ decrypt: decrypt,
4906
+ decryptHex: decryptHex,
4907
+ decryptWithKey: decryptWithKey,
4908
+ deriveConversationKey: deriveConversationKey,
4909
+ deriveConversationKeyHex: deriveConversationKeyHex,
3701
4910
  encrypt: encrypt,
3702
- encryptHex: encryptHex
4911
+ encryptHex: encryptHex,
4912
+ encryptWithKey: encryptWithKey,
4913
+ pad: pad,
4914
+ unpad: unpad
3703
4915
  });
3704
4916
 
3705
4917
  /**
@@ -3863,7 +5075,7 @@
3863
5075
  */
3864
5076
  async encrypt(message, recipientPublicKey) {
3865
5077
  this.ensureNotCleared();
3866
- return encrypt(message, this.privateKey, recipientPublicKey);
5078
+ return encrypt$1(message, this.privateKey, recipientPublicKey);
3867
5079
  }
3868
5080
  /**
3869
5081
  * Encrypt a message using hex-encoded recipient public key.
@@ -3874,7 +5086,7 @@
3874
5086
  async encryptHex(message, recipientPublicKeyHex) {
3875
5087
  this.ensureNotCleared();
3876
5088
  const recipientPublicKey = hexToBytes(recipientPublicKeyHex);
3877
- return encrypt(message, this.privateKey, recipientPublicKey);
5089
+ return encrypt$1(message, this.privateKey, recipientPublicKey);
3878
5090
  }
3879
5091
  /**
3880
5092
  * Decrypt a NIP-04 encrypted message.
@@ -3884,7 +5096,7 @@
3884
5096
  */
3885
5097
  async decrypt(encryptedContent, senderPublicKey) {
3886
5098
  this.ensureNotCleared();
3887
- return decrypt(encryptedContent, this.privateKey, senderPublicKey);
5099
+ return decrypt$1(encryptedContent, this.privateKey, senderPublicKey);
3888
5100
  }
3889
5101
  /**
3890
5102
  * Decrypt a message using hex-encoded sender public key.
@@ -3895,7 +5107,7 @@
3895
5107
  async decryptHex(encryptedContent, senderPublicKeyHex) {
3896
5108
  this.ensureNotCleared();
3897
5109
  const senderPublicKey = hexToBytes(senderPublicKeyHex);
3898
- return decrypt(encryptedContent, this.privateKey, senderPublicKey);
5110
+ return decrypt$1(encryptedContent, this.privateKey, senderPublicKey);
3899
5111
  }
3900
5112
  /**
3901
5113
  * Derive a shared secret using ECDH.
@@ -3906,6 +5118,62 @@
3906
5118
  this.ensureNotCleared();
3907
5119
  return deriveSharedSecret(this.privateKey, theirPublicKey);
3908
5120
  }
5121
+ // ============================================================================
5122
+ // NIP-44 Encryption (XChaCha20-Poly1305)
5123
+ // ============================================================================
5124
+ /**
5125
+ * Encrypt a message using NIP-44 encryption.
5126
+ * Uses XChaCha20-Poly1305 with HKDF key derivation.
5127
+ * @param message Message to encrypt
5128
+ * @param recipientPublicKey 32-byte x-only public key of recipient
5129
+ * @returns Base64-encoded encrypted content
5130
+ */
5131
+ encryptNip44(message, recipientPublicKey) {
5132
+ this.ensureNotCleared();
5133
+ return encrypt(message, this.privateKey, recipientPublicKey);
5134
+ }
5135
+ /**
5136
+ * Encrypt a message using NIP-44 with hex-encoded recipient public key.
5137
+ * @param message Message to encrypt
5138
+ * @param recipientPublicKeyHex Hex-encoded recipient public key
5139
+ * @returns Base64-encoded encrypted content
5140
+ */
5141
+ encryptNip44Hex(message, recipientPublicKeyHex) {
5142
+ this.ensureNotCleared();
5143
+ const recipientPublicKey = hexToBytes(recipientPublicKeyHex);
5144
+ return encrypt(message, this.privateKey, recipientPublicKey);
5145
+ }
5146
+ /**
5147
+ * Decrypt a NIP-44 encrypted message.
5148
+ * @param encryptedContent Base64-encoded encrypted content
5149
+ * @param senderPublicKey 32-byte x-only public key of sender
5150
+ * @returns Decrypted message
5151
+ */
5152
+ decryptNip44(encryptedContent, senderPublicKey) {
5153
+ this.ensureNotCleared();
5154
+ return decrypt(encryptedContent, this.privateKey, senderPublicKey);
5155
+ }
5156
+ /**
5157
+ * Decrypt a NIP-44 message using hex-encoded sender public key.
5158
+ * @param encryptedContent Base64-encoded encrypted content
5159
+ * @param senderPublicKeyHex Hex-encoded sender public key
5160
+ * @returns Decrypted message
5161
+ */
5162
+ decryptNip44Hex(encryptedContent, senderPublicKeyHex) {
5163
+ this.ensureNotCleared();
5164
+ const senderPublicKey = hexToBytes(senderPublicKeyHex);
5165
+ return decrypt(encryptedContent, this.privateKey, senderPublicKey);
5166
+ }
5167
+ /**
5168
+ * Derive NIP-44 conversation key with another party.
5169
+ * Uses ECDH + HKDF with sorted public keys as salt.
5170
+ * @param theirPublicKey 32-byte x-only public key
5171
+ * @returns 32-byte conversation key
5172
+ */
5173
+ deriveConversationKey(theirPublicKey) {
5174
+ this.ensureNotCleared();
5175
+ return deriveConversationKey(this.privateKey, theirPublicKey);
5176
+ }
3909
5177
  /**
3910
5178
  * Check if a public key matches this key manager's public key.
3911
5179
  * @param publicKeyHex Hex-encoded public key to check
@@ -4311,6 +5579,12 @@
4311
5579
  const ENCRYPTED_DM = 4;
4312
5580
  /** NIP-09: Event deletion */
4313
5581
  const DELETION = 5;
5582
+ /** NIP-17: Seal (signed, encrypted rumor) */
5583
+ const SEAL = 13;
5584
+ /** NIP-17: Private chat message (rumor - unsigned inner event) */
5585
+ const CHAT_MESSAGE = 14;
5586
+ /** NIP-17: Read receipt (rumor kind) */
5587
+ const READ_RECEIPT = 15;
4314
5588
  /** NIP-25: Reactions (likes, etc.) */
4315
5589
  const REACTION = 7;
4316
5590
  /** NIP-59: Gift wrap for private events */
@@ -4383,6 +5657,12 @@
4383
5657
  return 'Encrypted DM';
4384
5658
  case DELETION:
4385
5659
  return 'Deletion';
5660
+ case SEAL:
5661
+ return 'Seal';
5662
+ case CHAT_MESSAGE:
5663
+ return 'Chat Message';
5664
+ case READ_RECEIPT:
5665
+ return 'Read Receipt';
4386
5666
  case REACTION:
4387
5667
  return 'Reaction';
4388
5668
  case GIFT_WRAP:
@@ -4420,6 +5700,7 @@
4420
5700
  AGENT_LOCATION: AGENT_LOCATION,
4421
5701
  AGENT_PROFILE: AGENT_PROFILE,
4422
5702
  APP_DATA: APP_DATA,
5703
+ CHAT_MESSAGE: CHAT_MESSAGE,
4423
5704
  CONTACTS: CONTACTS,
4424
5705
  DELETION: DELETION,
4425
5706
  ENCRYPTED_DM: ENCRYPTED_DM,
@@ -4428,8 +5709,10 @@
4428
5709
  PAYMENT_REQUEST: PAYMENT_REQUEST,
4429
5710
  PROFILE: PROFILE,
4430
5711
  REACTION: REACTION,
5712
+ READ_RECEIPT: READ_RECEIPT,
4431
5713
  RECOMMEND_RELAY: RECOMMEND_RELAY,
4432
5714
  RELAY_LIST: RELAY_LIST,
5715
+ SEAL: SEAL,
4433
5716
  TEXT_NOTE: TEXT_NOTE,
4434
5717
  TOKEN_TRANSFER: TOKEN_TRANSFER,
4435
5718
  getName: getName,
@@ -4476,6 +5759,235 @@
4476
5759
  return String(event.data);
4477
5760
  }
4478
5761
 
5762
+ /**
5763
+ * NIP-17 Private Direct Messages Protocol.
5764
+ * Implements gift-wrapping for sender anonymity using NIP-44 encryption.
5765
+ *
5766
+ * Message flow:
5767
+ * 1. Create Rumor (kind 14, unsigned) with actual message content
5768
+ * 2. Create Seal (kind 13, signed by sender) encrypting the rumor
5769
+ * 3. Create Gift Wrap (kind 1059, signed by random ephemeral key) encrypting the seal
5770
+ *
5771
+ * Only the recipient can decrypt and verify the true sender.
5772
+ */
5773
+ // Randomization window for timestamps (+/- 2 days in seconds)
5774
+ const TIMESTAMP_RANDOMIZATION = 2 * 24 * 60 * 60;
5775
+ /**
5776
+ * Create a gift-wrapped private message.
5777
+ *
5778
+ * @param senderKeys Sender's key manager
5779
+ * @param recipientPubkeyHex Recipient's public key (hex)
5780
+ * @param content Message content
5781
+ * @param options Optional message options (reply-to, etc.)
5782
+ * @returns Gift-wrapped event (kind 1059)
5783
+ */
5784
+ function createGiftWrap(senderKeys, recipientPubkeyHex, content, options) {
5785
+ // 1. Create Rumor (kind 14, unsigned)
5786
+ const rumor = createRumor(senderKeys.getPublicKeyHex(), recipientPubkeyHex, content, CHAT_MESSAGE, options?.replyToEventId);
5787
+ // 2. Create Seal (kind 13, signed by sender, encrypts rumor)
5788
+ const seal = createSeal(senderKeys, recipientPubkeyHex, rumor);
5789
+ // 3. Create Gift Wrap (kind 1059, signed by ephemeral key, encrypts seal)
5790
+ return wrapSeal(seal, recipientPubkeyHex);
5791
+ }
5792
+ /**
5793
+ * Create a gift-wrapped read receipt.
5794
+ *
5795
+ * @param senderKeys Sender's key manager
5796
+ * @param recipientPubkeyHex Recipient (original sender) public key
5797
+ * @param messageEventId Event ID of the message being acknowledged
5798
+ * @returns Gift-wrapped read receipt event
5799
+ */
5800
+ function createReadReceipt(senderKeys, recipientPubkeyHex, messageEventId) {
5801
+ // Create rumor with kind 15 (read receipt)
5802
+ const tags = [
5803
+ ['p', recipientPubkeyHex],
5804
+ ['e', messageEventId],
5805
+ ];
5806
+ // Use actual timestamp for rumor (privacy via outer layers)
5807
+ const actualTimestamp = Math.floor(Date.now() / 1000);
5808
+ const rumor = {
5809
+ id: '', // Will be computed
5810
+ pubkey: senderKeys.getPublicKeyHex(),
5811
+ created_at: actualTimestamp,
5812
+ kind: READ_RECEIPT,
5813
+ tags,
5814
+ content: '', // Read receipts have empty content
5815
+ };
5816
+ // Compute the rumor ID
5817
+ rumor.id = computeRumorId(rumor);
5818
+ const seal = createSeal(senderKeys, recipientPubkeyHex, rumor);
5819
+ return wrapSeal(seal, recipientPubkeyHex);
5820
+ }
5821
+ /**
5822
+ * Unwrap a gift-wrapped message.
5823
+ *
5824
+ * @param giftWrap Gift wrap event (kind 1059)
5825
+ * @param recipientKeys Recipient's key manager
5826
+ * @returns Parsed private message
5827
+ */
5828
+ function unwrap(giftWrap, recipientKeys) {
5829
+ if (giftWrap.kind !== GIFT_WRAP) {
5830
+ throw new Error(`Event is not a gift wrap (kind ${giftWrap.kind})`);
5831
+ }
5832
+ // Get ephemeral sender's pubkey from gift wrap
5833
+ const ephemeralPubkey = giftWrap.pubkey;
5834
+ const ephemeralPubkeyBytes = hexToBytes(ephemeralPubkey);
5835
+ // Decrypt seal from gift wrap content
5836
+ const sealJson = decrypt(giftWrap.content, recipientKeys.getPrivateKey(), ephemeralPubkeyBytes);
5837
+ const sealData = JSON.parse(sealJson);
5838
+ if (sealData.kind !== SEAL) {
5839
+ throw new Error(`Inner event is not a seal (kind ${sealData.kind})`);
5840
+ }
5841
+ // Verify seal signature
5842
+ const sealPubkey = sealData.pubkey;
5843
+ const sealIdBytes = hexToBytes(sealData.id);
5844
+ const sigBytes = hexToBytes(sealData.sig);
5845
+ const pubkeyBytes = hexToBytes(sealPubkey);
5846
+ if (!verify(sigBytes, sealIdBytes, pubkeyBytes)) {
5847
+ throw new Error('Seal signature verification failed');
5848
+ }
5849
+ // Decrypt rumor from seal content
5850
+ const rumorJson = decrypt(sealData.content, recipientKeys.getPrivateKey(), pubkeyBytes);
5851
+ const rumor = JSON.parse(rumorJson);
5852
+ // Extract reply-to event ID if present
5853
+ const replyToEventId = getTagValue(rumor.tags, 'e');
5854
+ return {
5855
+ eventId: giftWrap.id,
5856
+ senderPubkey: sealPubkey,
5857
+ recipientPubkey: recipientKeys.getPublicKeyHex(),
5858
+ content: rumor.content,
5859
+ timestamp: rumor.created_at,
5860
+ kind: rumor.kind,
5861
+ replyToEventId,
5862
+ };
5863
+ }
5864
+ // ========== Helper Functions ==========
5865
+ /**
5866
+ * Create an unsigned rumor (kind 14 or 15).
5867
+ * Note: Rumor uses actual timestamp for correct message ordering.
5868
+ * Only seal and gift wrap use randomized timestamps for privacy.
5869
+ */
5870
+ function createRumor(senderPubkey, recipientPubkey, content, kind, replyToEventId) {
5871
+ const tags = [['p', recipientPubkey]];
5872
+ if (replyToEventId) {
5873
+ tags.push(['e', replyToEventId, '', 'reply']);
5874
+ }
5875
+ // Use actual timestamp for rumor (inner message) - needed for correct ordering
5876
+ // Privacy is provided by randomized timestamps on seal and gift wrap layers
5877
+ const actualTimestamp = Math.floor(Date.now() / 1000);
5878
+ const rumor = {
5879
+ id: '', // Will be computed
5880
+ pubkey: senderPubkey,
5881
+ created_at: actualTimestamp,
5882
+ kind,
5883
+ tags,
5884
+ content,
5885
+ };
5886
+ // Compute the rumor ID
5887
+ rumor.id = computeRumorId(rumor);
5888
+ return rumor;
5889
+ }
5890
+ /**
5891
+ * Compute the rumor ID from serialized data.
5892
+ * ID = SHA-256([0, pubkey, created_at, kind, tags, content])
5893
+ */
5894
+ function computeRumorId(rumor) {
5895
+ const serialized = JSON.stringify([
5896
+ 0,
5897
+ rumor.pubkey,
5898
+ rumor.created_at,
5899
+ rumor.kind,
5900
+ rumor.tags,
5901
+ rumor.content,
5902
+ ]);
5903
+ const hash = sha256(new TextEncoder().encode(serialized));
5904
+ return bytesToHex(hash);
5905
+ }
5906
+ /**
5907
+ * Create a seal (kind 13) that encrypts a rumor.
5908
+ */
5909
+ function createSeal(senderKeys, recipientPubkeyHex, rumor) {
5910
+ const rumorJson = JSON.stringify(rumor);
5911
+ // Encrypt rumor with NIP-44
5912
+ const recipientPubkey = hexToBytes(recipientPubkeyHex);
5913
+ const encryptedRumor = encrypt(rumorJson, senderKeys.getPrivateKey(), recipientPubkey);
5914
+ // Create seal data
5915
+ const pubkey = senderKeys.getPublicKeyHex();
5916
+ const created_at = randomizeTimestamp();
5917
+ const kind = SEAL;
5918
+ const tags = []; // Seals have no tags
5919
+ const content = encryptedRumor;
5920
+ // Calculate ID
5921
+ const sealId = Event.calculateId(pubkey, created_at, kind, tags, content);
5922
+ // Sign
5923
+ const sealIdBytes = hexToBytes(sealId);
5924
+ const sig = senderKeys.signHex(sealIdBytes);
5925
+ return new Event({
5926
+ id: sealId,
5927
+ pubkey,
5928
+ created_at,
5929
+ kind,
5930
+ tags,
5931
+ content,
5932
+ sig,
5933
+ });
5934
+ }
5935
+ /**
5936
+ * Wrap a seal in a gift wrap (kind 1059) using an ephemeral key.
5937
+ */
5938
+ function wrapSeal(seal, recipientPubkeyHex) {
5939
+ // Generate ephemeral key for the gift wrap
5940
+ const ephemeralKeys = NostrKeyManager.generate();
5941
+ const sealJson = JSON.stringify(seal.toJSON());
5942
+ // Encrypt seal with NIP-44 using ephemeral key
5943
+ const recipientPubkey = hexToBytes(recipientPubkeyHex);
5944
+ const encryptedSeal = encrypt(sealJson, ephemeralKeys.getPrivateKey(), recipientPubkey);
5945
+ // Create gift wrap data
5946
+ const pubkey = ephemeralKeys.getPublicKeyHex();
5947
+ const created_at = randomizeTimestamp();
5948
+ const kind = GIFT_WRAP;
5949
+ const tags = [['p', recipientPubkeyHex]];
5950
+ const content = encryptedSeal;
5951
+ // Calculate ID
5952
+ const giftWrapId = Event.calculateId(pubkey, created_at, kind, tags, content);
5953
+ // Sign with ephemeral key
5954
+ const giftWrapIdBytes = hexToBytes(giftWrapId);
5955
+ const sig = ephemeralKeys.signHex(giftWrapIdBytes);
5956
+ // Clear ephemeral key from memory
5957
+ ephemeralKeys.clear();
5958
+ return new Event({
5959
+ id: giftWrapId,
5960
+ pubkey,
5961
+ created_at,
5962
+ kind,
5963
+ tags,
5964
+ content,
5965
+ sig,
5966
+ });
5967
+ }
5968
+ /**
5969
+ * Generate a randomized timestamp for privacy (+/- 2 days).
5970
+ */
5971
+ function randomizeTimestamp() {
5972
+ const now = Math.floor(Date.now() / 1000);
5973
+ const randomOffset = Math.floor(Math.random() * 2 * TIMESTAMP_RANDOMIZATION) - TIMESTAMP_RANDOMIZATION;
5974
+ return now + randomOffset;
5975
+ }
5976
+ /**
5977
+ * Get the first value of a tag by name from a tags array.
5978
+ */
5979
+ function getTagValue(tags, tagName) {
5980
+ const tag = tags.find((t) => t[0] === tagName);
5981
+ return tag?.[1];
5982
+ }
5983
+
5984
+ var nip17 = /*#__PURE__*/Object.freeze({
5985
+ __proto__: null,
5986
+ createGiftWrap: createGiftWrap,
5987
+ createReadReceipt: createReadReceipt,
5988
+ unwrap: unwrap
5989
+ });
5990
+
4479
5991
  /**
4480
5992
  * NostrClient - Main entry point for Nostr protocol operations.
4481
5993
  * Handles relay connections, event publishing, and subscriptions.
@@ -4963,6 +6475,51 @@
4963
6475
  const event = Event.create(this.keyManager, data);
4964
6476
  return this.publishEvent(event);
4965
6477
  }
6478
+ // ========== NIP-17 Private Messages ==========
6479
+ /**
6480
+ * Send a private message using NIP-17 gift-wrapping.
6481
+ * @param recipientPubkeyHex Recipient's public key (hex)
6482
+ * @param message Message content
6483
+ * @param options Optional message options (reply-to, etc.)
6484
+ * @returns Promise that resolves with the gift wrap event ID
6485
+ */
6486
+ async sendPrivateMessage(recipientPubkeyHex, message, options) {
6487
+ const giftWrap = createGiftWrap(this.keyManager, recipientPubkeyHex, message, options);
6488
+ return this.publishEvent(giftWrap);
6489
+ }
6490
+ /**
6491
+ * Send a private message to a recipient identified by their nametag.
6492
+ * Resolves the nametag to a pubkey automatically.
6493
+ * @param recipientNametag Recipient's nametag (Unicity ID)
6494
+ * @param message Message content
6495
+ * @param options Optional message options (reply-to, etc.)
6496
+ * @returns Promise that resolves with the gift wrap event ID
6497
+ */
6498
+ async sendPrivateMessageToNametag(recipientNametag, message, options) {
6499
+ const pubkey = await this.queryPubkeyByNametag(recipientNametag);
6500
+ if (!pubkey) {
6501
+ throw new Error(`Nametag not found: ${recipientNametag}`);
6502
+ }
6503
+ return this.sendPrivateMessage(pubkey, message, options);
6504
+ }
6505
+ /**
6506
+ * Send a read receipt for a message using NIP-17 gift-wrapping.
6507
+ * @param recipientPubkeyHex Recipient (original sender) public key
6508
+ * @param messageEventId Event ID of the message being acknowledged
6509
+ * @returns Promise that resolves with the gift wrap event ID
6510
+ */
6511
+ async sendReadReceipt(recipientPubkeyHex, messageEventId) {
6512
+ const giftWrap = createReadReceipt(this.keyManager, recipientPubkeyHex, messageEventId);
6513
+ return this.publishEvent(giftWrap);
6514
+ }
6515
+ /**
6516
+ * Unwrap a gift-wrapped private message.
6517
+ * @param giftWrap Gift wrap event (kind 1059)
6518
+ * @returns Parsed private message
6519
+ */
6520
+ unwrapPrivateMessage(giftWrap) {
6521
+ return unwrap(giftWrap, this.keyManager);
6522
+ }
4966
6523
  }
4967
6524
 
4968
6525
  /**
@@ -7974,7 +9531,7 @@
7974
9531
  return false;
7975
9532
  }
7976
9533
  // Count non-digit characters (excluding common phone number chars)
7977
- const cleanedLength = str.replace(/[\s\-\(\)\.]/g, '').length;
9534
+ const cleanedLength = str.replace(/[\s\-().]/g, '').length;
7978
9535
  const digitRatio = digitCount / cleanedLength;
7979
9536
  return digitRatio > 0.5;
7980
9537
  }
@@ -8431,7 +9988,9 @@
8431
9988
  */
8432
9989
  function generateRequestId() {
8433
9990
  const bytes = new Uint8Array(4);
9991
+ // eslint-disable-next-line no-undef
8434
9992
  if (typeof crypto !== 'undefined' && crypto.getRandomValues) {
9993
+ // eslint-disable-next-line no-undef
8435
9994
  crypto.getRandomValues(bytes);
8436
9995
  }
8437
9996
  else {
@@ -8653,10 +10212,27 @@
8653
10212
  parsePaymentRequest: parsePaymentRequest
8654
10213
  });
8655
10214
 
10215
+ /**
10216
+ * NIP-17 Messaging Types - Private Direct Messages
10217
+ */
10218
+ /**
10219
+ * Check if a message is a chat message (kind 14).
10220
+ */
10221
+ function isChatMessage(message) {
10222
+ return message.kind === 14;
10223
+ }
10224
+ /**
10225
+ * Check if a message is a read receipt (kind 15).
10226
+ */
10227
+ function isReadReceipt(message) {
10228
+ return message.kind === 15;
10229
+ }
10230
+
8656
10231
  exports.AGENT_LOCATION = AGENT_LOCATION;
8657
10232
  exports.AGENT_PROFILE = AGENT_PROFILE;
8658
10233
  exports.APP_DATA = APP_DATA;
8659
10234
  exports.Bech32 = bech32;
10235
+ exports.CHAT_MESSAGE = CHAT_MESSAGE;
8660
10236
  exports.CLOSED = CLOSED;
8661
10237
  exports.CLOSING = CLOSING;
8662
10238
  exports.CONNECTING = CONNECTING;
@@ -8671,6 +10247,8 @@
8671
10247
  exports.FilterBuilder = FilterBuilder;
8672
10248
  exports.GIFT_WRAP = GIFT_WRAP;
8673
10249
  exports.NIP04 = nip04;
10250
+ exports.NIP17 = nip17;
10251
+ exports.NIP44 = nip44;
8674
10252
  exports.NametagBinding = NametagBinding;
8675
10253
  exports.NametagUtils = NametagUtils;
8676
10254
  exports.NostrClient = NostrClient;
@@ -8680,38 +10258,38 @@
8680
10258
  exports.PROFILE = PROFILE;
8681
10259
  exports.PaymentRequestProtocol = PaymentRequestProtocol;
8682
10260
  exports.REACTION = REACTION;
10261
+ exports.READ_RECEIPT = READ_RECEIPT;
8683
10262
  exports.RECOMMEND_RELAY = RECOMMEND_RELAY;
8684
10263
  exports.RELAY_LIST = RELAY_LIST;
10264
+ exports.SEAL = SEAL;
8685
10265
  exports.SchnorrSigner = schnorr;
8686
10266
  exports.TEXT_NOTE = TEXT_NOTE;
8687
10267
  exports.TOKEN_TRANSFER = TOKEN_TRANSFER;
8688
10268
  exports.TokenTransferProtocol = TokenTransferProtocol;
8689
10269
  exports.areSameNametag = areSameNametag;
8690
10270
  exports.createBindingEvent = createBindingEvent;
10271
+ exports.createGiftWrap = createGiftWrap;
8691
10272
  exports.createNametagToPubkeyFilter = createNametagToPubkeyFilter;
8692
10273
  exports.createPubkeyToNametagFilter = createPubkeyToNametagFilter;
10274
+ exports.createReadReceipt = createReadReceipt;
8693
10275
  exports.createWebSocket = createWebSocket;
8694
10276
  exports.decode = decode;
8695
10277
  exports.decodeNpub = decodeNpub;
8696
10278
  exports.decodeNsec = decodeNsec;
8697
- exports.decrypt = decrypt;
8698
- exports.decryptHex = decryptHex;
8699
- exports.deriveSharedSecret = deriveSharedSecret;
8700
- exports.deriveSharedSecretHex = deriveSharedSecretHex;
8701
10279
  exports.encode = encode;
8702
10280
  exports.encodeNpub = encodeNpub;
8703
10281
  exports.encodeNsec = encodeNsec;
8704
- exports.encrypt = encrypt;
8705
- exports.encryptHex = encryptHex;
8706
10282
  exports.extractMessageData = extractMessageData;
8707
10283
  exports.formatForDisplay = formatForDisplay;
8708
10284
  exports.getName = getName;
8709
10285
  exports.getPublicKey = getPublicKey;
8710
10286
  exports.getPublicKeyHex = getPublicKeyHex;
8711
10287
  exports.hashNametag = hashNametag;
10288
+ exports.isChatMessage = isChatMessage;
8712
10289
  exports.isEphemeral = isEphemeral;
8713
10290
  exports.isParameterizedReplaceable = isParameterizedReplaceable;
8714
10291
  exports.isPhoneNumber = isPhoneNumber;
10292
+ exports.isReadReceipt = isReadReceipt;
8715
10293
  exports.isReplaceable = isReplaceable;
8716
10294
  exports.isValidBindingEvent = isValidBindingEvent;
8717
10295
  exports.normalizeNametag = normalizeNametag;
@@ -8719,6 +10297,7 @@
8719
10297
  exports.parseNametagHashFromEvent = parseNametagHashFromEvent;
8720
10298
  exports.sign = sign;
8721
10299
  exports.signHex = signHex;
10300
+ exports.unwrap = unwrap;
8722
10301
  exports.verify = verify;
8723
10302
  exports.verifyHex = verifyHex;
8724
10303