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