feat: Implement secure database operations and caching for encrypted accounts
This commit is contained in:
@@ -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>
|
||||
)
|
||||
Reference in New Issue
Block a user