codex-types.ts
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. Recovery words are never included in the PDF — they live6 * on paper separately by design. See peach_codex_v2.md + peach_codex_addendum.md7 * for the full design doc.8 *9 * These types are the contract between four execution boundaries:10 * - service worker (`src/background/codex-handlers.ts`)11 * - popup library (`src/popup/codex/*`)12 * - popup views (`src/popup/views/CodexGenerateView.tsx`, `CodexRestoreView.tsx`)13 * - tests (`src/popup/codex/*.test.ts`)14 *15 * Bump `version` (and `ChunkEnvelope.v`) only on breaking changes to the16 * on-paper format. A v2 Codex must always be decodable by future Peach17 * versions.18 */1920import type { VaultEntry } from "./types";2122/** Payload that the Codex encodes. Compressed, chunked, erasure-coded, then printed. */23export interface CodexPayload {24 /** Format version. Increment on breaking changes. */25 version: 2;26 /** 12-char alphanumeric: `PX-XXXX-XXXX`. Identifies a vault across Codex prints. */27 verifyCode: string;28 /** Base64url-encoded JSON `{ a: slotA, b: slotB|null }` — both encrypted vault slots. */29 vaultBlob: string;30 /** Vault last-modified unix timestamp (ms). Used to compare against on-device vault on restore. */31 timestamp: number;32 /** Visible entry count for user confirmation at restore time. */33 entryCount: number;34 /** First 16 chars of SHA-256(vaultBlob), uppercase hex. Verifies reconstruction integrity. */35 vaultFingerprint: string;36 /** When this Codex PDF was generated. Printed in the footer. */37 generatedAt: number;38}3940/**41 * One Aztec barcode's contents. Each envelope is a JSON-encoded chunk of the42 * compressed payload plus a per-chunk integrity hash.43 *44 * Field names are intentionally single letters to keep the encoded JSON45 * compact — every byte saved shrinks the printed page count.46 */47export interface ChunkEnvelope {48 /** Envelope format version. Increment on breaking changes. */49 v: 2;50 /** First 12 chars of `verifyCode` ("PX-XXXX-XXXX"). Lets the scanner reject foreign Codexes. */51 c: string;52 /** Chunk index, 0-based. */53 i: number;54 /** Total chunk count (data + parity). */55 t: number;56 /** True if this is a Reed-Solomon parity shard. */57 p: boolean;58 /** Chunk data, base64url-encoded. */59 d: string;60 /** First 8 chars of SHA-256(rawChunkBytes), uppercase hex. Catches per-chunk corruption. */61 h: string;62}6364/** Bookkeeping returned alongside the generated PDF. */65export interface CodexManifest {66 verifyCode: string;67 totalChunks: number;68 dataChunks: number;69 parityChunks: number;70 pageCount: number;71 entryCount: number;72 vaultFingerprint: string;73 generatedAt: number;74}7576/** Dependencies the generator needs from the service worker. */77export interface CodexDeps {78 getSlotA: () => Promise<string | null>;79 getSlotB: () => Promise<string | null>;80 getLastModified: () => Promise<string | null>;81 getAllEntries: () => Promise<VaultEntry[]>;82 getVerifyCode: () => Promise<string>;83 isVaultUnlocked: () => boolean;84}8586/**87 * Progress reporter for the generator. Called at named milestones; for the88 * Aztec step `current`/`total` advance per code so the UI can render a bar.89 */90export type CodexProgress = (91 step: "compress" | "erasure" | "aztec" | "verify" | "pdf",92 current?: number,93 total?: number,94) => void;9596/** Payload sent from the popup to the SW to perform the actual vault restoration. */97export interface CodexRestorePayload {98 vaultBlob: string;99 timestamp: number;100 verifyCode: string;101 vaultFingerprint: string;102 entryCount: number;103 masterPassphrase: string;104}105106/** Vault data the SW exposes to the popup so it can build a CodexPayload. */107export interface CodexVaultSnapshot {108 slotA: string | null;109 slotB: string | null;110 lastModified: string | null;111 entryCount: number;112 recoveryWords: string[] | null;113 verifyCode: string;114 vaultUnlocked: boolean;115}116117/**118 * Every Codex error is loud and named. The `code` field is a stable identifier119 * for log analysis; the human-readable message lives in `.message`.120 */121export class CodexError extends Error {122 constructor(123 public code: CodexErrorCode,124 message: string,125 ) {126 super(message);127 this.name = "CodexError";128 }129}130131export type CodexErrorCode =132 | "VAULT_LOCKED"133 | "VAULT_EMPTY"134 | "COMPRESSION_ROUNDTRIP_FAILED"135 | "ERASURE_CODING_VERIFICATION_FAILED"136 | "ERASURE_CODING_CONTENT_MISMATCH"137 | "AZTEC_GENERATION_FAILED"138 | "AZTEC_ROUNDTRIP_FAILED"139 | "AZTEC_CONTENT_MISMATCH"140 | "INSUFFICIENT_CHUNKS"141 | "RECONSTRUCTION_SIMULATION_FAILED"142 | "RECONSTRUCTION_VERIFY_CODE_MISMATCH"143 | "RECONSTRUCTION_FINGERPRINT_MISMATCH"144 | "RECONSTRUCTION_COUNT_MISMATCH"145 | "PDF_GENERATION_FAILED"146 | "RS_LIBRARY_FAILED"147 | "PRO_REQUIRED"148 | "WRONG_PASSPHRASE"149 | "INVALID_VAULT_BLOB"150 | "RESTORE_FAILED";151152// ── Constants — shared across generation and restoration ─────────────────────153154/**155 * Bytes of binary data per chunk. Each chunk becomes one Aztec code; this size156 * keeps the printed code at ~28mm (CODE_SIZE in PDF rendering) while staying157 * within Aztec's 1914-byte ECC-25% capacity for compact codes.158 *159 * Conservative: leaves headroom for the JSON envelope overhead (~120 bytes).160 */161export const CHUNK_SIZE = 1024;162163/**164 * Number of parity shards. With 2 parity shards, the Codex survives the loss165 * of any 2 chunks (torn corner, coffee stain over one code, etc.) without166 * data loss. Raising this beyond 2 grows the page count meaningfully.167 */168export const PARITY_SHARDS = 2;169170/** Aztec error-correction percentage. 25% balances barcode density vs. resilience. */171export const AZTEC_ECC_PERCENT = 25;172173/** PDF layout: 6 columns × 4 rows = 24 codes per A4 page. */174export const CODES_PER_ROW = 6;175export const CODES_PER_COL = 4;176export const CODES_PER_PAGE = CODES_PER_ROW * CODES_PER_COL;177178/** Stable storage keys for Codex bookkeeping. */179export const STORAGE_KEY_VERIFY_CODE = "peach_codex_verify_code";180export const STORAGE_KEY_LAST_GENERATED = "peach_codex_last_generated";181export const STORAGE_KEY_LAST_RESTORED = "peach_last_restored_at";