// 🤖 Generates internal/e2ee/testdata/vectors.json from web/static/crypto.js — // the ground truth for the Go port's wire-format compatibility. Every blob in // the file was produced by the exact code the browser runs; the Go tests must // decrypt/unwrap/open all of them and reproduce the deterministic derivations. // // Not part of `npm test` (vectors are committed). Regenerate after any // deliberate crypto.js change: // // node test/gen-e2ee-vectors.mjs # then re-run: go test ./internal/e2ee // // Randomness is fine here — the vectors carry their own keys. import { writeFileSync } from "node:fs"; import { bytesToB64url, generateKey, encrypt, encryptBytes, randomSecret, deriveInvite, wrapGroupKey, generateUserKeypair, wrapThreadKey, sealForPub, } from "../web/static/crypto.js"; const OUT = new URL("../internal/e2ee/testdata/vectors.json", import.meta.url); const aesPayloads = [ {}, { text: "hello world", from: "Paul" }, { text: "unicode: ünïcødé 🎉 — em-dash, \"quotes\", \\backslash\\, \nnewline" }, { nested: { deep: [1, 2.5, true, null, "x"] }, n: 1747 }, ]; const sealPayloads = [ { token: "cap-abc123", exp: 1751500000 }, { note: "🤖 sealed unicode ✓" }, ]; const vectors = { aesJSON: [], aesBytes: [], invites: [], threadWraps: [], seals: [] }; for (const obj of aesPayloads) { const { key, b64 } = await generateKey(); vectors.aesJSON.push({ key: b64, plaintext: obj, blob: await encrypt(key, obj) }); } for (const len of [0, 1, 13, 4096]) { const { key, b64 } = await generateKey(); const pt = crypto.getRandomValues(new Uint8Array(len)); vectors.aesBytes.push({ key: b64, plaintextB64: bytesToB64url(pt), blobB64: bytesToB64url(await encryptBytes(key, pt)), }); } for (let i = 0; i < 3; i++) { const t = randomSecret(); const inv = await deriveInvite(t); const groupKey = (await generateKey()).b64; vectors.invites.push({ t, id: inv.id, claimSecret: inv.claimSecret, claimHash: inv.claimHash, groupKey, wrappedGroupKey: await wrapGroupKey(groupKey, inv.wrapKey), }); } for (let i = 0; i < 3; i++) { const kp = await generateUserKeypair(); const threadKey = (await generateKey()).b64; vectors.threadWraps.push({ publicKey: kp.publicKey, privateKey: kp.privateKey, threadKey, wrapped: await wrapThreadKey(threadKey, kp.publicKey), }); } for (const obj of sealPayloads) { const kp = await generateUserKeypair(); vectors.seals.push({ publicKey: kp.publicKey, privateKey: kp.privateKey, plaintext: obj, sealed: await sealForPub(kp.publicKey, obj), }); } writeFileSync(OUT, JSON.stringify(vectors, null, 2) + "\n"); console.log(`wrote ${OUT.pathname}`);