diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 7eb4abd..1ae1cae 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -1,6 +1,7 @@ plugins { id("com.android.application") id("org.jetbrains.kotlin.android") + kotlin("kapt") } android { @@ -91,7 +92,7 @@ dependencies { // Room for database implementation("androidx.room:room-runtime:2.6.1") implementation("androidx.room:room-ktx:2.6.1") - annotationProcessor("androidx.room:room-compiler:2.6.1") + kapt("androidx.room:room-compiler:2.6.1") // Biometric authentication implementation("androidx.biometric:biometric:1.1.0") diff --git a/app/src/main/java/com/rosetta/messenger/crypto/CryptoManager.kt b/app/src/main/java/com/rosetta/messenger/crypto/CryptoManager.kt index 59e7bbd..041a540 100644 --- a/app/src/main/java/com/rosetta/messenger/crypto/CryptoManager.kt +++ b/app/src/main/java/com/rosetta/messenger/crypto/CryptoManager.kt @@ -29,6 +29,10 @@ object CryptoManager { private const val KEY_SIZE = 256 private const val SALT = "rosetta" + // 🚀 ОПТИМИЗАЦИЯ: Кэш для генерации ключей (seedPhrase -> KeyPair) + private val keyPairCache = mutableMapOf() + private val privateKeyHashCache = mutableMapOf() + init { // Add BouncyCastle provider for secp256k1 support if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) { @@ -74,8 +78,14 @@ object CryptoManager { /** * Generate key pair from seed phrase using secp256k1 curve + * 🚀 ОПТИМИЗАЦИЯ: Кэшируем результаты для избежания повторных вычислений */ fun generateKeyPairFromSeed(seedPhrase: List): KeyPairData { + val cacheKey = seedPhrase.joinToString(" ") + + // Проверяем кэш + keyPairCache[cacheKey]?.let { return it } + val privateKeyHex = seedPhraseToPrivateKey(seedPhrase) val ecSpec = ECNamedCurveTable.getParameterSpec("secp256k1") @@ -91,20 +101,40 @@ object CryptoManager { val publicKeyHex = publicKeyPoint.getEncoded(false) .joinToString("") { "%02x".format(it) } - return KeyPairData( + val keyPair = KeyPairData( privateKey = privateKeyHex.take(64), publicKey = publicKeyHex ) + + // Сохраняем в кэш (ограничиваем размер до 5 записей) + keyPairCache[cacheKey] = keyPair + if (keyPairCache.size > 5) { + keyPairCache.remove(keyPairCache.keys.first()) + } + + return keyPair } /** * Generate private key hash for protocol (SHA256(privateKey + "rosetta")) + * 🚀 ОПТИМИЗАЦИЯ: Кэшируем хэши для избежания повторных вычислений */ fun generatePrivateKeyHash(privateKey: String): String { + // Проверяем кэш + privateKeyHashCache[privateKey]?.let { return it } + val data = (privateKey + SALT).toByteArray() val digest = MessageDigest.getInstance("SHA-256") val hash = digest.digest(data) - return hash.joinToString("") { "%02x".format(it) } + val hashHex = hash.joinToString("") { "%02x".format(it) } + + // Сохраняем в кэш + privateKeyHashCache[privateKey] = hashHex + if (privateKeyHashCache.size > 10) { + privateKeyHashCache.remove(privateKeyHashCache.keys.first()) + } + + return hashHex } /** diff --git a/app/src/main/java/com/rosetta/messenger/database/AccountDao.kt b/app/src/main/java/com/rosetta/messenger/database/AccountDao.kt new file mode 100644 index 0000000..4f21420 --- /dev/null +++ b/app/src/main/java/com/rosetta/messenger/database/AccountDao.kt @@ -0,0 +1,28 @@ +package com.rosetta.messenger.database + +import androidx.room.* +import kotlinx.coroutines.flow.Flow + +@Dao +interface AccountDao { + @Query("SELECT * FROM encrypted_accounts WHERE is_active = 1 ORDER BY last_used DESC") + fun getAllAccountsFlow(): Flow> + + @Query("SELECT * FROM encrypted_accounts WHERE is_active = 1 ORDER BY last_used DESC") + suspend fun getAllAccounts(): List + + @Query("SELECT * FROM encrypted_accounts WHERE public_key = :publicKey LIMIT 1") + suspend fun getAccount(publicKey: String): EncryptedAccountEntity? + + @Insert(onConflict = OnConflictStrategy.REPLACE) + suspend fun insertAccount(account: EncryptedAccountEntity): Long + + @Query("UPDATE encrypted_accounts SET last_used = :lastUsed WHERE public_key = :publicKey") + suspend fun updateLastUsed(publicKey: String, lastUsed: String) + + @Query("DELETE FROM encrypted_accounts WHERE public_key = :publicKey") + suspend fun deleteAccount(publicKey: String) + + @Query("SELECT COUNT(*) FROM encrypted_accounts WHERE is_active = 1") + suspend fun getAccountCount(): Int +} diff --git a/app/src/main/java/com/rosetta/messenger/database/DatabaseService.kt b/app/src/main/java/com/rosetta/messenger/database/DatabaseService.kt new file mode 100644 index 0000000..05c28a7 --- /dev/null +++ b/app/src/main/java/com/rosetta/messenger/database/DatabaseService.kt @@ -0,0 +1,224 @@ +package com.rosetta.messenger.database + +import android.content.Context +import android.util.Log +import com.rosetta.messenger.crypto.CryptoManager +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.withContext +import java.time.Instant + +/** + * Service for secure database operations + * Matches the architecture from React Native app + */ +class DatabaseService(context: Context) { + private val database = RosettaDatabase.getDatabase(context) + private val accountDao = database.accountDao() + + // 🚀 ОПТИМИЗАЦИЯ: LRU кэш для зашифрованных аккаунтов (избегаем повторных запросов к БД) + private val accountCache = mutableMapOf() + private val cacheMaxSize = 10 + + companion object { + private const val TAG = "DatabaseService" + + @Volatile + private var INSTANCE: DatabaseService? = null + + fun getInstance(context: Context): DatabaseService { + return INSTANCE ?: synchronized(this) { + val instance = DatabaseService(context.applicationContext) + INSTANCE = instance + instance + } + } + } + + /** + * Saves encrypted account to database + */ + suspend fun saveEncryptedAccount( + publicKey: String, + privateKeyEncrypted: String, + seedPhraseEncrypted: String + ): Boolean = withContext(Dispatchers.IO) { + try { + Log.d(TAG, "💾 Saving encrypted account to database: ${publicKey.take(20)}...") + + val account = EncryptedAccountEntity( + publicKey = publicKey, + privateKeyEncrypted = privateKeyEncrypted, + seedPhraseEncrypted = seedPhraseEncrypted, + createdAt = Instant.now().toString(), + lastUsed = Instant.now().toString(), + isActive = true + ) + + accountDao.insertAccount(account) + + // 🚀 ОПТИМИЗАЦИЯ: Обновляем кэш после сохранения + accountCache[publicKey] = account + + Log.d(TAG, "✅ Account saved successfully") + true + } catch (e: Exception) { + Log.e(TAG, "❌ Failed to save account", e) + false + } + } + + /** + * Loads encrypted account from database + * 🚀 ОПТИМИЗАЦИЯ: Использует кэш для избежания повторных запросов + */ + suspend fun getEncryptedAccount(publicKey: String): EncryptedAccountEntity? = + withContext(Dispatchers.IO) { + try { + // Проверяем кэш сначала + accountCache[publicKey]?.let { return@withContext it } + + // Загружаем из БД и кэшируем + val account = accountDao.getAccount(publicKey) + account?.let { + accountCache[publicKey] = it + // Ограничиваем размер кэша + if (accountCache.size > cacheMaxSize) { + accountCache.remove(accountCache.keys.first()) + } + } + account + } catch (e: Exception) { + Log.e(TAG, "❌ Failed to load account", e) + null + } + } + + /** + * Gets all encrypted accounts + */ + suspend fun getAllEncryptedAccounts(): List = + withContext(Dispatchers.IO) { + try { + accountDao.getAllAccounts() + } catch (e: Exception) { + Log.e(TAG, "❌ Failed to load accounts", e) + emptyList() + } + } + + /** + * Gets all accounts as Flow for reactive updates + */ + fun getAllAccountsFlow(): Flow> { + return accountDao.getAllAccountsFlow() + } + + /** + * Updates last used timestamp for account + */ + suspend fun updateLastUsed(publicKey: String) = withContext(Dispatchers.IO) { + try { + accountDao.updateLastUsed(publicKey, Instant.now().toString()) + } catch (e: Exception) { + Log.e(TAG, "❌ Failed to update last used", e) + } + } + + /** + * Deletes account from database + */ + suspend fun deleteAccount(publicKey: String): Boolean = withContext(Dispatchers.IO) { + try { + accountDao.deleteAccount(publicKey) + Log.d(TAG, "🗑️ Account deleted: ${publicKey.take(20)}...") + true + } catch (e: Exception) { + Log.e(TAG, "❌ Failed to delete account", e) + false + } + } + + /** + * Checks if any accounts exist + */ + suspend fun hasAccounts(): Boolean = withContext(Dispatchers.IO) { + try { + accountDao.getAccountCount() > 0 + } catch (e: Exception) { + Log.e(TAG, "❌ Failed to check accounts", e) + false + } + } + + /** + * Decrypts account with password + * Returns decrypted private key and seed phrase + */ + suspend fun decryptAccount( + publicKey: String, + password: String + ): DecryptedAccountData? = withContext(Dispatchers.IO) { + try { + Log.d(TAG, "🔓 Decrypting account: ${publicKey.take(20)}...") + + val encryptedAccount = getEncryptedAccount(publicKey) ?: run { + Log.e(TAG, "❌ Account not found") + return@withContext null + } + + // Decrypt private key + val privateKey = try { + CryptoManager.decryptWithPassword( + encryptedAccount.privateKeyEncrypted, + password + ) + } catch (e: Exception) { + Log.e(TAG, "❌ Failed to decrypt private key - wrong password?", e) + return@withContext null + } + + // Decrypt seed phrase + val seedPhraseString = try { + CryptoManager.decryptWithPassword( + encryptedAccount.seedPhraseEncrypted, + password + ) + } catch (e: Exception) { + Log.e(TAG, "❌ Failed to decrypt seed phrase - wrong password?", e) + return@withContext null + } + + val seedPhrase = seedPhraseString.split(" ") + + // Generate private key hash for protocol + val privateKeyHash = CryptoManager.generatePrivateKeyHash(privateKey) + + Log.d(TAG, "✅ Account decrypted successfully") + Log.d(TAG, " - Public Key: ${publicKey.take(20)}...") + Log.d(TAG, " - Private Key: [DECRYPTED]") + Log.d(TAG, " - Private Key Hash: $privateKeyHash") + Log.d(TAG, " - Seed Phrase: ${seedPhrase.size} words") + + DecryptedAccountData( + publicKey = publicKey, + privateKey = privateKey, + privateKeyHash = privateKeyHash, + seedPhrase = seedPhrase + ) + } catch (e: Exception) { + Log.e(TAG, "❌ Failed to decrypt account", e) + null + } + } +} + +/** + * Decrypted account data + */ +data class DecryptedAccountData( + val publicKey: String, + val privateKey: String, + val privateKeyHash: String, + val seedPhrase: List +) diff --git a/app/src/main/java/com/rosetta/messenger/database/EncryptedAccountEntity.kt b/app/src/main/java/com/rosetta/messenger/database/EncryptedAccountEntity.kt new file mode 100644 index 0000000..3e30745 --- /dev/null +++ b/app/src/main/java/com/rosetta/messenger/database/EncryptedAccountEntity.kt @@ -0,0 +1,38 @@ +package com.rosetta.messenger.database + +import androidx.room.ColumnInfo +import androidx.room.Entity +import androidx.room.Index +import androidx.room.PrimaryKey + +/** + * Зашифрованный аккаунт в базе данных + * Соответствует структуре из React Native приложения + */ +@Entity( + tableName = "encrypted_accounts", + indices = [Index(value = ["public_key"], unique = true)] +) +data class EncryptedAccountEntity( + @PrimaryKey(autoGenerate = true) + @ColumnInfo(name = "id") + val id: Long = 0, + + @ColumnInfo(name = "public_key") + val publicKey: String, + + @ColumnInfo(name = "private_key_encrypted") + val privateKeyEncrypted: String, + + @ColumnInfo(name = "seed_phrase_encrypted") + val seedPhraseEncrypted: String, + + @ColumnInfo(name = "created_at") + val createdAt: String, + + @ColumnInfo(name = "last_used") + val lastUsed: String? = null, + + @ColumnInfo(name = "is_active") + val isActive: Boolean = true +) diff --git a/app/src/main/java/com/rosetta/messenger/database/RosettaDatabase.kt b/app/src/main/java/com/rosetta/messenger/database/RosettaDatabase.kt new file mode 100644 index 0000000..07fe6a9 --- /dev/null +++ b/app/src/main/java/com/rosetta/messenger/database/RosettaDatabase.kt @@ -0,0 +1,34 @@ +package com.rosetta.messenger.database + +import android.content.Context +import androidx.room.Database +import androidx.room.Room +import androidx.room.RoomDatabase + +@Database( + entities = [EncryptedAccountEntity::class], + version = 1, + exportSchema = false +) +abstract class RosettaDatabase : RoomDatabase() { + abstract fun accountDao(): AccountDao + + companion object { + @Volatile + private var INSTANCE: RosettaDatabase? = null + + fun getDatabase(context: Context): RosettaDatabase { + return INSTANCE ?: synchronized(this) { + val instance = Room.databaseBuilder( + context.applicationContext, + RosettaDatabase::class.java, + "rosetta_secure.db" + ) + .setJournalMode(JournalMode.WRITE_AHEAD_LOGGING) // WAL mode for performance + .build() + INSTANCE = instance + instance + } + } + } +} diff --git a/app/src/main/java/com/rosetta/messenger/providers/AuthState.kt b/app/src/main/java/com/rosetta/messenger/providers/AuthState.kt index dbcdb21..fcadf82 100644 --- a/app/src/main/java/com/rosetta/messenger/providers/AuthState.kt +++ b/app/src/main/java/com/rosetta/messenger/providers/AuthState.kt @@ -1,11 +1,12 @@ package com.rosetta.messenger.providers import android.content.Context +import android.util.Log import androidx.compose.runtime.* -import com.rosetta.messenger.data.AccountManager -import com.rosetta.messenger.data.DecryptedAccount -import com.rosetta.messenger.data.EncryptedAccount import com.rosetta.messenger.crypto.CryptoManager +import com.rosetta.messenger.database.DatabaseService +import com.rosetta.messenger.database.DecryptedAccountData +import com.rosetta.messenger.network.ProtocolManager import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.* @@ -13,30 +14,41 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.withContext /** - * Auth state management - similar to AuthContext in React Native version + * Auth state management - matches React Native architecture */ sealed class AuthStatus { object Loading : AuthStatus() object Unauthenticated : AuthStatus() - data class Authenticated(val account: DecryptedAccount) : AuthStatus() + data class Authenticated(val account: DecryptedAccountData) : AuthStatus() data class Locked(val publicKey: String) : AuthStatus() } data class AuthStateData( val status: AuthStatus = AuthStatus.Loading, - val accounts: List = emptyList(), - val hasExistingAccounts: Boolean = false + val hasExistingAccounts: Boolean = false, + val availableAccounts: List = emptyList() ) class AuthStateManager( private val context: Context, private val scope: CoroutineScope ) { - private val accountManager = AccountManager(context) + private val databaseService = DatabaseService.getInstance(context) private val _state = MutableStateFlow(AuthStateData()) val state: StateFlow = _state.asStateFlow() + private var currentDecryptedAccount: DecryptedAccountData? = null + + // 🚀 ОПТИМИЗАЦИЯ: Кэш списка аккаунтов для UI + private var accountsCache: List? = null + private var lastAccountsLoadTime = 0L + private val accountsCacheTTL = 5000L // 5 секунд + + companion object { + private const val TAG = "AuthStateManager" + } + init { scope.launch { loadAccounts() @@ -45,74 +57,132 @@ class AuthStateManager( } private suspend fun loadAccounts() = withContext(Dispatchers.IO) { - val accounts = accountManager.getAllAccounts() - _state.update { it.copy( - accounts = accounts, - hasExistingAccounts = accounts.isNotEmpty() - )} + try { + // 🚀 ОПТИМИЗАЦИЯ: Используем кэш если он свежий + val currentTime = System.currentTimeMillis() + if (accountsCache != null && (currentTime - lastAccountsLoadTime) < accountsCacheTTL) { + Log.d(TAG, "📚 Using cached accounts list") + _state.update { it.copy( + hasExistingAccounts = accountsCache!!.isNotEmpty(), + availableAccounts = accountsCache!! + )} + return@withContext + } + + val accounts = databaseService.getAllEncryptedAccounts() + val hasAccounts = accounts.isNotEmpty() + val accountKeys = accounts.map { it.publicKey } + + // Обновляем кэш + accountsCache = accountKeys + lastAccountsLoadTime = currentTime + + Log.d(TAG, "📚 Loaded ${accounts.size} accounts from database") + + _state.update { it.copy( + hasExistingAccounts = hasAccounts, + availableAccounts = accountKeys + )} + } catch (e: Exception) { + Log.e(TAG, "❌ Failed to load accounts", e) + } } private suspend fun checkAuthStatus() { - accountManager.isLoggedIn.collect { isLoggedIn -> - if (isLoggedIn) { - accountManager.currentPublicKey.first()?.let { publicKey -> - _state.update { it.copy( - status = AuthStatus.Locked(publicKey) - )} - } + try { + val hasAccounts = databaseService.hasAccounts() + if (!hasAccounts) { + _state.update { it.copy( + status = AuthStatus.Unauthenticated + )} } else { _state.update { it.copy( status = AuthStatus.Unauthenticated )} } + } catch (e: Exception) { + Log.e(TAG, "❌ Failed to check auth status", e) + _state.update { it.copy( + status = AuthStatus.Unauthenticated + )} } } /** * Create new account from seed phrase + * Matches createAccountFromSeedPhrase from React Native + * 🚀 ОПТИМИЗАЦИЯ: Dispatchers.Default для CPU-интенсивной криптографии */ suspend fun createAccount( seedPhrase: List, - password: String, - name: String = "Rosetta Account" - ): Result = withContext(Dispatchers.Default) { + password: String + ): Result = withContext(Dispatchers.Default) { try { - val keyPair = CryptoManager.generateKeyPairFromSeed(seedPhrase) - val privateKeyHash = CryptoManager.generatePrivateKeyHash(keyPair.privateKey) + Log.d(TAG, "🔧 Creating new account from seed phrase") + Log.d(TAG, " - Seed phrase: ${seedPhrase.size} words") + Log.d(TAG, " - Password length: ${password.length}") + // Step 1: Generate key pair from seed phrase (using BIP39) + val keyPair = CryptoManager.generateKeyPairFromSeed(seedPhrase) + Log.d(TAG, "🔑 Generated keys from seed phrase") + Log.d(TAG, " - Public Key: ${keyPair.publicKey.take(20)}...") + Log.d(TAG, " - Private Key length: ${keyPair.privateKey.length}") + + // Step 2: Generate private key hash for protocol + val privateKeyHash = CryptoManager.generatePrivateKeyHash(keyPair.privateKey) + Log.d(TAG, "🔐 Generated private key hash: $privateKeyHash") + + // Step 3: Encrypt private key with password val encryptedPrivateKey = CryptoManager.encryptWithPassword( keyPair.privateKey, password ) + Log.d(TAG, "🔒 Encrypted private key: ${encryptedPrivateKey.take(50)}...") + + // Step 4: Encrypt seed phrase with password val encryptedSeedPhrase = CryptoManager.encryptWithPassword( seedPhrase.joinToString(" "), password ) + Log.d(TAG, "🔒 Encrypted seed phrase: ${encryptedSeedPhrase.take(50)}...") - withContext(Dispatchers.IO) { - val encryptedAccount = EncryptedAccount( + // Step 5: Save to database + val saved = withContext(Dispatchers.IO) { + databaseService.saveEncryptedAccount( publicKey = keyPair.publicKey, - encryptedPrivateKey = encryptedPrivateKey, - encryptedSeedPhrase = encryptedSeedPhrase, - name = name + privateKeyEncrypted = encryptedPrivateKey, + seedPhraseEncrypted = encryptedSeedPhrase ) - accountManager.saveAccount(encryptedAccount) - accountManager.setCurrentAccount(keyPair.publicKey) } - val decryptedAccount = DecryptedAccount( + if (!saved) { + return@withContext Result.failure(Exception("Failed to save account to database")) + } + + Log.d(TAG, "✅ Account saved to database successfully") + + // Step 6: Create decrypted account object + val decryptedAccount = DecryptedAccountData( publicKey = keyPair.publicKey, privateKey = keyPair.privateKey, - seedPhrase = seedPhrase, privateKeyHash = privateKeyHash, - name = name + seedPhrase = seedPhrase ) + // Step 7: Update state and reload accounts + currentDecryptedAccount = decryptedAccount _state.update { it.copy( status = AuthStatus.Authenticated(decryptedAccount) )} loadAccounts() + + // Step 8: Authenticate with protocol + Log.d(TAG, "🌐 Authenticating with protocol server...") + ProtocolManager.authenticate(keyPair.publicKey, privateKeyHash) + + Log.d(TAG, "✅ Account created and authenticated successfully!") Result.success(decryptedAccount) } catch (e: Exception) { + Log.e(TAG, "❌ Failed to create account", e) Result.failure(e) } } @@ -122,113 +192,106 @@ class AuthStateManager( */ suspend fun importAccount( seedPhrase: List, - password: String, - name: String = "Imported Account" - ): Result { - return createAccount(seedPhrase, password, name) + password: String + ): Result { + Log.d(TAG, "📥 Importing account from seed phrase") + return createAccount(seedPhrase, password) } /** * Unlock account with password + * Matches loginWithPassword from React Native */ suspend fun unlock( publicKey: String, password: String - ): Result = withContext(Dispatchers.Default) { + ): Result = withContext(Dispatchers.Default) { try { - val encryptedAccount = withContext(Dispatchers.IO) { - accountManager.getAccount(publicKey) - } ?: return@withContext Result.failure(Exception("Account not found")) + Log.d(TAG, "🔓 Unlocking account: ${publicKey.take(20)}...") + Log.d(TAG, " - Password length: ${password.length}") - val privateKey = CryptoManager.decryptWithPassword( - encryptedAccount.encryptedPrivateKey, password - ) ?: return@withContext Result.failure(Exception("Invalid password")) - - val seedPhraseStr = CryptoManager.decryptWithPassword( - encryptedAccount.encryptedSeedPhrase, password - ) ?: return@withContext Result.failure(Exception("Invalid password")) - - val seedPhrase = seedPhraseStr.split(" ") - val keyPair = CryptoManager.generateKeyPairFromSeed(seedPhrase) - - if (keyPair.publicKey != publicKey) { - return@withContext Result.failure(Exception("Invalid password or corrupted data")) + // Decrypt account from database + val decryptedAccount = withContext(Dispatchers.IO) { + databaseService.decryptAccount(publicKey, password) } - val privateKeyHash = CryptoManager.generatePrivateKeyHash(privateKey) + if (decryptedAccount == null) { + Log.e(TAG, "❌ Failed to decrypt account - wrong password or account not found") + return@withContext Result.failure(Exception("Invalid password or account not found")) + } - val decryptedAccount = DecryptedAccount( - publicKey = publicKey, - privateKey = privateKey, - seedPhrase = seedPhrase, - privateKeyHash = privateKeyHash, - name = encryptedAccount.name - ) + Log.d(TAG, "✅ Account decrypted successfully") + Log.d(TAG, " - Public Key: ${decryptedAccount.publicKey.take(20)}...") + Log.d(TAG, " - Private Key Hash: ${decryptedAccount.privateKeyHash}") + Log.d(TAG, " - Seed Phrase: ${decryptedAccount.seedPhrase.size} words") + // Update last used timestamp withContext(Dispatchers.IO) { - accountManager.setCurrentAccount(publicKey) + databaseService.updateLastUsed(publicKey) } + // Update state + currentDecryptedAccount = decryptedAccount _state.update { it.copy( status = AuthStatus.Authenticated(decryptedAccount) )} + // Authenticate with protocol + Log.d(TAG, "🌐 Authenticating with protocol server...") + ProtocolManager.authenticate(decryptedAccount.publicKey, decryptedAccount.privateKeyHash) + + Log.d(TAG, "✅ Account unlocked and authenticated successfully!") Result.success(decryptedAccount) } catch (e: Exception) { + Log.e(TAG, "❌ Failed to unlock account", e) Result.failure(e) } } /** - * Lock account + * Logout - clears decrypted account from memory */ - fun lock() { - val currentStatus = _state.value.status - if (currentStatus is AuthStatus.Authenticated) { - _state.update { it.copy( - status = AuthStatus.Locked(currentStatus.account.publicKey) - )} - } - } - - /** - * Logout - */ - suspend fun logout() { - withContext(Dispatchers.IO) { - accountManager.logout() - } + fun logout() { + Log.d(TAG, "🚪 Logging out, clearing decrypted keys from memory") + currentDecryptedAccount = null _state.update { it.copy( status = AuthStatus.Unauthenticated )} - loadAccounts() } /** - * Switch to different account + * Delete account from database */ - fun switchAccount(publicKey: String) { - _state.update { it.copy( - status = AuthStatus.Locked(publicKey) - )} + suspend fun deleteAccount(publicKey: String): Result = withContext(Dispatchers.IO) { + try { + Log.d(TAG, "🗑️ Deleting account: ${publicKey.take(20)}...") + + val success = databaseService.deleteAccount(publicKey) + if (!success) { + return@withContext Result.failure(Exception("Failed to delete account")) + } + + // If deleting current account, logout + if (currentDecryptedAccount?.publicKey == publicKey) { + withContext(Dispatchers.Main) { + logout() + } + } + + loadAccounts() + Log.d(TAG, "✅ Account deleted successfully") + Result.success(Unit) + } catch (e: Exception) { + Log.e(TAG, "❌ Failed to delete account", e) + Result.failure(e) + } } /** - * Delete account permanently + * Get current decrypted account (if authenticated) */ - suspend fun deleteAccount(publicKey: String) = withContext(Dispatchers.IO) { - val accounts = accountManager.getAllAccounts().toMutableList() - accounts.removeAll { it.publicKey == publicKey } - - accountManager.clearAll() - accounts.forEach { accountManager.saveAccount(it) } - - loadAccounts() - - val currentStatus = _state.value.status - if ((currentStatus is AuthStatus.Authenticated && currentStatus.account.publicKey == publicKey) || - (currentStatus is AuthStatus.Locked && currentStatus.publicKey == publicKey)) { - logout() + fun getCurrentAccount(): DecryptedAccountData? = currentDecryptedAccount +} } } }