feat: Implement secure database operations and caching for encrypted accounts

This commit is contained in:
2026-01-09 01:09:40 +05:00
parent 4adbbd095d
commit 2f77c16484
7 changed files with 523 additions and 105 deletions

View File

@@ -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<String, EncryptedAccountEntity>()
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<EncryptedAccountEntity> =
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<List<EncryptedAccountEntity>> {
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<String>
)