From d79b93327412f45e37b97d4fdabd790a564e4d21 Mon Sep 17 00:00:00 2001 From: davotoula Date: Tue, 15 Sep 2026 15:21:39 +0200 Subject: [PATCH] refactor(nwc): validate zapped a/e tags with the existing Address and HexKey helpers --- .../rpc/NwcTransactionMetadata.kt | 11 +++-- .../NwcOutgoingMetadataTest.kt | 48 ++++++------------- 2 files changed, 20 insertions(+), 39 deletions(-) diff --git a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip47WalletConnect/rpc/NwcTransactionMetadata.kt b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip47WalletConnect/rpc/NwcTransactionMetadata.kt index 46770c9698..7d90f3b256 100644 --- a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip47WalletConnect/rpc/NwcTransactionMetadata.kt +++ b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip47WalletConnect/rpc/NwcTransactionMetadata.kt @@ -20,10 +20,11 @@ */ package com.vitorpamplona.quartz.nip47WalletConnect.rpc +import com.vitorpamplona.quartz.nip01Core.core.Address import com.vitorpamplona.quartz.nip01Core.core.Event import com.vitorpamplona.quartz.nip01Core.core.RawJson +import com.vitorpamplona.quartz.nip01Core.core.isValid import com.vitorpamplona.quartz.nip19Bech32.decodePublicKeyAsHexOrNull -import com.vitorpamplona.quartz.utils.Hex class NwcTransactionMetadata( val comment: String?, @@ -45,8 +46,8 @@ class NwcTransactionMetadata( val pubkeyHex: String?, val recipientPubkeyHex: String?, val content: String?, - val zappedEventId: String? = null, - val zappedAddress: String? = null, + val zappedEventId: String?, + val zappedAddress: String?, ) fun senderPubkeyHex(): String? = nostr?.pubkeyHex ?: payerData?.pubkey?.let { decodePublicKeyAsHexOrNull(it) } @@ -109,8 +110,8 @@ class NwcTransactionMetadata( val value = tagList[1] as? String ?: return@forEach when (tagList[0]) { "p" -> if (recipientHex == null) recipientHex = value - "e" -> if (zappedEventId == null && value.length == 64 && Hex.isHex64(value)) zappedEventId = value - "a" -> if (zappedAddress == null) zappedAddress = value + "e" -> if (zappedEventId == null && value.isValid()) zappedEventId = value + "a" -> if (zappedAddress == null) zappedAddress = Address.parse(value)?.toValue() } } diff --git a/quartz/src/commonTest/kotlin/com/vitorpamplona/quartz/nip47WalletConnect/NwcOutgoingMetadataTest.kt b/quartz/src/commonTest/kotlin/com/vitorpamplona/quartz/nip47WalletConnect/NwcOutgoingMetadataTest.kt index 5b26d05454..8bddfc1446 100644 --- a/quartz/src/commonTest/kotlin/com/vitorpamplona/quartz/nip47WalletConnect/NwcOutgoingMetadataTest.kt +++ b/quartz/src/commonTest/kotlin/com/vitorpamplona/quartz/nip47WalletConnect/NwcOutgoingMetadataTest.kt @@ -254,23 +254,16 @@ class NwcOutgoingMetadataTest { assertEquals(recipientHex, parsed.recipientPubkeyHex(), "the p tag is still read with the e tag in front of it") } + private fun parseTags(vararg tags: List) = assertNotNull(NwcTransactionMetadata.parse(mapOf("nostr" to mapOf("tags" to tags.toList())))) + @Test fun aProfileZapHasNoNote() { - val parsed = - assertNotNull( - NwcTransactionMetadata.parse(mapOf("nostr" to mapOf("tags" to listOf(listOf("p", recipientHex))))), - ) - assertNull(parsed.zappedNoteId()) + assertNull(parseTags(listOf("p", recipientHex)).zappedNoteId()) } @Test fun tagOrderDoesNotMatter() { - val parsed = - assertNotNull( - NwcTransactionMetadata.parse( - mapOf("nostr" to mapOf("tags" to listOf(listOf("e", noteHex), listOf("p", recipientHex)))), - ), - ) + val parsed = parseTags(listOf("e", noteHex), listOf("p", recipientHex)) assertEquals(recipientHex, parsed.recipientPubkeyHex()) assertEquals(noteHex, parsed.zappedNoteId()) } @@ -278,33 +271,20 @@ class NwcOutgoingMetadataTest { @Test fun anAddressableZapPrefersTheAddress() { val address = "30023:$recipientHex:my-article" - val parsed = - assertNotNull( - NwcTransactionMetadata.parse( - mapOf("nostr" to mapOf("tags" to listOf(listOf("e", noteHex), listOf("a", address), listOf("p", recipientHex)))), - ), - ) - assertEquals(address, parsed.zappedNoteId()) + assertEquals(address, parseTags(listOf("e", noteHex), listOf("a", address), listOf("p", recipientHex)).zappedNoteId()) + } + + @Test + fun aMalformedAddressFallsBackToTheEventId() { + assertEquals(noteHex, parseTags(listOf("e", noteHex), listOf("a", "not-an-address"), listOf("p", recipientHex)).zappedNoteId()) } @Test fun aMalformedEventIdIsIgnored() { - val parsed = - assertNotNull( - NwcTransactionMetadata.parse( - mapOf("nostr" to mapOf("tags" to listOf(listOf("e", "not-a-note"), listOf("p", recipientHex)))), - ), - ) - assertNull(parsed.zappedNoteId()) - - // isHex64 checks only the first 64 chars; a longer id must not slip through. - val tooLong = - assertNotNull( - NwcTransactionMetadata.parse( - mapOf("nostr" to mapOf("tags" to listOf(listOf("e", noteHex + "ff"), listOf("p", recipientHex)))), - ), - ) - assertNull(tooLong.zappedNoteId()) + // "noteHex + ff": the id check must bound the length, not just scan 64 hex chars. + listOf("not-a-note", noteHex + "ff").forEach { bad -> + assertNull(parseTags(listOf("e", bad), listOf("p", recipientHex)).zappedNoteId(), bad) + } } @Test