Merge pull request #348 from believethehype/private_zaps

Send Private Zaps
pull/372/head
Vitor Pamplona 2023-04-22 14:52:06 -04:00 zatwierdzone przez GitHub
commit a720a636d3
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 4AEE18F83AFDEB23
6 zmienionych plików z 112 dodań i 13 usunięć

Wyświetl plik

@ -4,6 +4,7 @@ import android.util.Log
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import com.fasterxml.jackson.databind.DeserializationFeature import com.fasterxml.jackson.databind.DeserializationFeature
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import com.vitorpamplona.amethyst.service.NostrAccountDataSource.account
import com.vitorpamplona.amethyst.service.model.* import com.vitorpamplona.amethyst.service.model.*
import com.vitorpamplona.amethyst.service.relays.Relay import com.vitorpamplona.amethyst.service.relays.Relay
import com.vitorpamplona.amethyst.ui.components.BundledInsert import com.vitorpamplona.amethyst.ui.components.BundledInsert
@ -594,6 +595,16 @@ object LocalCache {
fun consume(event: LnZapEvent) { fun consume(event: LnZapEvent) {
val note = getOrCreateNote(event.id) val note = getOrCreateNote(event.id)
var decryptedContent = LnZapRequestEvent.checkForPrivateZap(event.zapRequest!!, account.loggedIn.privKey!!)
if (decryptedContent != null) {
Log.e(
"DC",
"Decrypted Content from Anon Tag: Sender: {${decryptedContent.pubKey}}, Message: {${decryptedContent.content}} "
// TODO Update Notification with this Sender and Message
)
}
// Already processed this event. // Already processed this event.
if (note.event != null) return if (note.event != null) return

Wyświetl plik

@ -16,7 +16,7 @@ class LnZapEvent(
// This event is also kept in LocalCache (same object) // This event is also kept in LocalCache (same object)
@Transient val zapRequest: LnZapRequestEvent? @Transient val zapRequest: LnZapRequestEvent?
private fun containedPost(): LnZapRequestEvent? = try { override fun containedPost(): LnZapRequestEvent? = try {
description()?.ifBlank { null }?.let { description()?.ifBlank { null }?.let {
fromJson(it, Client.lenient) fromJson(it, Client.lenient)
} as? LnZapRequestEvent } as? LnZapRequestEvent
@ -53,8 +53,7 @@ class LnZapEvent(
null null
} }
} }
override fun content(): String {
override fun message(): String {
return content return content
} }
@ -68,7 +67,7 @@ class LnZapEvent(
enum class ZapType() { enum class ZapType() {
PUBLIC, PUBLIC,
PRIVATE, // not yet implemented PRIVATE,
ANONYMOUS, ANONYMOUS,
NONZAP NONZAP
} }

Wyświetl plik

@ -16,5 +16,5 @@ interface LnZapEventInterface : EventInterface {
fun amount(): BigDecimal? fun amount(): BigDecimal?
fun message(): String fun containedPost(): Event?
} }

Wyświetl plik

@ -1,9 +1,15 @@
package com.vitorpamplona.amethyst.service.model package com.vitorpamplona.amethyst.service.model
import com.vitorpamplona.amethyst.model.HexKey import com.vitorpamplona.amethyst.model.*
import com.vitorpamplona.amethyst.model.toHexKey import nostr.postr.Bech32
import nostr.postr.Utils import nostr.postr.Utils
import java.util.Date 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
class LnZapRequestEvent( class LnZapRequestEvent(
id: HexKey, id: HexKey,
@ -30,7 +36,7 @@ class LnZapRequestEvent(
zapType: LnZapEvent.ZapType, zapType: LnZapEvent.ZapType,
createdAt: Long = Date().time / 1000 createdAt: Long = Date().time / 1000
): LnZapRequestEvent { ): LnZapRequestEvent {
val content = message var content = message
var privkey = privateKey var privkey = privateKey
var pubKey = Utils.pubkeyCreate(privateKey).toHexKey() var pubKey = Utils.pubkeyCreate(privateKey).toHexKey()
var tags = listOf( var tags = listOf(
@ -48,6 +54,14 @@ class LnZapRequestEvent(
tags = tags + listOf(listOf("anon", "")) tags = tags + listOf(listOf("anon", ""))
privkey = Utils.privkeyCreate() privkey = Utils.privkeyCreate()
pubKey = Utils.pubkeyCreate(privkey).toHexKey() pubKey = Utils.pubkeyCreate(privkey).toHexKey()
} else if (zapType == LnZapEvent.ZapType.PRIVATE) {
var encryptionPrivateKey = createEncryptionPrivateKey(privateKey.toHexKey(), originalNote.id(), createdAt)
var noteJson = (create(privkey, 9733, listOf(tags[0], tags[1]), message)).toJson()
var encryptedContent = encryptPrivateZapMessage(noteJson, encryptionPrivateKey, originalNote.pubKey().toByteArray())
tags = tags + listOf(listOf("anon", encryptedContent))
content = "" // make sure public content is empty, as the content is encrypted
privkey = encryptionPrivateKey // sign event with generated privkey
pubKey = Utils.pubkeyCreate(encryptionPrivateKey).toHexKey() // updated event with according pubkey
} }
val id = generateId(pubKey, createdAt, kind, tags, content) val id = generateId(pubKey, createdAt, kind, tags, content)
val sig = Utils.sign(id, privkey) val sig = Utils.sign(id, privkey)
@ -62,7 +76,7 @@ class LnZapRequestEvent(
zapType: LnZapEvent.ZapType, zapType: LnZapEvent.ZapType,
createdAt: Long = Date().time / 1000 createdAt: Long = Date().time / 1000
): LnZapRequestEvent { ): LnZapRequestEvent {
val content = message var content = message
var privkey = privateKey var privkey = privateKey
var pubKey = Utils.pubkeyCreate(privateKey).toHexKey() var pubKey = Utils.pubkeyCreate(privateKey).toHexKey()
var tags = listOf( var tags = listOf(
@ -70,18 +84,90 @@ class LnZapRequestEvent(
listOf("relays") + relays listOf("relays") + relays
) )
if (zapType == LnZapEvent.ZapType.ANONYMOUS) { if (zapType == LnZapEvent.ZapType.ANONYMOUS) {
tags = tags + listOf(listOf("anon", ""))
privkey = Utils.privkeyCreate() privkey = Utils.privkeyCreate()
pubKey = Utils.pubkeyCreate(privkey).toHexKey() pubKey = Utils.pubkeyCreate(privkey).toHexKey()
tags = tags + listOf(listOf("anon", ""))
} else if (zapType == LnZapEvent.ZapType.PRIVATE) {
var encryptionPrivateKey = createEncryptionPrivateKey(privateKey.toHexKey(), userHex, createdAt)
var noteJson = (create(privkey, 9733, listOf(tags[0], tags[1]), message)).toJson()
var encryptedContent = encryptPrivateZapMessage(noteJson, encryptionPrivateKey, userHex.toByteArray())
tags = tags + listOf(listOf("anon", encryptedContent))
content = ""
privkey = encryptionPrivateKey
pubKey = Utils.pubkeyCreate(encryptionPrivateKey).toHexKey()
} }
val id = generateId(pubKey, createdAt, kind, tags, content) val id = generateId(pubKey, createdAt, kind, tags, content)
val sig = Utils.sign(id, privkey) val sig = Utils.sign(id, privkey)
return LnZapRequestEvent(id.toHexKey(), pubKey, createdAt, tags, content, sig.toHexKey()) return LnZapRequestEvent(id.toHexKey(), pubKey, createdAt, tags, content, sig.toHexKey())
} }
fun createEncryptionPrivateKey(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)
}
fun encryptPrivateZapMessage(msg: String, privkey: ByteArray, pubkey: ByteArray): String {
var sharedSecret = Utils.getSharedSecret(privkey, pubkey)
val iv = ByteArray(16)
SecureRandom().nextBytes(iv)
val keySpec = SecretKeySpec(sharedSecret, "AES")
val ivSpec = IvParameterSpec(iv)
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(utf8message)
val encryptedMsgBech32 = Bech32.encode("pzap", Bech32.eight2five(encryptedMsg), Bech32.Encoding.Bech32)
val ivBech32 = Bech32.encode("iv", Bech32.eight2five(iv), Bech32.Encoding.Bech32)
return encryptedMsgBech32 + "_" + ivBech32
}
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")
}
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)))
try {
val decryptedMsgBytes = cipher.doFinal(Bech32.five2eight(encryptedMsg.second, 0))
return String(decryptedMsgBytes)
} catch (ex: BadPaddingException) {
throw IllegalArgumentException("Bad padding")
}
}
fun checkForPrivateZap(zaprequest: Event, loggedInUserPrivKey: ByteArray): Event? {
val anonTag = zaprequest.tags.firstOrNull { t -> t.count() >= 2 && t[0] == "anon" }
if (anonTag != null && anonTag.size > 1) {
val encnote = anonTag?.elementAt(1)
if (encnote != null && encnote != "") {
try {
val note = decryptPrivateZapMessage(encnote, loggedInUserPrivKey, zaprequest.pubKey.toByteArray())
val decryptedEvent = fromJson(note)
if (decryptedEvent.kind == 9733) {
return decryptedEvent
}
} catch (e: Exception) {
e.printStackTrace()
}
}
}
return null
}
} }
} }
/* /*
{ {
"pubkey": "32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245", "pubkey": "32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245",

Wyświetl plik

@ -67,6 +67,7 @@ fun ZapCustomDialog(onClose: () -> Unit, account: Account, accountViewModel: Acc
val zapTypes = listOf( val zapTypes = listOf(
Pair(LnZapEvent.ZapType.PUBLIC, "Public"), Pair(LnZapEvent.ZapType.PUBLIC, "Public"),
Pair(LnZapEvent.ZapType.PRIVATE, "Private"),
Pair(LnZapEvent.ZapType.ANONYMOUS, "Anonymous"), Pair(LnZapEvent.ZapType.ANONYMOUS, "Anonymous"),
Pair(LnZapEvent.ZapType.NONZAP, "Non-Zap") Pair(LnZapEvent.ZapType.NONZAP, "Non-Zap")
) )

Wyświetl plik

@ -89,6 +89,8 @@ open class CardFeedViewModel(val localFilter: FeedFilter<Note>) : ViewModel() {
if (zappedPost != null) { if (zappedPost != null) {
val zapRequest = zappedPost.zaps.filter { it.value == zapEvent }.keys.firstOrNull() val zapRequest = zappedPost.zaps.filter { it.value == zapEvent }.keys.firstOrNull()
if (zapRequest != null) { if (zapRequest != null) {
// var newZapRequestEvent = LocalCache.checkPrivateZap(zapRequest.event as Event)
// zapRequest.event = newZapRequestEvent
zapsPerEvent.getOrPut(zappedPost, { mutableMapOf() }).put(zapRequest, zapEvent) zapsPerEvent.getOrPut(zappedPost, { mutableMapOf() }).put(zapRequest, zapEvent)
} }
} else { } else {