carlos / contra Public

web/5a — QR scanning on Safari and Firefox needs a decoder of our own Feature Request open

🤖 Split out of web/5 (#11), which is otherwise done and on the canary.

The gap

Rung 2 of the fresh-device ladder — scan the QR on the Account Key card — is built, but it is offered only where BarcodeDetector exists: Chromium and Android. Safari and Firefox have no QR decoding API at all, so on those browsers the rung is hidden and a person types the code instead.

That is within the letter of DESIGN.md — "every screen offers all available rungs and never dead-ends", and the typing rung always works — but it is the wrong way round in practice. The device most likely to be scanning a card is a phone, and a large share of phones are Safari.

What it would take

We have the encoder (qr.js, qrtables.js, qrgf.js) and the GF(256) field arithmetic is already there and already tested against python-qrcode. A decoder still needs, roughly in order:

  1. Greyscale and adaptive binarisation of a video frame.
  2. Finder-pattern detection (the three 1:1:3:1:1 squares) and the alignment pattern for versions ≥ 2.
  3. A perspective transform from the four located corners, then sampling the module grid.
  4. Format-information decode — BCH(15,5), and note that the two copies run in opposite directions, which is the bug that cost us a day on the encoder side.
  5. Unmasking, de-interleaving the blocks, and Reed–Solomon decode — syndromes, Berlekamp–Massey, Chien search, Forney. The encoder only needed the field and polynomial multiplication; error correction is the harder half.
  6. Alphanumeric-mode segment decode, which is the only mode our cards use.

Call it 400–600 lines across two or three modules, against a 300-line cap per module, in the graph that holds the keys. Not a footnote.

Why it is worth doing rather than pulling a library

AGENTS.md: no build step, go:embed, modules a person can read. Vendoring zxing-js or jsQR means a large minified blob served from the origin that hands out the vault — which is precisely the thing the module-size rule exists to prevent. And the CSP the console should end up with does not want a CDN.

We already own the encoder, and we already have an oracle: test/qroracle.py drives zxing-cpp, so a decoder can be tested against real photographs of real cards rather than against our own encoder's output, which would only prove the two agree with each other.

Scope note

Only the alphanumeric mode and only versions 1–6 at EC-Q are needed — that is what kit.js emits. A general decoder is not the goal; reading our own card is. That cuts a lot of the table work.

Suggested order

After native/1 (#15), not before. The Swift crypto core is the other consumer of the printed code, and if the Mac app gets a camera scan first through Vision — which does this for free on Apple platforms — then the browser decoder's value drops to "Firefox, and Safari on iOS", which is worth knowing before writing six hundred lines.