fix(desktop): verify user pubkey via get_public_key after nostrconnect login

The nostrconnect flow was trusting params[0] from the signer's connect
message as the user's pubkey. Some signers (e.g. nsec.app) don't put
the actual user pubkey there, causing all relay subscriptions to query
the wrong identity — contact lists, profiles, and DMs all returned empty.

Now calls remoteSigner.getPublicKey() after the handshake to get the
verified pubkey from the signer. Also stabilizes FeedScreen relay
subscriptions with distinctUntilChanged() and adds relay.primal.net
to defaults.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
nrobi144
2026-03-09 12:44:58 +02:00
parent 3a1e38a2f2
commit 5475d6c2bc
15 changed files with 2634 additions and 110 deletions
@@ -42,12 +42,6 @@ import com.vitorpamplona.quartz.nip46RemoteSigner.NostrConnectEvent
import com.vitorpamplona.quartz.nip57Zaps.LnZapPrivateEvent
import com.vitorpamplona.quartz.nip57Zaps.LnZapRequestEvent
import com.vitorpamplona.quartz.utils.Hex
import com.vitorpamplona.quartz.utils.TimeUtils
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel
import kotlinx.coroutines.launch
class NostrSignerRemote(
val signer: NostrSignerInternal,
@@ -57,8 +51,6 @@ class NostrSignerRemote(
val permissions: String? = null,
val secret: String? = null,
) : NostrSigner(signer.pubKey) {
private val scope = CoroutineScope(Dispatchers.Default + SupervisorJob())
private val manager =
RemoteSignerManager(
signer = signer,
@@ -74,17 +66,10 @@ class NostrSignerRemote(
Filter(
kinds = listOf(NostrConnectEvent.KIND),
tags = mapOf("p" to listOf(signer.pubKey)),
since = TimeUtils.now() - 60,
),
) { event ->
if (event is NostrConnectEvent) {
scope.launch {
try {
manager.newResponse(event)
} catch (_: Exception) {
// Ignore events we can't decrypt or parse
}
}
manager.newResponse(event)
}
}
@@ -94,7 +79,6 @@ class NostrSignerRemote(
fun closeSubscription() {
subscription.close()
scope.cancel()
}
override fun isWriteable(): Boolean = true
@@ -303,10 +287,9 @@ class NostrSignerRemote(
permissions: String? = null,
): NostrSignerRemote {
if (!bunkerUri.startsWith("bunker://")) throw Exception("Invalid bunker uri")
val splitData = bunkerUri.split("?", limit = 2)
val splitData = bunkerUri.split("?")
val remotePubkey = splitData[0].removePrefix("bunker://")
if (!Hex.isHex(remotePubkey)) throw Exception("Invalid pubkey in bunker uri")
if (splitData.size < 2) throw Exception("Missing query parameters in bunker uri")
val params = splitData[1].split("&")
val relays = mutableSetOf<NormalizedRelayUrl>()
var secret: String? = null