// Cross-implementation room-crypto vectors: the CLJS port must derive the
// same room ids and OPEN envelopes sealed by the deployed TS implementation
// (fixtures generated while the TS was canon), plus seal/open round trips of
// its own (randomized-IV sealing can't be byte-fixed).
import test from "node:test";
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import * as lib from "../test-dist/testlib.js";
const v = JSON.parse(readFileSync(new URL("./vectors/room.json", import.meta.url), "utf8"));
test("room id derivation matches TS (HKDF roomid)", async () => {
const rc1 = await lib.createRoomCrypto(v.secret1, v.appSalt);
assert.equal(rc1.roomId, v.roomId1);
const rc2 = await lib.createRoomCrypto(v.secret2, v.appSalt);
assert.equal(rc2.roomId, v.roomId2);
});
test("room-scoped protocol id matches TS", async () => {
const rc = await lib.createRoomCrypto(v.secret1, v.appSalt);
assert.equal(lib.protocolOf(rc.roomId), v.protocol1);
assert.ok(lib.protocolOf(rc.roomId).startsWith(v.protocolPrefix));
});
test("opens TS-sealed hellos (plain and with identity block)", async () => {
const rc = await lib.createRoomCrypto(v.secret1, v.appSalt);
const plain = await rc.openHello(v.peerA, v.peerB, v.envAtoB);
assert.ok(plain, "A->B hello must open");
assert.equal(plain.v, 2);
const withId = await rc.openHello(v.peerB, v.peerA, v.envBtoA);
assert.ok(withId, "B->A hello must open");
assert.equal(withId.v, 2);
assert.deepEqual(withId.id, v.envBtoAId);
});
test("rejects swapped direction, wrong peer, wrong secret", async () => {
const rc = await lib.createRoomCrypto(v.secret1, v.appSalt);
assert.equal(await rc.openHello(v.peerB, v.peerA, v.envAtoB), null, "AAD direction");
assert.equal(await rc.openHello(v.peerA, v.peerA, v.envAtoB), null, "wrong recipient");
const other = await lib.createRoomCrypto(v.secret2, v.appSalt);
assert.equal(await other.openHello(v.peerA, v.peerB, v.envAtoB), null, "wrong room");
});
test("seal/open round trip with extra fields riding next to v", async () => {
const rc = await lib.createRoomCrypto(v.secret1, v.appSalt);
const env = await rc.sealHello(v.peerA, v.peerB, { id: { pub: "p", sig: "s" } });
assert.equal(env.v, 2);
assert.equal(typeof env.iv, "string");
assert.equal(typeof env.ct, "string");
const pt = await rc.openHello(v.peerA, v.peerB, env);
assert.ok(pt);
assert.equal(pt.v, 2);
assert.deepEqual(pt.id, { pub: "p", sig: "s" });
// and sequential seals never reuse an IV
const env2 = await rc.sealHello(v.peerA, v.peerB, undefined);
assert.notEqual(env2.iv, env.iv);
assert.ok(await rc.openHello(v.peerA, v.peerB, env2));
});
test("malformed envelopes and short secrets are rejected", async () => {
const rc = await lib.createRoomCrypto(v.secret1, v.appSalt);
for (const bad of [null, {}, { v: 1, iv: "", ct: "" }, { v: 2, iv: 42, ct: "" }]) {
assert.equal(await rc.openHello(v.peerA, v.peerB, bad), null);
}
await assert.rejects(() => lib.createRoomCrypto(v.tooShortSecret, v.appSalt));
});
test("newRoomSecret mints 32-byte b64url secrets", () => {
const s = lib.newRoomSecret();
assert.match(s, /^[A-Za-z0-9_-]{43}$/);
assert.equal(lib.fromB64url(s).length, 32);
assert.equal(lib.toB64url(lib.fromB64url(s)), s);
});