1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116 | /**
* One-shot golden-vector extractor. Run against the TS implementation while it
* is still canon:
*
* npx tsx test/vectors/generate.mjs
*
* Writes test/vectors/*.json: every deterministic derivation from compute.mjs
* plus the SEALED fixtures (random IVs/ephemeral keys — captured once here,
* then frozen; the CLJS port must be able to OPEN them, never re-produce them
* byte-for-byte). After the CLJS port these files are the wire-compat
* CONTRACT: do not regenerate them.
*/
import { writeFile } from "node:fs/promises";
import { computeAll, buildFixedEnvelope, TS_FIXED } from "./compute.mjs";
import { openEnvelope } from "../../dist/index.js";
import { utf8 } from "ardegazu-id-kit";
import { SUITE_SALT, wrapTo } from "ardegazu-id-kit/xkey";
const { ac, ch, sections } = await computeAll();
// ---- sealed envelope fixtures (random IV/eph key — frozen once) -------------
const e = await buildFixedEnvelope(ac);
const w = await wrapTo(SUITE_SALT, e.ctx, ac.idB.publicKeyB64, ac.xB.pubB64, utf8(e.inner_json));
const outer_json = JSON.stringify({ v: 1, w });
// an otherwise-valid envelope that expired at TS_FIXED+1000 — openEnvelope must reject
const expUnsigned = { ...e.unsigned, exp: TS_FIXED + 1000 };
const { envelopeSigPreimage } = await import("./compute.mjs");
const { toB64url } = await import("ardegazu-id-kit");
const expSig = toB64url(await ac.idA.signRaw(utf8(envelopeSigPreimage(e.ctx, expUnsigned))));
const expInnerJson = JSON.stringify({ ...expUnsigned, sig: expSig });
const expW = await wrapTo(SUITE_SALT, e.ctx, ac.idB.publicKeyB64, ac.xB.pubB64, utf8(expInnerJson));
const expired_outer_json = JSON.stringify({ v: 1, w: expW });
// sanity: the sealed fixture must open TODAY against the TS impl
const opened = await openEnvelope(ac.xB, e.ctx, utf8(outer_json));
if (!opened || opened.sig !== e.sig) throw new Error("generated envelope fixture does not open — refusing to write");
if ((await openEnvelope(ac.xB, e.ctx, utf8(expired_outer_json))) !== null)
throw new Error("expired fixture unexpectedly opens");
// ---- sealed pair-channel wires (random IVs — frozen once) -------------------
const pairPayload = { k: "hello", n: 1, msg: "hei ✨" };
const pair_wire_from_A = await ch.pairAB.seal(ac.idA.publicKeyB64, pairPayload);
const selfPayload = ["state", 2, { x: true }];
const self_wire = await ch.self.seal(ac.idA.publicKeyB64, selfPayload);
const openedPair = await ch.pairBA.open(pair_wire_from_A, [ac.idA.publicKeyB64, ac.idB.publicKeyB64]);
if (!openedPair || openedPair.from !== ac.idA.publicKeyB64) throw new Error("pair wire fixture does not open");
const openedSelf = await ch.self.open(self_wire, [ac.idA.publicKeyB64]);
if (!openedSelf) throw new Error("self wire fixture does not open");
// ---- assemble + write -------------------------------------------------------
const files = {
"canon.json": {
_doc: "canon.ts canonical-serialization byte contract: canon() outputs (string + UTF-8 hex + SHA-256), inputs the port must reject, and hkdfId/sha256B64url derivations. Inputs are described in input_desc (input_json only where a JSON round trip is canon-faithful); the construction code lives in compute.mjs canonCases().",
deterministic: sections.canon,
},
"envelope.json": {
_doc: "Signed envelope contract (envelope.ts). deterministic: fixed seeds -> keys, certs, the fixed inner envelope, its canonical sig preimage and byte-exact Ed25519 signature. sealed: TS-produced wrapbox blobs (random IV + ephemeral X25519 — NOT reproducible; the port must UNSEAL them via openEnvelope). exp is 2100-01-01 so the fixture never expires; sealed_expired must open to null.",
deterministic: sections.envelope,
sealed: {
recipient_seed: sections.envelope.seeds.recipient_B,
ctx: e.ctx,
outer_json,
expect_inner: { ...e.inner, from: { ...e.inner.from, name: "Ala" } },
expired_outer_json,
expect_expired: null,
},
},
"pair.json": {
_doc: "Pairwise/self channel derivations (pair.ts): static-static DH of the suite X keys -> channel id, gossip topic, mailbox room id, wrap ctx, sender tags — byte-exact, symmetric for both members. sealed: AES-GCM wires produced by the TS impl (random IVs; the port must open() them).",
deterministic: sections.pair,
sealed: {
pair_wire_from_A: { wire: pair_wire_from_A, payload: pairPayload, from_seed: "A" },
self_wire: { wire: self_wire, payload: selfPayload, from_seed: "A" },
},
},
"receipts.json": {
_doc: "Co-signed leaderboard receipts (receipts.ts): fixed 3-player match, exact canonical sig preimage, byte-exact signatures, receipt hash, and a verify accept/reject table (verifyReceipt semantics). ts is fixed at 2025-01-01 (past; no maxAgeMs in the table, so verdicts are time-stable until 2100 for the future-ts case).",
deterministic: sections.receipts,
},
"isoweek.json": {
_doc: "isoWeek/lbRoomId/lbTopic (receipts.ts): ISO-8601 week bucketing at UTC year boundaries incl. week-52/53 years, and the weekly-drop room/topic derivations.",
deterministic: sections.isoweek,
},
"mailbox.json": {
_doc: "Mailbox-facing deterministic derivations: identity-inbox room ids (pair.ts inboxRoomId) and the compiled-in endpoint fallbacks (consts.ts). The HTTP client itself is network-behavior, not vectored.",
deterministic: sections.mailbox,
},
"invites.json": {
_doc: "Invite composition (invites.ts): the SUITE_APPS catalog and the mintRoomUrl shape (random 32-byte fragment secret; shape-checked, not byte-fixed).",
deterministic: sections.invites,
},
"friends.json": {
_doc: "Friend links (friends.ts build/parse round trips with real X-certs — fully deterministic), sanitizeFriend shape clamps, and selfsync.ts profile-sync sanitize/fold vectors (incl. the equal-ts canon tiebreak).",
deterministic: sections.friends,
},
"apps-sync.json": {
_doc: "selfsync.cljs's `apps` section — the suite identity record's per-app map {<appKey>:{state,ts}}: sanitizeAppsSync shape/size/JSON-safety clamps and the PER-KEY foldAppsSync (newest ts wins that key, equal ts breaks on the greater canon of the entry, a key only one side has is adopted). Sizes are UTF-16 code units of JSON.stringify, id-kit's unit and id-kit's numbers. Size/map/JSON-safety rows carry input_desc + a verdict instead of the input, which is 16 KB+, cyclic or a function. The *_keys arrays exist because deepEqual is key-order-blind and key order is the wire.",
deterministic: sections["apps-sync"],
},
"text-keys.json": {
_doc: "The SocialTextKey i18n contract (text.ts): full sorted key list, the built-in EN table (wire-relevant: legacy invite labels), fill() substitution and mkT() fallback semantics.",
deterministic: sections["text-keys"],
},
};
for (const [name, data] of Object.entries(files)) {
const url = new URL(`./${name}`, import.meta.url);
await writeFile(url, JSON.stringify(data, null, 2) + "\n");
console.log(`wrote ${name}`);
}
console.log("done — fixtures extracted from the TS implementation");
|