From 5e6e2835ca0087c666c6a38b430230d1a126d27b Mon Sep 17 00:00:00 2001 From: Believethehype Date: Mon, 17 Apr 2023 20:10:36 +0200 Subject: [PATCH] Receiving and decoding private Zaps Private zaps get decoded correctly. TODO: Update events or at least notifications in Damus to replace anon sender with the one from decrypted message. Bonus: Show Content of the Zap Message --- .../amethyst/model/LocalCache.kt | 22 ++++++++++ .../amethyst/service/model/Event.kt | 4 +- .../amethyst/service/model/LnZapEvent.kt | 5 +-- .../service/model/LnZapEventInterface.kt | 1 - .../service/model/LnZapRequestEvent.kt | 41 ++++++++++--------- .../amethyst/ui/screen/CardFeedViewModel.kt | 10 ++--- 6 files changed, 51 insertions(+), 32 deletions(-) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt b/app/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt index cef63d463..be45ab09d 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt @@ -7,6 +7,7 @@ import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper import com.vitorpamplona.amethyst.service.model.* import com.vitorpamplona.amethyst.service.relays.Relay import com.vitorpamplona.amethyst.ui.components.BundledUpdate +import com.vitorpamplona.amethyst.ui.dal.NotificationFeedFilter import fr.acinq.secp256k1.Hex import kotlinx.coroutines.* import nostr.postr.toNpub @@ -622,6 +623,27 @@ object LocalCache { } } + fun checkPrivateZap(zaprequest: Event): Event { + var anonTag = zaprequest.tags.firstOrNull { t -> t.count() >= 2 && t[0] == "anon" } + if (anonTag != null && anonTag.size > 1) { + var encnote = anonTag?.elementAt(1) + if (encnote != null && encnote != "") { + try { + val loggedInUserHex = NotificationFeedFilter.account.loggedIn.privKey!! // Replace without Filter + var note = LnZapRequestEvent.decrypt_privatezap_message(encnote, loggedInUserHex, zaprequest.pubKey.toByteArray()) + var decryptedEvent = Event.fromJson(note) + if (decryptedEvent.kind == 9733) { + zaprequest.pubKey = decryptedEvent.pubKey + zaprequest.content = decryptedEvent.content + // return decryptedEvent + } + } catch (e: Exception) { + e.printStackTrace() + } + } + } + return zaprequest + } fun consume(event: LnZapRequestEvent) { val note = getOrCreateNote(event.id) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/model/Event.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/model/Event.kt index 818a2d3b2..b3e7dc182 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/model/Event.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/model/Event.kt @@ -15,11 +15,11 @@ import java.util.* open class Event( val id: HexKey, - @SerializedName("pubkey") val pubKey: HexKey, + @SerializedName("pubkey") var pubKey: HexKey, @SerializedName("created_at") val createdAt: Long, val kind: Int, val tags: List>, - val content: String, + var content: String, val sig: HexKey ) : EventInterface { override fun id(): HexKey = id diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/model/LnZapEvent.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/model/LnZapEvent.kt index 9f3921c67..eaa82509a 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/model/LnZapEvent.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/model/LnZapEvent.kt @@ -42,10 +42,9 @@ class LnZapEvent( null } } - override fun message(): String { - return message + override fun content(): String { + return content } - val message = content override fun containedPost(): Event? = try { description()?.ifBlank { null }?.let { diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/model/LnZapEventInterface.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/model/LnZapEventInterface.kt index b69e02781..b60fcd5b1 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/model/LnZapEventInterface.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/model/LnZapEventInterface.kt @@ -18,5 +18,4 @@ interface LnZapEventInterface : EventInterface { fun containedPost(): Event? - fun message(): String } 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 660e27d9a..a3b3b6285 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 @@ -6,6 +6,7 @@ import nostr.postr.Utils import java.nio.charset.Charset import java.security.SecureRandom import java.util.* +import javax.crypto.BadPaddingException import javax.crypto.Cipher import javax.crypto.spec.IvParameterSpec import javax.crypto.spec.SecretKeySpec @@ -52,21 +53,21 @@ class LnZapRequestEvent( privkey = Utils.privkeyCreate() pubKey = Utils.pubkeyCreate(privkey).toHexKey() } else if (zapType == LnZapEvent.ZapType.PRIVATE) { - var enc_prkey = create_private_key(privateKey, originalNote.id(), createdAt) + var encryptionprkey = create_private_key(privateKey.toHexKey(), originalNote.id(), createdAt) var noteJson = (create(privkey, 9733, listOf(tags[0], tags[1]), message)).toJson() - var privreq = encrypt_privatezap_message(noteJson, enc_prkey, originalNote.pubKey().toByteArray()) + var privreq = encrypt_privatezap_message(noteJson, encryptionprkey, originalNote.pubKey().toByteArray()) tags = tags + listOf(listOf("anon", privreq)) content = "" - privkey = enc_prkey - pubKey = Utils.pubkeyCreate(enc_prkey).toHexKey() + privkey = encryptionprkey + pubKey = Utils.pubkeyCreate(encryptionprkey).toHexKey() } val id = generateId(pubKey, createdAt, kind, tags, content) val sig = Utils.sign(id, privkey) return LnZapRequestEvent(id.toHexKey(), pubKey, createdAt, tags, content, sig.toHexKey()) } - fun create_private_key(privkey: ByteArray, id: String, createdAt: Long): ByteArray { - var str = privkey.toHexKey() + id + createdAt.toString() + fun create_private_key(privkey: String, id: String, createdAt: Long): ByteArray { + var str = privkey + id + createdAt.toString() var strbyte = str.toByteArray(Charset.forName("utf-8")) return sha256.digest(strbyte) } @@ -79,10 +80,10 @@ class LnZapRequestEvent( val keySpec = SecretKeySpec(sharedSecret, "AES") val ivSpec = IvParameterSpec(iv) - var utf8_message = msg.toByteArray(Charset.forName("utf-8")) + var utf8message = msg.toByteArray(Charset.forName("utf-8")) val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding") cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec) - val encryptedMsg = cipher.doFinal(utf8_message) + val encryptedMsg = cipher.doFinal(utf8message) val encryptedMsgBech32 = Bech32.encode("pzap", Bech32.eight2five(encryptedMsg), Bech32.Encoding.Bech32) val ivBech32 = Bech32.encode("iv", Bech32.eight2five(iv), Bech32.Encoding.Bech32) @@ -92,24 +93,26 @@ class LnZapRequestEvent( fun decrypt_privatezap_message(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") + } val parts = msg.split("_") + if (parts.size != 2) { + throw IllegalArgumentException("Invalid message format") + } val iv = parts[1].run { Bech32.decode(this) } val encryptedMsg = parts.first().run { Bech32.decode(this) } val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding") cipher.init(Cipher.DECRYPT_MODE, SecretKeySpec(sharedSecret, "AES"), IvParameterSpec(Bech32.five2eight(iv.second, 0))) - return String(cipher.doFinal(Bech32.five2eight(encryptedMsg.second, 0))) - } - fun test_decrypt(privateKey: ByteArray, event: Event): String { - val anonTag = event.tags.firstOrNull { t -> t.count() >= 2 && t[0] == "anon" } - var encnote = anonTag?.elementAt(1) - if (encnote != null) { - var enc_prkey2 = create_private_key(privateKey, event.id(), event.createdAt) - return decrypt_privatezap_message(encnote, enc_prkey2, event.pubKey().toByteArray()) - } else { - return "" + try { + val decryptedMsgBytes = cipher.doFinal(Bech32.five2eight(encryptedMsg.second, 0)) + return String(decryptedMsgBytes) + } catch (ex: BadPaddingException) { + throw IllegalArgumentException("Bad padding") } } + fun create( userHex: String, relays: Set, @@ -130,7 +133,7 @@ class LnZapRequestEvent( pubKey = Utils.pubkeyCreate(privkey).toHexKey() tags = tags + listOf(listOf("anon", "")) } else if (zapType == LnZapEvent.ZapType.PRIVATE) { - var enc_prkey = create_private_key(privateKey, userHex, createdAt) + var enc_prkey = create_private_key(privateKey.toHexKey(), userHex, createdAt) var noteJson = (create(privkey, 9733, listOf(tags[0], tags[1]), message)).toJson() var privreq = encrypt_privatezap_message(noteJson, enc_prkey, userHex.toByteArray()) tags = tags + listOf(listOf("anon", privreq)) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/CardFeedViewModel.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/CardFeedViewModel.kt index 3c83d0945..5cb55bb81 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/CardFeedViewModel.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/CardFeedViewModel.kt @@ -8,13 +8,7 @@ import com.vitorpamplona.amethyst.model.LocalCache import com.vitorpamplona.amethyst.model.LocalCacheState import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.model.User -import com.vitorpamplona.amethyst.service.model.BadgeAwardEvent -import com.vitorpamplona.amethyst.service.model.ChannelCreateEvent -import com.vitorpamplona.amethyst.service.model.ChannelMetadataEvent -import com.vitorpamplona.amethyst.service.model.LnZapEvent -import com.vitorpamplona.amethyst.service.model.PrivateDmEvent -import com.vitorpamplona.amethyst.service.model.ReactionEvent -import com.vitorpamplona.amethyst.service.model.RepostEvent +import com.vitorpamplona.amethyst.service.model.* import com.vitorpamplona.amethyst.ui.components.BundledUpdate import com.vitorpamplona.amethyst.ui.dal.FeedFilter import com.vitorpamplona.amethyst.ui.dal.NotificationFeedFilter @@ -88,6 +82,8 @@ open class CardFeedViewModel(val dataSource: FeedFilter) : ViewModel() { if (zappedPost != null) { val zapRequest = zappedPost.zaps.filter { it.value == zapEvent }.keys.firstOrNull() if (zapRequest != null) { + // var newZapRequestEvent = LocalCache.checkPrivateZap(zapRequest.event as Event) + // zapRequest.event = newZapRequestEvent zapsPerEvent.getOrPut(zappedPost, { mutableMapOf() }).put(zapRequest, zapEvent) } } else {