/** Dev-only crypto self-test: roundtrip, cross-key and AAD-mismatch must fail. */
import { newRoomSecret, RoomCrypto } from "./crypto";
export async function runSelfTest(): Promise<void> {
const secret = newRoomSecret();
const a = await RoomCrypto.create(secret, "selftest/v1");
const b = await RoomCrypto.create(secret, "selftest/v1");
const other = await RoomCrypto.create(newRoomSecret(), "selftest/v1");
const otherApp = await RoomCrypto.create(secret, "selftest/v2");
const assert = (cond: boolean, what: string) => {
if (!cond) throw new Error(`crypto self-test FAILED: ${what}`);
};
assert(a.roomId === b.roomId, "same secret ⇒ same roomId");
assert(a.roomId !== other.roomId, "different secret ⇒ different roomId");
assert(a.roomId !== otherApp.roomId, "different app salt ⇒ different roomId");
// signaling roundtrip a → b
const sig = await a.sealSignal(b.peerId, { kind: "offer", sdp: "x", name: "n" });
assert(((await b.openSignal(a.peerId, sig)) as { sdp: string }).sdp === "x", "signal roundtrip");
assert((await b.openSignal(b.peerId, sig)) === null, "wrong `from` must fail (AAD)");
const c = await RoomCrypto.create(secret, "selftest/v1");
assert((await c.openSignal(a.peerId, sig)) === null, "wrong `to` must fail (AAD)");
assert((await other.openSignal(a.peerId, sig)) === null, "wrong room must fail");
// datachannel JSON roundtrip + binary
const msg = await a.sealMsg({ kind: "chat", text: "hi" });
assert(((await b.openMsg(a.peerId, msg)) as { text: string }).text === "hi", "msg roundtrip");
assert((await b.openMsg(b.peerId, msg)) === null, "msg wrong sender must fail");
const data = crypto.getRandomValues(new Uint8Array(1000));
const bin = await a.sealMsgBinary(data);
const openedBin = await b.openMsgBinary(a.peerId, bin);
assert(!!openedBin && openedBin.every((v, i) => v === data[i]), "binary roundtrip");
assert((await b.openMsgBinary(b.peerId, bin)) === null, "binary wrong sender must fail");
// IVs must never repeat for one sealer
const ivs = new Set<string>();
for (let i = 0; i < 200; i++) ivs.add((await a.sealMsg({ i })).iv);
assert(ivs.size === 200, "IV uniqueness");
// identity: deterministic from seed, assertion verifies, wrong context fails
const { Identity, verifyAssertion } = await import("./identity");
const seed = Identity.newSeed();
const id1 = await Identity.fromSeed(seed);
const id2 = await Identity.fromSeed(seed);
assert(id1.publicKeyB64 === id2.publicKeyB64, "identity deterministic from seed");
assert(id1.fingerprint.emoji.length > 0 && id1.fingerprint.hex.length === 64, "fingerprint shape");
const idSig = await id1.assert("selftest/v1", a.roomId, a.peerId);
assert((await verifyAssertion("selftest/v1", a.roomId, a.peerId, id1.publicKeyB64, idSig)) !== null, "assertion verifies");
assert((await verifyAssertion("selftest/v1", a.roomId, b.peerId, id1.publicKeyB64, idSig)) === null, "assertion bound to peerId");
assert((await verifyAssertion("selftest/v1", other.roomId, a.peerId, id1.publicKeyB64, idSig)) === null, "assertion bound to room");
console.info("✅ crypto self-test passed");
}