Caching Private Zap decryption

This commit is contained in:
Vitor Pamplona
2023-05-06 09:46:25 -04:00
parent f3316f53ea
commit 97d56d61d1
5 changed files with 60 additions and 73 deletions
@@ -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)
@@ -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()}")
}
@@ -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
@@ -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 {
@@ -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": <base64-encoded OTS file data> // TODO
}
*/