/** * Transient.chat - Encryption First Engine * Zero-Knowledge End-to-End Encryption (E2EE) * All chat payloads (text & voice audio) are encrypted client-side using AES-256-GCM. * Server memory stores strictly ephemeral encrypted ciphertext and IVs. */ (function() { // Helper: string to ArrayBuffer (UTF-8) function strToBuf(str) { return new TextEncoder().encode(str); } // Helper: ArrayBuffer to string (UTF-8) function bufToStr(buf) { return new TextDecoder().decode(buf); } // Helper: ArrayBuffer to hex string function bufToHex(buf) { return Array.from(new Uint8Array(buf)) .map(b => b.toString(16).padStart(2, '0')) .join(''); } // Helper: hex string to ArrayBuffer function hexToBuf(hex) { if (!hex) return new Uint8Array(0).buffer; const bytes = new Uint8Array(hex.length / 2); for (let i = 0; i < bytes.length; i++) { bytes[i] = parseInt(hex.substr(i * 2, 2), 16); } return bytes.buffer; } /** * Derives a 256-bit AES-GCM key from the room password and room name. * Uses PBKDF2 with SHA-256 and 100,000 iterations with a room-bound salt. */ window.deriveRoomKey = async function(password, roomName) { const passwordBuffer = strToBuf(password); const saltBuffer = strToBuf("transient.chat.room." + roomName); const baseKey = await window.crypto.subtle.importKey( "raw", passwordBuffer, "PBKDF2", false, ["deriveKey"] ); return await window.crypto.subtle.deriveKey( { name: "PBKDF2", salt: saltBuffer, iterations: 100000, hash: "SHA-256" }, baseKey, { name: "AES-GCM", length: 256 }, false, ["encrypt", "decrypt"] ); }; /** * Encrypts a message payload object (text + optional audio) using the derived room key. * Returns hex-encoded ciphertext and 96-bit (12-byte) initialization vector (IV). */ window.encryptRoomPayload = async function(cryptoKey, payloadObj) { const jsonStr = JSON.stringify(payloadObj); const iv = window.crypto.getRandomValues(new Uint8Array(12)); const encryptedBuffer = await window.crypto.subtle.encrypt( { name: "AES-GCM", iv: iv }, cryptoKey, strToBuf(jsonStr) ); return { ciphertext: bufToHex(encryptedBuffer), iv: bufToHex(iv) }; }; /** * Decrypts ciphertext and IV hex strings back to the original payload object. */ window.decryptRoomPayload = async function(cryptoKey, ciphertextHex, ivHex) { const decryptedBuffer = await window.crypto.subtle.decrypt( { name: "AES-GCM", iv: hexToBuf(ivHex) }, cryptoKey, hexToBuf(ciphertextHex) ); return JSON.parse(bufToStr(decryptedBuffer)); }; })();