social-kit / test / vectors.test.mjs
  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
117
/**
 * Wire-compat golden-vector suite.
 *
 * Extracted from the TypeScript implementation while it was canon (v1.2.0);
 * since the CLJS port this suite runs against the committed dist/ (`npm test`)
 * and is the byte-level contract the port must keep green. Two kinds of
 * checks:
 *
 *  1. deterministic sections: every derivation is recomputed from the
 *     implementation and deep-compared against the committed fixture;
 *  2. sealed fixtures (random IV / ephemeral key blobs, frozen at extraction
 *     time): the implementation must OPEN them — TS seals -> port unseals is
 *     the cross-implementation proof for the randomized layers.
 *
 * Do not regenerate the fixtures after the port; they are the contract.
 */

import { test } from "node:test";
import assert from "node:assert/strict";
import { readFile } from "node:fs/promises";
import { computeAll, canonThrowCases } from "./vectors/compute.mjs";
import { canon, openEnvelope, mintRoomUrl } from "../dist/index.js";
import { utf8 } from "ardegazu-id-kit";

const fixture = async (name) => JSON.parse(await readFile(new URL(`./vectors/${name}`, import.meta.url), "utf8"));

const { ac, ch, sections } = await computeAll();

// ---- deterministic sections -------------------------------------------------

for (const name of ["canon", "envelope", "pair", "receipts", "isoweek", "mailbox", "invites", "friends", "apps-sync", "text-keys"]) {
  test(`${name}.json deterministic section matches the implementation`, async () => {
    const f = await fixture(`${name}.json`);
    assert.deepEqual(sections[name], f.deterministic);
  });
}

// ---- canon extras -----------------------------------------------------------

test("canon.json: self-contained input_json rows re-canonicalize to the committed bytes", async () => {
  const f = await fixture("canon.json");
  let checked = 0;
  for (const c of f.deterministic.cases) {
    if (c.input_json === null) continue;
    assert.equal(canon(JSON.parse(c.input_json)), c.canon, c.name);
    checked++;
  }
  assert.ok(checked >= 20, `only ${checked} self-contained rows`);
});

test("canon rejects unsupported values (throw cases)", async () => {
  const f = await fixture("canon.json");
  const impl = canonThrowCases();
  assert.deepEqual(impl.map((c) => c.name), f.deterministic.throws.map((c) => c.name));
  for (const c of impl) assert.throws(() => canon(c.make()), undefined, c.name);
});

// ---- sealed envelope --------------------------------------------------------

test("sealed envelope: the committed TS-sealed blob opens and verifies", async () => {
  const f = await fixture("envelope.json");
  const { ctx, outer_json, expect_inner, expired_outer_json } = f.sealed;
  const opened = await openEnvelope(ac.xB, ctx, utf8(outer_json));
  assert.deepEqual(opened, expect_inner);
  assert.equal(await openEnvelope(ac.xB, ctx, utf8(expired_outer_json)), null, "expired envelope must not open");
});

test("sealed envelope: context binding, key binding and tamper rejection", async () => {
  const f = await fixture("envelope.json");
  const { ctx, outer_json } = f.sealed;
  assert.equal(await openEnvelope(ac.xB, "to|v1|somewhere-else", utf8(outer_json)), null, "wrong ctx");
  assert.equal(await openEnvelope(ac.xA, ctx, utf8(outer_json)), null, "wrong recipient key");
  const tampered = JSON.parse(outer_json);
  const ct = atob(tampered.w.ct);
  tampered.w.ct = btoa(String.fromCharCode(ct.charCodeAt(0) ^ 1) + ct.slice(1));
  assert.equal(await openEnvelope(ac.xB, ctx, utf8(JSON.stringify(tampered))), null, "tampered ct");
  assert.equal(await openEnvelope(ac.xB, ctx, utf8("not json")), null, "garbage bytes");
  assert.equal(await openEnvelope(ac.xB, ctx, utf8(JSON.stringify({ v: 2, w: tampered.w }))), null, "wrong outer version");
});

// ---- sealed pair wires ------------------------------------------------------

test("sealed pair wires: committed TS-sealed channel messages open for members only", async () => {
  const f = await fixture("pair.json");
  const members = [ac.idA.publicKeyB64, ac.idB.publicKeyB64];
  const p = await ch.pairBA.open(f.sealed.pair_wire_from_A.wire, members);
  assert.deepEqual(p, { from: ac.idA.publicKeyB64, payload: f.sealed.pair_wire_from_A.payload });
  const s = await ch.self.open(f.sealed.self_wire.wire, [ac.idA.publicKeyB64]);
  assert.deepEqual(s, { from: ac.idA.publicKeyB64, payload: f.sealed.self_wire.payload });
  assert.equal(await ch.pairAB.open(f.sealed.pair_wire_from_A.wire, [ac.idB.publicKeyB64]), null, "sender not in member list -> tag never matches");
  const tampered = JSON.parse(f.sealed.pair_wire_from_A.wire);
  const ct = atob(tampered.ct);
  tampered.ct = btoa(String.fromCharCode(ct.charCodeAt(0) ^ 1) + ct.slice(1));
  assert.equal(await ch.pairAB.open(JSON.stringify(tampered), members), null, "tampered ct");
  assert.equal(await ch.self.open(f.sealed.pair_wire_from_A.wire, members), null, "pair wire must not open on the self channel");
});

// ---- shape-only checks ------------------------------------------------------

test("mintRoomUrl mints a 43-char b64url fragment secret", async () => {
  const f = await fixture("invites.json");
  for (const app of f.deterministic.suite_apps) {
    const url = mintRoomUrl(app.host);
    assert.match(url, new RegExp(`^https://${app.host.replaceAll(".", "\\.")}/#[A-Za-z0-9_-]{43}$`));
  }
  assert.notEqual(mintRoomUrl("x.example"), mintRoomUrl("x.example"), "fresh randomness per mint");
});

test("receipts verify table verdicts hold when run straight off the fixture", async () => {
  const f = await fixture("receipts.json");
  const { verifyReceipt } = await import("../dist/index.js");
  for (const row of f.deterministic.verify_table) {
    const v = await verifyReceipt(row.receipt, row.opts);
    assert.equal(v !== null, row.accept, row.name);
    if (row.accept) assert.equal(v.hash, row.hash, `${row.name} hash`);
  }
});

static mirror of HEAD · about · clone: git clone https://git.ardegazu.ro/social-kit.git