3c4de4d66983
fix Mayan decoder (estimateModuleSize, findOthalan filter); fix passkey export in PeachScript/Mayan (entryTypeByte + ENTRY_TYPE_PASSKEY encoding for v2.11+ sub-object schema)
- time
- 2026-05-01 14:18 UTC · 5 months ago
- machine
- koh2
- parent
- 8fa1082f4aae
↓ peach-browser-3c4de4d6.tar.gzatlas at this save
files changed4 files
manifest.firefox.json+1 −1manifest.json+1 −1src/crypto/mayan.ts+10 −20src/crypto/peachPayload.ts+25 −14manifest.firefox.json+1 −1
@@ -1,7 +1,7 @@11 {22 "manifest_version": 3,33 "name": "Peach",4- "version": "2.21.0",4+ "version": "2.22.0",55 "description": "Zero-knowledge password manager",66 "browser_specific_settings": {77 "gecko": {manifest.json+1 −1
@@ -1,7 +1,7 @@11 {22 "manifest_version": 3,33 "name": "Peach",4- "version": "2.21.0",4+ "version": "2.22.0",55 "description": "Zero-knowledge password manager",66 "action": {77 "default_popup": "src/popup/index.html",src/crypto/mayan.ts+10 −20
@@ -451,10 +451,11 @@451451 const dark2Len = x - dark2Start;452452 453453 if (454- darkLen >= 2 &&455- dark2Len >= 2 &&456- Math.abs(darkLen - dark2Len) <= darkLen * 0.5 &&457- lightLen > darkLen * 1.5454+ darkLen >= 4 &&455+ dark2Len >= 4 &&456+ Math.abs(darkLen - dark2Len) <= darkLen * 0.3 &&457+ lightLen > darkLen * 1.5 &&458+ lightLen < darkLen * 12458459 ) {459460 const cx = Math.floor((darkStart + x) / 2);460461 const size = x - darkStart;@@ -483,24 +484,13 @@483484 }484485 485486 function estimateModuleSize(486- binary: boolean[][],487+ _binary: boolean[][],487488 rune: { cx: number; cy: number; size: number },488- W: number,489+ _W: number,489490 ): number {490- const y = rune.cy;491- const strokeWidths: number[] = [];492- for (let x = Math.max(0, rune.cx - rune.size); x < rune.cx + rune.size; x++) {493- if (x >= W) break;494- if (binary[y][x] && (x === 0 || !binary[y][x - 1])) {495- let runLen = 0;496- let xx = x;497- while (xx < W && binary[y][xx]) { runLen++; xx++; }498- if (runLen > 1 && runLen < rune.size / 2) strokeWidths.push(runLen);499- }500- }501- if (strokeWidths.length === 0) return rune.size / 23;502- const avg = strokeWidths.reduce((a, b) => a + b, 0) / strokeWidths.length;503- return avg / 3;491+ // Rune spans ~23 modules at its widest horizontal cross-section.492+ // Round to integer: module boundaries are always integer pixels.493+ return Math.round(rune.size / 23);504494 }505495 506496 function detectRotation(src/crypto/peachPayload.ts+25 −14
@@ -471,21 +471,28 @@471471 }472472 } else if (type === ENTRY_TYPE_PASSKEY) {473473 // JWK + COSE + opaque IDs do not pack cleanly. Inline a JSON blob.474+ // v2.11+ stores the passkey in a nested sub-object; pre-v2.11 used flat fields.475+ const pk = entry.passkey;476+ const flat = entry as unknown as Record<string, unknown>;474477 const blob = encodeJsonSection({475- rpId: entry.rpId,476- rpName: entry.rpName,477- credentialId: entry.credentialId,478- publicKeyCose: entry.publicKeyCose,479- privateKeyJwk: entry.privateKeyJwk,480- userHandle: entry.userHandle,481- userName: entry.userName,482- userDisplayName: entry.userDisplayName,483- algorithm: entry.algorithm,484- signCount: entry.signCount,485- aaguid: entry.aaguid,486- backupEligible: entry.backupEligible,487- backupState: entry.backupState,488- lastUsedAt: entry.lastUsedAt,478+ rpId: pk ? pk.rpId : flat["rpId"],479+ rpName: pk ? pk.rpName : flat["rpName"],480+ credentialId: pk ? pk.credentialId : flat["credentialId"],481+ publicKeyCose: pk ? pk.publicKeyCose : flat["publicKeyCose"],482+ privateKeyJwk: pk ? pk.privateKeyJwk : flat["privateKeyJwk"],483+ userHandle: pk ? pk.userHandle : flat["userHandle"],484+ userName: pk ? pk.userName : flat["userName"],485+ userDisplayName: pk ? pk.userDisplayName : flat["userDisplayName"],486+ algorithm: pk ? pk.algorithm : flat["algorithm"],487+ signCount: pk ? pk.signCount : flat["signCount"],488+ aaguid: pk ? pk.aaguid : flat["aaguid"],489+ backupEligible: pk ? pk.backupEligible : flat["backupEligible"],490+ backupState: pk ? pk.backupState : flat["backupState"],491+ lastUsedAt: pk ? pk.lastUsedAt : flat["lastUsedAt"],492+ // Preserve login fields for passkey-bearing login entries.493+ ...(pk && entry.password !== undefined ? { password: entry.password } : {}),494+ ...(pk && entry.url !== undefined ? { url: entry.url } : {}),495+ ...(pk && entry.username !== undefined ? { username: entry.username } : {}),489496 });490497 writer.u32(blob.length);491498 writer.bytes(blob);@@ -649,6 +656,10 @@649656 }650657 651658 function entryTypeByte(entry: VaultEntry): number {659+ // Passkey sub-object takes precedence — the v2.11+ schema stores passkeys660+ // as type:"login" entries with a nested passkey sub-object, so the type661+ // field alone is not sufficient to detect them.662+ if (entry.passkey) return ENTRY_TYPE_PASSKEY;652663 switch (entry.type) {653664 case "note":654665 return ENTRY_TYPE_NOTE;