@profullstack/qryptchat 0.4.4 → 0.4.6

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.
@@ -12,32 +12,10 @@ import { MlKem1024 as MlKem10242 } from "mlkem";
12
12
 
13
13
  // src/lib/crypto/post-quantum-encryption.js
14
14
  import { MlKem1024, MlKem768 } from "mlkem";
15
+ import { decrypt as decryptEnvelope, encrypt as encryptEnvelope } from "@profullstack/encrypt";
15
16
 
16
17
  // src/lib/crypto/index.js
17
18
  import { chacha20poly1305 } from "@noble/ciphers/chacha.js";
18
- var CRYPTO_CONFIG = {
19
- KYBER: {
20
- PUBLIC_KEY_SIZE: 1568,
21
- PRIVATE_KEY_SIZE: 3168,
22
- CIPHERTEXT_SIZE: 1568,
23
- SHARED_SECRET_SIZE: 32
24
- },
25
- DILITHIUM: {
26
- PUBLIC_KEY_SIZE: 2592,
27
- PRIVATE_KEY_SIZE: 4864,
28
- SIGNATURE_SIZE: 4595
29
- },
30
- CHACHA20_POLY1305: {
31
- KEY_SIZE: 32,
32
- NONCE_SIZE: 12,
33
- TAG_SIZE: 16
34
- },
35
- HKDF: {
36
- HASH: "SHA-256",
37
- SALT_SIZE: 32,
38
- INFO_PREFIX: "QryptChat-v1"
39
- }
40
- };
41
19
  var Base64 = {
42
20
  encode(bytes) {
43
21
  let binary = "";
@@ -56,134 +34,6 @@ var Base64 = {
56
34
  }
57
35
  };
58
36
 
59
- class SecureRandom {
60
- static getRandomBytes(length) {
61
- const bytes = new Uint8Array(length);
62
- crypto.getRandomValues(bytes);
63
- return bytes;
64
- }
65
- static generateSalt() {
66
- return this.getRandomBytes(CRYPTO_CONFIG.HKDF.SALT_SIZE);
67
- }
68
- static generateNonce() {
69
- return this.getRandomBytes(CRYPTO_CONFIG.CHACHA20_POLY1305.NONCE_SIZE);
70
- }
71
- }
72
-
73
- class HKDF {
74
- static async derive(inputKeyMaterial, salt, info, length) {
75
- const key = await crypto.subtle.importKey("raw", inputKeyMaterial, { name: "HKDF" }, false, ["deriveKey", "deriveBits"]);
76
- const derivedBits = await crypto.subtle.deriveBits({
77
- name: "HKDF",
78
- hash: CRYPTO_CONFIG.HKDF.HASH,
79
- salt,
80
- info: new TextEncoder().encode(`${CRYPTO_CONFIG.HKDF.INFO_PREFIX}-${info}`)
81
- }, key, length * 8);
82
- return new Uint8Array(derivedBits);
83
- }
84
- static async deriveRootAndChainKeys(sharedSecret, salt, context) {
85
- const derivedKey = await this.derive(sharedSecret, salt, context, 64);
86
- return {
87
- rootKey: derivedKey.slice(0, 32),
88
- chainKey: derivedKey.slice(32, 64)
89
- };
90
- }
91
- static async deriveMessageKey(chainKey, messageNumber) {
92
- const info = `MessageKey-${messageNumber}`;
93
- return await this.derive(chainKey, new Uint8Array(0), info, CRYPTO_CONFIG.CHACHA20_POLY1305.KEY_SIZE);
94
- }
95
- }
96
-
97
- class ChaCha20Poly1305 {
98
- static async encrypt(key, nonce, plaintext, additionalData = new Uint8Array(0)) {
99
- try {
100
- const cipher = chacha20poly1305(key, nonce, additionalData);
101
- const ciphertext = cipher.encrypt(plaintext);
102
- return ciphertext;
103
- } catch (error) {
104
- throw new EncryptionError(`ChaCha20-Poly1305 encryption failed: ${error.message}`);
105
- }
106
- }
107
- static async decrypt(key, nonce, ciphertext, additionalData = new Uint8Array(0)) {
108
- try {
109
- const cipher = chacha20poly1305(key, nonce, additionalData);
110
- const plaintext = cipher.decrypt(ciphertext);
111
- return plaintext;
112
- } catch (error) {
113
- throw new DecryptionError(`ChaCha20-Poly1305 decryption failed: ${error.message}`);
114
- }
115
- }
116
- }
117
-
118
- class CryptoError extends Error {
119
- constructor(message, code) {
120
- super(message);
121
- this.name = "CryptoError";
122
- this.code = code;
123
- }
124
- }
125
- class EncryptionError extends CryptoError {
126
- constructor(message) {
127
- super(message, "ENCRYPTION_ERROR");
128
- this.name = "EncryptionError";
129
- }
130
- }
131
-
132
- class DecryptionError extends CryptoError {
133
- constructor(message) {
134
- super(message, "DECRYPTION_ERROR");
135
- this.name = "DecryptionError";
136
- }
137
- }
138
- class CryptoUtils {
139
- static constantTimeEqual(a, b) {
140
- if (a.length !== b.length) {
141
- return false;
142
- }
143
- let result = 0;
144
- for (let i = 0;i < a.length; i++) {
145
- result |= a[i] ^ b[i];
146
- }
147
- return result === 0;
148
- }
149
- static secureClear(data) {
150
- if (data && data.fill) {
151
- data.fill(0);
152
- }
153
- }
154
- static concatenate(...arrays) {
155
- const totalLength = arrays.reduce((sum, arr) => sum + arr.length, 0);
156
- const result = new Uint8Array(totalLength);
157
- let offset = 0;
158
- for (const array of arrays) {
159
- result.set(array, offset);
160
- offset += array.length;
161
- }
162
- return result;
163
- }
164
- static generateKeyId() {
165
- const randomBytes = SecureRandom.getRandomBytes(16);
166
- return Base64.encode(randomBytes).replace(/[+/=]/g, "").substring(0, 22);
167
- }
168
- static async hash(data) {
169
- const hashBuffer = await crypto.subtle.digest("SHA-256", data);
170
- return new Uint8Array(hashBuffer);
171
- }
172
- static validateKeySize(key, algorithm, keyType) {
173
- const config = CRYPTO_CONFIG[algorithm];
174
- if (!config) {
175
- throw new CryptoError(`Unknown algorithm: ${algorithm}`);
176
- }
177
- const expectedSize = config[`${keyType.toUpperCase()}_KEY_SIZE`];
178
- if (!expectedSize) {
179
- throw new CryptoError(`Unknown key type: ${keyType} for algorithm: ${algorithm}`);
180
- }
181
- if (key.length !== expectedSize) {
182
- throw new CryptoError(`Invalid ${keyType} key size for ${algorithm}. Expected ${expectedSize}, got ${key.length}`);
183
- }
184
- }
185
- }
186
-
187
37
  // src/lib/crypto/indexed-db-manager.js
188
38
  var DB_NAME = "QryptChatKeys";
189
39
  var DB_VERSION = 1;
@@ -375,96 +225,34 @@ class PostQuantumEncryptionService {
375
225
  return this.publicKeyCache.get(userId) || null;
376
226
  }
377
227
  async encryptForRecipient(message, recipientPublicKey) {
228
+ if (!this.isInitialized) {
229
+ throw new Error("Post-quantum encryption service not initialized");
230
+ }
231
+ if (!recipientPublicKey || typeof recipientPublicKey !== "string") {
232
+ throw new Error("Invalid recipient public key format");
233
+ }
234
+ if (!/^[A-Za-z0-9+/=]+$/.test(recipientPublicKey)) {
235
+ throw new Error("Public key contains invalid Base64 characters");
236
+ }
237
+ let recipientPubKeyBytes;
378
238
  try {
379
- if (!this.isInitialized) {
380
- throw new Error("Post-quantum encryption service not initialized");
381
- }
382
- console.log(`\uD83D\uDD10 Encrypting message using ML-KEM-1024...`);
383
- if (!recipientPublicKey || typeof recipientPublicKey !== "string") {
384
- throw new Error("Invalid recipient public key format");
385
- }
386
- if (!/^[A-Za-z0-9+/=]+$/.test(recipientPublicKey)) {
387
- throw new Error("Public key contains invalid Base64 characters");
388
- }
389
- let recipientPubKeyBytes;
390
- try {
391
- recipientPubKeyBytes = Base64.decode(recipientPublicKey);
392
- } catch (decodeError) {
393
- console.error("\uD83D\uDD10 [ERROR] Failed to decode public key from Base64:", decodeError);
394
- throw new Error("Failed to decode public key");
395
- }
396
- recipientPubKeyBytes = this.stripKeyHeaderIfPresent(recipientPubKeyBytes);
397
- if (recipientPubKeyBytes.length !== this.ML_KEM_1024_PUBLIC_KEY_SIZE && recipientPubKeyBytes.length !== this.ML_KEM_768_PUBLIC_KEY_SIZE) {
398
- throw new Error(`Invalid recipient public key size: ${recipientPubKeyBytes.length} bytes. ` + `Expected ${this.ML_KEM_1024_PUBLIC_KEY_SIZE} (ML-KEM-1024) or ${this.ML_KEM_768_PUBLIC_KEY_SIZE} (ML-KEM-768). ` + `Recipient may need to use Nuclear Key Reset in Settings.`);
399
- }
400
- let kemAlgorithm = this.kemAlgorithm;
401
- let kemName = this.kemName;
402
- console.log(`\uD83D\uDD10 [DEBUG] Recipient public key length: ${recipientPubKeyBytes.length} bytes`);
403
- if (!this.isValidPublicKey(recipientPubKeyBytes)) {
404
- console.error(`\uD83D\uDD10 [ERROR] Invalid public key format detected (length: ${recipientPubKeyBytes.length} bytes)`);
405
- throw new Error("Invalid public key format");
406
- }
407
- if (recipientPubKeyBytes.length === this.ML_KEM_768_PUBLIC_KEY_SIZE) {
408
- console.log(`\uD83D\uDD10 [COMPATIBILITY] Detected ML-KEM-768 public key, using ML-KEM-768 for encryption`);
409
- try {
410
- const [kem768Ciphertext, kem768SharedSecret] = await this.kemAlgorithm768.encap(recipientPubKeyBytes);
411
- const salt = SecureRandom.generateSalt();
412
- const chachaKey = await HKDF.derive(kem768SharedSecret, salt, "ChaCha20-Poly1305", 32);
413
- const nonce = SecureRandom.generateNonce();
414
- const plaintext = new TextEncoder().encode(message);
415
- const messageCiphertext = await ChaCha20Poly1305.encrypt(chachaKey, nonce, plaintext);
416
- const encryptedMessage = {
417
- v: 3,
418
- alg: this.kemName768,
419
- kem: Base64.encode(kem768Ciphertext),
420
- s: Base64.encode(salt),
421
- n: Base64.encode(nonce),
422
- c: Base64.encode(messageCiphertext),
423
- t: Date.now()
424
- };
425
- CryptoUtils.secureClear(chachaKey);
426
- CryptoUtils.secureClear(kem768SharedSecret);
427
- const result = JSON.stringify(encryptedMessage);
428
- console.log(`\uD83D\uDD10 ✅ Encrypted message using ${this.kemName768}`);
429
- return result;
430
- } catch (kem768Error) {
431
- console.error(`\uD83D\uDD10 ❌ ML-KEM-768 encryption failed:`, kem768Error);
432
- throw kem768Error;
433
- }
434
- } else {
435
- kemAlgorithm = this.kemAlgorithm;
436
- kemName = this.kemName;
437
- }
438
- try {
439
- const [kemCiphertext, sharedSecret] = await kemAlgorithm.encap(recipientPubKeyBytes);
440
- const salt = SecureRandom.generateSalt();
441
- const chachaKey = await HKDF.derive(sharedSecret, salt, "ChaCha20-Poly1305", 32);
442
- const nonce = SecureRandom.generateNonce();
443
- const plaintext = new TextEncoder().encode(message);
444
- const messageCiphertext = await ChaCha20Poly1305.encrypt(chachaKey, nonce, plaintext);
445
- const encryptedMessage = {
446
- v: 3,
447
- alg: kemName,
448
- kem: Base64.encode(kemCiphertext),
449
- s: Base64.encode(salt),
450
- n: Base64.encode(nonce),
451
- c: Base64.encode(messageCiphertext),
452
- t: Date.now()
453
- };
454
- CryptoUtils.secureClear(chachaKey);
455
- CryptoUtils.secureClear(sharedSecret);
456
- const result = JSON.stringify(encryptedMessage);
457
- console.log(`\uD83D\uDD10 ✅ Encrypted message using ${kemName}`);
458
- return result;
459
- } catch (encapError) {
460
- console.error(`\uD83D\uDD10 ❌ ML-KEM encapsulation failed:`, encapError);
461
- const keyInfo = this.analyzePublicKey(recipientPubKeyBytes);
462
- console.log(`\uD83D\uDD10 [DEBUG] Public key analysis:`, keyInfo);
463
- throw new Error(`ML-KEM encryption failed: ${encapError.message}`);
464
- }
239
+ recipientPubKeyBytes = Base64.decode(recipientPublicKey);
240
+ } catch {
241
+ throw new Error("Failed to decode public key");
242
+ }
243
+ recipientPubKeyBytes = this.stripKeyHeaderIfPresent(recipientPubKeyBytes);
244
+ if (recipientPubKeyBytes.length !== this.ML_KEM_1024_PUBLIC_KEY_SIZE && recipientPubKeyBytes.length !== this.ML_KEM_768_PUBLIC_KEY_SIZE) {
245
+ throw new Error(`Invalid recipient public key size: ${recipientPubKeyBytes.length} bytes. ` + `Expected ${this.ML_KEM_1024_PUBLIC_KEY_SIZE} (ML-KEM-1024) or ${this.ML_KEM_768_PUBLIC_KEY_SIZE} (ML-KEM-768). ` + `Recipient may need to use Nuclear Key Reset in Settings.`);
246
+ }
247
+ if (!this.isValidPublicKey(recipientPubKeyBytes)) {
248
+ throw new Error("Invalid public key format");
249
+ }
250
+ const algorithm = recipientPubKeyBytes.length === this.ML_KEM_768_PUBLIC_KEY_SIZE ? this.kemName768 : this.kemName;
251
+ try {
252
+ return await encryptEnvelope(message, recipientPubKeyBytes, { algorithm });
465
253
  } catch (error) {
466
- console.error(`\uD83D\uDD10 ❌ All encryption methods failed:`, error);
467
- throw new Error("Unable to encrypt message with any available method");
254
+ console.error("\uD83D\uDD10 ❌ ML-KEM encryption failed:", this.analyzePublicKey(recipientPubKeyBytes));
255
+ throw new Error(`ML-KEM encryption failed: ${error?.message ?? error}`);
468
256
  }
469
257
  }
470
258
  analyzePublicKey(keyBytes) {
@@ -530,118 +318,51 @@ class PostQuantumEncryptionService {
530
318
  if (!this.isInitialized) {
531
319
  throw new Error("Post-quantum encryption service not initialized");
532
320
  }
533
- console.log(`\uD83D\uDD10 [DEBUG] Starting decryption...`);
534
- console.log(`\uD83D\uDD10 [DEBUG] Encrypted content type:`, typeof encryptedContent);
535
- console.log(`\uD83D\uDD10 [DEBUG] Encrypted content length:`, encryptedContent?.length || 0);
536
- console.log(`\uD83D\uDD10 [DEBUG] Encrypted content preview:`, encryptedContent?.substring(0, 100) || "N/A");
537
321
  let messageData;
538
322
  try {
539
323
  messageData = JSON.parse(encryptedContent);
540
- console.log(`\uD83D\uDD10 [DEBUG] Successfully parsed JSON, algorithm:`, messageData.alg);
541
- } catch (parseError) {
542
- const errorMessage = parseError instanceof Error ? parseError.message : String(parseError);
543
- console.log("\uD83D\uDD10 [DEBUG] Content is not JSON, parse error:", errorMessage);
544
- console.log("\uD83D\uDD10 [DEBUG] Raw content that failed to parse:", encryptedContent);
324
+ } catch {
545
325
  return "[Encrypted message]";
546
326
  }
547
327
  const version = messageData.v || messageData.version || 0;
548
328
  const algorithm = messageData.alg || messageData.algorithm || "";
549
- const kemCiphertextBase64 = messageData.kem || messageData.kemCiphertext || "";
550
- const saltBase64 = messageData.s || messageData.salt || "";
551
- const nonceBase64 = messageData.n || messageData.nonce || "";
552
- const ciphertextBase64 = messageData.c || messageData.ciphertext || "";
553
- let decryptionAlgorithm, userKeysToUse;
554
- if (algorithm === this.kemName) {
555
- console.log(`\uD83D\uDD10 [DEBUG] Using ${this.kemName} for decryption`);
556
- decryptionAlgorithm = this.kemAlgorithm;
557
- userKeysToUse = await this.getUserKeys();
558
- } else if (algorithm === this.kemName768) {
559
- console.log(`\uD83D\uDD10 [DEBUG] Using ${this.kemName768} for decryption`);
560
- decryptionAlgorithm = this.kemAlgorithm768;
561
- userKeysToUse = await this.getUserKeys768();
562
- } else if (algorithm === "FALLBACK-AES-GCM" || algorithm === "FALLBACK-AES") {
563
- console.log("\uD83D\uDD10 [LEGACY] AES encrypted message detected - no longer supported");
329
+ const envelope = {
330
+ v: version,
331
+ kem: messageData.kem || messageData.kemCiphertext || "",
332
+ s: messageData.s || messageData.salt || "",
333
+ n: messageData.n || messageData.nonce || "",
334
+ c: messageData.c || messageData.ciphertext || ""
335
+ };
336
+ if (algorithm === "FALLBACK-AES-GCM" || algorithm === "FALLBACK-AES") {
564
337
  return "[Legacy encrypted message - please delete]";
565
- } else {
566
- if (!version || version !== 3 || !kemCiphertextBase64 || !saltBase64 || !nonceBase64 || !ciphertextBase64) {
567
- console.log("\uD83D\uDD10 [DEBUG] Content is not post-quantum encrypted format, fields:", {
568
- version,
569
- algorithm,
570
- hasKem: !!kemCiphertextBase64,
571
- hasSalt: !!saltBase64,
572
- hasNonce: !!nonceBase64,
573
- hasCiphertext: !!ciphertextBase64
574
- });
575
- return "[Encrypted message - format error]";
338
+ }
339
+ const open = async (alg) => {
340
+ const keys = alg === this.kemName768 ? await this.getUserKeys768() : await this.getUserKeys();
341
+ const privateKeyBytes = this.stripKeyHeaderIfPresent(Base64.decode(keys.privateKey));
342
+ const expected = alg === this.kemName768 ? 2400 : 3168;
343
+ if (privateKeyBytes.length !== expected) {
344
+ throw new Error(`Invalid private key size: expected ${expected} bytes, got ${privateKeyBytes.length}. ` + `Please use Nuclear Key Reset in Settings to generate new encryption keys.`);
576
345
  }
577
- console.log(`\uD83D\uDD10 [DEBUG] Unknown algorithm "${algorithm}", trying ML-KEM-1024 first, then ML-KEM-768 if needed`);
346
+ return decryptEnvelope({ ...envelope, alg }, privateKeyBytes);
347
+ };
348
+ if (algorithm === this.kemName || algorithm === this.kemName768) {
349
+ return await open(algorithm);
350
+ }
351
+ if (version !== 3 || !envelope.kem || !envelope.s || !envelope.n || !envelope.c) {
352
+ return "[Encrypted message - format error]";
353
+ }
354
+ try {
355
+ return await open(this.kemName);
356
+ } catch {
578
357
  try {
579
- decryptionAlgorithm = this.kemAlgorithm;
580
- userKeysToUse = await this.getUserKeys();
581
- console.log(`\uD83D\uDD10 [DEBUG] Using keys with algorithm ${this.kemName}`);
582
- console.log(`\uD83D\uDD10 [DEBUG] Public key length:`, userKeysToUse?.publicKey?.length || 0);
583
- let privateKeyBytes = Base64.decode(userKeysToUse.privateKey);
584
- const kemCiphertext = Base64.decode(kemCiphertextBase64);
585
- privateKeyBytes = this.stripKeyHeaderIfPresent(privateKeyBytes);
586
- console.log(`\uD83D\uDD10 [DEBUG] Decoded private key length:`, privateKeyBytes.length);
587
- console.log(`\uD83D\uDD10 [DEBUG] Decoded KEM ciphertext length:`, kemCiphertext.length);
588
- console.log(`\uD83D\uDD10 [DEBUG] Starting ML-KEM decapsulation with algorithm:`, this.kemName);
589
- const sharedSecret = await decryptionAlgorithm.decap(kemCiphertext, privateKeyBytes);
590
- console.log(`\uD83D\uDD10 [DEBUG] ML-KEM decapsulation successful, shared secret length:`, sharedSecret.length);
591
- } catch (err1024) {
592
- console.warn(`\uD83D\uDD10 [DEBUG] ML-KEM-1024 decryption failed, trying ML-KEM-768. Error:`, err1024);
593
- try {
594
- decryptionAlgorithm = this.kemAlgorithm768;
595
- userKeysToUse = await this.getUserKeys768();
596
- console.log(`\uD83D\uDD10 [DEBUG] Using keys with algorithm ${this.kemName768}`);
597
- console.log(`\uD83D\uDD10 [DEBUG] Public key length:`, userKeysToUse?.publicKey?.length || 0);
598
- const privateKeyBytes = Base64.decode(userKeysToUse.privateKey);
599
- const kemCiphertext = Base64.decode(kemCiphertextBase64);
600
- console.log(`\uD83D\uDD10 [DEBUG] Decoded private key length:`, privateKeyBytes.length);
601
- console.log(`\uD83D\uDD10 [DEBUG] Decoded KEM ciphertext length:`, kemCiphertext.length);
602
- console.log(`\uD83D\uDD10 [DEBUG] Starting ML-KEM decapsulation with algorithm:`, this.kemName768);
603
- const sharedSecret = await decryptionAlgorithm.decap(kemCiphertext, privateKeyBytes);
604
- console.log(`\uD83D\uDD10 [DEBUG] ML-KEM decapsulation successful, shared secret length:`, sharedSecret.length);
605
- } catch (err768) {
606
- console.error(`\uD83D\uDD10 [DEBUG] ML-KEM-768 decryption also failed. Error:`, err768);
607
- return "[Encrypted message - could not decrypt with any supported algorithm]";
608
- }
358
+ return await open(this.kemName768);
359
+ } catch {
360
+ return "[Encrypted message - could not decrypt with any supported algorithm]";
609
361
  }
610
362
  }
611
- console.log(`\uD83D\uDD10 [DEBUG] Using keys with algorithm ${userKeysToUse ? algorithm === this.kemName768 ? this.kemName768 : this.kemName : "unknown"}`);
612
- console.log(`\uD83D\uDD10 [DEBUG] Public key length:`, userKeysToUse?.publicKey?.length || 0);
613
- let privateKeyBytes = Base64.decode(userKeysToUse.privateKey);
614
- const kemCiphertext = Base64.decode(kemCiphertextBase64);
615
- privateKeyBytes = this.stripKeyHeaderIfPresent(privateKeyBytes);
616
- const targetPrivateKeySize = algorithm === this.kemName768 ? 2400 : 3168;
617
- if (privateKeyBytes.length !== targetPrivateKeySize) {
618
- throw new Error(`Invalid private key size: expected ${targetPrivateKeySize} bytes, got ${privateKeyBytes.length}. ` + `Please use Nuclear Key Reset in Settings to generate new encryption keys.`);
619
- }
620
- console.log(`\uD83D\uDD10 [DEBUG] Decoded private key length:`, privateKeyBytes.length);
621
- console.log(`\uD83D\uDD10 [DEBUG] Decoded KEM ciphertext length:`, kemCiphertext.length);
622
- console.log(`\uD83D\uDD10 [DEBUG] Starting ML-KEM decapsulation with algorithm:`, algorithm === this.kemName768 ? this.kemName768 : this.kemName);
623
- const sharedSecret = await decryptionAlgorithm.decap(kemCiphertext, privateKeyBytes);
624
- console.log(`\uD83D\uDD10 [DEBUG] ML-KEM decapsulation successful, shared secret length:`, sharedSecret.length);
625
- const salt = Base64.decode(saltBase64);
626
- const chachaKey = await HKDF.derive(sharedSecret, salt, "ChaCha20-Poly1305", 32);
627
- const nonce = Base64.decode(nonceBase64);
628
- const messageCiphertext = Base64.decode(ciphertextBase64);
629
- console.log(`\uD83D\uDD10 [DEBUG] Nonce length:`, nonce.length);
630
- console.log(`\uD83D\uDD10 [DEBUG] Message ciphertext length:`, messageCiphertext.length);
631
- console.log(`\uD83D\uDD10 [DEBUG] Starting ChaCha20-Poly1305 decryption...`);
632
- const plaintext = await ChaCha20Poly1305.decrypt(chachaKey, nonce, messageCiphertext);
633
- console.log(`\uD83D\uDD10 [DEBUG] ChaCha20-Poly1305 decryption successful, plaintext length:`, plaintext.length);
634
- const messageText = new TextDecoder("utf-8").decode(plaintext);
635
- CryptoUtils.secureClear(chachaKey);
636
- CryptoUtils.secureClear(sharedSecret);
637
- console.log(`\uD83D\uDD10 ✅ Successfully decrypted message using ${algorithm === this.kemName768 ? this.kemName768 : this.kemName}`);
638
- return messageText;
639
363
  } catch (error) {
640
- console.error(`\uD83D\uDD10 ❌ Failed to decrypt message with ${this.kemName}:`, error);
641
- if (error instanceof Error && error.stack) {
642
- console.error(`\uD83D\uDD10 ❌ Error stack:`, error.stack);
643
- }
644
364
  const errorMsg = error instanceof Error ? error.message : String(error);
365
+ console.error(`\uD83D\uDD10 ❌ Failed to decrypt message: ${errorMsg}`);
645
366
  if (errorMsg.includes("Algorithm mismatch")) {
646
367
  return "[Message encrypted with different keys]";
647
368
  }
@@ -1371,8 +1092,9 @@ Usage:
1371
1092
  <chat> is a chat id or part of its name. Options: --json, --url <server> (or QC_URL).
1372
1093
  Keys and tokens are sealed (ChaCha20-Poly1305) in ${"$"}QC_HOME or ~/.config/qc; the key is in your
1373
1094
  OS keychain, or comes from a passphrase (QC_PASSPHRASE for scripts and qc mcp).
1374
- HD: emoji and icons are drawn as our OpenEmoji/OpenIcon images in Kitty and Ghostty. Over SSH or in
1375
- tmux (set -g allow-passthrough on), run with QC_HD=1; QC_HD=0 turns it off.`;
1095
+ HD: emoji and icons are drawn as our OpenEmoji/OpenIcon images in Kitty, Ghostty, WezTerm and iTerm2.
1096
+ Over SSH or in tmux (set -g allow-passthrough on) say which: QC_HD=1 (Kitty/Ghostty) or
1097
+ QC_HD=wezterm (WezTerm/iTerm2). QC_HD=0 turns it off.`;
1376
1098
  function parseArgs2(argv) {
1377
1099
  const args = { _: [], flags: {} };
1378
1100
  for (let i = 0;i < argv.length; i++) {
package/dist/commands.js CHANGED
@@ -3,7 +3,7 @@ import {
3
3
  parseArgs2,
4
4
  findChat2,
5
5
  main2
6
- } from "./chunk-jdt6p3k7.js";
6
+ } from "./chunk-kabknfjj.js";
7
7
  import"./chunk-vfahhydf.js";
8
8
  export {
9
9
  HELP2 as HELP,
package/dist/mcp.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import"./chunk-vfahhydf.js";
2
2
  import {
3
3
  findChat2
4
- } from "./chunk-jdt6p3k7.js";
4
+ } from "./chunk-kabknfjj.js";
5
5
 
6
6
  // src/cli/mcp.js
7
7
  import { createInterface } from "node:readline";
package/dist/tui.js CHANGED
@@ -215,6 +215,7 @@ async function runTui(client, { initialChat } = {}) {
215
215
  const redraw = () => app.invalidate();
216
216
  const env = process.env.QC_HD && !process.env.HQTUI_IMAGES ? { ...process.env, HQTUI_IMAGES: process.env.QC_HD } : process.env;
217
217
  state.images = createImageStore({ write: (seq) => app.terminal.write(seq), onReady: redraw, env });
218
+ state.images.attach(app);
218
219
  const note = (msg, { sticky = false } = {}) => {
219
220
  state.notice = msg;
220
221
  redraw();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@profullstack/qryptchat",
3
- "version": "0.4.4",
3
+ "version": "0.4.6",
4
4
  "description": "qc: qrypt.chat in your terminal. A full-screen end-to-end encrypted chat client (ML-KEM-1024), plus a scriptable CLI and an MCP server.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -17,7 +17,8 @@
17
17
  },
18
18
  "dependencies": {
19
19
  "@noble/ciphers": "^2.0.0",
20
- "@profullstack/hqtui": "^0.9.0",
20
+ "@profullstack/encrypt": "^0.1.0",
21
+ "@profullstack/hqtui": "^0.10.0",
21
22
  "mlkem": "^2.5.0"
22
23
  },
23
24
  "keywords": [