carlos / contra Public

Clone
git clone https://amadan.net/carlos/contra

Plain git — no account needed to clone.

Download

Download this file

1"""The QR oracle: two independent implementations, neither of them ours.
2
3The Emergency Kit's QR codes are the one artifact in this product that has to
4be right years after it was printed, on a phone we have never seen, when the
5person holding it has already lost everything else. "It looks like a QR code"
6is not verification of that.
7
8So every symbol the JS encoder produces is checked twice here:
9
10 1. Decoded back to its payload by **ZXing** (zxing-cpp), which is the
11 library most phone scanners are descended from. This answers "will it
12 scan?".
13 2. Compared **bit-for-bit** against Python's `qrcode` at the same version,
14 EC level and mask. This answers "is it the right symbol?", which
15 decoding alone does not — error correction can hide a real mistake in a
16 symbol that still reads.
17
18Neither tool is ours, and they were checked against each other before either
19was trusted (test/qr.test.mjs's fixtures come from this file).
20
21Reads a JSON list of {text, ec, version, mask, modules} on stdin; writes a
22JSON verdict per case on stdout.
23
24Needs: pip install --user zxing-cpp qrcode pillow numpy
25"""
26
27import json
28import sys
29
30import numpy as np
31import qrcode
32import zxingcpp
33from PIL import Image
34
35EC = {
36 "L": qrcode.constants.ERROR_CORRECT_L,
37 "M": qrcode.constants.ERROR_CORRECT_M,
38 "Q": qrcode.constants.ERROR_CORRECT_Q,
39 "H": qrcode.constants.ERROR_CORRECT_H,
40}
41
42
43def to_image(modules, scale=8, quiet=4):
44 """Render a module matrix the way a printer would: dark on white, with the
45 quiet zone the specification requires. Without the quiet zone a correct
46 symbol simply does not decode, which is a real failure mode and not one we
47 want to discover on paper."""
48 n = len(modules)
49 side = (n + quiet * 2) * scale
50 img = Image.new("L", (side, side), 255)
51 px = img.load()
52 for r in range(n):
53 for c in range(n):
54 if modules[r][c]:
55 for y in range(scale):
56 for x in range(scale):
57 px[(c + quiet) * scale + x, (r + quiet) * scale + y] = 0
58 return img
59
60
61def reference_matrix(text, ec, mask):
62 """The reference symbol, forced to alphanumeric mode.
63
64 python-qrcode picks the most efficient mode for the data, so an all-digit
65 payload comes out in numeric mode — a different, equally valid symbol that
66 decodes to the same string. Our encoder is alphanumeric-only by design, so
67 comparing against an auto-selected reference would report a difference
68 that is not a defect. Forcing the mode compares like with like.
69
70 Real CONTRA codes begin "A1-"/"C1-" and are never all digits, so this only
71 affects synthetic payloads in the sweep — but a test that is wrong on the
72 easy cases teaches you to ignore it on the hard ones."""
73 q = qrcode.QRCode(error_correction=EC[ec], mask_pattern=mask, border=0)
74 q.add_data(qrcode.util.QRData(text.encode("ascii"),
75 mode=qrcode.util.MODE_ALPHA_NUM))
76 q.make(fit=True)
77 return [[1 if cell else 0 for cell in row] for row in q.get_matrix()], q.version
78
79
80def check(case):
81 text, ec, mask = case["text"], case["ec"], case["mask"]
82 modules = case["modules"]
83 out = {"text": text, "ec": ec, "mask": mask, "version": case["version"]}
84
85 # 1. Does it scan?
86 decoded = zxingcpp.read_barcode(np.array(to_image(modules)))
87 out["decoded"] = None if decoded is None else decoded.text
88 out["scans"] = decoded is not None and decoded.text == text
89
90 # 2. Is it the same symbol another encoder would have made?
91 try:
92 ref, ref_version = reference_matrix(text, ec, mask)
93 out["reference_version"] = ref_version
94 out["same_size"] = len(ref) == len(modules)
95 out["identical"] = out["same_size"] and all(
96 ref[r][c] == modules[r][c] for r in range(len(ref)) for c in range(len(ref))
97 )
98 if out["same_size"] and not out["identical"]:
99 out["differing_modules"] = sum(
100 ref[r][c] != modules[r][c]
101 for r in range(len(ref))
102 for c in range(len(ref))
103 )
104 except Exception as exc: # noqa: BLE001 - the verdict carries the reason
105 out["identical"] = False
106 out["reference_error"] = str(exc)
107
108 out["ok"] = bool(out["scans"] and out.get("identical"))
109 return out
110
111
112def main():
113 cases = json.load(sys.stdin)
114 print(json.dumps([check(c) for c in cases]))
115
116
117if __name__ == "__main__":
118 main()
119