From 97d56d61d1a0223543f1d6cf871d55afb43a0ed2 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Sat, 6 May 2023 09:46:25 -0400 Subject: [PATCH] Caching Private Zap decryption --- .../vitorpamplona/amethyst/PrivateZapTests.kt | 5 +- .../vitorpamplona/amethyst/model/Account.kt | 4 +- .../service/model/LnZapPaymentRequestEvent.kt | 12 ++- .../model/LnZapPaymentResponseEvent.kt | 27 ++++-- .../service/model/LnZapRequestEvent.kt | 85 ++++++------------- 5 files changed, 60 insertions(+), 73 deletions(-) diff --git a/app/src/androidTest/java/com/vitorpamplona/amethyst/PrivateZapTests.kt b/app/src/androidTest/java/com/vitorpamplona/amethyst/PrivateZapTests.kt index c131a8af3..3b35cdad1 100644 --- a/app/src/androidTest/java/com/vitorpamplona/amethyst/PrivateZapTests.kt +++ b/app/src/androidTest/java/com/vitorpamplona/amethyst/PrivateZapTests.kt @@ -78,7 +78,7 @@ class PrivateZapTests { if (recepientPK != null && recepientPost != null) { val privateKey = createEncryptionPrivateKey(loggedIn.toHexKey(), recepientPost, privateZapRequest.createdAt) val decodedPrivateZap = - LnZapRequestEvent.checkForPrivateZap(privateZapRequest, privateKey, recepientPK) + privateZapRequest.getPrivateZapEvent(privateKey, recepientPK) println(decodedPrivateZap?.toJson()) assertNotNull(decodedPrivateZap) @@ -127,8 +127,7 @@ class PrivateZapTests { if (recepientPK != null && recepientPost != null) { val privateKey = createEncryptionPrivateKey(loggedIn.toHexKey(), recepientPost, privateZapRequest.createdAt) - val decodedPrivateZap = - LnZapRequestEvent.checkForPrivateZap(privateZapRequest, privateKey, recepientPK) + val decodedPrivateZap = privateZapRequest.getPrivateZapEvent(privateKey, recepientPK) println(decodedPrivateZap?.toJson()) assertNotNull(decodedPrivateZap) 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 b15189cec..2cb3cdf18 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/model/Account.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/model/Account.kt @@ -847,7 +847,7 @@ class Account( val privateKeyToUse = loggedInPrivateKey val pubkeyToUse = event.pubKey - LnZapRequestEvent.checkForPrivateZap(event, privateKeyToUse, pubkeyToUse) + event.getPrivateZapEvent(privateKeyToUse, pubkeyToUse) } else { // if the sender is logged in, these are the params val altPubkeyToUse = recipientPK @@ -868,7 +868,7 @@ class Account( } if (altPrivateKeyToUse != null && altPubkeyToUse != null) { - val result = LnZapRequestEvent.checkForPrivateZap(event, altPrivateKeyToUse, altPubkeyToUse) + val result = event.getPrivateZapEvent(altPrivateKeyToUse, altPubkeyToUse) if (result == null) { Log.w("Private ZAP Decrypt", "Fail to decrypt Zap from ${note.author?.toBestDisplayName()} ${note.idNote()}") } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/model/LnZapPaymentRequestEvent.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/model/LnZapPaymentRequestEvent.kt index 63464b5d9..4946fb4c9 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/model/LnZapPaymentRequestEvent.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/model/LnZapPaymentRequestEvent.kt @@ -21,9 +21,17 @@ class LnZapPaymentRequestEvent( sig: HexKey ) : Event(id, pubKey, createdAt, kind, tags, content, sig) { + // Once one of an app user decrypts the payment, all users else can see it. + @Transient + private var lnInvoice: String? = null + fun walletServicePubKey() = tags.firstOrNull() { it.size > 1 && it[0] == "p" }?.get(1) fun lnInvoice(privKey: ByteArray, pubkey: ByteArray): String? { + if (lnInvoice != null) { + return lnInvoice + } + return try { val sharedSecret = Utils.getSharedSecret(privKey, pubkey) @@ -31,7 +39,9 @@ class LnZapPaymentRequestEvent( val payInvoiceMethod = gson.fromJson(jsonText, Request::class.java) - return (payInvoiceMethod as? PayInvoiceMethod)?.params?.invoice + lnInvoice = (payInvoiceMethod as? PayInvoiceMethod)?.params?.invoice + + return lnInvoice } catch (e: Exception) { Log.w("BookmarkList", "Error decrypting the message ${e.message}") null diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/model/LnZapPaymentResponseEvent.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/model/LnZapPaymentResponseEvent.kt index 42f71f7c5..5d8ec2e95 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/model/LnZapPaymentResponseEvent.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/model/LnZapPaymentResponseEvent.kt @@ -19,10 +19,14 @@ class LnZapPaymentResponseEvent( sig: HexKey ) : Event(id, pubKey, createdAt, kind, tags, content, sig) { + // Once one of an app user decrypts the payment, all users else can see it. + @Transient + private var response: Response? = null + fun requestAuthor() = tags.firstOrNull() { it.size > 1 && it[0] == "p" }?.get(1) fun requestId() = tags.firstOrNull() { it.size > 1 && it[0] == "e" }?.get(1) - fun decrypt(privKey: ByteArray, pubKey: ByteArray): String? { + private fun decrypt(privKey: ByteArray, pubKey: ByteArray): String? { return try { val sharedSecret = Utils.getSharedSecret(privKey, pubKey) @@ -39,16 +43,21 @@ class LnZapPaymentResponseEvent( } } - fun response(privKey: ByteArray, pubKey: ByteArray): Response? = try { - if (content.isNotEmpty()) { - val decrypted = decrypt(privKey, pubKey) - gson.fromJson(decrypted, Response::class.java) - } else { + fun response(privKey: ByteArray, pubKey: ByteArray): Response? { + if (response != null) response + + return try { + if (content.isNotEmpty()) { + val decrypted = decrypt(privKey, pubKey) + response = gson.fromJson(decrypted, Response::class.java) + response + } else { + null + } + } catch (e: Exception) { + Log.w("LnZapPaymentResponseEvent", "Can't parse content as a payment response: $content", e) null } - } catch (e: Exception) { - Log.w("LnZapPaymentResponseEvent", "Can't parse content as a payment response: $content", e) - null } companion object { diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/model/LnZapRequestEvent.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/model/LnZapRequestEvent.kt index 44cfb5213..8586849a6 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/model/LnZapRequestEvent.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/model/LnZapRequestEvent.kt @@ -20,12 +20,37 @@ class LnZapRequestEvent( sig: HexKey ) : Event(id, pubKey, createdAt, kind, tags, content, sig) { + @Transient + private var privateZapEvent: Event? = null + fun zappedPost() = tags.filter { it.size > 1 && it[0] == "e" }.map { it[1] } fun zappedAuthor() = tags.filter { it.size > 1 && it[0] == "p" }.map { it[1] } fun isPrivateZap() = tags.any { t -> t.size >= 2 && t[0] == "anon" && t[1].isNotBlank() } + fun getPrivateZapEvent(loggedInUserPrivKey: ByteArray, pubKey: HexKey): Event? { + if (privateZapEvent != null) return privateZapEvent + + val anonTag = tags.firstOrNull { t -> t.size >= 2 && t[0] == "anon" } + if (anonTag != null) { + val encnote = anonTag[1] + if (encnote.isNotBlank()) { + try { + val note = decryptPrivateZapMessage(encnote, loggedInUserPrivKey, pubKey.hexToByteArray()) + val decryptedEvent = fromJson(note) + if (decryptedEvent.kind == 9733) { + privateZapEvent = decryptedEvent + return privateZapEvent + } + } catch (e: Exception) { + e.printStackTrace() + } + } + } + return null + } + companion object { const val kind = 9734 @@ -109,7 +134,7 @@ class LnZapRequestEvent( return sha256.digest(strbyte) } - fun encryptPrivateZapMessage(msg: String, privkey: ByteArray, pubkey: ByteArray): String { + private fun encryptPrivateZapMessage(msg: String, privkey: ByteArray, pubkey: ByteArray): String { var sharedSecret = Utils.getSharedSecret(privkey, pubkey) val iv = ByteArray(16) SecureRandom().nextBytes(iv) @@ -128,7 +153,7 @@ class LnZapRequestEvent( return encryptedMsgBech32 + "_" + ivBech32 } - fun decryptPrivateZapMessage(msg: String, privkey: ByteArray, pubkey: ByteArray): String { + private fun decryptPrivateZapMessage(msg: String, privkey: ByteArray, pubkey: ByteArray): String { var sharedSecret = Utils.getSharedSecret(privkey, pubkey) if (sharedSecret.size != 16 && sharedSecret.size != 32) { throw IllegalArgumentException("Invalid shared secret size") @@ -150,61 +175,5 @@ class LnZapRequestEvent( throw IllegalArgumentException("Bad padding: ${ex.message}") } } - - fun checkForPrivateZap(zapRequest: LnZapRequestEvent, loggedInUserPrivKey: ByteArray, pubKey: HexKey): Event? { - val anonTag = zapRequest.tags.firstOrNull { t -> t.size >= 2 && t[0] == "anon" } - if (anonTag != null) { - val encnote = anonTag[1] - if (encnote.isNotBlank()) { - try { - val note = decryptPrivateZapMessage(encnote, loggedInUserPrivKey, pubKey.hexToByteArray()) - val decryptedEvent = fromJson(note) - if (decryptedEvent.kind == 9733) { - return decryptedEvent - } - } catch (e: Exception) { - e.printStackTrace() - } - } - } - return null - } } } -/* -{ - "pubkey": "32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245", - "content": "", - "id": "d9cc14d50fcb8c27539aacf776882942c1a11ea4472f8cdec1dea82fab66279d", - "created_at": 1674164539, - "sig": "77127f636577e9029276be060332ea565deaf89ff215a494ccff16ae3f757065e2bc59b2e8c113dd407917a010b3abd36c8d7ad84c0e3ab7dab3a0b0caa9835d", - "kind": 9734, - "tags": [ - [ - "e", - "3624762a1274dd9636e0c552b53086d70bc88c165bc4dc0f9e836a1eaf86c3b8" - ], - [ - "p", - "32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245" - ], - [ - "relays", - "wss://relay.damus.io", - "wss://nostr-relay.wlvs.space", - "wss://nostr.fmt.wiz.biz", - "wss://relay.nostr.bg", - "wss://nostr.oxtr.dev", - "wss://nostr.v0l.io", - "wss://brb.io", - "wss://nostr.bitcoiner.social", - "ws://monad.jb55.com:8080", - "wss://relay.snort.social" - ], - [ - "poll_option", "n" - ] - ], - "ots": // TODO -} -*/