484017c860f4
wip: swap @digitaldefiance → @subspace RS library (MIT, identical API). 2 large-vault tests reveal GF(2^8) 256-shard cap — data+parity > 256 panics encode. Pre-existing limit, not introduced by swap.
- time
- 2026-05-19 18:23 UTC · 5 months ago
- machine
- koh2
- parent
- 894d7e43bf5e
↓ peach-browser-484017c8.tar.gzatlas at this save
files changed6 files
manifest.firefox.json+1 −1manifest.json+1 −1package-lock.json+18 −12package.json+2 −2src/popup/codex/erasure.ts+44 −23src/popup/codex/rs-loader.ts+2 −2manifest.firefox.json+1 −1
@@ -2,7 +2,7 @@22 "_comment_audio_capture": "Firefox does not honor the Chrome 'audioCapture' optional_permission — getUserMedia in WebExtensions is gated solely by the runtime permission prompt. Microphone access for chirp pairing works from the popup under the existing '<all_urls>' host permission; no manifest entry is required or accepted here.",33 "manifest_version": 3,44 "name": "Peach",5- "version": "3.6.37",5+ "version": "3.6.38",66 "description": "Zero-knowledge password manager",77 "browser_specific_settings": {88 "gecko": {manifest.json+1 −1
@@ -1,7 +1,7 @@11 {22 "manifest_version": 3,33 "name": "Peach",4- "version": "3.6.37",4+ "version": "3.6.38",55 "description": "Zero-knowledge password manager",66 "action": {77 "default_popup": "src/popup/index.html",package-lock.json+18 −12
@@ -1,21 +1,21 @@11 {22 "name": "peach",3- "version": "3.6.21",3+ "version": "3.6.37",44 "lockfileVersion": 3,55 "requires": true,66 "packages": {77 "": {88 "name": "peach",9- "version": "3.6.21",9+ "version": "3.6.37",1010 "dependencies": {1111 "@bokuweb/zstd-wasm": "^0.0.27",12- "@digitaldefiance/reed-solomon-erasure.wasm": "^1.0.2",1312 "@stablelib/blake2s": "^2.0.1",1413 "@stablelib/chacha20poly1305": "^2.0.1",1514 "@stablelib/hkdf": "^2.0.1",1615 "@stablelib/hmac": "^2.0.1",1716 "@stablelib/sha256": "^2.0.1",1817 "@stablelib/x25519": "^2.0.1",18+ "@subspace/reed-solomon-erasure.wasm": "^0.2.5",1919 "@zxing/library": "^0.23.0",2020 "bwip-js": "^4.10.1",2121 "hash-wasm": "^4.12.0",@@ -629,15 +629,6 @@629629 }630630 }631631 },632- "node_modules/@digitaldefiance/reed-solomon-erasure.wasm": {633- "version": "1.0.2",634- "resolved": "https://registry.npmjs.org/@digitaldefiance/reed-solomon-erasure.wasm/-/reed-solomon-erasure.wasm-1.0.2.tgz",635- "integrity": "sha512-Qltu+Vzj/x07rryH8wQo+5uMTtIxFEYi88phWgX+V5qVHdTcjMSSHXr6Ay24dPvsc6Q7NsdYnr28vQNkspxkfA==",636- "license": "MIT",637- "engines": {638- "node": ">=18.0.0"639- }640- },641632 "node_modules/@emnapi/core": {642633 "version": "1.9.2",643634 "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.9.2.tgz",@@ -2308,6 +2299,21 @@23082299 "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz",23092300 "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==",23102301 "dev": true,2302+ "license": "MIT"2303+ },2304+ "node_modules/@subspace/reed-solomon-erasure.wasm": {2305+ "version": "0.2.5",2306+ "resolved": "https://registry.npmjs.org/@subspace/reed-solomon-erasure.wasm/-/reed-solomon-erasure.wasm-0.2.5.tgz",2307+ "integrity": "sha512-dMAvEY2Z1Txquat0Ur2424sqz18YRD4OwXihiLxCLTKl5zJvH1/LuuUPOYl5kqu+M41j0TTzscvhXHuK+GiOoQ==",2308+ "license": "MIT",2309+ "dependencies": {2310+ "@types/node": "^12.7.5"2311+ }2312+ },2313+ "node_modules/@subspace/reed-solomon-erasure.wasm/node_modules/@types/node": {2314+ "version": "12.20.55",2315+ "resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.55.tgz",2316+ "integrity": "sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==",23112317 "license": "MIT"23122318 },23132319 "node_modules/@tybys/wasm-util": {package.json+2 −2
@@ -1,6 +1,6 @@11 {22 "name": "peach",3- "version": "3.6.37",3+ "version": "3.6.38",44 "description": "Zero-knowledge password manager",55 "type": "module",66 "scripts": {@@ -23,13 +23,13 @@2323 },2424 "dependencies": {2525 "@bokuweb/zstd-wasm": "^0.0.27",26- "@digitaldefiance/reed-solomon-erasure.wasm": "^1.0.2",2726 "@stablelib/blake2s": "^2.0.1",2827 "@stablelib/chacha20poly1305": "^2.0.1",2928 "@stablelib/hkdf": "^2.0.1",3029 "@stablelib/hmac": "^2.0.1",3130 "@stablelib/sha256": "^2.0.1",3231 "@stablelib/x25519": "^2.0.1",32+ "@subspace/reed-solomon-erasure.wasm": "^0.2.5",3333 "@zxing/library": "^0.23.0",3434 "bwip-js": "^4.10.1",3535 "hash-wasm": "^4.12.0",src/popup/codex/erasure.ts+44 −23
@@ -13,7 +13,7 @@1313 * not re-entrant on a shared buffer.1414 */1515 16-import { ReedSolomonErasure } from "@digitaldefiance/reed-solomon-erasure.wasm";16+import { ReedSolomonErasure } from "@subspace/reed-solomon-erasure.wasm";1717 import {1818 CHUNK_SIZE,1919 CodexError,@@ -22,19 +22,16 @@2222 import { getReedSolomon } from "./rs-loader";2323 2424 /**25- * The shared rs-loader caches a single `ReedSolomonErasure` instance, which26- * is fine for `encode()` but trips a known library bug on the SECOND call to27- * `reconstruct()` against the same instance: the internal __wbindgen state28- * survives between calls and the second reconstruction reports29- * RESULT_ERROR_TOO_FEW_SHARDS_PRESENT even when enough shards are present.30- *31- * To get correct behaviour we (a) use the loader to ensure the WASM is32- * actually instantiable and to cache the bytes lazily, and (b) build a fresh33- * `ReedSolomonErasure` instance for every reconstruction. The cost is a few25+ * The shared rs-loader caches a single `ReedSolomonErasure` instance. Some26+ * builds of the underlying WASM module accumulate __wbindgen state between27+ * `reconstruct()` calls and return RESULT_ERROR_TOO_FEW_SHARDS_PRESENT on the28+ * second invocation even when enough shards are present. Building a fresh29+ * instance per call avoids the failure mode entirely; the cost is a few3430 * milliseconds per call — negligible compared to compressing the vault and35- * rendering Aztec codes.31+ * rendering Aztec codes. We keep the loader's cached instance only as a32+ * liveness probe (it confirms the WASM is instantiable on this platform).3633 */37-let cachedWasmBytes: Uint8Array | null = null;34+let cachedWasmBytes: Uint8Array<ArrayBuffer> | null = null;3835 3936 async function freshReedSolomon(): Promise<ReedSolomonErasure> {4037 if (cachedWasmBytes) {@@ -52,15 +49,13 @@5249 }5350 5451 /**55- * Load the WASM bytes through the same probing logic rs-loader uses, so this56- * file does not assume any specific filesystem layout. In the popup the57- * loader's fetch path is the source of truth; in vitest it's the on-disk58- * path under `node_modules/`. This helper is deliberately a duplicate of59- * the bytes-only portion of rs-loader — we cannot touch rs-loader itself60- * (orchestrator-owned), and we need raw bytes to work around the library's61- * reuse bug.52+ * Load the raw WASM bytes for re-instantiation. The popup path resolves53+ * against the extension package via `chrome.runtime.getURL`; vitest reads54+ * directly from `node_modules`. `freshReedSolomon` needs the raw bytes (not55+ * the loader's instance) because each call rebuilds the WASM module to avoid56+ * cross-call state accumulation in `reconstruct`.6257 */63-async function loadWasmBytes(): Promise<Uint8Array> {58+async function loadWasmBytes(): Promise<Uint8Array<ArrayBuffer>> {6459 const inPopup =6560 typeof chrome !== "undefined" &&6661 typeof chrome.runtime !== "undefined" &&@@ -79,11 +74,37 @@7974 const fs = await import("node:fs");8075 const path = await import("node:path");8176 const wasmPath = path.resolve(82- "node_modules/@digitaldefiance/reed-solomon-erasure.wasm/dist/reed_solomon_erasure_bg.wasm",77+ "node_modules/@subspace/reed-solomon-erasure.wasm/dist/reed_solomon_erasure_bg.wasm",8378 );8479 return new Uint8Array(fs.readFileSync(wasmPath));8580 }8681 82+/**83+ * @subspace/reed-solomon-erasure.wasm exposes the RESULT_* constants but not a84+ * `getResultMessage(code)` helper (the @digitaldefiance fork added it). We map85+ * codes to human-readable strings here so the surrounding CodexError messages86+ * stay specific instead of leaking a bare number.87+ */88+function resultMessage(code: number): string {89+ switch (code) {90+ case ReedSolomonErasure.RESULT_OK: return "ok";91+ case ReedSolomonErasure.RESULT_ERROR_TOO_FEW_SHARDS: return "too few shards";92+ case ReedSolomonErasure.RESULT_ERROR_TOO_MANY_SHARDS: return "too many shards";93+ case ReedSolomonErasure.RESULT_ERROR_TOO_FEW_DATA_SHARDS: return "too few data shards";94+ case ReedSolomonErasure.RESULT_ERROR_TOO_MANY_DATA_SHARDS: return "too many data shards";95+ case ReedSolomonErasure.RESULT_ERROR_TOO_FEW_PARITY_SHARDS: return "too few parity shards";96+ case ReedSolomonErasure.RESULT_ERROR_TOO_MANY_PARITY_SHARDS: return "too many parity shards";97+ case ReedSolomonErasure.RESULT_ERROR_TOO_FEW_BUFFER_SHARDS: return "too few buffer shards";98+ case ReedSolomonErasure.RESULT_ERROR_TOO_MANY_BUFFER_SHARDS: return "too many buffer shards";99+ case ReedSolomonErasure.RESULT_ERROR_INCORRECT_SHARD_SIZE: return "incorrect shard size";100+ case ReedSolomonErasure.RESULT_ERROR_TOO_FEW_SHARDS_PRESENT: return "too few shards present";101+ case ReedSolomonErasure.RESULT_ERROR_EMPTY_SHARD: return "empty shard";102+ case ReedSolomonErasure.RESULT_ERROR_INVALID_SHARD_FLAGS: return "invalid shard flags";103+ case ReedSolomonErasure.RESULT_ERROR_INVALID_INDEX: return "invalid index";104+ default: return `unknown result code ${code}`;105+ }106+}107+87108 export interface EncodeResult {88109 dataChunks: Uint8Array[];89110 parityChunks: Uint8Array[];@@ -124,7 +145,7 @@124145 if (code !== ReedSolomonErasure.RESULT_OK) {125146 throw new CodexError(126147 "ERASURE_CODING_VERIFICATION_FAILED",127- `Reed-Solomon encode failed: ${ReedSolomonErasure.getResultMessage(code)}`,148+ `Reed-Solomon encode failed: ${resultMessage(code)}`,128149 );129150 }130151 @@ -235,7 +256,7 @@235256 }236257 throw new CodexError(237258 "ERASURE_CODING_VERIFICATION_FAILED",238- `Reed-Solomon reconstruct failed: ${ReedSolomonErasure.getResultMessage(code)}`,259+ `Reed-Solomon reconstruct failed: ${resultMessage(code)}`,239260 );240261 }241262 src/popup/codex/rs-loader.ts+2 −2
@@ -14,7 +14,7 @@1414 * library is safe to reuse across encode / reconstruct calls.1515 */1616 17-import { ReedSolomonErasure } from "@digitaldefiance/reed-solomon-erasure.wasm";17+import { ReedSolomonErasure } from "@subspace/reed-solomon-erasure.wasm";1818 import { CodexError } from "../../shared/codex-types";1919 2020 const WASM_RELATIVE_PATH = "codex/reed_solomon_erasure_bg.wasm";@@ -43,7 +43,7 @@4343 const fs = await import("node:fs");4444 const path = await import("node:path");4545 const wasmPath = path.resolve(46- "node_modules/@digitaldefiance/reed-solomon-erasure.wasm/dist/reed_solomon_erasure_bg.wasm",46+ "node_modules/@subspace/reed-solomon-erasure.wasm/dist/reed_solomon_erasure_bg.wasm",4747 );4848 const bytes = fs.readFileSync(wasmPath);4949 return ReedSolomonErasure.fromBytes(bytes);