· 2 min read

A QR code from scratch: Reed-Solomon, masks and proving it right

How a zero-dependency QR encoder in TypeScript is built, and four independent ways to check that the matrix is correct.

Project: qr-svg

qr-svg has no runtime dependency at all: the whole of ISO/IEC 18004 for versions 1 to 40 lives in src/qr/. The interesting part is not generation but proof.

What the encoder consists of

  • The GF(256) field and its arithmetic.
  • Reed-Solomon encoding: correction bytes as a polynomial division remainder.
  • Block interleaving for versions with several blocks.
  • Patterns (finder, alignment, timing), format and version information.
  • Eight masks and penalties to pick the best one.

How correctness is checked

  1. Standard references. The ISO example "01234567" in 1-M and the "HELLO WORLD" walkthrough in 1-Q match in the correction bytes.
  2. An independent implementation. Matrices are compared bit for bit with qrcode-generator (a devDependency): 12 texts × 4 levels and 80 random strings.
  3. A third-party decoder. jsQR reads all 160 version × level combinations except 23-L, which the reference does not read either (the matrices are identical, a test records this).
  4. An own decoder that checks zero Reed-Solomon syndromes, independent of the division in the encoder.

Masks

Automatic mask selection often differs from the reference: penalties are computed slightly differently, both masks are valid. So there is deliberately no "picked the same mask" test. The comparison uses the mask taken from the reference's format information.

A caveat

There is no ready-made matrix from the standard itself in the repository: comparison with an independent implementation plays that role. No Kanji, ECI, mixed segments or Micro QR. 45 tests; under a load with a quarter of heavy requests (version 37), p50 is 131 ms and p95 272 ms.