peachScript.ts

2.2 KB · raw

1/**2 * peachScript.ts — PeachScript encoding/decoding.3 *4 * PeachScript is the device-to-device export format. Each byte of an5 * encrypted vault payload maps to one Unicode Private Use Area code point6 * in U+E000..U+E0FF. The bundled `PeachScript-Regular.otf` renders those7 * 256 code points as the visual script. Outside Peach the string renders8 * as boxes — that is the desired property: PeachScript is meant to be9 * read by Peach, not by humans.10 *11 * The encoding is trivially reversible: byte 0 → U+E000, byte 255 →12 * U+E0FF. No padding, no base conversion, no alignment fix-ups. The font13 * does the visual work; this module is the codec.14 */1516const BASE = 0xe000;1718/** Encode a binary payload to a PeachScript string (one byte per code point). */19export function encodePeachScript(bytes: Uint8Array): string {20  const out: string[] = [];21  for (let i = 0; i < bytes.length; i++) {22    out.push(String.fromCodePoint(BASE + bytes[i]));23  }24  return out.join("");25}2627/**28 * Decode a PeachScript string back to bytes. Throws29 * `INVALID_PEACHSCRIPT_CHARACTER` when any character is outside the30 * allotted PUA range — i.e. the string was edited, truncated mid-pair,31 * or is not PeachScript at all.32 */33export function decodePeachScript(str: string): Uint8Array {34  const codePoints = Array.from(str);35  const bytes = new Uint8Array(codePoints.length);36  for (let i = 0; i < codePoints.length; i++) {37    const cp = codePoints[i].codePointAt(0)!;38    if (cp < BASE || cp > BASE + 0xff) {39      throw new Error("INVALID_PEACHSCRIPT_CHARACTER");40    }41    bytes[i] = cp - BASE;42  }43  return bytes;44}4546/** Strip whitespace from a PeachScript string before decoding. */47export function normalisePeachScript(str: string): string {48  return str.replace(/\s+/g, "");49}5051/**52 * Cheap pre-flight: every non-whitespace char must be in the PUA range and53 * the string must be non-empty. Used by the import UI to gate the decode54 * button without throwing.55 */56export function isPeachScript(str: string): boolean {57  const trimmed = normalisePeachScript(str);58  if (trimmed.length === 0) return false;59  for (const ch of trimmed) {60    const cp = ch.codePointAt(0)!;61    if (cp < BASE || cp > BASE + 0xff) return false;62  }63  return true;64}