@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.
- package/dist/{chunk-jdt6p3k7.js → chunk-kabknfjj.js} +61 -339
- package/dist/commands.js +1 -1
- package/dist/mcp.js +1 -1
- package/dist/tui.js +1 -0
- package/package.json +3 -2
|
@@ -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
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
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(
|
|
467
|
-
throw new Error(
|
|
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
|
-
|
|
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
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
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
|
-
}
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
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
|
-
|
|
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
|
-
|
|
580
|
-
|
|
581
|
-
|
|
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
|
|
1375
|
-
tmux (set -g allow-passthrough on)
|
|
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
package/dist/mcp.js
CHANGED
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.
|
|
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/
|
|
20
|
+
"@profullstack/encrypt": "^0.1.0",
|
|
21
|
+
"@profullstack/hqtui": "^0.10.0",
|
|
21
22
|
"mlkem": "^2.5.0"
|
|
22
23
|
},
|
|
23
24
|
"keywords": [
|