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
6 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 −2
manifest.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": {

view file

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",

view file

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": {

view file

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",

view file

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 

view file

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);

view file