From ff4d704bde3fe0855d7dff19d7ac7cb33feec999 Mon Sep 17 00:00:00 2001 From: greenart7c3 Date: Mon, 11 Sep 2023 15:30:00 -0300 Subject: [PATCH] add support for private zaps --- .../vitorpamplona/amethyst/model/Account.kt | 47 ++++++-- .../amethyst/service/AmberUtils.kt | 22 ++-- .../EventNotificationConsumer.kt | 103 ++++++++++++------ .../events/LnZapPaymentResponseEvent.kt | 16 +++ .../quartz/events/LnZapRequestEvent.kt | 26 +++++ 5 files changed, 162 insertions(+), 52 deletions(-) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/model/Account.kt b/app/src/main/java/com/vitorpamplona/amethyst/model/Account.kt index 42cf78580..3e93e153d 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/model/Account.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/model/Account.kt @@ -461,6 +461,21 @@ class Account( AmberUtils.content ) } + + LnZapEvent.ZapType.PRIVATE -> { + val unsignedEvent = LnZapRequestEvent.createPrivateZap( + event, + userProfile().latestContactList?.relays()?.keys?.ifEmpty { null } + ?: localRelays.map { it.url }.toSet(), + keyPair.pubKey.toHexKey(), + pollOption, + message + ) + AmberUtils.content = "" + AmberUtils.openAmber(unsignedEvent) + if (AmberUtils.content.isBlank()) return null + return Event.fromJson(AmberUtils.content) as LnZapRequestEvent + } else -> null } } else { @@ -485,10 +500,14 @@ class Account( fun isNIP47Author(pubkeyHex: String?): Boolean { val privKey = zapPaymentRequest?.secret?.hexToByteArray() ?: keyPair.privKey - if (privKey == null) return false + if (privKey == null && !loginWithAmber) return false - val pubKey = CryptoUtils.pubkeyCreate(privKey).toHexKey() - return (pubKey == pubkeyHex) + if (privKey != null) { + val pubKey = CryptoUtils.pubkeyCreate(privKey).toHexKey() + return (pubKey == pubkeyHex) + } + + return (keyPair.pubKey.toHexKey() == pubkeyHex) } fun decryptZapPaymentResponseEvent(zapResponseEvent: LnZapPaymentResponseEvent): Response? { @@ -497,9 +516,14 @@ class Account( val privKey = myNip47.secret?.hexToByteArray() ?: keyPair.privKey val pubKey = myNip47.pubKeyHex.hexToByteArray() - if (privKey == null) return null + if (privKey == null && !loginWithAmber) return null - return zapResponseEvent.response(privKey, pubKey) + if (privKey != null) return zapResponseEvent.response(privKey, pubKey) + + Log.d("zaps", "decrypt with amber") + AmberUtils.decrypt(zapResponseEvent.content, pubKey.toHexKey(), zapResponseEvent.id) + if (AmberUtils.content.isBlank()) return null + return zapResponseEvent.response(AmberUtils.content) } fun calculateIfNoteWasZappedByAccount(zappedNote: Note?): Boolean { @@ -2546,12 +2570,21 @@ class Account( if (loginWithAmber && event is LnZapRequestEvent && event.isPrivateZap()) { val decryptedContent = AmberUtils.cachedDecryptedContent[event.id] if (decryptedContent != null) { - return Event.fromJson(decryptedContent) + return try { + Event.fromJson(decryptedContent) + } catch (e: Exception) { + null + } } AmberUtils.decryptZapEvent(event) if (AmberUtils.content.isBlank()) return null + if (AmberUtils.content == "Could not decrypt the message") return null AmberUtils.cachedDecryptedContent[event.id] = AmberUtils.content - return Event.fromJson(AmberUtils.content) + return try { + Event.fromJson(AmberUtils.content) + } catch (e: Exception) { + null + } } return if (event is LnZapRequestEvent && loggedInPrivateKey != null && event.isPrivateZap()) { diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/AmberUtils.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/AmberUtils.kt index b2205e46e..582bd6230 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/AmberUtils.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/AmberUtils.kt @@ -105,18 +105,16 @@ object AmberUtils { } fun decryptZapEvent(event: LnZapRequestEvent) { - if (content.isBlank()) { - isActivityRunning = true - openAmber( - event.toJson(), - SignerType.DECRYPT_ZAP_EVENT, - IntentUtils.activityResultLauncher, - event.pubKey, - event.id - ) - while (isActivityRunning) { - // do nothing - } + isActivityRunning = true + openAmber( + event.toJson(), + SignerType.DECRYPT_ZAP_EVENT, + IntentUtils.activityResultLauncher, + event.pubKey, + event.id + ) + while (isActivityRunning) { + // do nothing } } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/notifications/EventNotificationConsumer.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/notifications/EventNotificationConsumer.kt index 892935d28..4420c7c3c 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/notifications/EventNotificationConsumer.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/notifications/EventNotificationConsumer.kt @@ -34,27 +34,57 @@ class EventNotificationConsumer(private val applicationContext: Context) { // Test with all logged in accounts LocalPreferences.allSavedAccounts().forEach { val acc = LocalPreferences.loadFromEncryptedStorage(it.npub) - if (acc != null && acc.keyPair.privKey != null) { + if (acc != null && (acc.keyPair.privKey != null || acc.loginWithAmber)) { consumeIfMatchesAccount(event, acc) } } } private suspend fun consumeIfMatchesAccount(pushWrappedEvent: GiftWrapEvent, account: Account) { - val key = account.keyPair.privKey ?: return - pushWrappedEvent.unwrap(key)?.let { notificationEvent -> - if (!LocalCache.justVerify(notificationEvent)) return // invalid event - if (LocalCache.notes[notificationEvent.id] != null) return // already processed + val key = account.keyPair.privKey + if (account.loginWithAmber) { + var cached = AmberUtils.cachedDecryptedContent[pushWrappedEvent.id] + if (cached == null) { + AmberUtils.content = "" + AmberUtils.decrypt( + pushWrappedEvent.content, + pushWrappedEvent.pubKey, + pushWrappedEvent.id, + SignerType.NIP44_DECRYPT + ) + cached = AmberUtils.cachedDecryptedContent[pushWrappedEvent.id] ?: "" + } + pushWrappedEvent.unwrap(cached)?.let { notificationEvent -> + if (!LocalCache.justVerify(notificationEvent)) return // invalid event + if (LocalCache.notes[notificationEvent.id] != null) return // already processed - LocalCache.justConsume(notificationEvent, null) + LocalCache.justConsume(notificationEvent, null) - unwrapAndConsume(notificationEvent, account)?.let { innerEvent -> - if (innerEvent is PrivateDmEvent) { - notify(innerEvent, account) - } else if (innerEvent is LnZapEvent) { - notify(innerEvent, account) - } else if (innerEvent is ChatMessageEvent) { - notify(innerEvent, account) + unwrapAndConsume(notificationEvent, account)?.let { innerEvent -> + if (innerEvent is PrivateDmEvent) { + notify(innerEvent, account) + } else if (innerEvent is LnZapEvent) { + notify(innerEvent, account) + } else if (innerEvent is ChatMessageEvent) { + notify(innerEvent, account) + } + } + } + } else if (key != null) { + pushWrappedEvent.unwrap(key)?.let { notificationEvent -> + if (!LocalCache.justVerify(notificationEvent)) return // invalid event + if (LocalCache.notes[notificationEvent.id] != null) return // already processed + + LocalCache.justConsume(notificationEvent, null) + + unwrapAndConsume(notificationEvent, account)?.let { innerEvent -> + if (innerEvent is PrivateDmEvent) { + notify(innerEvent, account) + } else if (innerEvent is LnZapEvent) { + notify(innerEvent, account) + } else if (innerEvent is ChatMessageEvent) { + notify(innerEvent, account) + } } } } @@ -71,16 +101,20 @@ class EventNotificationConsumer(private val applicationContext: Context) { unwrapAndConsume(it, account) } } else if (account.loginWithAmber) { - AmberUtils.content = "" - AmberUtils.decrypt( - event.content, - event.pubKey, - event.id, - SignerType.NIP44_DECRYPT - ) - val decryptedContent = AmberUtils.cachedDecryptedContent[event.id] ?: "" - if (decryptedContent.isNotBlank()) { - event.cachedGift(account.keyPair.pubKey, decryptedContent)?.let { + var cached = AmberUtils.cachedDecryptedContent[event.id] + if (cached == null) { + AmberUtils.content = "" + AmberUtils.decrypt( + event.content, + event.pubKey, + event.id, + SignerType.NIP44_DECRYPT + ) + cached = AmberUtils.cachedDecryptedContent[event.id] ?: "" + } + + if (cached.isNotBlank()) { + event.cachedGift(account.keyPair.pubKey, cached)?.let { LocalCache.justConsume(it, null) it } @@ -100,16 +134,19 @@ class EventNotificationConsumer(private val applicationContext: Context) { it } } else if (account.loginWithAmber) { - AmberUtils.content = "" - AmberUtils.decrypt( - event.content, - event.pubKey, - event.id, - SignerType.NIP44_DECRYPT - ) - val decryptedContent = AmberUtils.cachedDecryptedContent[event.id] ?: "" - if (decryptedContent.isNotBlank()) { - event.cachedGossip(account.keyPair.pubKey, decryptedContent)?.let { + var cached = AmberUtils.cachedDecryptedContent[event.id] + if (cached == null) { + AmberUtils.content = "" + AmberUtils.decrypt( + event.content, + event.pubKey, + event.id, + SignerType.NIP44_DECRYPT + ) + cached = AmberUtils.cachedDecryptedContent[event.id] ?: "" + } + if (cached.isNotBlank()) { + event.cachedGossip(account.keyPair.pubKey, cached)?.let { LocalCache.justConsume(it, null) it } diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/events/LnZapPaymentResponseEvent.kt b/quartz/src/main/java/com/vitorpamplona/quartz/events/LnZapPaymentResponseEvent.kt index 9055bbebd..fa6953e6e 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/events/LnZapPaymentResponseEvent.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/events/LnZapPaymentResponseEvent.kt @@ -61,6 +61,22 @@ class LnZapPaymentResponseEvent( } } + fun response(decryptedContent: String): Response? { + if (response != null) response + + return try { + if (content.isNotEmpty()) { + response = mapper.readValue(decryptedContent, Response::class.java) + response + } else { + null + } + } catch (e: Exception) { + Log.w("LnZapPaymentResponseEvent", "Can't parse content as a payment response: $content", e) + null + } + } + companion object { const val kind = 23195 } diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/events/LnZapRequestEvent.kt b/quartz/src/main/java/com/vitorpamplona/quartz/events/LnZapRequestEvent.kt index 58696cdd4..974c37183 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/events/LnZapRequestEvent.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/events/LnZapRequestEvent.kt @@ -123,6 +123,32 @@ class LnZapRequestEvent( return LnZapRequestEvent(id.toHexKey(), pubKey, createdAt, tags, message, "") } + fun createPrivateZap( + originalNote: EventInterface, + relays: Set, + pubKey: HexKey, + pollOption: Int?, + message: String, + createdAt: Long = TimeUtils.now() + ): LnZapRequestEvent { + val content = message + var tags = listOf( + listOf("e", originalNote.id()), + listOf("p", originalNote.pubKey()), + listOf("relays") + relays + ) + if (originalNote is AddressableEvent) { + tags = tags + listOf(listOf("a", originalNote.address().toTag())) + } + if (pollOption != null && pollOption >= 0) { + tags = tags + listOf(listOf(POLL_OPTION, pollOption.toString())) + } + + tags = tags + listOf(listOf("anon", "")) + + return LnZapRequestEvent("", pubKey, createdAt, tags, content, "") + } + fun createAnonymous( originalNote: EventInterface, relays: Set,