// Cross-implementation gate for the BINARY crypto path — encryptBytes / // decryptBytes, the raw `iv||ct||tag` bytes attachments upload and download as // (no base64, no JSON). The native client opens the same blobs (LChatCore's // GroupKey.decryptBytes, via BlobImageLoader); drift here means photos decrypt // on one platform and not the other. Pinned two ways: // // 1. a fresh encryptBytes → decryptBytes round-trips in JS, and // 2. the committed fixture (LChatCore's Tests/…/Fixtures/attachment-bytes-vector.json) // still decrypts to the expected bytes — the SAME fixture the Swift test // (`swift test` in ios/LChatCore, AttachmentBytesVectorTests) consumes. // // If (2) fails, regenerate with --emit and update LChatCore in the same change. import { readFileSync, writeFileSync } from "node:fs"; import { fileURLToPath } from "node:url"; import { dirname, join } from "node:path"; import { generateKey, importKey, encryptBytes, decryptBytes, bytesToB64url, b64urlToBytes } from "../web/static/crypto.js"; const here = dirname(fileURLToPath(import.meta.url)); const fixturePath = join( here, "..", "ios", "LChatCore", "Tests", "LChatCoreTests", "Fixtures", "attachment-bytes-vector.json" ); // Every byte value once — catches any encoding/sign slip in either language. function knownBytes() { const raw = new Uint8Array(256); for (let i = 0; i < 256; i++) raw[i] = i; return raw; } function assert(cond, msg) { if (!cond) { console.error("FAIL:", msg); process.exit(1); } } // `node test/blobbytes.test.mjs --emit` regenerates the committed fixture (the // encrypt uses a fresh IV, so the fixture must be frozen, not recomputed). async function emit() { const { key, b64 } = await generateKey(); const blob = await encryptBytes(key, knownBytes()); const fixture = { _comment: "Attachment-bytes test vector (binary iv||ct||tag path). Generated by " + "test/blobbytes.test.mjs from web/static/crypto.js. LChatCore's " + "AttachmentBytesVectorTests opens blobB64url's raw bytes with keyB64 via " + "GroupKey.decryptBytes and must get plaintextB64url's bytes back — the " + "gate for native attachment/avatar decryption. " + "Regenerate with: node test/blobbytes.test.mjs --emit", keyB64: b64, blobB64url: bytesToB64url(blob), plaintextB64url: bytesToB64url(knownBytes()), }; writeFileSync(fixturePath, JSON.stringify(fixture, null, 2) + "\n"); console.log("wrote", fixturePath); } async function test() { // (1) crypto.js self-consistency on the binary path. const { key } = await generateKey(); const round = await decryptBytes(key, await encryptBytes(key, knownBytes())); assert(bytesToB64url(round) === bytesToB64url(knownBytes()), "fresh encryptBytes/decryptBytes round-trips"); // (2) the committed fixture (shared with the Swift test) still decrypts. const fx = JSON.parse(readFileSync(fixturePath, "utf8")); const got = await decryptBytes(await importKey(fx.keyB64), b64urlToBytes(fx.blobB64url)); assert(bytesToB64url(got) === fx.plaintextB64url, "committed fixture decrypts to expected bytes"); console.log("blobbytes.test.mjs OK"); } if (process.argv.includes("--emit")) await emit(); else await test();