/** * Page de génération du wallet * Deuxième étape du processus d'initialisation */ import { DATABASE_CONFIG } from '../../services/database-config'; document.addEventListener('DOMContentLoaded', async () => { console.log('💰 Wallet setup page loaded'); 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: Initialisation des services updateStatus('🔄 Initialisation des services...', 'loading'); updateProgress(10); let services: any; // Déclarer services au niveau supérieur try { console.log('🔄 Importing services...'); const serviceModule = await import('../../services/service'); console.log('✅ Service module imported:', Object.keys(serviceModule)); // 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'); } console.log('🔄 Waiting for services to be ready...'); // 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 { console.log(`🔄 Attempting to get services (attempt ${attempts + 1}/${maxAttempts})...`); services = await Services.getInstance(); console.log('✅ Services initialized successfully'); break; } catch (error) { console.log(`⏳ Services not ready yet (attempt ${attempts + 1}/${maxAttempts}):`, error instanceof Error ? error.message : String(error)); // Diagnostic plus détaillé if (attempts === 5) { console.log('🔍 Diagnostic: Checking memory usage...'); if ((performance as any).memory) { const memory = (performance as any).memory; console.log(`📊 Memory usage: ${Math.round(memory.usedJSHeapSize / 1024 / 1024)}MB / ${Math.round(memory.totalJSHeapSize / 1024 / 1024)}MB`); } } 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) { console.error('❌ Services not available:', error); throw error; } // Étape 2: Génération des credentials sécurisés updateStatus('🔐 Génération des clés de sécurité...', 'loading'); updateProgress(30); try { const { SecureCredentialsService } = await import('../../services/secure-credentials.service'); const secureCredentialsService = SecureCredentialsService.getInstance(); // Vérifier si des credentials existent déjà // 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 juste que la clé PBKDF2 existe console.log('🔐 Checking for existing PBKDF2 key...'); } catch (error) { // Erreur lors de la vérification, on continuera pour tester les clés PBKDF2 console.log('ℹ️ Could not check credentials status, will test PBKDF2 keys directly'); } // Étape 3: Sauvegarde du wallet avec état birthday_waiting updateStatus('💰 Sauvegarde du portefeuille...', 'loading'); updateProgress(60); try { console.log('🔐 Sauvegarde du wallet avec état birthday_waiting...'); // DÉTECTER le mode de sécurité via la clé PBKDF2 disponible // Le mode de sécurité est identifié par la clé PBKDF2 qui fonctionne const { SecureCredentialsService } = await import('../../services/secure-credentials.service'); const secureCredentialsService = SecureCredentialsService.getInstance(); // TEST: Log credentialId from sessionStorage const storedCredentialId = sessionStorage.getItem('webauthn_credential_id'); if (storedCredentialId) { console.log('🔐 TEST: credentialId from sessionStorage in wallet-setup:', storedCredentialId); } else { console.log('🔐 TEST: No credentialId found in sessionStorage'); } console.log('🔍 Testing all security modes to find a PBKDF2 key...'); // Tester tous les modes de sécurité pour trouver une clé PBKDF2 valide const allSecurityModes: Array<'browser' | 'otp' | 'password' | 'none' | 'os' | 'proton-pass'> = ['browser', 'otp', 'password', 'none', 'os', 'proton-pass']; let currentMode: string | null = null; for (const mode of allSecurityModes) { console.log(`🔍 Testing security mode: ${mode}`); try { const key = await secureCredentialsService.retrievePBKDF2Key(mode); if (key) { currentMode = mode; console.log(`✅ PBKDF2 key found for security mode: ${mode}`); break; } } catch (error) { console.log(`⚠️ No PBKDF2 key found for mode ${mode}`); } } // CRITICAL: Si aucune clé PBKDF2 n'est disponible, arrêter immédiatement if (!currentMode) { console.error('❌ CRITICAL: No PBKDF2 key found for any security mode.'); console.error('❌ Cannot proceed with wallet creation without encryption key.'); updateStatus('❌ Erreur: Aucune clé de chiffrement trouvée. Veuillez configurer la sécurité.', 'error'); throw new Error('CRITICAL: No PBKDF2 key found. Cannot create wallet without encryption key.'); } console.log('🔐 Using security mode for wallet encryption:', currentMode); // 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() }; console.log('🔐 Wallet data generated:', 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 console.log('🔐 Retrieving existing PBKDF2 key for security mode:', currentMode); const pbkdf2Key = await secureCredentialsService.retrievePBKDF2Key(currentMode as any); if (!pbkdf2Key) { console.error('❌ CRITICAL: Failed to retrieve PBKDF2 key for mode:', currentMode); updateStatus('❌ Erreur: Impossible de récupérer la clé de chiffrement.', 'error'); throw new Error('CRITICAL: Failed to retrieve PBKDF2 key'); } console.log('🔐 PBKDF2 key retrieved for wallet encryption'); // Chiffrer le wallet avec la clé PBKDF2 const encryptedWallet = await encryptionService.encrypt( JSON.stringify(walletData), pbkdf2Key ); console.log('🔐 Wallet encrypted with PBKDF2 key'); console.log('🔐 Encrypted wallet data:', encryptedWallet); // Ouvrir la base de données 4nk existante sans la modifier console.log(`🔍 Opening IndexedDB database "${DATABASE_CONFIG.name}" version ${DATABASE_CONFIG.version}...`); const db = await new Promise((resolve, reject) => { const request = indexedDB.open(DATABASE_CONFIG.name, DATABASE_CONFIG.version); // Utiliser la version centralisée request.onerror = () => { console.error('❌ Failed to open IndexedDB:', request.error); reject(request.error); }; request.onsuccess = () => { console.log('✅ IndexedDB opened successfully'); console.log('🔍 Database name:', request.result.name); console.log('🔍 Database version:', request.result.version); console.log('🔍 Available stores:', Array.from(request.result.objectStoreNames)); resolve(request.result); }; request.onupgradeneeded = () => { const db = request.result; console.log('🔄 IndexedDB upgrade needed, checking wallet store...'); // 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 }); console.log(`✅ Wallet store created with keyPath: ${DATABASE_CONFIG.stores.wallet.keyPath}`); } else { console.log('✅ Wallet store already exists'); } }; }); // Étape 1: Sauvegarder le wallet dans le format attendu par getDeviceFromDatabase // Utiliser le SDK directement pour éviter le service worker qui bloque console.log('🔧 Creating device using SDK...'); // 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 console.log('🔧 Creating new device with birthday 0...'); const spAddress = await services.sdkClient.create_new_device(0, 'signet'); console.log('✅ Device created with address:', spAddress); // Force wallet generation to ensure keys are created console.log('🔧 Forcing wallet generation...'); try { const wallet = await services.sdkClient.dump_wallet(); console.log('✅ Wallet generated:', JSON.stringify(wallet)); } catch (walletError) { console.warn('⚠️ Wallet generation failed:', walletError); } // Récupérer le device créé par le SDK const device = services.dumpDeviceFromMemory(); console.log('🔍 Device structure from SDK:', { hasSpWallet: !!device.sp_wallet, hasSpClient: !!device.sp_client, spClientKeys: device.sp_client ? Object.keys(device.sp_client) : 'none' }); console.log(`🔍 Opening transaction for ${DATABASE_CONFIG.stores.wallet.name} store...`); // 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 console.log('🔐 Encrypting device data before storage...'); const deviceString = JSON.stringify(device); const encryptedDevice = await encryptionService.encrypt(deviceString, pbkdf2Key); console.log('🔐 Device encrypted successfully'); await new Promise((resolve, reject) => { const transaction = db.transaction([DATABASE_CONFIG.stores.wallet.name], 'readwrite'); const store = transaction.objectStore(DATABASE_CONFIG.stores.wallet.name); console.log('🔍 Store opened:', store.name); // 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é security_mode: currentMode // Mode de sécurité utilisé }; console.log('🔍 Attempting to save encrypted wallet object'); console.log('🔐 Object contains only encrypted data:', { hasEncryptedDevice: !!walletObject.encrypted_device, hasEncryptedWallet: !!walletObject.encrypted_wallet, securityMode: walletObject.security_mode, // 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 = () => { console.log('✅ Wallet saved in IndexedDB with correct format'); console.log('🔍 Saved wallet object:', walletObject); resolve(); }; request.onerror = () => { console.error('❌ Failed to save wallet in IndexedDB:', request.error); reject(request.error); }; transaction.oncomplete = () => { console.log('✅ Transaction completed successfully'); console.log('🔍 Transaction completed for store:', store.name); }; transaction.onerror = () => { console.error('❌ Transaction failed:', transaction.error); console.error('🔍 Transaction error details:', { 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 = () => { console.log('🔍 Verification result structure:', verificationRequest.result ? { hasPreId: !!verificationRequest.result.pre_id, hasEncryptedDevice: !!verificationRequest.result.encrypted_device, hasEncryptedWallet: !!verificationRequest.result.encrypted_wallet, hasSecurityMode: !!verificationRequest.result.security_mode, hasDeviceInClear: !!verificationRequest.result.device // DEVRAIT ÊTRE NULL } : 'null'); if (verificationRequest.result) { console.log('✅ Wallet verification: Found in IndexedDB with key "1"'); // Vérifier qu'il n'y a AUCUN wallet en clair if (verificationRequest.result.device) { console.error('❌ CRITICAL: Device in clear found in database! This should be encrypted.'); 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) { console.error('❌ Wallet verification failed: encrypted data missing'); reject(new Error('Encrypted data missing')); return; } console.log('✅ Wallet stored correctly: only encrypted data present'); resolve(); } else { console.error('❌ Wallet verification: Not found in IndexedDB with key "1"'); reject(new Error('Wallet not found after save')); } }; verificationRequest.onerror = () => { console.error('❌ Wallet verification failed:', verificationRequest.error); 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 console.log('🔐 Security mode is implicitly stored via PBKDF2 key:', currentMode); // Vérification finale : s'assurer que le wallet est bien dans IndexedDB console.log('🔍 Final verification: checking wallet in IndexedDB...'); 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) { console.error('❌ CRITICAL: Final verification - Device in clear found!'); throw new Error('Security violation: wallet stored in clear'); } console.log('✅ Wallet saved exclusively in IndexedDB and verified'); console.log('🔍 Wallet contains only encrypted data:', { hasEncryptedDevice: !!finalVerification.encrypted_device, hasEncryptedWallet: !!finalVerification.encrypted_wallet, securityMode: finalVerification.security_mode, hasDeviceInClear: !!finalVerification.device // DEVRAIT ÊTRE FALSE }); // TEST: Déchiffrer le wallet pour valider que ça fonctionne console.log('🔐 TEST: Attempting to decrypt wallet to validate encryption...'); try { const pbkdf2KeyTest = await secureCredentialsService.retrievePBKDF2Key(currentMode as any); if (!pbkdf2KeyTest) { console.error('❌ TEST: Failed to retrieve PBKDF2 key for decryption test'); } else { console.log('✅ TEST: PBKDF2 key retrieved for decryption test'); // 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 }); console.log('✅ TEST: Full decryption test passed - wallet and device decrypt correctly'); } } catch (decryptError) { console.error('❌ TEST: Decryption test failed:', decryptError); console.error('❌ This indicates an issue with encryption/decryption logic'); } } else { console.error('❌ Final wallet verification failed - wallet not found in IndexedDB'); 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!', 'success'); updateProgress(100); console.log('🎉 Wallet setup completed successfully - wallet saved with birthday_waiting state'); console.log('🔗 Ready to proceed to network connection and birthday setup'); // Activer le bouton continuer continueBtn.disabled = false; } 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', () => { console.log('🔗 Redirecting to pairing page...'); window.location.href = '/src/pages/birthday-setup/birthday-setup.html'; }); });