refactor(commons): share NIP-59 gift-wrap+seal unwrap helper
Three call sites did the same two-layer peel — kind:1059 → (nip44 decrypt) → kind:13 sealed rumor → (nip44 decrypt) → rumor Extracted as `GiftWrapEvent.unwrapAndUnsealOrNull(signer)` in commons/relayClient/nip17Dm/, and collapsed: - commons/marmot/MarmotIngest.kt — was an inline `when` switch; now a one-liner. Behaviour unchanged (still passes through if the inner isn't a seal, matching the previous defensive `else -> inner` branch). - desktopApp/Main.kt — dropped the nested unwrap/unseal block in the kind:1059 case of the LocalCache dispatcher. - cli/DmCommands.kt — replaced the manual chain in `decryptChatMessages`. Android's DecryptAndIndexProcessor keeps its two separate handlers (processNewGiftWrap / processNewSealedRumor) because the LocalCache pipeline re-enters on each peel — that's a different shape and not touched. No behaviour change on any platform.
This commit is contained in:
+7
-14
@@ -20,6 +20,7 @@
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.commons.marmot
|
||||
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.nip17Dm.unwrapAndUnsealOrNull
|
||||
import com.vitorpamplona.quartz.marmot.GroupEventResult
|
||||
import com.vitorpamplona.quartz.marmot.MarmotInboundProcessor
|
||||
import com.vitorpamplona.quartz.marmot.WelcomeResult
|
||||
@@ -27,7 +28,6 @@ import com.vitorpamplona.quartz.marmot.mip02Welcome.WelcomeEvent
|
||||
import com.vitorpamplona.quartz.marmot.mip03GroupMessages.GroupEvent
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import com.vitorpamplona.quartz.nip59Giftwrap.seals.SealedRumorEvent
|
||||
import com.vitorpamplona.quartz.nip59Giftwrap.wraps.GiftWrapEvent
|
||||
|
||||
/**
|
||||
@@ -96,19 +96,12 @@ suspend fun MarmotManager.ingest(event: Event): MarmotIngestResult =
|
||||
|
||||
private suspend fun MarmotManager.ingestGiftWrap(wrap: GiftWrapEvent): MarmotIngestResult =
|
||||
try {
|
||||
// NIP-59 wraps contain TWO encryption layers:
|
||||
// kind:1059 gift wrap → kind:13 sealed rumor → the rumor itself.
|
||||
// `GiftWrapEvent.unwrapOrNull` only peels the outer layer; when the
|
||||
// result is a [SealedRumorEvent] we must unseal it to reach the
|
||||
// kind:444 Welcome rumor. The old code checked `isWelcomeEvent` on
|
||||
// the seal (kind:13) and always took the Ignored branch, which is
|
||||
// why every inbound Welcome was silently dropped by the CLI and by
|
||||
// any non-Amethyst consumer.
|
||||
val rumor =
|
||||
when (val inner = wrap.unwrapOrNull(signer) ?: return MarmotIngestResult.Ignored) {
|
||||
is SealedRumorEvent -> inner.unsealOrNull(signer) ?: return MarmotIngestResult.Ignored
|
||||
else -> inner
|
||||
}
|
||||
// NIP-59 wraps carry two encryption layers (kind:1059 → kind:13 → rumor).
|
||||
// [unwrapAndUnsealOrNull] peels both so we land directly on the inner
|
||||
// kind:444 Welcome rumor. Checking `isWelcomeEvent` on the seal itself
|
||||
// (the old bug) always took the Ignored branch and silently dropped
|
||||
// every inbound Welcome.
|
||||
val rumor = wrap.unwrapAndUnsealOrNull(signer) ?: return MarmotIngestResult.Ignored
|
||||
if (!MarmotInboundProcessor.isWelcomeEvent(rumor) || rumor !is WelcomeEvent) {
|
||||
return MarmotIngestResult.Ignored
|
||||
}
|
||||
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Vitor Pamplona
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.commons.relayClient.nip17Dm
|
||||
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
import com.vitorpamplona.quartz.nip01Core.signers.NostrSigner
|
||||
import com.vitorpamplona.quartz.nip59Giftwrap.seals.SealedRumorEvent
|
||||
import com.vitorpamplona.quartz.nip59Giftwrap.wraps.GiftWrapEvent
|
||||
|
||||
/**
|
||||
* Fully decrypt a NIP-59 gift wrap down to the inner rumor.
|
||||
*
|
||||
* A gift wrap carries two encryption layers:
|
||||
* kind:1059 [GiftWrapEvent] → kind:13 [SealedRumorEvent] → the rumor.
|
||||
*
|
||||
* [GiftWrapEvent.unwrapOrNull] peels only the outer layer. Every non-Android
|
||||
* consumer that looks at the rumor directly (the CLI's `dm list`, the desktop
|
||||
* chat receive path, the Marmot welcome ingest in commons) needs both peels.
|
||||
*
|
||||
* If the inner content isn't a [SealedRumorEvent] (malformed, or a future
|
||||
* NIP-59 variant that wraps a rumor directly), the outer unwrap result is
|
||||
* returned as-is so callers can still route on kind. Either unwrap returning
|
||||
* null propagates as null.
|
||||
*/
|
||||
suspend fun GiftWrapEvent.unwrapAndUnsealOrNull(signer: NostrSigner): Event? {
|
||||
val inner = unwrapOrNull(signer) ?: return null
|
||||
return if (inner is SealedRumorEvent) inner.unsealOrNull(signer) else inner
|
||||
}
|
||||
Reference in New Issue
Block a user