53fdf206c18d

scaffold codex types, RS WASM loader, public asset, deps; bump to v2.42.0

time
2026-05-15 17:17 UTC · 5 months ago
machine
koh2
parent
096884e49b99
7 files manifest.firefox.json+1 −1manifest.json+1 −1package-lock.json+139 −7package.json+7 −1public/codex/reed_solomon_erasure_bg.wasmbinarysrc/popup/codex/rs-loader.ts+69 −0src/shared/codex-types.ts+180 −0
manifest.firefox.json+1 −1
@@ -1,7 +1,7 @@11 {22   "manifest_version": 3,33   "name": "Peach",4-  "version": "2.41.4",4+  "version": "2.42.0",55   "description": "Zero-knowledge password manager",66   "browser_specific_settings": {77     "gecko": {

view file

manifest.json+1 −1
@@ -1,7 +1,7 @@11 {22   "manifest_version": 3,33   "name": "Peach",4-  "version": "2.41.4",4+  "version": "2.42.0",55   "description": "Zero-knowledge password manager",66   "action": {77     "default_popup": "src/popup/index.html",

view file

package-lock.json+139 −7
@@ -1,24 +1,29 @@11 {22   "name": "peach",3-  "version": "2.35.1",3+  "version": "2.41.3",44   "lockfileVersion": 3,55   "requires": true,66   "packages": {77     "": {88       "name": "peach",9-      "version": "2.35.1",9+      "version": "2.41.3",1010       "dependencies": {1111         "@bokuweb/zstd-wasm": "^0.0.27",12+        "@digitaldefiance/reed-solomon-erasure.wasm": "^1.0.2",1213         "@stablelib/blake2s": "^2.0.1",1314         "@stablelib/chacha20poly1305": "^2.0.1",1415         "@stablelib/hkdf": "^2.0.1",1516         "@stablelib/hmac": "^2.0.1",1617         "@stablelib/sha256": "^2.0.1",1718         "@stablelib/x25519": "^2.0.1",18-        "@types/qrcode": "^1.5.6",19+        "@zxing/library": "^0.23.0",20+        "bwip-js": "^4.10.1",1921         "hash-wasm": "^4.12.0",22+        "jsqr": "^1.4.0",2023         "otpauth": "^9.5.0",24+        "pako": "^2.1.0",2125         "papaparse": "^5.5.3",26+        "pdf-lib": "^1.17.1",2227         "qrcode": "^1.5.4",2328         "tldts": "^7.0.28",2429         "zxcvbn": "^4.4.2"@@ -28,7 +33,9 @@2833         "@playwright/test": "^1.59.1",2934         "@types/chrome": "^0.1.40",3035         "@types/node": "^22.0.0",36+        "@types/pako": "^2.0.4",3137         "@types/papaparse": "^5.5.2",38+        "@types/qrcode": "^1.5.6",3239         "@types/zxcvbn": "^4.4.5",3340         "@vitest/ui": "^4.1.4",3441         "jsdom": "^29.0.2",@@ -620,6 +627,15 @@620627         }621628       }622629     },630+    "node_modules/@digitaldefiance/reed-solomon-erasure.wasm": {631+      "version": "1.0.2",632+      "resolved": "https://registry.npmjs.org/@digitaldefiance/reed-solomon-erasure.wasm/-/reed-solomon-erasure.wasm-1.0.2.tgz",633+      "integrity": "sha512-Qltu+Vzj/x07rryH8wQo+5uMTtIxFEYi88phWgX+V5qVHdTcjMSSHXr6Ay24dPvsc6Q7NsdYnr28vQNkspxkfA==",634+      "license": "MIT",635+      "engines": {636+        "node": ">=18.0.0"637+      }638+    },623639     "node_modules/@emnapi/core": {624640       "version": "1.9.2",625641       "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.9.2.tgz",@@ -999,6 +1015,36 @@9991015         "url": "https://github.com/sponsors/Boshen"10001016       }10011017     },1018+    "node_modules/@pdf-lib/standard-fonts": {1019+      "version": "1.0.0",1020+      "resolved": "https://registry.npmjs.org/@pdf-lib/standard-fonts/-/standard-fonts-1.0.0.tgz",1021+      "integrity": "sha512-hU30BK9IUN/su0Mn9VdlVKsWBS6GyhVfqjwl1FjZN4TxP6cCw0jP2w7V3Hf5uX7M0AZJ16vey9yE0ny7Sa59ZA==",1022+      "license": "MIT",1023+      "dependencies": {1024+        "pako": "^1.0.6"1025+      }1026+    },1027+    "node_modules/@pdf-lib/standard-fonts/node_modules/pako": {1028+      "version": "1.0.11",1029+      "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",1030+      "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==",1031+      "license": "(MIT AND Zlib)"1032+    },1033+    "node_modules/@pdf-lib/upng": {1034+      "version": "1.0.1",1035+      "resolved": "https://registry.npmjs.org/@pdf-lib/upng/-/upng-1.0.1.tgz",1036+      "integrity": "sha512-dQK2FUMQtowVP00mtIksrlZhdFXQZPC+taih1q4CvPZ5vqdxR/LKBaFg0oAfzd1GlHZXXSPdQfzQnt+ViGvEIQ==",1037+      "license": "MIT",1038+      "dependencies": {1039+        "pako": "^1.0.10"1040+      }1041+    },1042+    "node_modules/@pdf-lib/upng/node_modules/pako": {1043+      "version": "1.0.11",1044+      "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",1045+      "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==",1046+      "license": "(MIT AND Zlib)"1047+    },10021048     "node_modules/@playwright/test": {10031049       "version": "1.59.1",10041050       "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.59.1.tgz",@@ -1632,11 +1678,19 @@16321678       "version": "22.19.17",16331679       "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.17.tgz",16341680       "integrity": "sha512-wGdMcf+vPYM6jikpS/qhg6WiqSV/OhG+jeeHT/KlVqxYfD40iYJf9/AE1uQxVWFvU7MipKRkRv8NSHiCGgPr8Q==",1681+      "dev": true,16351682       "license": "MIT",16361683       "dependencies": {16371684         "undici-types": "~6.21.0"16381685       }16391686     },1687+    "node_modules/@types/pako": {1688+      "version": "2.0.4",1689+      "resolved": "https://registry.npmjs.org/@types/pako/-/pako-2.0.4.tgz",1690+      "integrity": "sha512-VWDCbrLeVXJM9fihYodcLiIv0ku+AlOa/TQ1SvYOaBuyrSKgEcro95LJyIsJ4vSo6BXIxOKxiJAat04CmST9Fw==",1691+      "dev": true,1692+      "license": "MIT"1693+    },16401694     "node_modules/@types/papaparse": {16411695       "version": "5.5.2",16421696       "resolved": "https://registry.npmjs.org/@types/papaparse/-/papaparse-5.5.2.tgz",@@ -1651,6 +1705,7 @@16511705       "version": "1.5.6",16521706       "resolved": "https://registry.npmjs.org/@types/qrcode/-/qrcode-1.5.6.tgz",16531707       "integrity": "sha512-te7NQcV2BOvdj2b1hCAHzAoMNuj65kNBMz0KBaxM6c3VGBOhU0dURQKOtH8CFNI/dsKkwlv32p26qYQTWoB5bw==",1708+      "dev": true,16541709       "license": "MIT",16551710       "dependencies": {16561711         "@types/node": "*"@@ -1839,6 +1894,28 @@18391894       "dev": true,18401895       "license": "BSD-3-Clause"18411896     },1897+    "node_modules/@zxing/library": {1898+      "version": "0.23.0",1899+      "resolved": "https://registry.npmjs.org/@zxing/library/-/library-0.23.0.tgz",1900+      "integrity": "sha512-6fkkoFwP8CHxl6ugnPsj74PLJgX2iRv5zczGAyt5OBzQgxFhuhF0NCEc4t4OvSr8xAv2MRLlI0Iu9ZGDZQ2urA==",1901+      "license": "Apache-2.0",1902+      "dependencies": {1903+        "ts-custom-error": "^3.3.1"1904+      },1905+      "engines": {1906+        "node": ">= 24.0.0"1907+      },1908+      "optionalDependencies": {1909+        "@zxing/text-encoding": "~0.9.0"1910+      }1911+    },1912+    "node_modules/@zxing/text-encoding": {1913+      "version": "0.9.0",1914+      "resolved": "https://registry.npmjs.org/@zxing/text-encoding/-/text-encoding-0.9.0.tgz",1915+      "integrity": "sha512-U/4aVJ2mxI0aDNI8Uq0wEhMgY+u4CNtEb0om3+y3+niDAsoTCOB33UF0sxpzqzdqXLqmvc+vZyAt4O8pPdfkwA==",1916+      "license": "(Unlicense OR Apache-2.0)",1917+      "optional": true1918+    },18421919     "node_modules/acorn": {18431920       "version": "8.16.0",18441921       "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz",@@ -2515,6 +2592,15 @@25152592         "url": "https://github.com/sponsors/sindresorhus"25162593       }25172594     },2595+    "node_modules/bwip-js": {2596+      "version": "4.10.1",2597+      "resolved": "https://registry.npmjs.org/bwip-js/-/bwip-js-4.10.1.tgz",2598+      "integrity": "sha512-I/cEPiXsu7dRCp78PpVY4gdIXmbH752n8dMC+DStM77XPkrzeathdYrjnZ/i/vZPIxXTUWc+JxgJ/MvbodqPLA==",2599+      "license": "MIT",2600+      "bin": {2601+        "bwip-js": "bin/bwip-js.js"2602+      }2603+    },25182604     "node_modules/callsites": {25192605       "version": "3.1.0",25202606       "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",@@ -4570,6 +4656,12 @@45704656         "graceful-fs": "^4.1.6"45714657       }45724658     },4659+    "node_modules/jsqr": {4660+      "version": "1.4.0",4661+      "resolved": "https://registry.npmjs.org/jsqr/-/jsqr-1.4.0.tgz",4662+      "integrity": "sha512-dxLob7q65Xg2DvstYkRpkYtmKm2sPJ9oFhrhmudT1dZvNFFTlroai3AWSpLey/w5vMcLBXRgOJsbXpdN9HzU/A==",4663+      "license": "Apache-2.0"4664+    },45734665     "node_modules/jszip": {45744666       "version": "3.10.1",45754667       "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz",@@ -4583,6 +4675,13 @@45834675         "setimmediate": "^1.0.5"45844676       }45854677     },4678+    "node_modules/jszip/node_modules/pako": {4679+      "version": "1.0.11",4680+      "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",4681+      "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==",4682+      "dev": true,4683+      "license": "(MIT AND Zlib)"4684+    },45864685     "node_modules/keyv": {45874686       "version": "4.5.4",45884687       "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",@@ -5352,10 +5451,9 @@53525451       }53535452     },53545453     "node_modules/pako": {5355-      "version": "1.0.11",5356-      "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",5357-      "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==",5358-      "dev": true,5454+      "version": "2.1.0",5455+      "resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz",5456+      "integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==",53595457       "license": "(MIT AND Zlib)"53605458     },53615459     "node_modules/papaparse": {@@ -5486,6 +5584,30 @@54865584       "dev": true,54875585       "license": "MIT"54885586     },5587+    "node_modules/pdf-lib": {5588+      "version": "1.17.1",5589+      "resolved": "https://registry.npmjs.org/pdf-lib/-/pdf-lib-1.17.1.tgz",5590+      "integrity": "sha512-V/mpyJAoTsN4cnP31vc0wfNA1+p20evqqnap0KLoRUN0Yk/p3wN52DOEsL4oBFcLdb76hlpKPtzJIgo67j/XLw==",5591+      "license": "MIT",5592+      "dependencies": {5593+        "@pdf-lib/standard-fonts": "^1.0.0",5594+        "@pdf-lib/upng": "^1.0.1",5595+        "pako": "^1.0.11",5596+        "tslib": "^1.11.1"5597+      }5598+    },5599+    "node_modules/pdf-lib/node_modules/pako": {5600+      "version": "1.0.11",5601+      "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",5602+      "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==",5603+      "license": "(MIT AND Zlib)"5604+    },5605+    "node_modules/pdf-lib/node_modules/tslib": {5606+      "version": "1.14.1",5607+      "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",5608+      "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==",5609+      "license": "0BSD"5610+    },54895611     "node_modules/pend": {54905612       "version": "1.2.0",54915613       "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz",@@ -6595,6 +6717,15 @@65956717         "node": ">=20"65966718       }65976719     },6720+    "node_modules/ts-custom-error": {6721+      "version": "3.3.1",6722+      "resolved": "https://registry.npmjs.org/ts-custom-error/-/ts-custom-error-3.3.1.tgz",6723+      "integrity": "sha512-5OX1tzOjxWEgsr/YEUWSuPrQ00deKLh6D7OTWcvNHm12/7QPyRh8SYpyWvA4IZv8H/+GQWQEh/kwo95Q9OVW1A==",6724+      "license": "MIT",6725+      "engines": {6726+        "node": ">=14.0.0"6727+      }6728+    },65986729     "node_modules/tslib": {65996730       "version": "2.8.1",66006731       "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",@@ -6663,6 +6794,7 @@66636794       "version": "6.21.0",66646795       "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",66656796       "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",6797+      "dev": true,66666798       "license": "MIT"66676799     },66686800     "node_modules/universalify": {

view file

package.json+7 −1
@@ -1,6 +1,6 @@11 {22   "name": "peach",3-  "version": "2.41.3",3+  "version": "2.42.0",44   "description": "Zero-knowledge password manager",55   "type": "module",66   "scripts": {@@ -19,16 +19,21 @@1919   },2020   "dependencies": {2121     "@bokuweb/zstd-wasm": "^0.0.27",22+    "@digitaldefiance/reed-solomon-erasure.wasm": "^1.0.2",2223     "@stablelib/blake2s": "^2.0.1",2324     "@stablelib/chacha20poly1305": "^2.0.1",2425     "@stablelib/hkdf": "^2.0.1",2526     "@stablelib/hmac": "^2.0.1",2627     "@stablelib/sha256": "^2.0.1",2728     "@stablelib/x25519": "^2.0.1",29+    "@zxing/library": "^0.23.0",30+    "bwip-js": "^4.10.1",2831     "hash-wasm": "^4.12.0",2932     "jsqr": "^1.4.0",3033     "otpauth": "^9.5.0",34+    "pako": "^2.1.0",3135     "papaparse": "^5.5.3",36+    "pdf-lib": "^1.17.1",3237     "qrcode": "^1.5.4",3338     "tldts": "^7.0.28",3439     "zxcvbn": "^4.4.2"@@ -38,6 +43,7 @@3843     "@playwright/test": "^1.59.1",3944     "@types/chrome": "^0.1.40",4045     "@types/node": "^22.0.0",46+    "@types/pako": "^2.0.4",4147     "@types/papaparse": "^5.5.2",4248     "@types/qrcode": "^1.5.6",4349     "@types/zxcvbn": "^4.4.5",

view file

public/codex/reed_solomon_erasure_bg.wasmbinary

binary file, not shown

view file

src/popup/codex/rs-loader.ts+69 −0
@@ -0,0 +1,69 @@1+/**2+ * rs-loader.ts — load the Reed-Solomon erasure WASM module.3+ *4+ * The same code runs in two contexts:5+ *  - Extension popup (browser): fetch the WASM via `chrome.runtime.getURL`6+ *    so the asset path resolves against the extension package. The WASM7+ *    file is shipped from `public/codex/reed_solomon_erasure_bg.wasm`.8+ *  - Vitest (Node + jsdom): read the WASM bytes from `node_modules`9+ *    synchronously. We avoid the library's `fromCurrentDirectory*` helpers10+ *    because they probe `__dirname`, which is unreliable when the test11+ *    file is bundled by Vite.12+ *13+ * The instance is cached — WASM instantiation is non-trivial work and the14+ * library is safe to reuse across encode / reconstruct calls.15+ */16+17+import { ReedSolomonErasure } from "@digitaldefiance/reed-solomon-erasure.wasm";18+import { CodexError } from "../../shared/codex-types";19+20+const WASM_RELATIVE_PATH = "codex/reed_solomon_erasure_bg.wasm";21+22+let cached: Promise<ReedSolomonErasure> | null = null;23+24+export function getReedSolomon(): Promise<ReedSolomonErasure> {25+  if (!cached) cached = loadReedSolomon();26+  return cached;27+}28+29+async function loadReedSolomon(): Promise<ReedSolomonErasure> {30+  try {31+    if (isExtensionPopup()) {32+      const url = chrome.runtime.getURL(WASM_RELATIVE_PATH);33+      const response = await fetch(url);34+      if (!response.ok) {35+        throw new Error(`fetch ${url} → ${response.status}`);36+      }37+      const bytes = new Uint8Array(await response.arrayBuffer());38+      return ReedSolomonErasure.fromBytes(bytes);39+    }40+41+    // Node / vitest path. Dynamic import so the bundler does not try to42+    // resolve `node:fs` for the browser build.43+    const fs = await import("node:fs");44+    const path = await import("node:path");45+    const wasmPath = path.resolve(46+      "node_modules/@digitaldefiance/reed-solomon-erasure.wasm/dist/reed_solomon_erasure_bg.wasm",47+    );48+    const bytes = fs.readFileSync(wasmPath);49+    return ReedSolomonErasure.fromBytes(bytes);50+  } catch (err) {51+    throw new CodexError(52+      "RS_LIBRARY_FAILED",53+      `Failed to load Reed-Solomon WASM module: ${err instanceof Error ? err.message : String(err)}`,54+    );55+  }56+}57+58+function isExtensionPopup(): boolean {59+  return (60+    typeof chrome !== "undefined" &&61+    typeof chrome.runtime !== "undefined" &&62+    typeof chrome.runtime.getURL === "function"63+  );64+}65+66+/** Reset the cache. Test-only — never called from production code. */67+export function _resetReedSolomonCacheForTest(): void {68+  cached = null;69+}

view file

src/shared/codex-types.ts+180 −0
@@ -0,0 +1,180 @@1+/**2+ * codex-types.ts — shared shapes for the Peach Codex.3+ *4+ * The Codex is a printed A4 PDF that encodes the user's encrypted vault as5+ * Aztec barcodes plus optional recovery words. See /Users/june/Downloads/peach_codex_v2.md6+ * for the full design doc.7+ *8+ * These types are the contract between four execution boundaries:9+ *  - service worker (`src/background/codex-handlers.ts`)10+ *  - popup library (`src/popup/codex/*`)11+ *  - popup views (`src/popup/views/CodexGenerateView.tsx`, `CodexRestoreView.tsx`)12+ *  - tests (`src/popup/codex/*.test.ts`)13+ *14+ * Bump `version` (and `ChunkEnvelope.v`) only on breaking changes to the15+ * on-paper format. A v2 Codex must always be decodable by future Peach16+ * versions.17+ */18+19+import type { VaultEntry } from "./types";20+21+/** Payload that the Codex encodes. Compressed, chunked, erasure-coded, then printed. */22+export interface CodexPayload {23+  /** Format version. Increment on breaking changes. */24+  version: 2;25+  /** 12-char alphanumeric: `PX-XXXX-XXXX`. Identifies a vault across Codex prints. */26+  verifyCode: string;27+  /** Base64url-encoded JSON `{ a: slotA, b: slotB|null }` — both encrypted vault slots. */28+  vaultBlob: string;29+  /** Vault last-modified unix timestamp (ms). Used to compare against on-device vault on restore. */30+  timestamp: number;31+  /** Visible entry count for user confirmation at restore time. */32+  entryCount: number;33+  /** First 16 chars of SHA-256(vaultBlob), uppercase hex. Verifies reconstruction integrity. */34+  vaultFingerprint: string;35+  /** When this Codex PDF was generated. Printed in the footer. */36+  generatedAt: number;37+  /** True if the last page includes the 12 recovery words. */38+  includesRecoveryWords: boolean;39+}40+41+/**42+ * One Aztec barcode's contents. Each envelope is a JSON-encoded chunk of the43+ * compressed payload plus a per-chunk integrity hash.44+ *45+ * Field names are intentionally single letters to keep the encoded JSON46+ * compact — every byte saved shrinks the printed page count.47+ */48+export interface ChunkEnvelope {49+  /** Envelope format version. Increment on breaking changes. */50+  v: 2;51+  /** First 12 chars of `verifyCode` ("PX-XXXX-XXXX"). Lets the scanner reject foreign Codexes. */52+  c: string;53+  /** Chunk index, 0-based. */54+  i: number;55+  /** Total chunk count (data + parity). */56+  t: number;57+  /** True if this is a Reed-Solomon parity shard. */58+  p: boolean;59+  /** Chunk data, base64url-encoded. */60+  d: string;61+  /** First 8 chars of SHA-256(rawChunkBytes), uppercase hex. Catches per-chunk corruption. */62+  h: string;63+}64+65+/** Bookkeeping returned alongside the generated PDF. */66+export interface CodexManifest {67+  verifyCode: string;68+  totalChunks: number;69+  dataChunks: number;70+  parityChunks: number;71+  pageCount: number;72+  entryCount: number;73+  vaultFingerprint: string;74+  generatedAt: number;75+  includesRecoveryWords: boolean;76+}77+78+/** Dependencies the generator needs from the service worker. */79+export interface CodexDeps {80+  getSlotA: () => Promise<string | null>;81+  getSlotB: () => Promise<string | null>;82+  getLastModified: () => Promise<string | null>;83+  getAllEntries: () => Promise<VaultEntry[]>;84+  getRecoveryWords: () => Promise<string[] | null>;85+  getVerifyCode: () => Promise<string>;86+  isVaultUnlocked: () => boolean;87+}88+89+export interface CodexOptions {90+  includeRecoveryWords: boolean;91+}92+93+/** Payload sent from the popup to the SW to perform the actual vault restoration. */94+export interface CodexRestorePayload {95+  vaultBlob: string;96+  timestamp: number;97+  verifyCode: string;98+  vaultFingerprint: string;99+  entryCount: number;100+  masterPassphrase: string;101+}102+103+/** Vault data the SW exposes to the popup so it can build a CodexPayload. */104+export interface CodexVaultSnapshot {105+  slotA: string | null;106+  slotB: string | null;107+  lastModified: string | null;108+  entryCount: number;109+  recoveryWords: string[] | null;110+  verifyCode: string;111+  vaultUnlocked: boolean;112+}113+114+/**115+ * Every Codex error is loud and named. The `code` field is a stable identifier116+ * for log analysis; the human-readable message lives in `.message`.117+ */118+export class CodexError extends Error {119+  constructor(120+    public code: CodexErrorCode,121+    message: string,122+  ) {123+    super(message);124+    this.name = "CodexError";125+  }126+}127+128+export type CodexErrorCode =129+  | "VAULT_LOCKED"130+  | "VAULT_EMPTY"131+  | "RECOVERY_WORDS_MISSING"132+  | "RECOVERY_WORDS_INVALID"133+  | "COMPRESSION_ROUNDTRIP_FAILED"134+  | "ERASURE_CODING_VERIFICATION_FAILED"135+  | "ERASURE_CODING_CONTENT_MISMATCH"136+  | "AZTEC_GENERATION_FAILED"137+  | "AZTEC_ROUNDTRIP_FAILED"138+  | "AZTEC_CONTENT_MISMATCH"139+  | "INSUFFICIENT_CHUNKS"140+  | "RECONSTRUCTION_SIMULATION_FAILED"141+  | "RECONSTRUCTION_VERIFY_CODE_MISMATCH"142+  | "RECONSTRUCTION_FINGERPRINT_MISMATCH"143+  | "RECONSTRUCTION_COUNT_MISMATCH"144+  | "PDF_GENERATION_FAILED"145+  | "RS_LIBRARY_FAILED"146+  | "PRO_REQUIRED"147+  | "WRONG_PASSPHRASE"148+  | "INVALID_VAULT_BLOB"149+  | "RESTORE_FAILED";150+151+// ── Constants — shared across generation and restoration ─────────────────────152+153+/**154+ * Bytes of binary data per chunk. Each chunk becomes one Aztec code; this size155+ * keeps the printed code at ~28mm (CODE_SIZE in PDF rendering) while staying156+ * within Aztec's 1914-byte ECC-25% capacity for compact codes.157+ *158+ * Conservative: leaves headroom for the JSON envelope overhead (~120 bytes).159+ */160+export const CHUNK_SIZE = 1024;161+162+/**163+ * Number of parity shards. With 2 parity shards, the Codex survives the loss164+ * of any 2 chunks (torn corner, coffee stain over one code, etc.) without165+ * data loss. Raising this beyond 2 grows the page count meaningfully.166+ */167+export const PARITY_SHARDS = 2;168+169+/** Aztec error-correction percentage. 25% balances barcode density vs. resilience. */170+export const AZTEC_ECC_PERCENT = 25;171+172+/** PDF layout: 6 columns × 4 rows = 24 codes per A4 page. */173+export const CODES_PER_ROW = 6;174+export const CODES_PER_COL = 4;175+export const CODES_PER_PAGE = CODES_PER_ROW * CODES_PER_COL;176+177+/** Stable storage keys for Codex bookkeeping. */178+export const STORAGE_KEY_VERIFY_CODE = "peach_codex_verify_code";179+export const STORAGE_KEY_LAST_GENERATED = "peach_codex_last_generated";180+export const STORAGE_KEY_LAST_RESTORED = "peach_last_restored_at";

view file