kopia lustrzana https://github.com/vitorpamplona/amethyst
commit
a720a636d3
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,5 +16,5 @@ interface LnZapEventInterface : EventInterface {
|
||||||
|
|
||||||
fun amount(): BigDecimal?
|
fun amount(): BigDecimal?
|
||||||
|
|
||||||
fun message(): String
|
fun containedPost(): Event?
|
||||||
}
|
}
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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")
|
||||||
)
|
)
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
Ładowanie…
Reference in New Issue