From 102e4ce12aa176a5a43c9549e575d65736eea3f8 Mon Sep 17 00:00:00 2001 From: Kgothatso Ngako Date: Sat, 6 Jun 2026 01:25:42 +0200 Subject: [PATCH] Check giftWraps to us and from us... --- .../compose/database/dao/NostrDao.kt | 366 +++++++++--------- .../ChatRoomDetailMessageListViewModel.kt | 6 +- 2 files changed, 190 insertions(+), 182 deletions(-) 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 cdb8981f..88c0500b 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 @@ -21,6 +21,8 @@ import ac.cord.auxiliary.compose.managers.toHex import androidx.room3.Dao import androidx.room3.Transaction import co.touchlab.kermit.Logger +import com.vitorpamplona.quartz.nip01Core.core.toHexKey +import com.vitorpamplona.quartz.nip01Core.crypto.KeyPair import com.vitorpamplona.quartz.nip01Core.metadata.MetadataEvent import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl import com.vitorpamplona.quartz.nip01Core.tags.events.taggedEvents @@ -341,198 +343,204 @@ abstract class NostrDao( ) // TODO: Might want to process this somewhere else where privateKey is available... - SeedManager.activeKeyPair().privKey?.let { activeUserPrivateKey -> - giftWrapMessage.decryptGiftWrapSeal( - activeUserPrivateKey - )?.let { giftWrapSeal -> - database.giftWrapSealDao().upsert( - giftWrapSeal - ) - - giftWrapSeal.decryptGiftWrapPayload( + if (giftWrapMessage.receiverPublicKey == SeedManager.activePublicKey().toHexKey()) { + // This is a gift wrap message meant for us... + SeedManager.activeKeyPair().privKey?.let { activeUserPrivateKey -> + giftWrapMessage.decryptGiftWrapSeal( 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 + )?.let { giftWrapSeal -> + database.giftWrapSealDao().upsert( + giftWrapSeal ) - 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 + 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 ) - 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/ui/view/model/ChatRoomDetailMessageListViewModel.kt b/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/ui/view/model/ChatRoomDetailMessageListViewModel.kt index 248a9f82..59d78bae 100755 --- a/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/ui/view/model/ChatRoomDetailMessageListViewModel.kt +++ b/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/ui/view/model/ChatRoomDetailMessageListViewModel.kt @@ -108,9 +108,6 @@ class ChatRoomDetailMessageListViewModel( kinds = arrayOf( GiftWrapEvent.KIND, ), - authors = arrayOf( - localChatRoom.chatRoom.userPublicKey - ), tags = mapOf( Pair("p", listOf(recipients.participant.participantPublicKey)) ), @@ -119,6 +116,7 @@ class ChatRoomDetailMessageListViewModel( ) } else { + // Try to find the ChatMessageRelayList for this participant... isReceiverChatMessageRelayListMissing.value = true // TODO: compute the timestamp for when list we sent messages and use that as since... Pair( @@ -135,6 +133,8 @@ class ChatRoomDetailMessageListViewModel( ) } + logger.i("GiftWrapFilter: ${relayAndSynchronizationFilter.second}") + val negentropySynchronizeRequests = relayAndSynchronizationFilter.first.map { normalizedRelayUrl -> NegentropySynchronizeRequest( id = NegentropySynchronizeRequest.computeId(