From f67ebd60b29d5eafed2a63fc00ddb5808558503c Mon Sep 17 00:00:00 2001 From: Kgothatso Ngako Date: Sat, 6 Jun 2026 03:52:08 +0200 Subject: [PATCH] GiftWrap error handling --- .../compose/database/dao/NostrDao.kt | 560 ++++++++++++------ .../compose/database/model/GiftWrapMessage.kt | 56 +- .../repository/DatabaseNostrRepository.kt | 61 +- .../GiftWrapSealDecryptionException.kt | 4 + .../exceptions/GiftWrapUnsealException.kt | 4 + 5 files changed, 463 insertions(+), 222 deletions(-) create mode 100644 composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/exceptions/GiftWrapSealDecryptionException.kt create mode 100644 composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/exceptions/GiftWrapUnsealException.kt diff --git a/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/database/dao/NostrDao.kt b/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/database/dao/NostrDao.kt index 88c0500b..e8410d1f 100644 --- a/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/database/dao/NostrDao.kt +++ b/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/database/dao/NostrDao.kt @@ -16,6 +16,8 @@ import ac.cord.auxiliary.compose.database.model.Profile import ac.cord.auxiliary.compose.database.model.UnsignedNostrEvent import ac.cord.auxiliary.compose.database.model.types.SynchronizationFilter import ac.cord.auxiliary.compose.exceptions.GiftWrapImpersonationException +import ac.cord.auxiliary.compose.exceptions.GiftWrapSealDecryptionException +import ac.cord.auxiliary.compose.exceptions.GiftWrapUnsealException import ac.cord.auxiliary.compose.managers.SeedManager import ac.cord.auxiliary.compose.managers.toHex import androidx.room3.Dao @@ -343,204 +345,400 @@ abstract class NostrDao( ) // TODO: Might want to process this somewhere else where privateKey is available... - if (giftWrapMessage.receiverPublicKey == SeedManager.activePublicKey().toHexKey()) { - // This is a gift wrap message meant for us... - SeedManager.activeKeyPair().privKey?.let { activeUserPrivateKey -> - giftWrapMessage.decryptGiftWrapSeal( - activeUserPrivateKey - )?.let { giftWrapSeal -> +// SeedManager.activeKeyPair().let { activeKeyPair -> +// giftWrapMessage.decryptGiftWrapSeal( +// activeKeyPair +// )?.let { giftWrapSeal -> +// database.giftWrapSealDao().upsert( +// giftWrapSeal +// ) +// +// giftWrapSeal.decryptGiftWrapPayload( +// activeKeyPair.privKey!! +// )?.let { decryptedGiftWrapPayload -> +// if (giftWrapSeal.publicKey.lowercase() != decryptedGiftWrapPayload.publicKey.lowercase()) { +// val giftWrapImpersonation = GiftWrapImpersonationException( +// "Seal pubkey (${giftWrapSeal.publicKey}) and payload pubkey (${decryptedGiftWrapPayload.publicKey}) needs to be the same for the giftWrap ${nostrEvent.id}" +// ) +// logger.e("Impersonation", giftWrapImpersonation) +// throw giftWrapImpersonation // Once this is thrown the database transaction fails and nothing gets saved to the DB... +// } +// val result = database.giftWrapPayloadDao().upsert( +// decryptedGiftWrapPayload +// ) +// +// val giftWrapPayload = if (result != -1L) { +// decryptedGiftWrapPayload.copy( +// id = result +// ) +// } else { +// decryptedGiftWrapPayload +// } +// +// val chatRoomId = giftWrapPayload.aggregatedParticipantsPublicKey() +// logger.d("ChatRoomId: $chatRoomId") +// +// if (chatRoomId != null) { +// // Find or create chatRoom +// val localChatRoom = database.chatRoomDao().findChatRoomById( +// chatRoomId +// ) +// +// if (localChatRoom == null) { +// val userPublicKey = SeedManager.activeKeyPair().pubKey.toHex() +// database.chatRoomDao().upsert( +// ChatRoom( +// id = chatRoomId, +// userPublicKey = userPublicKey, +// subject = decryptedGiftWrapPayload.parseSubject(), +// createdAt = decryptedGiftWrapPayload.createdAt, +// initialGiftWrapPayloadId = giftWrapPayload.id +// ) +// ) +// +// // Add participants... +// val participants = giftWrapPayload.participantPTags( +// NormalizedRelayUrl(relayURL) // TODO: Get relayURL for publicKey profile... +// ).map { +// Participant( +// participantPublicKey = it.pubKey, +// chatRoomId = chatRoomId, +// relayHint = it.relayHint?.url +// ) +// } +// +// // Sync missing participant profiles... +// participants.forEach { participant -> +// val giftWrapParticipantProfile = database.profileDao().getProfileByPublicKey(participant.participantPublicKey) +// +// if (giftWrapParticipantProfile == null) { +// // Save a placeholder +// database.profileDao().insertPlaceholderProfile( +// Profile( +// displayName = "LOADING...", +// publicKey = participant.participantPublicKey, +// createdAt = GENESIS_AT, +// nostrEventId = nostrEvent.id, // Will get overwriting by sync, +// ) +// ) +// +// val recommendRelayUrl = giftWrapMessage.receiverRelayHit +// +// if (recommendRelayUrl != null) { +// if (profilePublicKeysToSync[recommendRelayUrl] == null) { +// profilePublicKeysToSync[recommendRelayUrl] = mutableSetOf() +// } +// profilePublicKeysToSync[recommendRelayUrl]?.add(participant.participantPublicKey) +// } else { +// if (profilePublicKeysToSync[relayURL] == null) { +// profilePublicKeysToSync[relayURL] = mutableSetOf() +// } +// profilePublicKeysToSync[relayURL]?.add( +// participant.participantPublicKey +// ) +// } +// } +// } +// +// database.participantDao().upsert( +// participants +// ) +// +// participants.filter { it.participantPublicKey != userPublicKey }.forEach { participant -> +// val publicKey = participant.participantPublicKey +// +// val chatMessageRelayListEvent = database.nostrEventDao().getAuthoredNostrEvents( +// kinds = arrayOf( +// ChatMessageRelayListEvent.KIND +// ), +// authors = arrayOf( +// publicKey +// ), +// since = GENESIS_AT, +// limit = 5 +// ).firstOrNull()?.let { nostrEvent -> +// if (nostrEvent.kind != ChatMessageRelayListEvent.KIND) { +// logger.e("getAuthoredNostrEvents returned invalid ChatMessageRelayListEvent for ${publicKey}: $nostrEvent") +// null +// } else { +// ChatMessageRelayListEvent( +// id = nostrEvent.id, +// tags = nostrEvent.tags, +// pubKey = nostrEvent.pubKey, +// content = nostrEvent.content, +// createdAt = nostrEvent.createdAt.epochSeconds, +// sig = nostrEvent.sig +// ) +// } +// } +// +// if (chatMessageRelayListEvent != null) { +// // Sync messages from this relay that were sent by us +// val synchronizationFilter = SynchronizationFilter( +// kinds = arrayOf( +// GiftWrapEvent.KIND, +// ), +// authors = arrayOf( +// userPublicKey +// ), +// tags = mapOf( +// Pair("p", listOf(participant.participantPublicKey)) +// ), +// limit = 50 +// ) +// database.negentropySynchronizeRequestDao().insert( +// chatMessageRelayListEvent.relays().map { normalizedRelayUrl -> +// NegentropySynchronizeRequest( +// id = NegentropySynchronizeRequest.computeId( +// relayURL = normalizedRelayUrl.url, +// synchronizationFilter = synchronizationFilter +// ), +// purpose = "sent-messages", +// synchronizationFilter = synchronizationFilter, +// relayURL = normalizedRelayUrl.url, +// level = 0 +// ) +// } +// ) +// } else { +// logger.w("We don't have a chatMessageRelayListEvent for the pubkey $publicKey") +// +// // Sync ChatMessageRelayListEvent publicKey... +// // TODO: Get relayHint form participant... +// if (profilePublicKeysToSync.containsKey(relayURL)) { +// profilePublicKeysToSync[relayURL] = mutableSetOf() +// } +// profilePublicKeysToSync[relayURL]?.add(participant.participantPublicKey) +// } +// } +// } else { +// giftWrapPayload.parseSubject()?.let { subject -> +// database.chatRoomDao().upsert( +// localChatRoom.chatRoom.copy( +// subject = subject, +// updatedAt = giftWrapPayload.createdAt +// ) +// ) +// } +// } +// +// database.chatMessageDao().upsert( +// ChatMessage( +// giftWrapPayloadId = giftWrapPayload.id, +// senderPublicKey = giftWrapPayload.publicKey, +// isUserMessage = SeedManager.activePublicKey().toHex() == giftWrapPayload.publicKey, +// chatRoomId = chatRoomId, +// createdAt = giftWrapPayload.createdAt, +// content = giftWrapPayload.content, +// ) +// ) +// } else { +// logger.w("Failed to setup chatRoom for message") +// } +// } +// } +// } + + SeedManager.activeKeyPair().let { activeKeyPair -> + giftWrapMessage.decryptGiftWrapSeal( + activeKeyPair + ).let { giftWrapSeal -> + if (giftWrapSeal == null) { + throw GiftWrapUnsealException("Failed to unseal ${nostrEvent.id} from the giftWrap") + } else { database.giftWrapSealDao().upsert( giftWrapSeal ) giftWrapSeal.decryptGiftWrapPayload( - activeUserPrivateKey - )?.let { decryptedGiftWrapPayload -> - if (giftWrapSeal.publicKey.lowercase() != decryptedGiftWrapPayload.publicKey.lowercase()) { - val giftWrapImpersonation = GiftWrapImpersonationException( - "Seal pubkey (${giftWrapSeal.publicKey}) and payload pubkey (${decryptedGiftWrapPayload.publicKey}) needs to be the same for the giftWrap ${nostrEvent.id}" - ) - logger.e("Impersonation", giftWrapImpersonation) - throw giftWrapImpersonation // Once this is thrown the database transaction fails and nothing gets saved to the DB... - } - val result = database.giftWrapPayloadDao().upsert( - decryptedGiftWrapPayload - ) - - val giftWrapPayload = if (result != -1L) { - decryptedGiftWrapPayload.copy( - id = result - ) + activeKeyPair.privKey!! + ).let { decryptedGiftWrapPayload -> + if (decryptedGiftWrapPayload == null) { + throw GiftWrapSealDecryptionException("Failed to decrypt sealed ${giftWrapSeal.id} payload") } else { - decryptedGiftWrapPayload - } - - val chatRoomId = giftWrapPayload.aggregatedParticipantsPublicKey() - logger.d("ChatRoomId: $chatRoomId") - - if (chatRoomId != null) { - // Find or create chatRoom - val localChatRoom = database.chatRoomDao().findChatRoomById( - chatRoomId + if (giftWrapSeal.publicKey.lowercase() != decryptedGiftWrapPayload.publicKey.lowercase()) { + val giftWrapImpersonation = GiftWrapImpersonationException( + "Seal pubkey (${giftWrapSeal.publicKey}) and payload pubkey (${decryptedGiftWrapPayload.publicKey}) needs to be the same for the giftWrap ${nostrEvent.id}" + ) + logger.e("Impersonation", giftWrapImpersonation) + throw giftWrapImpersonation // Once this is thrown the database transaction fails and nothing gets saved to the DB... + } + val result = database.giftWrapPayloadDao().upsert( + decryptedGiftWrapPayload ) - if (localChatRoom == null) { - val userPublicKey = SeedManager.activeKeyPair().pubKey.toHex() - database.chatRoomDao().upsert( - ChatRoom( - id = chatRoomId, - userPublicKey = userPublicKey, - subject = decryptedGiftWrapPayload.parseSubject(), - createdAt = decryptedGiftWrapPayload.createdAt, - initialGiftWrapPayloadId = giftWrapPayload.id - ) + val giftWrapPayload = if (result != -1L) { + decryptedGiftWrapPayload.copy( + id = result ) - - // Add participants... - val participants = giftWrapPayload.participantPTags( - NormalizedRelayUrl(relayURL) // TODO: Get relayURL for publicKey profile... - ).map { - Participant( - participantPublicKey = it.pubKey, - chatRoomId = chatRoomId, - relayHint = it.relayHint?.url - ) - } - - // Sync missing participant profiles... - participants.forEach { participant -> - val giftWrapParticipantProfile = database.profileDao().getProfileByPublicKey(participant.participantPublicKey) - - if (giftWrapParticipantProfile == null) { - // Save a placeholder - database.profileDao().insertPlaceholderProfile( - Profile( - displayName = "LOADING...", - publicKey = participant.participantPublicKey, - createdAt = GENESIS_AT, - nostrEventId = nostrEvent.id, // Will get overwriting by sync, - ) - ) - - val recommendRelayUrl = giftWrapMessage.receiverRelayHit - - if (recommendRelayUrl != null) { - if (profilePublicKeysToSync[recommendRelayUrl] == null) { - profilePublicKeysToSync[recommendRelayUrl] = mutableSetOf() - } - profilePublicKeysToSync[recommendRelayUrl]?.add(participant.participantPublicKey) - } else { - if (profilePublicKeysToSync[relayURL] == null) { - profilePublicKeysToSync[relayURL] = mutableSetOf() - } - profilePublicKeysToSync[relayURL]?.add( - participant.participantPublicKey - ) - } - } - } - - database.participantDao().upsert( - participants - ) - - participants.filter { it.participantPublicKey != userPublicKey }.forEach { participant -> - val publicKey = participant.participantPublicKey - - val chatMessageRelayListEvent = database.nostrEventDao().getAuthoredNostrEvents( - kinds = arrayOf( - ChatMessageRelayListEvent.KIND - ), - authors = arrayOf( - publicKey - ), - since = GENESIS_AT, - limit = 5 - ).firstOrNull()?.let { nostrEvent -> - if (nostrEvent.kind != ChatMessageRelayListEvent.KIND) { - logger.e("getAuthoredNostrEvents returned invalid ChatMessageRelayListEvent for ${publicKey}: $nostrEvent") - null - } else { - ChatMessageRelayListEvent( - id = nostrEvent.id, - tags = nostrEvent.tags, - pubKey = nostrEvent.pubKey, - content = nostrEvent.content, - createdAt = nostrEvent.createdAt.epochSeconds, - sig = nostrEvent.sig - ) - } - } - - if (chatMessageRelayListEvent != null) { - // Sync messages from this relay that were sent by us - val synchronizationFilter = SynchronizationFilter( - kinds = arrayOf( - GiftWrapEvent.KIND, - ), - authors = arrayOf( - userPublicKey - ), - tags = mapOf( - Pair("p", listOf(participant.participantPublicKey)) - ), - limit = 50 - ) - database.negentropySynchronizeRequestDao().insert( - chatMessageRelayListEvent.relays().map { normalizedRelayUrl -> - NegentropySynchronizeRequest( - id = NegentropySynchronizeRequest.computeId( - relayURL = normalizedRelayUrl.url, - synchronizationFilter = synchronizationFilter - ), - purpose = "sent-messages", - synchronizationFilter = synchronizationFilter, - relayURL = normalizedRelayUrl.url, - level = 0 - ) - } - ) - } else { - logger.w("We don't have a chatMessageRelayListEvent for the pubkey $publicKey") - - // Sync ChatMessageRelayListEvent publicKey... - // TODO: Get relayHint form participant... - if (profilePublicKeysToSync.containsKey(relayURL)) { - profilePublicKeysToSync[relayURL] = mutableSetOf() - } - profilePublicKeysToSync[relayURL]?.add(participant.participantPublicKey) - } - } } else { - giftWrapPayload.parseSubject()?.let { subject -> - database.chatRoomDao().upsert( - localChatRoom.chatRoom.copy( - subject = subject, - updatedAt = giftWrapPayload.createdAt - ) - ) - } + decryptedGiftWrapPayload } - database.chatMessageDao().upsert( - ChatMessage( - giftWrapPayloadId = giftWrapPayload.id, - senderPublicKey = giftWrapPayload.publicKey, - isUserMessage = SeedManager.activePublicKey().toHex() == giftWrapPayload.publicKey, - chatRoomId = chatRoomId, - createdAt = giftWrapPayload.createdAt, - content = giftWrapPayload.content, + val chatRoomId = giftWrapPayload.aggregatedParticipantsPublicKey() + logger.d("ChatRoomId: $chatRoomId") + + if (chatRoomId != null) { + // Find or create chatRoom + val localChatRoom = database.chatRoomDao().findChatRoomById( + chatRoomId ) - ) - } else { - logger.w("Failed to setup chatRoom for message") + + if (localChatRoom == null) { + val userPublicKey = SeedManager.activeKeyPair().pubKey.toHex() + database.chatRoomDao().upsert( + ChatRoom( + id = chatRoomId, + userPublicKey = userPublicKey, + subject = decryptedGiftWrapPayload.parseSubject(), + createdAt = decryptedGiftWrapPayload.createdAt, + initialGiftWrapPayloadId = giftWrapPayload.id + ) + ) + + // Add participants... + val participants = giftWrapPayload.participantPTags( + NormalizedRelayUrl(relayURL) // TODO: Get relayURL for publicKey profile... + ).map { + Participant( + participantPublicKey = it.pubKey, + chatRoomId = chatRoomId, + relayHint = it.relayHint?.url + ) + } + + // Sync missing participant profiles... + participants.forEach { participant -> + val giftWrapParticipantProfile = database.profileDao().getProfileByPublicKey(participant.participantPublicKey) + + if (giftWrapParticipantProfile == null) { + // Save a placeholder + database.profileDao().insertPlaceholderProfile( + Profile( + displayName = "LOADING...", + publicKey = participant.participantPublicKey, + createdAt = GENESIS_AT, + nostrEventId = nostrEvent.id, // Will get overwriting by sync, + ) + ) + + val recommendRelayUrl = giftWrapMessage.receiverRelayHit + + if (recommendRelayUrl != null) { + if (profilePublicKeysToSync[recommendRelayUrl] == null) { + profilePublicKeysToSync[recommendRelayUrl] = mutableSetOf() + } + profilePublicKeysToSync[recommendRelayUrl]?.add(participant.participantPublicKey) + } else { + if (profilePublicKeysToSync[relayURL] == null) { + profilePublicKeysToSync[relayURL] = mutableSetOf() + } + profilePublicKeysToSync[relayURL]?.add( + participant.participantPublicKey + ) + } + } + } + + database.participantDao().upsert( + participants + ) + + participants.filter { it.participantPublicKey != userPublicKey }.forEach { participant -> + val publicKey = participant.participantPublicKey + + val chatMessageRelayListEvent = database.nostrEventDao().getAuthoredNostrEvents( + kinds = arrayOf( + ChatMessageRelayListEvent.KIND + ), + authors = arrayOf( + publicKey + ), + since = GENESIS_AT, + limit = 5 + ).firstOrNull()?.let { nostrEvent -> + if (nostrEvent.kind != ChatMessageRelayListEvent.KIND) { + logger.e("getAuthoredNostrEvents returned invalid ChatMessageRelayListEvent for ${publicKey}: $nostrEvent") + null + } else { + ChatMessageRelayListEvent( + id = nostrEvent.id, + tags = nostrEvent.tags, + pubKey = nostrEvent.pubKey, + content = nostrEvent.content, + createdAt = nostrEvent.createdAt.epochSeconds, + sig = nostrEvent.sig + ) + } + } + + if (chatMessageRelayListEvent != null) { + // Sync messages from this relay that were sent by us + val synchronizationFilter = SynchronizationFilter( + kinds = arrayOf( + GiftWrapEvent.KIND, + ), + authors = arrayOf( + userPublicKey + ), + tags = mapOf( + Pair("p", listOf(participant.participantPublicKey)) + ), + limit = 50 + ) + database.negentropySynchronizeRequestDao().insert( + chatMessageRelayListEvent.relays().map { normalizedRelayUrl -> + NegentropySynchronizeRequest( + id = NegentropySynchronizeRequest.computeId( + relayURL = normalizedRelayUrl.url, + synchronizationFilter = synchronizationFilter + ), + purpose = "sent-messages", + synchronizationFilter = synchronizationFilter, + relayURL = normalizedRelayUrl.url, + level = 0 + ) + } + ) + } else { + logger.w("We don't have a chatMessageRelayListEvent for the pubkey $publicKey") + + // Sync ChatMessageRelayListEvent publicKey... + // TODO: Get relayHint form participant... + if (profilePublicKeysToSync.containsKey(relayURL)) { + profilePublicKeysToSync[relayURL] = mutableSetOf() + } + profilePublicKeysToSync[relayURL]?.add(participant.participantPublicKey) + } + } + } else { + giftWrapPayload.parseSubject()?.let { subject -> + database.chatRoomDao().upsert( + localChatRoom.chatRoom.copy( + subject = subject, + updatedAt = giftWrapPayload.createdAt + ) + ) + } + } + + database.chatMessageDao().upsert( + ChatMessage( + giftWrapPayloadId = giftWrapPayload.id, + senderPublicKey = giftWrapPayload.publicKey, + isUserMessage = SeedManager.activePublicKey().toHex() == giftWrapPayload.publicKey, + chatRoomId = chatRoomId, + createdAt = giftWrapPayload.createdAt, + content = giftWrapPayload.content, + ) + ) + } else { + logger.w("Failed to setup chatRoom for message") + } } } } } - } else { - // This may be a gift wrap message to someone we sent a message to... - logger.w("We may have sent this message to someone") } } diff --git a/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/database/model/GiftWrapMessage.kt b/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/database/model/GiftWrapMessage.kt index 17939214..9f8a5908 100644 --- a/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/database/model/GiftWrapMessage.kt +++ b/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/database/model/GiftWrapMessage.kt @@ -15,13 +15,17 @@ import com.vitorpamplona.quartz.nip01Core.core.HexKey import com.vitorpamplona.quartz.nip01Core.core.Kind import com.vitorpamplona.quartz.nip01Core.core.TagArray import com.vitorpamplona.quartz.nip01Core.core.TagArrayBuilder +import com.vitorpamplona.quartz.nip01Core.core.hexToByteArray +import com.vitorpamplona.quartz.nip01Core.core.toHexKey import com.vitorpamplona.quartz.nip01Core.crypto.KeyPair import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl import com.vitorpamplona.quartz.nip01Core.signers.NostrSignerInternal import com.vitorpamplona.quartz.nip01Core.tags.people.PTag +import com.vitorpamplona.quartz.nip44Encryption.Nip44 import com.vitorpamplona.quartz.nip59Giftwrap.seals.SealedRumorEvent import com.vitorpamplona.quartz.nip59Giftwrap.wraps.GiftWrapEvent import fr.acinq.bitcoin.PrivateKey +import kotlin.math.log import kotlin.time.Clock import kotlin.time.Instant @@ -87,7 +91,7 @@ data class GiftWrapMessage( private val logger = Logger.withTag("GiftWrapMessage") suspend fun decryptGiftWrapSeal( - privateKey: ByteArray + keyPair: KeyPair ): GiftWrapSeal? { if (kind == GiftWrapEvent.KIND) { val giftWrapEvent = GiftWrapEvent( @@ -105,27 +109,45 @@ data class GiftWrapMessage( ) logger.d("giftWrapEvent: ${giftWrapEvent.toJson()}") - val nostrSigner = NostrSignerInternal( - keyPair = KeyPair(privKey = privateKey) - ) + giftWrapEvent.recipientPubKey()?.let { recipientPublicKey -> + logger.d("Recipient PublicKey: $recipientPublicKey") - giftWrapEvent.unwrapOrNull( - nostrSigner - )?.let { giftWrapSeal -> + if (keyPair.pubKey.toHexKey() != recipientPublicKey) { + logger.e("We are unwrapping a message we may have sent from ${keyPair.pubKey.toHexKey()}") - logger.d("giftWrapSeal: ${giftWrapSeal.toJson()}") - return GiftWrapSeal( - id = giftWrapSeal.id, - publicKey = giftWrapSeal.pubKey, - content = giftWrapSeal.content, - tags = giftWrapSeal.tags, - createdAt = Instant.fromEpochSeconds(giftWrapSeal.createdAt), - signature = giftWrapSeal.sig - ) + keyPair.privKey?.let { privateKey -> + val sealJSON = Nip44.decrypt( + giftWrapEvent.content, + privateKey = privateKey, + pubKey = giftWrapEvent.pubKey.hexToByteArray() + ) + logger.d("SealJSON: $sealJSON") + + null + } + } else { + val nostrSigner = NostrSignerInternal( + keyPair = KeyPair(privKey = keyPair.privKey) + ) + + giftWrapEvent.unwrapOrNull( + nostrSigner + )?.let { giftWrapSeal -> + + logger.d("giftWrapSeal: ${giftWrapSeal.toJson()}") + return GiftWrapSeal( + id = giftWrapSeal.id, + publicKey = giftWrapSeal.pubKey, + content = giftWrapSeal.content, + tags = giftWrapSeal.tags, + createdAt = Instant.fromEpochSeconds(giftWrapSeal.createdAt), + signature = giftWrapSeal.sig + ) + } + } } } - return null } } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/database/repository/DatabaseNostrRepository.kt b/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/database/repository/DatabaseNostrRepository.kt index 3d43b9a6..d7bce4d7 100644 --- a/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/database/repository/DatabaseNostrRepository.kt +++ b/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/database/repository/DatabaseNostrRepository.kt @@ -463,21 +463,26 @@ class DatabaseNostrRepository( ) { storeNostrEventMutex.withLock { logger.d("saveNostrEvent: $nostrEvent") - database.nostrDao().storeNostrEvent( - nostrEvent.copy( - broadcastedAt = nostrEvent.createdAt, - ), - synchronizationRelayURLs = synchronizationRelayURLs, - relayURL = synchronizeNostrEventRequest.relayURL, - level = synchronizeNostrEventRequest.level - ) - - database.synchronizeNostrEventRequestDao().upsert( - synchronizeNostrEventRequest.copy( - nostrEventId = nostrEvent.id, - status = "processed" + try { + database.nostrDao().storeNostrEvent( + nostrEvent.copy( + broadcastedAt = nostrEvent.createdAt, + ), + synchronizationRelayURLs = synchronizationRelayURLs, + relayURL = synchronizeNostrEventRequest.relayURL, + level = synchronizeNostrEventRequest.level ) - ) + + database.synchronizeNostrEventRequestDao().upsert( + synchronizeNostrEventRequest.copy( + nostrEventId = nostrEvent.id, + status = "processed" + ) + ) + } catch (e: Throwable) { + logger.e("Failed to save nostrEvent ${nostrEvent.id}", e) + } + } } @@ -487,18 +492,26 @@ class DatabaseNostrRepository( synchronizationRelayURLs: List ) { logger.d("saveNostrEvent: $nostrEvent") - database.nostrDao().storeNostrEvent( - nostrEvent, - relayURL = negentropySynchronizeRequest.relayURL, - synchronizationRelayURLs = synchronizationRelayURLs, - level = negentropySynchronizeRequest.level - ) - database.negentropySynchronizeRequestDao().upsert( - negentropySynchronizeRequest.copy( - status = "processed" + try { + database.nostrDao().storeNostrEvent( + nostrEvent, + relayURL = negentropySynchronizeRequest.relayURL, + synchronizationRelayURLs = synchronizationRelayURLs, + level = negentropySynchronizeRequest.level ) - ) + + database.negentropySynchronizeRequestDao().upsert( + negentropySynchronizeRequest.copy( + status = "processed" + ) + ) + } catch (e: Throwable) { + logger.e("Failed to save nostr event $nostrEvent.id", e) + } + + + } override suspend fun queueSynchronizeNostrEvent( diff --git a/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/exceptions/GiftWrapSealDecryptionException.kt b/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/exceptions/GiftWrapSealDecryptionException.kt new file mode 100644 index 00000000..815e4b6e --- /dev/null +++ b/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/exceptions/GiftWrapSealDecryptionException.kt @@ -0,0 +1,4 @@ +package ac.cord.auxiliary.compose.exceptions + +open class GiftWrapSealDecryptionException(message: String? = null, cause: Throwable? = null) : Exception(message, cause) { +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/exceptions/GiftWrapUnsealException.kt b/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/exceptions/GiftWrapUnsealException.kt new file mode 100644 index 00000000..7a35c38c --- /dev/null +++ b/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/exceptions/GiftWrapUnsealException.kt @@ -0,0 +1,4 @@ +package ac.cord.auxiliary.compose.exceptions + +open class GiftWrapUnsealException(message: String? = null, cause: Throwable? = null) : Exception(message, cause) { +} \ No newline at end of file