/** * Guardian-side persistence for received shares. * * Each guardian holds, per protected user, exactly one record per * `setupId`: * * { * originalAddress, // the protected user's Shade address * setupId, // disambiguates if a user re-runs setup * shareIndex, // their assigned x-coordinate * shareBytes, // base64-encoded Shamir share * shareSecretCiphertext, // base64 — encrypted backup payload * shareSecretNonce, // base64 — AES-GCM nonce * setupFingerprint, // safety number at deposit time * guardianCount, threshold, // informational * receivedAt * } * * The interface is deliberately minimal so consumers can back it with * whatever they already use for app state — IndexedDB, AsyncStorage, * SQLite, etc. The package ships an in-memory implementation suitable * for tests and small one-device demos. Guardian apps that survive * restart MUST supply a persistent implementation. */ export interface GuardianShareEntry { originalAddress: string; setupId: string; shareIndex: number; shareBytes: string; shareSecretCiphertext: string; shareSecretNonce: string; setupFingerprint: string; guardianCount: number; threshold: number; receivedAt: number; } /** * The minimal contract a guardian-side share store must implement. * * Shapes: * - `save` is upsert by (originalAddress, setupId). If a user re-runs * setup with a different setupId, both entries persist; the guardian * can choose which to release based on the recovery-request's * setupId. If the same (originalAddress, setupId) is saved twice, * the second write wins (idempotent re-deposit). * - `get` returns the deposit for the (originalAddress, setupId) pair, * or `null` when none exists. * - `list` enumerates everything (used by guardian-UX widgets). * - `delete` removes a single deposit; the guardian-UX exposes this so * a user can prune deposits from people they no longer wish to * protect. */ export interface RecoveryStore { save(entry: GuardianShareEntry): Promise; get(originalAddress: string, setupId: string): Promise; list(): Promise; delete(originalAddress: string, setupId: string): Promise; } /** * Process-local in-memory store. Suitable for tests and one-shot demos; * LOSES STATE ON RESTART. Production guardian apps must supply a * persistent `RecoveryStore` (see `docs/recovery.md` for backing-store * recommendations). */ export class MemoryRecoveryStore implements RecoveryStore { private readonly entries = new Map(); async save(entry: GuardianShareEntry): Promise { this.entries.set(keyOf(entry.originalAddress, entry.setupId), { ...entry }); } async get(originalAddress: string, setupId: string): Promise { const found = this.entries.get(keyOf(originalAddress, setupId)); return found === undefined ? null : { ...found }; } async list(): Promise { return Array.from(this.entries.values()).map((e) => ({ ...e })); } async delete(originalAddress: string, setupId: string): Promise { this.entries.delete(keyOf(originalAddress, setupId)); } } function keyOf(originalAddress: string, setupId: string): string { return `${originalAddress} ${setupId}`; }