// Platform-agnostic crypto backend contract. Each host (desktop / mobile) // registers its own implementation at boot via `setCryptoBackend`. // // Why an interface? // - Desktop: libsodium-wrappers (WASM, needs async ready-gate). // - Mobile: react-native-libsodium (native bindings, different API shape). // - Tests: deterministic backend with seeded RNG. // // Backends are expected to be *synchronous* after registration. Any async // initialisation (e.g. libsodium's WASM warm-up) happens before the backend // object is handed to `setCryptoBackend`. export interface KeyPair { publicKey: Uint8Array; privateKey: Uint8Array; } export interface CryptoBackend { readonly name: string; readonly nonceLength: number; // crypto_box_NONCEBYTES — 24 readonly publicKeyLength: number; // 32 readonly privateKeyLength: number; // 32 readonly secretboxKeyLength: number; // crypto_secretbox_KEYBYTES — 32 readonly secretboxNonceLength: number; // crypto_secretbox_NONCEBYTES — 24 randomBytes(n: number): Uint8Array; generateKeyPair(): KeyPair; // Authenticated encryption (X25519 + XSalsa20-Poly1305, AKA crypto_box). box( plaintext: Uint8Array, nonce: Uint8Array, recipientPublicKey: Uint8Array, senderPrivateKey: Uint8Array, ): Uint8Array; // Must throw on authentication failure. boxOpen( ciphertext: Uint8Array, nonce: Uint8Array, senderPublicKey: Uint8Array, recipientPrivateKey: Uint8Array, ): Uint8Array; // Symmetric authenticated encryption (XSalsa20-Poly1305, crypto_secretbox). // Used for large blobs (attachments) — one key per blob, key itself is // distributed per recipient via crypto_box envelopes. secretbox(plaintext: Uint8Array, nonce: Uint8Array, key: Uint8Array): Uint8Array; secretboxOpen(ciphertext: Uint8Array, nonce: Uint8Array, key: Uint8Array): Uint8Array; } let current: CryptoBackend | null = null; export function setCryptoBackend(backend: CryptoBackend): void { current = backend; } export function getCryptoBackend(): CryptoBackend { if (!current) { throw new Error( 'Crypto backend not configured. Call setCryptoBackend() at app boot before any crypto operation.', ); } return current; } export function isCryptoBackendReady(): boolean { return current !== null; }