pyrtan / sourcev0.19.0 · main

pyrtan / tests / conformance / identity.cjs

3,797 bytes · last changed 2026-10-28 in identity.js: the documents a root signs, and encrypted backups, agreeing with Python

1// Run identity.json through identity.js and compare with what Python said.
2// node tests/conformance/identity.cjs (exit 1 on any difference)
3const fs = require("fs");
4const path = require("path");
5const ROOT = path.resolve(__dirname, "..", "..");
6const Id = require(path.join(ROOT, "src", "pyrtan", "js", "identity.js"));
7const v = JSON.parse(fs.readFileSync(path.join(__dirname, "identity.json"), "utf8"));
8const bytes = (h) => Uint8Array.from(h.match(/../g) || [], (x) => parseInt(x, 16));
9
10(async () => {
11 let failed = 0, total = 0;
12 const check = (label, python, js) => {
13 total++;
14 const a = Id.canonical(python === undefined ? null : python), b = Id.canonical(js === undefined ? null : js);
15 if (a === b) { console.log(`ok ${label}`); return; }
16 failed++;
17 console.log(`DIFF ${label}\n python: ${a.slice(0, 400)}\n js: ${b.slice(0, 400)}`);
18 };
19 for (const s of v.sha3_256) check(`sha3-256 of ${s.in.length / 2} bytes`, s.out, Id.hex(Id.sha3_256(bytes(s.in))));
20 for (const i of v.ids) {
21 check(`id of ${i.key.slice(0, 8)}…`, i.id, Id.encode(bytes(i.key)));
22 check(`key of ${Id.short(i.id)}`, i.key, Id.hex(Id.decode(i.id)));
23 }
24 for (const t of v.refuse) check(`refuses ${t.slice(0, 24)}…`, false, Id.valid(t));
25 for (const t of v.accept) check(`accepts ${t.slice(0, 24)}…`, true, Id.valid(t));
26 for (const c of v.canonical) check(`canonical ${c.canonical.slice(0, 40)}`, c.canonical, Id.canonical(c.value));
27 check("a pasted note: good", null, Id.noteProblem(v.note, v.note.device, v.note.issued));
28 check("a pasted note: another key's", true, /not this browser's key/.test(Id.noteProblem(v.note, v.ids[2].id, v.note.issued)));
29 check("a pasted note: expired", "the note has expired", Id.noteProblem(v.note, v.note.device, v.note.expires + 1));
30 for (const r of v.requests) {
31 const label = `${r.method} ${r.path}${r.note ? " (device)" : ""}`;
32 check(`message ${label}`, r.message, new TextDecoder().decode(Id.requestMessage(r.audience, r.method, r.path, r.date, r.body)));
33 const key = await Id.fromSeedAndPublic(bytes(r.seed), bytes(r.public));
34 check(`headers ${label}`, r.headers, await Id.signRequest(key, r.audience, r.method, r.path, r.body, r.note, r.date));
35 }
36 for (const d of v.documents) {
37 const key = await Id.fromSeedAndPublic(bytes(d.seed), bytes(d.public)), m = d.make;
38 let got;
39 if (d.kind === "identity") got = await Id.makeIdentity(key, m);
40 else if (d.kind === "device") got = await Id.makeDevice(key, m.device, m.may, m);
41 else if (d.kind === "name") got = await Id.makeName(key, m.name, m);
42 else if (d.kind === "handover") got = await Id.makeName(key, m.name, Object.assign({}, m, { handoverFrom: await Id.fromSeedAndPublic(bytes(d.from_seed), bytes(d.from_public)) }));
43 else if (d.kind === "release") got = await Id.makeRelease(key, m.name, m);
44 check(`signs a ${d.kind}`, d.expect, got);
45 }
46 for (const b of v.backups) {
47 const seed = await Id.openBackup(b.expect, b.passphrase);
48 check("opens a backup Python wrote", b.seed, Id.hex(seed));
49 check("writes the backup Python writes", b.expect, await Id.makeBackup(bytes(b.seed), b.expect.id, b.passphrase, { salt: bytes(b.salt), iv: bytes(b.iv), iterations: b.iterations, made: b.made, document: b.expect.document }));
50 let refused = null; try { await Id.openBackup(b.expect, b.passphrase + "x"); } catch (e) { refused = e.message; }
51 check("refuses a wrong passphrase", true, /does not open/.test(refused || ""));
52 check("refuses a weakened kdf", true, /at least/.test(Id.backupProblem(Object.assign({}, b.expect, { kdf: Object.assign({}, b.expect.kdf, { iterations: 1000 }) })) || ""));
53 }
54 console.log(`${total - failed}/${total} agree`);
55 process.exit(failed ? 1 : 0);
56})();