quadqr-js 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/FORMAT.md +390 -0
- package/LICENSE +661 -0
- package/README.md +1042 -0
- package/bin/quadqr.js +102 -0
- package/dist/benchmark.cjs +1 -0
- package/dist/benchmark.js +1 -0
- package/dist/browser.js +2 -0
- package/dist/esm/benchmark.js +193 -0
- package/dist/esm/geometry.js +149 -0
- package/dist/esm/node.js +275 -0
- package/dist/esm/quadqr.js +1962 -0
- package/dist/esm/reed-solomon.js +371 -0
- package/dist/esm/security.js +402 -0
- package/dist/esm/vision.js +752 -0
- package/dist/esm/wasm.js +98 -0
- package/dist/index.cjs +1 -0
- package/dist/index.js +2 -0
- package/dist/node.cjs +1 -0
- package/dist/node.js +1 -0
- package/dist/quadqr.js +3651 -0
- package/dist/quadqr.min.js +3593 -0
- package/dist/wasm/quadqr-core.wasm +0 -0
- package/docs/API.md +188 -0
- package/docs/BROWSER_CDN.md +83 -0
- package/docs/GETTING_STARTED.md +91 -0
- package/docs/NODE.md +92 -0
- package/docs/PUBLISHING.md +115 -0
- package/docs/README.md +26 -0
- package/docs/SECURITY.md +78 -0
- package/docs/WASM.md +36 -0
- package/package.json +95 -0
- package/types/benchmark.d.ts +7 -0
- package/types/index.d.ts +139 -0
- package/types/node.d.ts +9 -0
package/FORMAT.md
ADDED
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
# QuadQR Format v5
|
|
2
|
+
|
|
3
|
+
## Status
|
|
4
|
+
|
|
5
|
+
Experimental custom symbology. It is not ISO QR Code.
|
|
6
|
+
|
|
7
|
+
Only this RGBW format is implemented. Earlier RGB/ternary formats are intentionally not supported.
|
|
8
|
+
|
|
9
|
+
## Matrix sizes
|
|
10
|
+
|
|
11
|
+
```text
|
|
12
|
+
size = 21 + 4 * (version - 1)
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Supported versions: 1 through 40.
|
|
16
|
+
|
|
17
|
+
## Cell alphabet
|
|
18
|
+
|
|
19
|
+
Structural black:
|
|
20
|
+
|
|
21
|
+
```text
|
|
22
|
+
BLACK = -1
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Four data states:
|
|
26
|
+
|
|
27
|
+
| Cell | 2-bit value | Internal value |
|
|
28
|
+
|---|---|---:|
|
|
29
|
+
| Red | `00` | 0 |
|
|
30
|
+
| Green | `01` | 1 |
|
|
31
|
+
| Blue | `10` | 2 |
|
|
32
|
+
| White | `11` | 3 |
|
|
33
|
+
|
|
34
|
+
Structural white and data white intentionally share the same visible/internal value. Reserved-position geometry distinguishes their roles.
|
|
35
|
+
|
|
36
|
+
## Finder structures
|
|
37
|
+
|
|
38
|
+
Three 7×7 black/white finder structures are placed at top-left, top-right, and bottom-left, with white separator cells where they fit inside the matrix.
|
|
39
|
+
|
|
40
|
+
## Alignment patterns
|
|
41
|
+
|
|
42
|
+
QuadQR always keeps exactly three primary 7×7 finder patterns. Larger versions do **not** add more primary finders. Instead, versions 2 through 40 use distributed black/white alignment markers following the same center-position schedule used by standard QR Code versions. Exactly one bottom-right **primary alignment marker remains 5×5**. Every additional distributed alignment marker is **3×3**, encoded as a black outer ring with a white center.
|
|
43
|
+
|
|
44
|
+
The three alignment positions that would overlap the primary finder corners are omitted. This produces progressively more alignment references as the matrix grows. Examples:
|
|
45
|
+
|
|
46
|
+
```text
|
|
47
|
+
v2 -> 1 alignment pattern
|
|
48
|
+
v7 -> 6 alignment patterns
|
|
49
|
+
v14 -> 13 alignment patterns
|
|
50
|
+
v28 -> 33 alignment patterns
|
|
51
|
+
v40 -> 46 alignment patterns
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Version 1 is a QuadQR-specific exception. Standard QR v1 has no alignment pattern, but QuadQR keeps one legacy 5×5 bottom-right bootstrap alignment marker with a one-cell white separator so the camera scanner still has a fourth projective reference point.
|
|
55
|
+
|
|
56
|
+
For versions 2 through 40, the scanner uses the 5×5 bottom-right member of the distributed alignment grid as the primary fourth homography reference and then scores the full expected grid, including the 3×3 secondary markers, to strengthen version/geometry validation.
|
|
57
|
+
|
|
58
|
+
## Timing structures
|
|
59
|
+
|
|
60
|
+
Alternating black/white timing cells use row 6 and column 6 between the main finder regions.
|
|
61
|
+
|
|
62
|
+
## Color calibration
|
|
63
|
+
|
|
64
|
+
Twelve reserved cells provide three 2×2 color patches:
|
|
65
|
+
|
|
66
|
+
- 4 red cells
|
|
67
|
+
- 4 green cells
|
|
68
|
+
- 4 blue cells
|
|
69
|
+
|
|
70
|
+
Known finder/separator/alignment cells provide black and white references. Therefore white is calibrated without requiring a separate white swatch.
|
|
71
|
+
|
|
72
|
+
## Data placement
|
|
73
|
+
|
|
74
|
+
Data positions use a two-column vertical zig-zag beginning at the bottom-right. Reserved finder, separator, timing, alignment, and calibration cells are skipped.
|
|
75
|
+
|
|
76
|
+
## Byte-to-cell mapping
|
|
77
|
+
|
|
78
|
+
Each byte is serialized most-significant pair first:
|
|
79
|
+
|
|
80
|
+
```text
|
|
81
|
+
bits 7..6 -> cell 0
|
|
82
|
+
bits 5..4 -> cell 1
|
|
83
|
+
bits 3..2 -> cell 2
|
|
84
|
+
bits 1..0 -> cell 3
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Example:
|
|
88
|
+
|
|
89
|
+
```text
|
|
90
|
+
11001001
|
|
91
|
+
11 00 10 01
|
|
92
|
+
W R B G
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Therefore:
|
|
96
|
+
|
|
97
|
+
```text
|
|
98
|
+
1 byte = exactly 4 RGBW data cells
|
|
99
|
+
1 RGBW data cell = exactly 2 raw bits
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Masks
|
|
103
|
+
|
|
104
|
+
Four masks are defined. Each returns a 2-bit value 0..3:
|
|
105
|
+
|
|
106
|
+
```text
|
|
107
|
+
mask 0 = (row + col) mod 4
|
|
108
|
+
mask 1 = (2*row + col) mod 4
|
|
109
|
+
mask 2 = (row + 2*col) mod 4
|
|
110
|
+
mask 3 = (row*col + row + col) mod 4
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Encoding and decoding use XOR:
|
|
114
|
+
|
|
115
|
+
```text
|
|
116
|
+
visible = raw XOR mask
|
|
117
|
+
raw = visible XOR mask
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
The encoder evaluates all four masks using run-length and four-state balance penalties. The mask ID is not serialized. The decoder tries all four and accepts only a path whose protected header, ECC, and CRC validate.
|
|
121
|
+
|
|
122
|
+
## Header
|
|
123
|
+
|
|
124
|
+
### Versions 2 through 40
|
|
125
|
+
|
|
126
|
+
The normal logical header is 10 bytes.
|
|
127
|
+
|
|
128
|
+
| Offset | Size | Meaning |
|
|
129
|
+
|---|---:|---|
|
|
130
|
+
| 0 | 4 | ASCII magic `QQRW` |
|
|
131
|
+
| 4 | 1 | format version `5` |
|
|
132
|
+
| 5 | 1 | flags |
|
|
133
|
+
| 6 | 4 | payload byte length, big-endian |
|
|
134
|
+
|
|
135
|
+
It is protected with 8 RS parity bytes:
|
|
136
|
+
|
|
137
|
+
```text
|
|
138
|
+
10 data bytes + 8 parity bytes = 18 RS bytes
|
|
139
|
+
18 bytes * 4 cells/byte = 72 data cells
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
The normal header can correct up to four damaged byte symbols.
|
|
143
|
+
|
|
144
|
+
### Version 1 compact header
|
|
145
|
+
|
|
146
|
+
The 21×21 symbol uses a compact 4-byte header because the matrix size already identifies version 1.
|
|
147
|
+
|
|
148
|
+
| Offset | Size | Meaning |
|
|
149
|
+
|---|---:|---|
|
|
150
|
+
| 0 | 1 | compact format marker `0xC3` |
|
|
151
|
+
| 1 | 1 | flags |
|
|
152
|
+
| 2 | 1 | payload byte length |
|
|
153
|
+
| 3 | 1 | payload length XOR `0xFF` |
|
|
154
|
+
|
|
155
|
+
The compact header is protected with 4 RS parity bytes:
|
|
156
|
+
|
|
157
|
+
```text
|
|
158
|
+
4 data bytes + 4 parity bytes = 8 RS bytes
|
|
159
|
+
8 bytes * 4 cells/byte = 32 data cells
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
It corrects up to two damaged header byte symbols. The complemented length byte provides an additional structural validity check.
|
|
163
|
+
|
|
164
|
+
Flags for both header forms:
|
|
165
|
+
|
|
166
|
+
```text
|
|
167
|
+
bit 0 UTF-8 text flag
|
|
168
|
+
bits 1..2 ECC profile id
|
|
169
|
+
bit 3 Secure Payload envelope flag
|
|
170
|
+
bits 4..7 reserved
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
ECC ids:
|
|
174
|
+
|
|
175
|
+
```text
|
|
176
|
+
0 = L
|
|
177
|
+
1 = M
|
|
178
|
+
2 = Q
|
|
179
|
+
3 = H
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
## Secure Payload v1
|
|
183
|
+
|
|
184
|
+
When header flag bit 3 is set, the body payload bytes contain a versioned encrypted envelope instead of plaintext application bytes. Spectrum ECC and CRC operate on the envelope exactly like any other byte payload.
|
|
185
|
+
|
|
186
|
+
The envelope begins with this fixed 24-byte metadata block:
|
|
187
|
+
|
|
188
|
+
| Offset | Size | Field |
|
|
189
|
+
|---:|---:|---|
|
|
190
|
+
| 0 | 4 | ASCII magic `QSEC` |
|
|
191
|
+
| 4 | 1 | Secure Payload version (`1`) |
|
|
192
|
+
| 5 | 1 | Mode (`1` password, `2` raw 256-bit key) |
|
|
193
|
+
| 6 | 1 | Algorithm (`1` AES-256-GCM) |
|
|
194
|
+
| 7 | 1 | KDF (`1` PBKDF2-HMAC-SHA-256, `0` none) |
|
|
195
|
+
| 8 | 1 | Security flags |
|
|
196
|
+
| 9 | 1 | Salt length |
|
|
197
|
+
| 10 | 1 | Nonce length |
|
|
198
|
+
| 11 | 1 | Authentication-tag length |
|
|
199
|
+
| 12 | 1 | Key-ID length |
|
|
200
|
+
| 13 | 3 | Reserved, zero |
|
|
201
|
+
| 16 | 4 | PBKDF2 iterations, big-endian (`0` for raw-key mode) |
|
|
202
|
+
| 20 | 4 | Plaintext byte length, big-endian |
|
|
203
|
+
|
|
204
|
+
The fixed header is followed by:
|
|
205
|
+
|
|
206
|
+
```text
|
|
207
|
+
keyId || salt || nonce || ciphertext || GCM tag
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
Current required sizes:
|
|
211
|
+
|
|
212
|
+
```text
|
|
213
|
+
AES-GCM nonce = 12 bytes
|
|
214
|
+
AES-GCM tag = 16 bytes
|
|
215
|
+
password salt = 16 bytes
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
### Password mode
|
|
219
|
+
|
|
220
|
+
Password bytes are UTF-8 encoded and processed with:
|
|
221
|
+
|
|
222
|
+
```text
|
|
223
|
+
PBKDF2-HMAC-SHA-256
|
|
224
|
+
output = 256-bit AES key
|
|
225
|
+
default iterations = 600000
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
A fresh random 16-byte salt and 12-byte nonce are generated for every encryption operation.
|
|
229
|
+
|
|
230
|
+
### Raw 256-bit key mode
|
|
231
|
+
|
|
232
|
+
The application supplies an exact 32-byte key. No password KDF or salt is used. Unless disabled or overridden, the encoder stores the first 8 bytes of `SHA-256(rawKey)` as a non-secret key fingerprint. This key ID is only a routing hint and is not sufficient to decrypt the payload.
|
|
233
|
+
|
|
234
|
+
### Authentication
|
|
235
|
+
|
|
236
|
+
AES-256-GCM additional authenticated data (AAD) is:
|
|
237
|
+
|
|
238
|
+
```text
|
|
239
|
+
fixed security header || keyId || salt || nonce
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
Therefore the mode, KDF settings, key ID, salt, nonce, plaintext length, ciphertext, and authentication tag are cryptographically bound. Decryption must fail if the password/key is wrong or authenticated envelope data was changed.
|
|
243
|
+
|
|
244
|
+
The QuadQR CRC still protects the decoded encrypted envelope against scanner/ECC corruption before any decryption is attempted. AES-GCM authentication then protects the secure payload cryptographically.
|
|
245
|
+
|
|
246
|
+
## Payload and CRC
|
|
247
|
+
|
|
248
|
+
CRC-32 is calculated over:
|
|
249
|
+
|
|
250
|
+
```text
|
|
251
|
+
header || payload
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
Four CRC bytes are appended to the payload before body ECC.
|
|
255
|
+
|
|
256
|
+
## GF(256) Reed-Solomon
|
|
257
|
+
|
|
258
|
+
Header and body ECC use:
|
|
259
|
+
|
|
260
|
+
```text
|
|
261
|
+
GF(2^8) = GF(256)
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
Primitive polynomial:
|
|
265
|
+
|
|
266
|
+
```text
|
|
267
|
+
x^8 + x^4 + x^3 + x^2 + 1
|
|
268
|
+
0x11d
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
A codeword is limited to 255 byte symbols.
|
|
272
|
+
|
|
273
|
+
## ECC profiles
|
|
274
|
+
|
|
275
|
+
### Versions 2 through 40
|
|
276
|
+
|
|
277
|
+
| Profile | Parity bytes per body block | Correctable byte symbols per block |
|
|
278
|
+
|---|---:|---:|
|
|
279
|
+
| L | 12 | 6 |
|
|
280
|
+
| M | 24 | 12 |
|
|
281
|
+
| Q | 36 | 18 |
|
|
282
|
+
| H | 48 | 24 |
|
|
283
|
+
|
|
284
|
+
### Version 1 compact body ECC
|
|
285
|
+
|
|
286
|
+
| Profile | Parity bytes | Correctable byte symbols |
|
|
287
|
+
|---|---:|---:|
|
|
288
|
+
| L | 4 | 2 |
|
|
289
|
+
| M | 8 | 4 |
|
|
290
|
+
| Q | 12 | 6 |
|
|
291
|
+
| H | 16 | 8 |
|
|
292
|
+
|
|
293
|
+
This scaling prevents fixed parity overhead from consuming the entire 21×21 symbol. With the unchanged geometry and CRC-32, v1-M now carries 24 user payload bytes.
|
|
294
|
+
|
|
295
|
+
For each profile:
|
|
296
|
+
|
|
297
|
+
```text
|
|
298
|
+
max data bytes per block = 255 - parityBytes
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
Larger bodies are split into multiple RS blocks.
|
|
302
|
+
|
|
303
|
+
## Interleaving
|
|
304
|
+
|
|
305
|
+
### Reed-Solomon block interleaving
|
|
306
|
+
|
|
307
|
+
Encoded RS blocks are interleaved column-wise:
|
|
308
|
+
|
|
309
|
+
```text
|
|
310
|
+
block0[0], block1[0], block2[0], ...,
|
|
311
|
+
block0[1], block1[1], block2[1], ...
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
The decoder derives block lengths from payload length and ECC profile, reverses the interleaving, and corrects each block independently.
|
|
315
|
+
|
|
316
|
+
### Spectral-spatial cell interleaving
|
|
317
|
+
|
|
318
|
+
After bytes are split into four 2-bit RGBW cells, the complete logical data-cell stream is mapped through a deterministic version/length-dependent permutation before being written to physical data positions. The permutation is a seeded Fisher-Yates shuffle and contains every physical data-position index exactly once.
|
|
319
|
+
|
|
320
|
+
This layer has **zero capacity overhead**: no extra cells or parity symbols are added. Its purpose is to spread adjacent logical codeword cells across distant physical modules so localized damage is distributed over many RS symbols.
|
|
321
|
+
|
|
322
|
+
Masking is applied using the final physical row/column position. Decoding therefore performs operations in this order:
|
|
323
|
+
|
|
324
|
+
```text
|
|
325
|
+
physical sampled cells
|
|
326
|
+
-> physical-position unmasking
|
|
327
|
+
-> reverse spectral-spatial permutation
|
|
328
|
+
-> byte reconstruction
|
|
329
|
+
-> RS block deinterleaving
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
The decoder also tries legacy physical order as a fallback for older QuadQR matrices.
|
|
333
|
+
|
|
334
|
+
## Padding
|
|
335
|
+
|
|
336
|
+
Unused data positions are filled with deterministic pseudo-random values in the range 0..3. Padding is not semantically decoded.
|
|
337
|
+
|
|
338
|
+
## Confidence-aware error/erasure decoding
|
|
339
|
+
|
|
340
|
+
For image/camera scans, classification retains more than the winning RGBW state. Each sampled module also receives a confidence score derived from the separation between its nearest and second-nearest calibrated palette states.
|
|
341
|
+
|
|
342
|
+
One GF(256) symbol corresponds to four 2-bit data cells. The symbol confidence is the minimum confidence of those four constituent cells because an error in any one cell changes the reconstructed byte.
|
|
343
|
+
|
|
344
|
+
Decoding first attempts normal hard-decision Reed-Solomon correction. If that fails, low-confidence byte positions are progressively promoted to known erasures and the decoder retries error/erasure RS correction. Valid correction requires syndrome verification and the complete QuadQR payload still must pass CRC-32.
|
|
345
|
+
|
|
346
|
+
The RS budget follows the usual error/erasure relationship:
|
|
347
|
+
|
|
348
|
+
```text
|
|
349
|
+
2 * unknownErrors + knownErasures <= paritySymbols
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
No additional parity is added for this feature, so payload capacity is unchanged.
|
|
353
|
+
|
|
354
|
+
## Scanner pipeline
|
|
355
|
+
|
|
356
|
+
```text
|
|
357
|
+
RGB frame
|
|
358
|
+
-> grayscale conversion
|
|
359
|
+
-> global Otsu threshold for structural detection
|
|
360
|
+
-> 1:1:3:1:1 finder candidate detection
|
|
361
|
+
-> three-finder geometric ordering
|
|
362
|
+
-> candidate version estimation
|
|
363
|
+
-> primary bottom-right alignment search
|
|
364
|
+
-> four-point homography
|
|
365
|
+
-> distributed alignment-grid validation
|
|
366
|
+
-> projective module sampling
|
|
367
|
+
-> observed black/white/R/G/B calibration
|
|
368
|
+
-> nearest calibrated RGBW classification + confidence
|
|
369
|
+
-> four-state XOR unmasking
|
|
370
|
+
-> reverse spectral-spatial permutation
|
|
371
|
+
-> protected header GF(256) hard RS decode
|
|
372
|
+
-> confidence-guided error/erasure retry when needed
|
|
373
|
+
-> body block deinterleaving
|
|
374
|
+
-> body GF(256) error/erasure correction
|
|
375
|
+
-> CRC-32 verification
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
The scanner also has an axis-aligned fallback.
|
|
379
|
+
|
|
380
|
+
## Rotation
|
|
381
|
+
|
|
382
|
+
Matrix decoding tries 0°, 90°, 180°, and 270° rotations.
|
|
383
|
+
|
|
384
|
+
## Compatibility
|
|
385
|
+
|
|
386
|
+
Format v5 is intentionally incompatible with standard QR scanners and with the project's older ternary prototypes. The current encoder writes codeword cells using spectral-spatial placement. The current decoder also tries the pre-interleaver physical order as a compatibility fallback for older RGBW QuadQR matrices. Version 5 keeps the distributed alignment-center schedule introduced in v4, but shrinks every non-primary alignment marker from 5×5 to 3×3 while retaining the bottom-right primary marker at 5×5. Because reserved-cell geometry changed, v4 and v5 large-symbol matrices are not wire-compatible.
|
|
387
|
+
|
|
388
|
+
## Rendering profiles are not part of the wire format
|
|
389
|
+
|
|
390
|
+
The canonical QuadQR matrix is independent of presentation style. Renderers may offer styles such as `classic`, `depth`, `soft`, or `inset`, provided structural finder/timing/alignment/calibration references remain sufficiently faithful for decoding. Style selection is not encoded in the payload/header and does not change matrix cell values.
|