From 91f3f270d413f687a28add2c65d6cd1ad6eff06d Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Fri, 5 Sep 2025 13:26:38 +0200 Subject: [PATCH] Fix geohash filtering (#376) * fix(geohash): enforce client-side filter on subscription and validate 'g' tag in handler; move dedup after validation; avoid misrouting events across geohashes * cleanup duplicate code block * remove --- .../bitchat/android/nostr/NostrGeohashService.kt | 13 ++++++++++++- .../com/bitchat/android/nostr/NostrRelayManager.kt | 10 ++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/bitchat/android/nostr/NostrGeohashService.kt b/app/src/main/java/com/bitchat/android/nostr/NostrGeohashService.kt index 9b34f7fa..ff505e96 100644 --- a/app/src/main/java/com/bitchat/android/nostr/NostrGeohashService.kt +++ b/app/src/main/java/com/bitchat/android/nostr/NostrGeohashService.kt @@ -1167,13 +1167,24 @@ class NostrGeohashService( private fun handleUnifiedGeohashEvent(event: NostrEvent, geohash: String) { coroutineScope.launch(Dispatchers.Default) { try { - Log.v(TAG, "🔍 handleUnifiedGeohashEvent called - eventGeohash: $geohash, currentGeohash: $currentGeohash, eventKind: ${event.kind}, eventId: ${event.id.take(8)}...") + Log.v(TAG, "🔍 handleUnifiedGeohashEvent called - subGeohash: $geohash, currentGeohash: $currentGeohash, kind: ${event.kind}, id: ${event.id.take(8)}...") // Only handle ephemeral kind 20000 events if (event.kind != 20000) { Log.v(TAG, "❌ Skipping non-ephemeral event (kind ${event.kind})") return@launch } + + // VALIDATE EVENT G TAG AGAINST SUBSCRIPTION GEOHASH + val eventGeohash = event.tags.firstOrNull { it.size >= 2 && it[0] == "g" }?.getOrNull(1) + if (eventGeohash == null) { + Log.w(TAG, "🚫 Dropping kind=20000 without 'g' tag id=${event.id.take(8)}") + return@launch + } + if (!eventGeohash.equals(geohash, ignoreCase = true)) { + Log.w(TAG, "🚫 Dropping mismatched geohash event: sub=$geohash eventTag=$eventGeohash id=${event.id.take(8)}") + return@launch + } // Check Proof of Work validation BEFORE other processing val powSettings = PoWPreferenceManager.getCurrentSettings() diff --git a/app/src/main/java/com/bitchat/android/nostr/NostrRelayManager.kt b/app/src/main/java/com/bitchat/android/nostr/NostrRelayManager.kt index 2bf252ab..ace6e57b 100644 --- a/app/src/main/java/com/bitchat/android/nostr/NostrRelayManager.kt +++ b/app/src/main/java/com/bitchat/android/nostr/NostrRelayManager.kt @@ -666,6 +666,16 @@ class NostrRelayManager private constructor() { relay?.messagesReceived = (relay?.messagesReceived ?: 0) + 1 updateRelaysList() + // CLIENT-SIDE FILTER ENFORCEMENT: Ensure this event matches the subscription's filter + activeSubscriptions[response.subscriptionId]?.let { subInfo -> + val matches = try { subInfo.filter.matches(response.event) } catch (e: Exception) { true } + if (!matches) { + Log.v(TAG, "🚫 Dropping event ${response.event.id.take(16)}... not matching filter for sub=${response.subscriptionId}") + // Do NOT call deduplicator here to allow the correct subscription to process it later + return + } + } + // DEDUPLICATION: Check if we've already processed this event val wasProcessed = eventDeduplicator.processEvent(response.event) { event -> // Only log non-gift-wrap events to reduce noise