| 1 | // The Emergency Kit, rendered and read back. |
| 2 | // |
| 3 | // The matrix being right (test/qr.test.mjs) does not mean the document is |
| 4 | // right. Between a correct matrix and a scannable page sit the coordinate |
| 5 | // flip, the module size, the quiet zone, and whether the squares land on the |
| 6 | // same grid — and every one of those can be wrong in a document whose matrix |
| 7 | // is perfect. So this renders the actual PDF and scans it. |
| 8 | // |
| 9 | // Needs python3 with pymupdf, zxing-cpp, pypdf, pillow and numpy. A HARD |
| 10 | // gate: a skip here is indistinguishable from a pass, on the one artifact |
| 11 | // that has to work years from now with nothing else left. |
| 12 | |
| 13 | import { test } from "node:test"; |
| 14 | import assert from "node:assert/strict"; |
| 15 | import { fileURLToPath } from "node:url"; |
| 16 | import { dirname, join } from "node:path"; |
| 17 | import { run as runOracle } from "./oracle.mjs"; |
| 18 | |
| 19 | const here = dirname(fileURLToPath(import.meta.url)); |
| 20 | const mod = (name) => join(here, "..", "internal", "contra", "static", "crypto", name); |
| 21 | |
| 22 | const { accountKeyCard, recoverySheet, handle } = await import(mod("kit.js")); |
| 23 | const { formatCode, TAG_ACCOUNT_KEY, TAG_RECOVERY } = await import(mod("code.js")); |
| 24 | const { fromHex } = await import(mod("bytes.js")); |
| 25 | |
| 26 | // Generated fixtures, from the golden vector file. Nothing here is real. |
| 27 | const ACCOUNT_ID = "b22c6a5eae1be9d6a773f474fd0532b0"; |
| 28 | const ACCOUNT_KEY = fromHex("f60fb281e8c72f6725add4cbd40e7b08"); |
| 29 | const RECOVERY = fromHex("70da5ffc983510bba4672030946b9eed"); |
| 30 | const WHEN = new Date(Date.UTC(2026, 8, 2)); |
| 31 | |
| 32 | function ask(cases) { |
| 33 | return JSON.parse(runOracle("pdforacle.py", cases)); |
| 34 | } |
| 35 | |
| 36 | const b64 = (bytes) => Buffer.from(bytes).toString("base64"); |
| 37 | |
| 38 | test("both documents render, scan, and read back", async () => { |
| 39 | const cardCode = await formatCode(TAG_ACCOUNT_KEY, ACCOUNT_KEY); |
| 40 | const sheetCode = await formatCode(TAG_RECOVERY, RECOVERY); |
| 41 | |
| 42 | const verdicts = ask([ |
| 43 | { |
| 44 | name: "Account Key card", |
| 45 | pdf_base64: b64(accountKeyCard(ACCOUNT_ID, cardCode, WHEN)), |
| 46 | expect_code: cardCode, |
| 47 | expect_text: [ |
| 48 | "Store somewhere safe.", |
| 49 | "Opens nothing without your passphrase.", |
| 50 | `Account ${handle(ACCOUNT_ID)}`, |
| 51 | "Do not photograph this.", |
| 52 | ], |
| 53 | }, |
| 54 | { |
| 55 | name: "Recovery sheet", |
| 56 | pdf_base64: b64(recoverySheet(ACCOUNT_ID, sheetCode, WHEN)), |
| 57 | expect_code: sheetCode, |
| 58 | expect_text: [ |
| 59 | "Store securely.", |
| 60 | "This sheet alone can open your vault.", |
| 61 | "Nobody can reissue it.", |
| 62 | `Account ${handle(ACCOUNT_ID)}`, |
| 63 | "Printed 2026-09-02", |
| 64 | ], |
| 65 | }, |
| 66 | ]); |
| 67 | |
| 68 | for (const v of verdicts) { |
| 69 | assert.ok(v.qr_scans, |
| 70 | `${v.name}: the QR in the rendered page did not scan (found ${JSON.stringify(v.scanned)})`); |
| 71 | assert.ok(v.one_qr, `${v.name}: expected exactly one QR, found ${v.scanned.length}`); |
| 72 | assert.ok(v.code_in_text, `${v.name}: the code is not in the extractable text`); |
| 73 | assert.deepEqual(v.missing_text, [], `${v.name}: missing copy`); |
| 74 | assert.equal(v.pages, 1, `${v.name}: should be a single page`); |
| 75 | } |
| 76 | }); |
| 77 | |
| 78 | // The two documents exist to be two files. If either ever carried the other's |
| 79 | // secret, the whole reason for splitting them would be gone — so it is |
| 80 | // checked rather than assumed. |
| 81 | test("neither document carries the other's secret", async () => { |
| 82 | const cardCode = await formatCode(TAG_ACCOUNT_KEY, ACCOUNT_KEY); |
| 83 | const sheetCode = await formatCode(TAG_RECOVERY, RECOVERY); |
| 84 | |
| 85 | const card = Buffer.from(accountKeyCard(ACCOUNT_ID, cardCode, WHEN)).toString("latin1"); |
| 86 | const sheet = Buffer.from(recoverySheet(ACCOUNT_ID, sheetCode, WHEN)).toString("latin1"); |
| 87 | |
| 88 | assert.ok(card.includes(cardCode), "the card does not contain its own code"); |
| 89 | assert.ok(sheet.includes(sheetCode), "the sheet does not contain its own code"); |
| 90 | assert.ok(!card.includes(sheetCode), "the Account Key card carries the recovery code"); |
| 91 | assert.ok(!sheet.includes(cardCode), "the Recovery sheet carries the Account Key"); |
| 92 | }); |
| 93 | |
| 94 | // The base-14 fonts have no glyph for a curly quote in this encoding, so the |
| 95 | // writer refuses one rather than drawing a wrong character into the document |
| 96 | // somebody is reading at the worst moment of their year. |
| 97 | test("the writer refuses characters it cannot draw", async () => { |
| 98 | const { Page } = await import(mod("pdf.js")); |
| 99 | assert.throws(() => new Page().text(10, 10, "don’t"), /cannot draw/); |
| 100 | assert.throws(() => new Page().wrap(10, 10, 200, "an em — dash"), /cannot draw/); |
| 101 | // And the copy that ships is already clean, which is the point of the check. |
| 102 | assert.doesNotThrow(() => new Page().text(10, 10, "Keep it where you'd keep a passport")); |
| 103 | }); |
| 104 | |