eleven / ocho Public

Clone
git clone https://amadan.net/eleven/ocho

Plain git — no account needed to clone.

Download

Download this file

1// Cross-implementation gate for the per-viewer names blob (#577). The native
2// client (ios/LChatCore Nicknames.swift + Crypto.swift seal/open) reimplements
3// the web's sealForPub/openSealed for the {v:1, names:{uid:{name,seen}}} blob;
4// if either side drifts (wire shape, JSON shape), nicknames stop opening on
5// one platform. This pins the format two ways:
6//
7// 1. a fresh seal → open round-trips in JS (crypto.js self-consistency), and
8// 2. the committed fixture (LChatCore's Fixtures/nicknames-vector.json)
9// still opens to its expected map — the SAME fixture NicknamesVectorTests
10// consumes (it also seals natively and re-opens, covering both directions).
11//
12// Regenerate with: node test/nicknames.test.mjs --emit
13import { readFileSync, writeFileSync } from "node:fs";
14import { fileURLToPath } from "node:url";
15import { dirname, join } from "node:path";
16import { generateUserKeypair, sealForPub, openSealed } from "../web/static/crypto.js";
17
18const here = dirname(fileURLToPath(import.meta.url));
19const fixturePath = join(here, "..", "ios", "LChatCore", "Tests", "LChatCoreTests", "Fixtures", "nicknames-vector.json");
20
21// A stable map exercising the shapes that matter: a plain rename, a
22// multi-word name, and unicode.
23const NAMES = {
24 "uid-dermot": { name: "Dermo", seen: "Dermot" },
25 "uid-anna": { name: "Mum", seen: "Anna Lee" },
26 "uid-jose": { name: "José 🎉", seen: "Jose" },
27};
28
29function assert(cond, msg) {
30 if (!cond) {
31 console.error("FAIL:", msg);
32 process.exit(1);
33 }
34}
35
36async function emit() {
37 const kp = await generateUserKeypair();
38 const sealedB64 = await sealForPub(kp.publicKey, { v: 1, names: NAMES });
39 const opened = await openSealed(sealedB64, kp.privateKey);
40 assert(JSON.stringify(opened.names) === JSON.stringify(NAMES), "round-trip");
41 const fixture = {
42 _comment:
43 "Per-viewer names blob test vector (#577). Generated by test/nicknames.test.mjs " +
44 "from web/static/crypto.js sealForPub. LChatCore's NicknamesVectorTests opens " +
45 "sealedB64 with privPkcs8B64 and must get expectedNames — and seals its own " +
46 "blob to pubRawB64, which must open on both sides. base64url throughout. " +
47 "Regenerate with: node test/nicknames.test.mjs --emit",
48 privPkcs8B64: kp.privateKey,
49 pubRawB64: kp.publicKey,
50 sealedB64,
51 expectedNames: NAMES,
52 };
53 writeFileSync(fixturePath, JSON.stringify(fixture, null, 2) + "\n");
54 console.log("wrote", fixturePath);
55}
56
57async function test() {
58 // (1) crypto.js self-consistency.
59 const kp = await generateUserKeypair();
60 const sealed = await sealForPub(kp.publicKey, { v: 1, names: NAMES });
61 const opened = await openSealed(sealed, kp.privateKey);
62 assert(JSON.stringify(opened.names) === JSON.stringify(NAMES), "fresh seal round-trips");
63
64 // (2) the committed fixture still opens — the same bytes Swift consumes.
65 const fx = JSON.parse(readFileSync(fixturePath, "utf8"));
66 const fromFixture = await openSealed(fx.sealedB64, fx.privPkcs8B64);
67 assert(
68 JSON.stringify(fromFixture.names) === JSON.stringify(fx.expectedNames),
69 "committed fixture opens to expectedNames",
70 );
71 console.log("ALL PASSED");
72}
73
74if (process.argv.includes("--emit")) await emit();
75else await test();
76