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
1 file src/crypto/peachQR.ts+73 −0
src/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+}

view file