Check giftWraps to us and from us...

This commit is contained in:
Kgothatso Ngako
2026-06-06 01:25:42 +02:00
parent dd98fcc631
commit 102e4ce12a
2 changed files with 190 additions and 182 deletions

View File

@@ -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")
}
}

View File

@@ -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(