From c6b09c4b53a88bcd5e022d6fc78438ee5bc964ba Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 10 Jun 2026 23:41:26 +0000 Subject: [PATCH] fix: anonymous profile zaps were encrypted as private zaps; nutzap chips reply on long-press MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The user-only LnZapRequestEvent.create overload marked ANONYMOUS requests with a blank-valued anon tag, which the signer treats as an unsigned private zap: the message was encrypted to the recipient under the throwaway key instead of staying public. Use the valueless anon tag, as the event-targeted overload already does. Adds a regression test. Also carries the nutzap note into the notification gallery chips so the long-press reply-to-zap gesture works for NIP-61 nutzaps too — no extra tagging needed there since nutzaps are signed by the sender. https://claude.ai/code/session_01LM3KTECMMAdNBHZfs1dANa --- .../amethyst/ui/note/MultiSetCompose.kt | 1 + .../amethyst/ui/note/NutzapUserSetCompose.kt | 1 + .../quartz/nip57Zaps/LnZapRequestEvent.kt | 5 +++- .../nip57Zaps/LnZapRequestAnonTagTest.kt | 24 +++++++++++++++++++ 4 files changed, 30 insertions(+), 1 deletion(-) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/MultiSetCompose.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/MultiSetCompose.kt index 4e0d85cd0b..90151c26be 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/MultiSetCompose.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/MultiSetCompose.kt @@ -373,6 +373,7 @@ fun RenderNutzapGallery( user = note.author, comment = event?.content?.ifBlank { null }, amount = showAmount(java.math.BigDecimal(sats)), + zapNote = note, ) }.toImmutableList() } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/NutzapUserSetCompose.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/NutzapUserSetCompose.kt index 393c8e86ae..c40ef4da95 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/NutzapUserSetCompose.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/NutzapUserSetCompose.kt @@ -84,6 +84,7 @@ fun NutzapUserSetCompose( user = note.author, comment = event?.content?.ifBlank { null }, amount = showAmount(java.math.BigDecimal(sats)), + zapNote = note, ) }.toImmutableList() } diff --git a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip57Zaps/LnZapRequestEvent.kt b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip57Zaps/LnZapRequestEvent.kt index 4af0b077ca..b334dd73ab 100644 --- a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip57Zaps/LnZapRequestEvent.kt +++ b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip57Zaps/LnZapRequestEvent.kt @@ -176,7 +176,10 @@ class LnZapRequestEvent( } LnZapEvent.ZapType.ANONYMOUS -> { - tags += arrayOf(arrayOf("anon", "")) + // Valueless `anon` tag: a blank-valued one (`["anon", ""]`) is the + // marker for an *unsigned private* zap and would make the throwaway + // signer encrypt the message instead of keeping it public. + tags += arrayOf(arrayOf("anon")) NostrSignerInternal(KeyPair()).sign(createdAt, KIND, tags, message) } diff --git a/quartz/src/commonTest/kotlin/com/vitorpamplona/quartz/nip57Zaps/LnZapRequestAnonTagTest.kt b/quartz/src/commonTest/kotlin/com/vitorpamplona/quartz/nip57Zaps/LnZapRequestAnonTagTest.kt index 2d0dc8a575..3de89a1d2d 100644 --- a/quartz/src/commonTest/kotlin/com/vitorpamplona/quartz/nip57Zaps/LnZapRequestAnonTagTest.kt +++ b/quartz/src/commonTest/kotlin/com/vitorpamplona/quartz/nip57Zaps/LnZapRequestAnonTagTest.kt @@ -88,6 +88,30 @@ class LnZapRequestAnonTagTest { assertFalse(zapRequest.pubKey == signer.pubKey, "anonymous zaps must be signed by a throwaway key") } + /** + * Regression test for the user-only (profile zap) overload: its ANONYMOUS + * branch used a blank-valued `anon` tag, which [NostrSignerInternal] treats + * as an unsigned *private* zap and encrypts — silently turning a public + * anonymous comment into an encrypted one nobody but the recipient can read. + */ + @Test + fun `anonymous profile zap request keeps the message public`() = + runTest { + val zapRequest = + LnZapRequestEvent.create( + userHex = receiverPubKey, + relays = relays, + signer = signer, + message = "great work", + zapType = LnZapEvent.ZapType.ANONYMOUS, + ) + + assertTrue(zapRequest.hasAnonTag()) + assertFalse(zapRequest.isPrivateZap(), "anonymous zaps must not be encrypted as private zaps") + assertEquals("great work", zapRequest.content) + assertFalse(zapRequest.pubKey == signer.pubKey, "anonymous zaps must be signed by a throwaway key") + } + @Test fun `private zap request has anon tag, is private, and hides the sender key`() = runTest {