codex-types.ts

6.5 KB · raw

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 32 chars (128 bits) of SHA-256(vaultBlob), uppercase hex. Verifies reconstruction integrity. AUD-004: was 16 chars; legacy Codexes still verify. */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 32 chars (128 bits) of SHA-256(rawChunkBytes), hex. Catches per-chunk corruption with ≤2^-128 collision odds. AUD-004: was 16 chars; legacy Codexes still verify. */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.156 *157 * Empirical cap at AZTEC_ECC_PERCENT=25 is ~1386 bytes (envelope ~1945 chars)158 * — beyond that bwip-js reports `aztecNoValidSymbol: Maximum length exceeded`.159 * 1300 leaves headroom for envelope-overhead drift (larger index/total fields160 * as page counts grow). DO NOT raise without re-probing bwip-js: encode a161 * full-entropy envelope and confirm it still renders.162 *163 * Old Codexes printed with a smaller chunk size still restore — the erasure164 * decoder derives shard size from the scanned chunks, not from this constant.165 */166export const CHUNK_SIZE = 1300;167168/**169 * Number of parity shards. With 2 parity shards, the Codex survives the loss170 * of any 2 chunks (torn corner, coffee stain over one code, etc.) without171 * data loss. Raising this beyond 2 grows the page count meaningfully.172 */173export const PARITY_SHARDS = 2;174175/** Aztec error-correction percentage. 25% balances barcode density vs. resilience. */176export const AZTEC_ECC_PERCENT = 25;177178/** PDF layout: 6 columns × 6 rows = 36 codes per A4 page. */179export const CODES_PER_ROW = 6;180export const CODES_PER_COL = 6;181export const CODES_PER_PAGE = CODES_PER_ROW * CODES_PER_COL;182183/** Stable storage keys for Codex bookkeeping. */184export const STORAGE_KEY_VERIFY_CODE = "peach_codex_verify_code";185export const STORAGE_KEY_LAST_GENERATED = "peach_codex_last_generated";186export const STORAGE_KEY_LAST_RESTORED = "peach_last_restored_at";