/** * Page de génération du wallet * Deuxième étape du processus d'initialisation */ import { DATABASE_CONFIG } from '../../services/database-config'; import { secureLogger } from '../services/secure-logger'; import { checkPBKDF2Key } from '../../utils/prerequisites.utils'; document.addEventListener('DOMContentLoaded', async () => { secureLogger.info('💰 Wallet setup page loaded', { component: 'WalletSetup' }); const status = document.getElementById('status') as HTMLDivElement; const progressBar = document.getElementById('progressBar') as HTMLDivElement; const continueBtn = document.getElementById('continueBtn') as HTMLButtonElement; function updateStatus(message: string, type: 'loading' | 'success' | 'error') { status.textContent = message; status.className = `status ${type}`; } function updateProgress(percent: number) { progressBar.style.width = `${percent}%`; } // Méthode pour sauvegarder directement en IndexedDB dans la base 4nk async function saveCredentialsDirectly(credentials: any): Promise { return new Promise((resolve, reject) => { const request = indexedDB.open(DATABASE_CONFIG.name, DATABASE_CONFIG.version); request.onerror = () => reject(request.error); request.onsuccess = () => { const db = request.result; const transaction = db.transaction([DATABASE_CONFIG.stores.wallet.name], 'readwrite'); const store = transaction.objectStore(DATABASE_CONFIG.stores.wallet.name); const putRequest = store.put(credentials, '/4nk/credentials'); putRequest.onsuccess = () => resolve(); putRequest.onerror = () => reject(putRequest.error); }; request.onupgradeneeded = () => { const db = request.result; if (!db.objectStoreNames.contains(DATABASE_CONFIG.stores.wallet.name)) { db.createObjectStore(DATABASE_CONFIG.stores.wallet.name, { keyPath: DATABASE_CONFIG.stores.wallet.keyPath as string }); } }; }); } try { // Étape 1: Vérifier les prérequis AVANT d'initialiser Services updateStatus('🔍 Vérification des prérequis...', 'loading'); updateProgress(10); // CRITICAL: wallet-setup ne doit PAS générer de credentials // Les credentials et clé PBKDF2 doivent être créés dans security-setup // On vérifie que la clé PBKDF2 existe dans le store pbkdf2keys secureLogger.debug('🔐 Checking for existing PBKDF2 key in pbkdf2keys store...', { component: 'WalletSetup' }); const pbkdf2KeyResult = await checkPBKDF2Key(); if (!pbkdf2KeyResult) { secureLogger.warn('⚠️ No PBKDF2 key found in pbkdf2keys store, redirecting to security-setup...', { component: 'WalletSetup' }); updateStatus('⚠️ Redirection vers la configuration de sécurité...', 'loading'); setTimeout(() => { window.location.href = '/src/pages/security-setup/security-setup.html'; }, 1000); return; } const currentMode = pbkdf2KeyResult.mode; secureLogger.info(`Prerequisites verified: PBKDF2 key found in pbkdf2keys store for mode: ${currentMode}`, { component: 'WalletSetup' }); // Étape 1.5: Vérifier si un wallet existe déjà updateStatus('🔍 Vérification du wallet existant...', 'loading'); updateProgress(25); const { DeviceReaderService } = await import('../../services/device-reader.service'); const deviceReader = DeviceReaderService.getInstance(); const existingDevice = await deviceReader.getDeviceFromDatabase(); if (existingDevice && existingDevice.sp_wallet) { secureLogger.warn('✅ Wallet already exists, skipping creation', { component: 'WalletSetup' }); updateStatus('✅ Wallet existant trouvé', 'success'); updateProgress(100); // Activer le bouton et rediriger continueBtn.disabled = false; continueBtn.textContent = 'Continuer'; continueBtn.onclick = () => { window.location.href = '/src/pages/birthday-setup/birthday-setup.html'; }; // Auto-redirection après 2 secondes setTimeout(() => { window.location.href = '/src/pages/birthday-setup/birthday-setup.html'; }, 2000); return; } secureLogger.info('🔧 No existing wallet found, creating new one...', { component: 'WalletSetup' }); updateStatus('🔧 Création du nouveau wallet...', 'loading'); updateProgress(30); // Étape 2: Initialisation des services (uniquement si les prérequis sont OK) updateStatus('🔄 Initialisation des services...', 'loading'); updateProgress(35); // Vérifier la mémoire avant d'initialiser WebAssembly if ((performance as any).memory) { const memory = (performance as any).memory; const usedPercent = (memory.usedJSHeapSize / memory.jsHeapSizeLimit) * 100; const usedMB = memory.usedJSHeapSize / 1024 / 1024; const limitMB = memory.jsHeapSizeLimit / 1024 / 1024; secureLogger.info('📊 Current memory usage: ${usedPercent.toFixed(1)}% (${usedMB.toFixed(1)}MB / ${limitMB.toFixed(1)}MB)', { component: 'WalletSetup' }); // Si la mémoire est très élevée (>75%), tenter un nettoyage agressif if (usedPercent > 75) { secureLogger.warn('⚠️ High memory usage detected, attempting aggressive cleanup...', { component: 'WalletSetup' }); updateStatus('🧹 Nettoyage de la mémoire en cours...', 'loading'); // Nettoyage agressif if (window.gc) { for (let i = 0; i < 3; i++) { window.gc(); await new Promise(resolve => setTimeout(resolve, 100)); } } // Nettoyer les caches HTTP if ('caches' in window) { try { const cacheNames = await caches.keys(); await Promise.all(cacheNames.map(name => caches.delete(name))); secureLogger.info('🧹 Caches cleared', { component: 'WalletSetup' }); } catch (e) { secureLogger.warn('Cache cleanup error', e as Error, { component: 'WalletSetup' }); } } // Vérifier la mémoire après nettoyage const memoryAfter = (performance as any).memory; const usedPercentAfter = (memoryAfter.usedJSHeapSize / memoryAfter.jsHeapSizeLimit) * 100; secureLogger.info('📊 Memory after cleanup: ${usedPercentAfter.toFixed(1)}% (${(memoryAfter.usedJSHeapSize / 1024 / 1024).toFixed(1)}MB)', { component: 'WalletSetup' }); // Si toujours >90% après nettoyage, avertir l'utilisateur if (usedPercentAfter > 90) { secureLogger.error('❌ Memory still too high after cleanup', { component: 'WalletSetup' }); updateStatus('⚠️ Mémoire trop élevée. Fermez les autres onglets et actualisez la page.', 'error'); alert('⚠️ Mémoire insuffisante détectée.\n\nVeuillez :\n- Fermer les autres onglets du navigateur\n- Actualiser cette page\n\nSi le problème persiste, redémarrez le navigateur.'); return; } } } let services: any; // Déclarer services au niveau supérieur try { secureLogger.info('🔄 Importing services...', { component: 'WalletSetup' }); const serviceModule = await import('../../services/service'); secureLogger.debug(`Service module imported: ${Object.keys(serviceModule)}`, { component: 'WalletSetup' }); // La classe Services est exportée par défaut const Services = serviceModule.default; if (!Services) { throw new Error('Services class not found in default export'); } secureLogger.info('🔄 Waiting for services to be ready...', { component: 'WalletSetup' }); // Attendre que les services soient initialisés avec plus de patience let attempts = 0; const maxAttempts = 30; // Plus de tentatives const delayMs = 2000; // Délai plus long entre les tentatives while (attempts < maxAttempts) { try { secureLogger.info('🔄 Attempting to get services (attempt ${attempts + 1}/${maxAttempts})...', { component: 'WalletSetup' }); services = await Services.getInstance(); secureLogger.info('✅ Services initialized successfully', { component: 'WalletSetup' }); break; } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); secureLogger.debug(`Services not ready yet (attempt ${attempts + 1}/${maxAttempts}): ${errorMessage}`, { component: 'WalletSetup' }); // Si c'est une erreur de mémoire, arrêter immédiatement if (errorMessage.includes('Out of memory') || errorMessage.includes('insufficient memory')) { secureLogger.error('🚫 Memory error detected - stopping retry attempts', { component: 'WalletSetup' }); updateStatus('❌ Erreur: Mémoire insuffisante. Veuillez actualiser la page.', 'error'); throw new Error('WebAssembly initialization failed due to insufficient memory. Please refresh the page.'); } // Diagnostic plus détaillé if (attempts === 5) { secureLogger.debug('🔍 Diagnostic: Checking memory usage...', { component: 'WalletSetup' }); if ((performance as any).memory) { const memory = (performance as any).memory; secureLogger.info('📊 Memory usage: ${Math.round(memory.usedJSHeapSize / 1024 / 1024)}MB / ${Math.round(memory.totalJSHeapSize / 1024 / 1024)}MB', { component: 'WalletSetup' }); } } attempts++; if (attempts >= maxAttempts) { throw new Error(`Services failed to initialize after ${maxAttempts} attempts. This may be due to high memory usage or WebAssembly initialization issues.`); } await new Promise(resolve => setTimeout(resolve, delayMs)); } } } catch (error) { secureLogger.error('Services not available', error as Error, { component: 'WalletSetup' }); throw error; } // Étape 3: Sauvegarde du wallet avec état birthday_waiting updateStatus('💰 Sauvegarde du portefeuille...', 'loading'); updateProgress(60); try { secureLogger.info('🔐 Sauvegarde du wallet avec état birthday_waiting...', { component: 'WalletSetup' }); // Le mode de sécurité a déjà été trouvé dans la vérification des prérequis // currentMode est déjà défini et vérifié secureLogger.info(`Using security mode for wallet encryption: ${currentMode}`, { component: 'WalletSetup' }); // Générer un wallet temporaire avec état birthday_waiting const { EncryptionService } = await import('../../services/encryption.service'); const encryptionService = EncryptionService.getInstance(); // Générer des clés temporaires pour le wallet // Générer un wallet temporaire avec l'état birthday_waiting const walletData = { scan_sk: encryptionService.generateRandomKey(), spend_key: encryptionService.generateRandomKey(), network: 'signet', state: 'birthday_waiting', created_at: new Date().toISOString() }; secureLogger.debug('Wallet data generated', { component: 'WalletSetup', data: walletData }); // Récupérer la clé PBKDF2 existante pour le mode détecté // IMPORTANT: Ne PAS générer de nouvelle clé, utiliser celle qui existe secureLogger.info(`Retrieving existing PBKDF2 key for security mode: ${currentMode}`, { component: 'WalletSetup' }); const pbkdf2Key = pbkdf2KeyResult.key; if (!pbkdf2Key) { secureLogger.error(`CRITICAL: Failed to retrieve PBKDF2 key for mode: ${currentMode}`, { component: 'WalletSetup' }); updateStatus('❌ Erreur: Impossible de récupérer la clé de chiffrement.', 'error'); throw new Error('CRITICAL: Failed to retrieve PBKDF2 key'); } secureLogger.info('🔐 PBKDF2 key retrieved for wallet encryption', { component: 'WalletSetup' }); // Chiffrer le wallet avec la clé PBKDF2 const encryptedWallet = await encryptionService.encrypt( JSON.stringify(walletData), pbkdf2Key ); secureLogger.info('🔐 Wallet encrypted with PBKDF2 key', { component: 'WalletSetup' }); secureLogger.debug('Encrypted wallet data', { component: 'WalletSetup', data: encryptedWallet }); // Ouvrir la base de données 4nk existante sans la modifier secureLogger.info(`Opening IndexedDB database "${DATABASE_CONFIG.name}" version ${DATABASE_CONFIG.version}...`, { component: 'WalletSetup' }); const db = await new Promise((resolve, reject) => { const request = indexedDB.open(DATABASE_CONFIG.name, DATABASE_CONFIG.version); // Utiliser la version centralisée request.onerror = () => { secureLogger.error('Failed to open IndexedDB', request.error as Error, { component: 'WalletSetup' }); reject(request.error); }; request.onsuccess = () => { secureLogger.info('✅ IndexedDB opened successfully', { component: 'WalletSetup' }); secureLogger.debug(`Database name: ${request.result.name}`, { component: 'WalletSetup' }); secureLogger.debug(`Database version: ${request.result.version}`, { component: 'WalletSetup' }); secureLogger.debug(`Available stores: ${Array.from(request.result.objectStoreNames)}`, { component: 'WalletSetup' }); resolve(request.result); }; request.onupgradeneeded = () => { const db = request.result; secureLogger.debug('🔄 IndexedDB upgrade needed, checking wallet store...', { component: 'WalletSetup' }); // Créer le store wallet seulement s'il n'existe pas if (!db.objectStoreNames.contains(DATABASE_CONFIG.stores.wallet.name)) { const store = db.createObjectStore(DATABASE_CONFIG.stores.wallet.name, { keyPath: DATABASE_CONFIG.stores.wallet.keyPath as string }); secureLogger.info('✅ Wallet store created with keyPath: ${DATABASE_CONFIG.stores.wallet.keyPath}', { component: 'WalletSetup' }); } else { secureLogger.info('✅ Wallet store already exists', { component: 'WalletSetup' }); } }; }); // Étape 1: Sauvegarder le wallet dans le format attendu par getDeviceFromDatabase // Utiliser le SDK directement pour éviter le service worker qui bloque secureLogger.info('🔧 Creating device using SDK...', { component: 'WalletSetup' }); // Créer le device directement avec le SDK sans passer par saveDeviceInDatabase if (!services.sdkClient) { throw new Error('WebAssembly SDK not initialized'); } // We set birthday later when we have the chain tip from relay secureLogger.info('🔧 Creating new device with birthday 0...', { component: 'WalletSetup' }); const spAddress = await services.sdkClient.create_new_device(0, 'signet'); secureLogger.info(`Device created with address: ${spAddress}`, { component: 'WalletSetup' }); // Force wallet generation to ensure keys are created secureLogger.info('🔧 Forcing wallet generation...', { component: 'WalletSetup' }); try { const wallet = await services.sdkClient.dump_wallet(); secureLogger.debug('Wallet generated', { component: 'WalletSetup', data: wallet }); } catch (walletError) { secureLogger.warn('Wallet generation failed', walletError as Error, { component: 'WalletSetup' }); } // Récupérer le device créé par le SDK const device = services.dumpDeviceFromMemory(); secureLogger.debug('Device structure from SDK', { component: 'WalletSetup', data: { hasSpWallet: !!device.sp_wallet, hasSpClient: !!device.sp_client, spClientKeys: device.sp_client ? Object.keys(device.sp_client) : 'none' } }); secureLogger.debug(`Opening transaction for ${DATABASE_CONFIG.stores.wallet.name} store...`, { component: 'WalletSetup' }); // CRITICAL: Chiffrer TOUS les données du wallet avant stockage // Le device contient des données sensibles (sp_wallet) qui ne doivent JAMAIS être en clair secureLogger.info('🔐 Encrypting device data before storage...', { component: 'WalletSetup' }); const deviceString = JSON.stringify(device); const encryptedDevice = await encryptionService.encrypt(deviceString, pbkdf2Key); secureLogger.info('🔐 Device encrypted successfully', { component: 'WalletSetup' }); await new Promise((resolve, reject) => { const transaction = db.transaction([DATABASE_CONFIG.stores.wallet.name], 'readwrite'); const store = transaction.objectStore(DATABASE_CONFIG.stores.wallet.name); secureLogger.debug(`Store opened: ${store.name}`, { component: 'WalletSetup' }); // Stocker UNIQUEMENT des données chiffrées - aucun wallet en clair const walletObject = { pre_id: '1', encrypted_device: encryptedDevice, // Device complètement chiffré encrypted_wallet: encryptedWallet // Wallet chiffré }; secureLogger.debug('🔍 Attempting to save encrypted wallet object', { component: 'WalletSetup' }); secureLogger.debug('Object contains only encrypted data', { component: 'WalletSetup', data: { hasEncryptedDevice: !!walletObject.encrypted_device, hasEncryptedWallet: !!walletObject.encrypted_wallet // Ne pas logger le contenu chiffré } }); // Le store utilise des clés in-line (keyPath: 'pre_id'), ne pas fournir de clé explicite const request = store.put(walletObject); request.onsuccess = () => { secureLogger.info('✅ Wallet saved in IndexedDB with correct format', { component: 'WalletSetup' }); secureLogger.debug('Saved wallet object', { component: 'WalletSetup', data: walletObject }); resolve(); }; request.onerror = () => { secureLogger.error('Failed to save wallet in IndexedDB', request.error as Error, { component: 'WalletSetup' }); reject(request.error); }; transaction.oncomplete = () => { secureLogger.info('✅ Transaction completed successfully', { component: 'WalletSetup' }); secureLogger.debug(`Transaction completed for store: ${store.name}`, { component: 'WalletSetup' }); }; transaction.onerror = () => { secureLogger.error('Transaction failed', transaction.error as Error, { component: 'WalletSetup' }); secureLogger.error('Transaction error details', new Error('Transaction failed'), { component: 'WalletSetup', data: { error: transaction.error, store: store.name, mode: transaction.mode } }); reject(transaction.error); }; }); // Étape 2: Vérifier que le wallet est bien stocké (nouvelle transaction) await new Promise((resolve, reject) => { const transaction = db.transaction([DATABASE_CONFIG.stores.wallet.name], 'readonly'); const store = transaction.objectStore(DATABASE_CONFIG.stores.wallet.name); const verificationRequest = store.get('1'); verificationRequest.onsuccess = () => { secureLogger.debug('Verification result structure', { component: 'WalletSetup', data: verificationRequest.result ? { hasPreId: !!verificationRequest.result.pre_id, hasEncryptedDevice: !!verificationRequest.result.encrypted_device, hasEncryptedWallet: !!verificationRequest.result.encrypted_wallet, hasDeviceInClear: !!verificationRequest.result.device // DEVRAIT ÊTRE NULL } : 'null' }); if (verificationRequest.result) { secureLogger.info('Wallet verification: Found in IndexedDB with key "1"', { component: 'WalletSetup' }); // Vérifier qu'il n'y a AUCUN wallet en clair if (verificationRequest.result.device) { secureLogger.error('CRITICAL: Device in clear found in database! This should be encrypted.', { component: 'WalletSetup' }); reject(new Error('Security violation: wallet stored in clear')); return; } // Vérifier que les données chiffrées sont présentes if (!verificationRequest.result.encrypted_device || !verificationRequest.result.encrypted_wallet) { secureLogger.error('❌ Wallet verification failed: encrypted data missing', { component: 'WalletSetup' }); reject(new Error('Encrypted data missing')); return; } secureLogger.info('✅ Wallet stored correctly: only encrypted data present', { component: 'WalletSetup' }); resolve(); } else { secureLogger.error('Wallet verification: Not found in IndexedDB with key "1"', { component: 'WalletSetup' }); reject(new Error('Wallet not found after save')); } }; verificationRequest.onerror = () => { secureLogger.error('Wallet verification failed', verificationRequest.error as Error, { component: 'WalletSetup' }); reject(verificationRequest.error); }; }); // Le mode de sécurité est déjà stocké via la clé PBKDF2 dans le store pbkdf2keys // Pas besoin de le stocker séparément secureLogger.info(`Security mode is implicitly stored via PBKDF2 key: ${currentMode}`, { component: 'WalletSetup' }); // Vérification finale : s'assurer que le wallet est bien dans IndexedDB secureLogger.debug('🔍 Final verification: checking wallet in IndexedDB...', { component: 'WalletSetup' }); const finalVerification = await new Promise((resolve, reject) => { const transaction = db.transaction([DATABASE_CONFIG.stores.wallet.name], 'readonly'); const store = transaction.objectStore(DATABASE_CONFIG.stores.wallet.name); const request = store.get('1'); request.onsuccess = () => resolve(request.result); request.onerror = () => reject(request.error); }); if (finalVerification) { // Vérifier qu'il n'y a PAS de device en clair if (finalVerification.device) { secureLogger.error('❌ CRITICAL: Final verification - Device in clear found!', { component: 'WalletSetup' }); throw new Error('Security violation: wallet stored in clear'); } secureLogger.info('✅ Wallet saved exclusively in IndexedDB and verified', { component: 'WalletSetup' }); console.log('🔍 Wallet contains only encrypted data:', { hasEncryptedDevice: !!finalVerification.encrypted_device, hasEncryptedWallet: !!finalVerification.encrypted_wallet, hasDeviceInClear: !!finalVerification.device // DEVRAIT ÊTRE FALSE }); // TEST: Déchiffrer le wallet pour valider que ça fonctionne secureLogger.info('🔐 TEST: Attempting to decrypt wallet to validate encryption...', { component: 'WalletSetup' }); try { const pbkdf2KeyTest = pbkdf2KeyResult.key; if (!pbkdf2KeyTest) { secureLogger.error('❌ TEST: Failed to retrieve PBKDF2 key for decryption test', { component: 'WalletSetup' }); } else { secureLogger.info('✅ TEST: PBKDF2 key retrieved for decryption test', { component: 'WalletSetup' }); // Déchiffrer le wallet chiffré const decryptedWallet = await encryptionService.decrypt( finalVerification.encrypted_wallet, pbkdf2KeyTest ); const parsedWallet = JSON.parse(decryptedWallet); console.log('✅ TEST: Wallet decrypted successfully:', { hasScanSk: !!parsedWallet.scan_sk, hasSpendKey: !!parsedWallet.spend_key, network: parsedWallet.network, state: parsedWallet.state, created_at: parsedWallet.created_at }); // Déchiffrer le device chiffré const decryptedDevice = await encryptionService.decrypt( finalVerification.encrypted_device, pbkdf2KeyTest ); const parsedDevice = JSON.parse(decryptedDevice); console.log('✅ TEST: Device decrypted successfully:', { hasSpWallet: !!parsedDevice.sp_wallet, network: parsedDevice.network }); secureLogger.info('✅ TEST: Full decryption test passed - wallet and device decrypt correctly', { component: 'WalletSetup' }); } } catch (decryptError) { console.error('❌ TEST: Decryption test failed:', decryptError); secureLogger.error('❌ This indicates an issue with encryption/decryption logic', { component: 'WalletSetup' }); } } else { secureLogger.error('❌ Final wallet verification failed - wallet not found in IndexedDB', { component: 'WalletSetup' }); throw new Error('Wallet verification failed - wallet not found'); } } catch (error) { console.error('❌ Error during wallet save:', error); updateStatus('❌ Erreur: Échec de la sauvegarde du wallet', 'error'); throw error; } // Étape 4: Finalisation updateStatus('✅ Wallet sauvegardé avec succès! Redirection automatique dans 3 secondes...', 'success'); updateProgress(100); secureLogger.info('🎉 Wallet setup completed successfully - wallet saved with birthday_waiting state', { component: 'WalletSetup' }); secureLogger.info('🔗 Ready to proceed to network connection and birthday setup', { component: 'WalletSetup' }); // Activer le bouton continuer continueBtn.disabled = false; secureLogger.info('✅ Continue button enabled', { component: 'WalletSetup' }); // Redirection automatique après 3 secondes si l'utilisateur ne clique pas setTimeout(() => { secureLogger.info('🔄 Auto-redirecting to birthday setup after timeout...', { component: 'WalletSetup' }); window.location.href = '/src/pages/birthday-setup/birthday-setup.html'; }, 3000); } catch (error) { console.error('❌ Error during wallet setup:', error); updateStatus('❌ Erreur lors de la génération du wallet', 'error'); } // Gestion du bouton continuer continueBtn.addEventListener('click', async () => { secureLogger.info('🔗 Redirecting to birthday setup...', { component: 'WalletSetup' }); secureLogger.info('💰 Wallet setup completed, redirecting to birthday configuration...', { component: 'WalletSetup' }); // Rediriger directement vers la page de configuration de la date anniversaire window.location.href = '/src/pages/birthday-setup/birthday-setup.html'; }); });