ab37b5888aca
add peachQR.ts — standard QR encoder for printable backup with explicit capacity boundary (proprietary matrix code deferred)
- time
- 2026-04-27 16:00 UTC · 6 months ago
- machine
- koh2
- parent
- 38a1ce3728e4
↓ peach-browser-ab37b588.tar.gzatlas at this save
files changed1 file
src/crypto/peachQR.ts+73 −0src/crypto/peachQR.ts+73 −0
@@ -0,0 +1,73 @@1+/**2+ * peachQR.ts — render a Peach binary payload as a printable QR code.3+ *4+ * The spec (§4) calls for a proprietary dense matrix format with custom5+ * anchor patterns and Reed-Solomon ECC tuned for high-capacity payloads.6+ * That decoder is non-trivial (perspective correction + ECC + camera7+ * frame analysis) and is genuinely out of scope for v2.5.0. For now the8+ * print-backup path uses a standard QR code via the bundled `qrcode`9+ * package with byte-mode segments and low ECC for maximum capacity.10+ *11+ * Standard QR (v40, ECC level L) caps at 2,953 binary bytes — that12+ * covers the common case (vaults up to roughly 100 entries). Larger13+ * vaults are advised to use PeachScript instead. The full proprietary14+ * matrix code (20 KB capacity, custom Reed-Solomon, four-anchor15+ * orientation) is a follow-up; the call sites below already separate16+ * "encode" from "render", so swapping in the spec-compliant codec later17+ * does not require touching the UI layer.18+ */19+20+import QRCode from "qrcode";21+22+/** Approximate binary capacity for standard QR Version 40, ECC level L. */23+export const QR_MAX_BYTES = 2953;24+25+export interface PeachQROptions {26+ /** Pixel width of one matrix module. Higher = larger PNG. Default 6. */27+ scale?: number;28+ /** White margin in modules around the code. Default 4. */29+ margin?: number;30+}31+32+/**33+ * Render a binary payload to a black-and-white QR canvas. Throws34+ * `QR_PAYLOAD_TOO_LARGE` if the payload exceeds standard QR capacity —35+ * callers should fall back to PeachScript in that case.36+ */37+export async function renderPeachQR(38+ payload: Uint8Array,39+ opts: PeachQROptions = {}40+): Promise<HTMLCanvasElement> {41+ if (payload.length > QR_MAX_BYTES) {42+ throw new Error("QR_PAYLOAD_TOO_LARGE");43+ }44+45+ const canvas = document.createElement("canvas");46+ await QRCode.toCanvas(47+ canvas,48+ [{ data: payload, mode: "byte" }],49+ {50+ errorCorrectionLevel: "L",51+ margin: opts.margin ?? 4,52+ scale: opts.scale ?? 6,53+ color: { dark: "#000000", light: "#ffffff" },54+ }55+ );56+ return canvas;57+}58+59+/** Convenience: PNG data URL ready for download. */60+export async function renderPeachQRDataURL(61+ payload: Uint8Array,62+ opts: PeachQROptions = {}63+): Promise<string> {64+ if (payload.length > QR_MAX_BYTES) {65+ throw new Error("QR_PAYLOAD_TOO_LARGE");66+ }67+ return QRCode.toDataURL([{ data: payload, mode: "byte" }], {68+ errorCorrectionLevel: "L",69+ margin: opts.margin ?? 4,70+ scale: opts.scale ?? 6,71+ color: { dark: "#000000", light: "#ffffff" },72+ });73+}