/** * WASM Crypto Module Loader * * This module provides lazy-loading of the synor-crypto-wasm module * for client-side Dilithium3 post-quantum signatures. * * ## Why Lazy Loading? * * The WASM module is ~2MB, so we only load it when needed: * - User opts into client-side signing * - Hardware wallet mode (all keys local) * - Offline signing scenarios * * ## Usage * * ```typescript * import { loadWasmCrypto, isWasmLoaded } from './wasm-crypto'; * * // Load the WASM module (only loads once) * const wasm = await loadWasmCrypto(); * * // Create Dilithium keypair from seed * const dilithiumKey = wasm.DilithiumSigningKey.fromSeed(seed); * const signature = dilithiumKey.sign(message); * ``` */ // Re-export types from the generated WASM module // These are auto-generated by wasm-bindgen and match synor-crypto-wasm exports import type { DilithiumSigningKey as WasmDilithiumSigningKeyType, Keypair as WasmKeypairType, Mnemonic as WasmMnemonicType, } from '../wasm/synor_crypto'; // Export the types for external use export type WasmDilithiumSigningKey = WasmDilithiumSigningKeyType; export type WasmKeypair = WasmKeypairType; export type WasmMnemonic = WasmMnemonicType; // Interface for the loaded WASM module export interface SynorCryptoWasm { // Classes Keypair: typeof WasmKeypairType; DilithiumSigningKey: typeof WasmDilithiumSigningKeyType; Mnemonic: typeof WasmMnemonicType; // Standalone functions verifyWithPublicKey(publicKey: Uint8Array, message: Uint8Array, signature: Uint8Array): boolean; dilithiumVerify(signature: Uint8Array, message: Uint8Array, publicKey: Uint8Array): boolean; dilithiumSizes(): { publicKey: number; secretKey: number; signature: number }; sha3_256(data: Uint8Array): Uint8Array; blake3(data: Uint8Array): Uint8Array; deriveKey(inputKey: Uint8Array, salt: Uint8Array, info: Uint8Array, outputLen: number): Uint8Array; validateAddress(address: string): boolean; decodeAddress(address: string): unknown; } // Singleton instance of the loaded WASM module let wasmModule: SynorCryptoWasm | null = null; let loadingPromise: Promise | null = null; /** * Check if the WASM module is already loaded. */ export function isWasmLoaded(): boolean { return wasmModule !== null; } /** * Load the WASM crypto module. * * This function is idempotent - calling it multiple times will return * the same module instance. * * @returns Promise resolving to the WASM module * @throws Error if WASM loading fails */ export async function loadWasmCrypto(): Promise { // Return cached module if already loaded if (wasmModule) { return wasmModule; } // Return existing loading promise to avoid parallel loads if (loadingPromise) { return loadingPromise; } // Start loading the WASM module loadingPromise = (async () => { try { // Dynamic import for code splitting - Vite handles WASM bundling const wasm = await import('../wasm/synor_crypto'); // For wasm-bindgen bundler target, the default export is the init function // Some bundlers auto-init, others require explicit init call // eslint-disable-next-line @typescript-eslint/no-explicit-any const initFn = (wasm as any).default; if (typeof initFn === 'function') { await initFn(); } // Cast to our interface type wasmModule = { Keypair: wasm.Keypair, DilithiumSigningKey: wasm.DilithiumSigningKey, Mnemonic: wasm.Mnemonic, verifyWithPublicKey: wasm.verifyWithPublicKey, dilithiumVerify: wasm.dilithiumVerify, dilithiumSizes: wasm.dilithiumSizes as () => { publicKey: number; secretKey: number; signature: number }, sha3_256: wasm.sha3_256, blake3: wasm.blake3, deriveKey: wasm.deriveKey, validateAddress: wasm.validateAddress, decodeAddress: wasm.decodeAddress, }; return wasmModule; } catch (error) { loadingPromise = null; // Reset so it can be retried throw new Error( `Failed to load WASM crypto module: ${error instanceof Error ? error.message : 'Unknown error'}. ` + 'Make sure the WASM module is built and copied to apps/web/src/wasm/' ); } })(); return loadingPromise; } /** * Unload the WASM module to free memory. * This is useful for single-page apps that want to reclaim memory * after signing operations are complete. */ export function unloadWasmCrypto(): void { wasmModule = null; loadingPromise = null; } // ==================== High-Level WASM Crypto Functions ==================== /** * Create a Dilithium3 keypair from a 32-byte seed. * * The seed should be derived from the same mnemonic as the Ed25519 key * to maintain key correlation for the hybrid signature scheme. * * @param seed - 32-byte seed (typically from BIP-39 mnemonic) * @returns Dilithium signing key */ export async function createDilithiumKeyFromSeed( seed: Uint8Array ): Promise { const wasm = await loadWasmCrypto(); return wasm.DilithiumSigningKey.fromSeed(seed); } /** * Sign a message with Dilithium3. * * @param message - Message to sign * @param seed - 32-byte seed for key derivation * @returns 3293-byte Dilithium signature */ export async function signWithDilithium( message: Uint8Array, seed: Uint8Array ): Promise { const key = await createDilithiumKeyFromSeed(seed); try { return key.sign(message); } finally { key.free(); // Clean up WASM memory } } /** * Verify a Dilithium3 signature. * * @param message - Original message * @param signature - Dilithium signature (3293 bytes) * @param publicKey - Dilithium public key (1952 bytes) * @returns true if signature is valid */ export async function verifyDilithiumSignature( message: Uint8Array, signature: Uint8Array, publicKey: Uint8Array ): Promise { const wasm = await loadWasmCrypto(); return wasm.dilithiumVerify(signature, message, publicKey); } /** * Get Dilithium3 key and signature sizes. */ export async function getDilithiumSizes(): Promise<{ publicKey: number; secretKey: number; signature: number; }> { const wasm = await loadWasmCrypto(); return wasm.dilithiumSizes(); } // ==================== Ed25519 WASM Functions ==================== /** * Create an Ed25519 keypair from seed using WASM. * * This is an alternative to @noble/ed25519 that keeps all crypto in WASM. * Useful for consistency or when noble packages aren't available. */ export async function createEd25519KeyFromSeed( seed: Uint8Array ): Promise { const wasm = await loadWasmCrypto(); return wasm.Keypair.fromSeed(seed); } /** * Sign a message with Ed25519 using WASM. */ export async function signWithEd25519Wasm( message: Uint8Array, seed: Uint8Array ): Promise { const key = await createEd25519KeyFromSeed(seed); try { return key.sign(message); } finally { key.free(); } }