mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2025-11-10 07:37:12 +01:00
- Adds a sync Signer to facilitate library
- Separates Account actions from Account state in two objects - Changes Startup procedures to start with Account state and not the full account object - Moves scope for flows in Account from an Application-wide scope to ViewModel scope - Removes all LiveData objects from Account in favor of flows from the state object - Migrates settings saving logic to flows - Migrates PushNotification services to work without Account and only Account Settings. - Migrates the spam filter from LiveData to Flows - Adds Default lists for NIP-65 inbox and outbox relays - Adds Default lists for Search relays - Adds local backup for UserMetadata objects - Adds local backup for Mute lists - Adds local backup for NIP-65 relays - Adds local backup for DM Relays - Adds local backup for private home relays - Rewrites state flows initializers to avoid inconsistent startups
This commit is contained in:
@@ -24,6 +24,7 @@ import androidx.compose.runtime.Immutable
|
||||
import com.vitorpamplona.quartz.encoders.ATag
|
||||
import com.vitorpamplona.quartz.encoders.HexKey
|
||||
import com.vitorpamplona.quartz.signers.NostrSigner
|
||||
import com.vitorpamplona.quartz.signers.NostrSignerSync
|
||||
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||
|
||||
@Immutable
|
||||
@@ -139,6 +140,17 @@ class AdvertisedRelayListEvent(
|
||||
|
||||
signer.sign(createdAt, KIND, tags, msg, onReady)
|
||||
}
|
||||
|
||||
fun create(
|
||||
list: List<AdvertisedRelayInfo>,
|
||||
signer: NostrSignerSync,
|
||||
createdAt: Long = TimeUtils.now(),
|
||||
): AdvertisedRelayListEvent? {
|
||||
val tags = createTagArray(list)
|
||||
val msg = ""
|
||||
|
||||
return signer.sign(createdAt, KIND, tags, msg)
|
||||
}
|
||||
}
|
||||
|
||||
@Immutable data class AdvertisedRelayInfo(
|
||||
|
||||
@@ -24,6 +24,7 @@ import androidx.compose.runtime.Immutable
|
||||
import com.vitorpamplona.quartz.encoders.ATag
|
||||
import com.vitorpamplona.quartz.encoders.HexKey
|
||||
import com.vitorpamplona.quartz.signers.NostrSigner
|
||||
import com.vitorpamplona.quartz.signers.NostrSignerSync
|
||||
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||
|
||||
@Immutable
|
||||
@@ -97,5 +98,11 @@ class ChatMessageRelayListEvent(
|
||||
) {
|
||||
signer.sign(createdAt, KIND, createTagArray(relays), "", onReady)
|
||||
}
|
||||
|
||||
fun create(
|
||||
relays: List<String>,
|
||||
signer: NostrSignerSync,
|
||||
createdAt: Long = TimeUtils.now(),
|
||||
): ChatMessageRelayListEvent? = signer.sign(createdAt, KIND, createTagArray(relays), "")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ import com.vitorpamplona.quartz.encoders.HexKey
|
||||
import com.vitorpamplona.quartz.encoders.decodePublicKey
|
||||
import com.vitorpamplona.quartz.encoders.toHexKey
|
||||
import com.vitorpamplona.quartz.signers.NostrSigner
|
||||
import com.vitorpamplona.quartz.signers.NostrSignerSync
|
||||
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||
|
||||
@Immutable data class Contact(
|
||||
@@ -118,6 +119,46 @@ class ContactListEvent(
|
||||
const val KIND = 3
|
||||
const val ALT = "Follow List"
|
||||
|
||||
fun createFromScratch(
|
||||
followUsers: List<Contact> = emptyList(),
|
||||
followTags: List<String> = emptyList(),
|
||||
followGeohashes: List<String> = emptyList(),
|
||||
followCommunities: List<ATag> = emptyList(),
|
||||
followEvents: List<String> = emptyList(),
|
||||
relayUse: Map<String, ReadWrite>? = emptyMap(),
|
||||
signer: NostrSignerSync,
|
||||
createdAt: Long = TimeUtils.now(),
|
||||
): ContactListEvent? {
|
||||
val content =
|
||||
if (relayUse != null) {
|
||||
mapper.writeValueAsString(relayUse)
|
||||
} else {
|
||||
""
|
||||
}
|
||||
|
||||
val tags =
|
||||
listOf(arrayOf("alt", ALT)) +
|
||||
followUsers.map {
|
||||
if (it.relayUri != null) {
|
||||
arrayOf("p", it.pubKeyHex, it.relayUri)
|
||||
} else {
|
||||
arrayOf("p", it.pubKeyHex)
|
||||
}
|
||||
} +
|
||||
followTags.map { arrayOf("t", it) } +
|
||||
followEvents.map { arrayOf("e", it) } +
|
||||
followCommunities.map {
|
||||
if (it.relay != null) {
|
||||
arrayOf("a", it.toTag(), it.relay)
|
||||
} else {
|
||||
arrayOf("a", it.toTag())
|
||||
}
|
||||
} +
|
||||
followGeohashes.map { arrayOf("g", it) }
|
||||
|
||||
return signer.sign(createdAt, KIND, tags.toTypedArray(), content)
|
||||
}
|
||||
|
||||
fun createFromScratch(
|
||||
followUsers: List<Contact>,
|
||||
followTags: List<String>,
|
||||
|
||||
@@ -33,6 +33,7 @@ import com.fasterxml.jackson.databind.SerializerProvider
|
||||
import com.fasterxml.jackson.databind.deser.std.StdDeserializer
|
||||
import com.fasterxml.jackson.databind.module.SimpleModule
|
||||
import com.fasterxml.jackson.databind.ser.std.StdSerializer
|
||||
import com.fasterxml.jackson.module.kotlin.addDeserializer
|
||||
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
|
||||
import com.vitorpamplona.quartz.crypto.CryptoUtils
|
||||
import com.vitorpamplona.quartz.encoders.ATag
|
||||
|
||||
@@ -78,7 +78,14 @@ class GiftWrapEvent(
|
||||
) {
|
||||
try {
|
||||
plainContent(signer) { giftStr ->
|
||||
val gift = fromJson(giftStr)
|
||||
val gift =
|
||||
try {
|
||||
fromJson(giftStr)
|
||||
} catch (e: Exception) {
|
||||
Log.w("GiftWrapEvent", "Couldn't Parse the content " + this.toNostrUri() + " " + giftStr)
|
||||
return@plainContent
|
||||
}
|
||||
|
||||
if (gift is WrappedEvent) {
|
||||
gift.host = HostStub(this.id, this.pubKey, this.kind)
|
||||
}
|
||||
@@ -87,7 +94,7 @@ class GiftWrapEvent(
|
||||
onReady(gift)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.w("GiftWrapEvent", "Couldn't Decrypt the content", e)
|
||||
Log.w("GiftWrapEvent", "Couldn't Decrypt the content " + this.toNostrUri())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ package com.vitorpamplona.quartz.events
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.vitorpamplona.quartz.encoders.HexKey
|
||||
import com.vitorpamplona.quartz.signers.NostrSigner
|
||||
import com.vitorpamplona.quartz.signers.NostrSignerSync
|
||||
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||
|
||||
@Immutable
|
||||
@@ -47,5 +48,12 @@ class LnZapPrivateEvent(
|
||||
) {
|
||||
signer.sign(createdAt, KIND, tags, content, onReady)
|
||||
}
|
||||
|
||||
fun create(
|
||||
signer: NostrSignerSync,
|
||||
tags: Array<Array<String>> = emptyArray(),
|
||||
content: String = "",
|
||||
createdAt: Long = TimeUtils.now(),
|
||||
): LnZapPrivateEvent? = signer.sign(createdAt, KIND, tags, content)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode
|
||||
import com.vitorpamplona.quartz.encoders.HexKey
|
||||
import com.vitorpamplona.quartz.signers.NostrSigner
|
||||
import com.vitorpamplona.quartz.signers.NostrSignerSync
|
||||
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.StringWriter
|
||||
@@ -176,6 +177,27 @@ class MetadataEvent(
|
||||
companion object {
|
||||
const val KIND = 0
|
||||
|
||||
fun newUser(
|
||||
name: String?,
|
||||
signer: NostrSignerSync,
|
||||
createdAt: Long = TimeUtils.now(),
|
||||
): MetadataEvent? {
|
||||
// Tries to not delete any existing attribute that we do not work with.
|
||||
val currentJson = ObjectMapper().createObjectNode()
|
||||
|
||||
name?.let { addIfNotBlank(currentJson, "name", it.trim()) }
|
||||
val writer = StringWriter()
|
||||
ObjectMapper().writeValue(writer, currentJson)
|
||||
|
||||
val tags = mutableListOf<Array<String>>()
|
||||
|
||||
tags.add(
|
||||
arrayOf("alt", "User profile for ${name ?: currentJson.get("name").asText() ?: ""}"),
|
||||
)
|
||||
|
||||
return signer.sign(createdAt, KIND, tags.toTypedArray(), writer.buffer.toString())
|
||||
}
|
||||
|
||||
fun updateFromPast(
|
||||
latest: MetadataEvent?,
|
||||
name: String?,
|
||||
|
||||
@@ -24,6 +24,7 @@ import androidx.compose.runtime.Immutable
|
||||
import com.vitorpamplona.quartz.encoders.ATag
|
||||
import com.vitorpamplona.quartz.encoders.HexKey
|
||||
import com.vitorpamplona.quartz.signers.NostrSigner
|
||||
import com.vitorpamplona.quartz.signers.NostrSignerSync
|
||||
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||
|
||||
@Immutable
|
||||
@@ -97,5 +98,11 @@ class SearchRelayListEvent(
|
||||
) {
|
||||
signer.sign(createdAt, KIND, createTagArray(relays), "", onReady)
|
||||
}
|
||||
|
||||
fun create(
|
||||
relays: List<String>,
|
||||
signer: NostrSignerSync,
|
||||
createdAt: Long = TimeUtils.now(),
|
||||
): SearchRelayListEvent? = signer.sign(createdAt, KIND, createTagArray(relays), "")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,20 +20,18 @@
|
||||
*/
|
||||
package com.vitorpamplona.quartz.signers
|
||||
|
||||
import android.util.Log
|
||||
import com.vitorpamplona.quartz.crypto.CryptoUtils
|
||||
import com.vitorpamplona.quartz.crypto.KeyPair
|
||||
import com.vitorpamplona.quartz.encoders.HexKey
|
||||
import com.vitorpamplona.quartz.encoders.hexToByteArray
|
||||
import com.vitorpamplona.quartz.encoders.toHexKey
|
||||
import com.vitorpamplona.quartz.events.Event
|
||||
import com.vitorpamplona.quartz.events.EventFactory
|
||||
import com.vitorpamplona.quartz.events.LnZapPrivateEvent
|
||||
import com.vitorpamplona.quartz.events.LnZapRequestEvent
|
||||
|
||||
class NostrSignerInternal(
|
||||
val keyPair: KeyPair,
|
||||
) : NostrSigner(keyPair.pubKey.toHexKey()) {
|
||||
val signerSync = NostrSignerSync(keyPair)
|
||||
|
||||
override fun <T : Event> sign(
|
||||
createdAt: Long,
|
||||
kind: Int,
|
||||
@@ -41,46 +39,7 @@ class NostrSignerInternal(
|
||||
content: String,
|
||||
onReady: (T) -> Unit,
|
||||
) {
|
||||
if (keyPair.privKey == null) return
|
||||
|
||||
if (isUnsignedPrivateEvent(kind, tags)) {
|
||||
// this is a private zap
|
||||
signPrivateZap(createdAt, kind, tags, content, onReady)
|
||||
} else {
|
||||
signNormal(createdAt, kind, tags, content, onReady)
|
||||
}
|
||||
}
|
||||
|
||||
fun isUnsignedPrivateEvent(
|
||||
kind: Int,
|
||||
tags: Array<Array<String>>,
|
||||
): Boolean =
|
||||
kind == LnZapRequestEvent.KIND &&
|
||||
tags.any { t -> t.size > 1 && t[0] == "anon" && t[1].isBlank() }
|
||||
|
||||
fun <T : Event> signNormal(
|
||||
createdAt: Long,
|
||||
kind: Int,
|
||||
tags: Array<Array<String>>,
|
||||
content: String,
|
||||
onReady: (T) -> Unit,
|
||||
) {
|
||||
if (keyPair.privKey == null) return
|
||||
|
||||
val id = Event.generateId(pubKey, createdAt, kind, tags, content)
|
||||
val sig = CryptoUtils.sign(id, keyPair.privKey).toHexKey()
|
||||
|
||||
onReady(
|
||||
EventFactory.create(
|
||||
id.toHexKey(),
|
||||
pubKey,
|
||||
createdAt,
|
||||
kind,
|
||||
tags,
|
||||
content,
|
||||
sig,
|
||||
) as T,
|
||||
)
|
||||
signerSync.sign<T>(createdAt, kind, tags, content)?.let { onReady(it) }
|
||||
}
|
||||
|
||||
override fun nip04Encrypt(
|
||||
@@ -88,15 +47,7 @@ class NostrSignerInternal(
|
||||
toPublicKey: HexKey,
|
||||
onReady: (String) -> Unit,
|
||||
) {
|
||||
if (keyPair.privKey == null) return
|
||||
|
||||
onReady(
|
||||
CryptoUtils.encryptNIP04(
|
||||
decryptedContent,
|
||||
keyPair.privKey,
|
||||
toPublicKey.hexToByteArray(),
|
||||
),
|
||||
)
|
||||
signerSync.nip04Encrypt(decryptedContent, toPublicKey)?.let { onReady(it) }
|
||||
}
|
||||
|
||||
override fun nip04Decrypt(
|
||||
@@ -104,16 +55,7 @@ class NostrSignerInternal(
|
||||
fromPublicKey: HexKey,
|
||||
onReady: (String) -> Unit,
|
||||
) {
|
||||
if (keyPair.privKey == null) return
|
||||
|
||||
try {
|
||||
val sharedSecret =
|
||||
CryptoUtils.getSharedSecretNIP04(keyPair.privKey, fromPublicKey.hexToByteArray())
|
||||
|
||||
onReady(CryptoUtils.decryptNIP04(encryptedContent, sharedSecret))
|
||||
} catch (e: Exception) {
|
||||
Log.w("NIP04Decrypt", "Error decrypting the message ${e.message} on $encryptedContent")
|
||||
}
|
||||
signerSync.nip04Decrypt(encryptedContent, fromPublicKey)?.let { onReady(it) }
|
||||
}
|
||||
|
||||
override fun nip44Encrypt(
|
||||
@@ -121,16 +63,7 @@ class NostrSignerInternal(
|
||||
toPublicKey: HexKey,
|
||||
onReady: (String) -> Unit,
|
||||
) {
|
||||
if (keyPair.privKey == null) return
|
||||
|
||||
onReady(
|
||||
CryptoUtils
|
||||
.encryptNIP44(
|
||||
decryptedContent,
|
||||
keyPair.privKey,
|
||||
toPublicKey.hexToByteArray(),
|
||||
).encodePayload(),
|
||||
)
|
||||
signerSync.nip44Encrypt(decryptedContent, toPublicKey)?.let { onReady(it) }
|
||||
}
|
||||
|
||||
override fun nip44Decrypt(
|
||||
@@ -138,119 +71,13 @@ class NostrSignerInternal(
|
||||
fromPublicKey: HexKey,
|
||||
onReady: (String) -> Unit,
|
||||
) {
|
||||
if (keyPair.privKey == null) return
|
||||
|
||||
CryptoUtils
|
||||
.decryptNIP44(
|
||||
payload = encryptedContent,
|
||||
privateKey = keyPair.privKey,
|
||||
pubKey = fromPublicKey.hexToByteArray(),
|
||||
)?.let { onReady(it) }
|
||||
}
|
||||
|
||||
private fun <T> signPrivateZap(
|
||||
createdAt: Long,
|
||||
kind: Int,
|
||||
tags: Array<Array<String>>,
|
||||
content: String,
|
||||
onReady: (T) -> Unit,
|
||||
) {
|
||||
if (keyPair.privKey == null) return
|
||||
|
||||
val zappedEvent = tags.firstOrNull { it.size > 1 && it[0] == "e" }?.let { it[1] }
|
||||
val userHex = tags.firstOrNull { it.size > 1 && it[0] == "p" }?.let { it[1] } ?: return
|
||||
|
||||
// if it is a Zap for an Event, use event.id if not, use the user's pubkey
|
||||
val idToGeneratePrivateKey = zappedEvent ?: userHex
|
||||
|
||||
val encryptionPrivateKey =
|
||||
LnZapRequestEvent.createEncryptionPrivateKey(
|
||||
keyPair.privKey.toHexKey(),
|
||||
idToGeneratePrivateKey,
|
||||
createdAt,
|
||||
)
|
||||
|
||||
val fullTagsNoAnon = tags.filter { t -> t.getOrNull(0) != "anon" }.toTypedArray()
|
||||
|
||||
LnZapPrivateEvent.create(this, fullTagsNoAnon, content) {
|
||||
val noteJson = it.toJson()
|
||||
val encryptedContent =
|
||||
LnZapRequestEvent.encryptPrivateZapMessage(
|
||||
noteJson,
|
||||
encryptionPrivateKey,
|
||||
userHex.hexToByteArray(),
|
||||
)
|
||||
|
||||
val newTags =
|
||||
tags.filter { t -> t.getOrNull(0) != "anon" } + listOf(arrayOf("anon", encryptedContent))
|
||||
val newContent = ""
|
||||
|
||||
NostrSignerInternal(KeyPair(encryptionPrivateKey))
|
||||
.signNormal(createdAt, kind, newTags.toTypedArray(), newContent, onReady)
|
||||
}
|
||||
signerSync.nip44Decrypt(encryptedContent, fromPublicKey)?.let { onReady(it) }
|
||||
}
|
||||
|
||||
override fun decryptZapEvent(
|
||||
event: LnZapRequestEvent,
|
||||
onReady: (LnZapPrivateEvent) -> Unit,
|
||||
) {
|
||||
if (keyPair.privKey == null) return
|
||||
|
||||
val recipientPK = event.zappedAuthor().firstOrNull()
|
||||
val recipientPost = event.zappedPost().firstOrNull()
|
||||
val privateEvent =
|
||||
if (recipientPK == pubKey) {
|
||||
// if the receiver is logged in, these are the params.
|
||||
val privateKeyToUse = keyPair.privKey
|
||||
val pubkeyToUse = event.pubKey
|
||||
|
||||
event.getPrivateZapEvent(privateKeyToUse, pubkeyToUse)
|
||||
} else {
|
||||
// if the sender is logged in, these are the params
|
||||
val altPubkeyToUse = recipientPK
|
||||
val altPrivateKeyToUse =
|
||||
if (recipientPost != null) {
|
||||
LnZapRequestEvent.createEncryptionPrivateKey(
|
||||
keyPair.privKey.toHexKey(),
|
||||
recipientPost,
|
||||
event.createdAt,
|
||||
)
|
||||
} else if (recipientPK != null) {
|
||||
LnZapRequestEvent.createEncryptionPrivateKey(
|
||||
keyPair.privKey.toHexKey(),
|
||||
recipientPK,
|
||||
event.createdAt,
|
||||
)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
try {
|
||||
if (altPrivateKeyToUse != null && altPubkeyToUse != null) {
|
||||
val altPubKeyFromPrivate = CryptoUtils.pubkeyCreate(altPrivateKeyToUse).toHexKey()
|
||||
|
||||
if (altPubKeyFromPrivate == event.pubKey) {
|
||||
val result = event.getPrivateZapEvent(altPrivateKeyToUse, altPubkeyToUse)
|
||||
|
||||
if (result == null) {
|
||||
Log.w(
|
||||
"Private ZAP Decrypt",
|
||||
"Fail to decrypt Zap from ${event.id}",
|
||||
)
|
||||
}
|
||||
result
|
||||
} else {
|
||||
null
|
||||
}
|
||||
} else {
|
||||
null
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e("Account", "Failed to create pubkey for ZapRequest ${event.id}", e)
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
privateEvent?.let { onReady(it) }
|
||||
signerSync.decryptZapEvent(event)?.let { onReady(it) }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,241 @@
|
||||
/**
|
||||
* Copyright (c) 2024 Vitor Pamplona
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.quartz.signers
|
||||
|
||||
import android.util.Log
|
||||
import com.vitorpamplona.quartz.crypto.CryptoUtils
|
||||
import com.vitorpamplona.quartz.crypto.KeyPair
|
||||
import com.vitorpamplona.quartz.encoders.HexKey
|
||||
import com.vitorpamplona.quartz.encoders.hexToByteArray
|
||||
import com.vitorpamplona.quartz.encoders.toHexKey
|
||||
import com.vitorpamplona.quartz.events.Event
|
||||
import com.vitorpamplona.quartz.events.EventFactory
|
||||
import com.vitorpamplona.quartz.events.LnZapPrivateEvent
|
||||
import com.vitorpamplona.quartz.events.LnZapRequestEvent
|
||||
|
||||
class NostrSignerSync(
|
||||
val keyPair: KeyPair,
|
||||
val pubKey: HexKey = keyPair.pubKey.toHexKey(),
|
||||
) {
|
||||
fun <T : Event> sign(
|
||||
createdAt: Long,
|
||||
kind: Int,
|
||||
tags: Array<Array<String>>,
|
||||
content: String,
|
||||
): T? {
|
||||
if (keyPair.privKey == null) return null
|
||||
|
||||
return if (isUnsignedPrivateZapEvent(kind, tags)) {
|
||||
// this is a private zap
|
||||
signPrivateZap(createdAt, kind, tags, content)
|
||||
} else {
|
||||
signNormal(createdAt, kind, tags, content)
|
||||
}
|
||||
}
|
||||
|
||||
fun isUnsignedPrivateZapEvent(
|
||||
kind: Int,
|
||||
tags: Array<Array<String>>,
|
||||
): Boolean =
|
||||
kind == LnZapRequestEvent.KIND &&
|
||||
tags.any { t -> t.size > 1 && t[0] == "anon" && t[1].isBlank() }
|
||||
|
||||
fun <T : Event> signNormal(
|
||||
createdAt: Long,
|
||||
kind: Int,
|
||||
tags: Array<Array<String>>,
|
||||
content: String,
|
||||
): T? {
|
||||
if (keyPair.privKey == null) return null
|
||||
|
||||
val id = Event.generateId(pubKey, createdAt, kind, tags, content)
|
||||
val sig = CryptoUtils.sign(id, keyPair.privKey).toHexKey()
|
||||
|
||||
return EventFactory.create(
|
||||
id.toHexKey(),
|
||||
pubKey,
|
||||
createdAt,
|
||||
kind,
|
||||
tags,
|
||||
content,
|
||||
sig,
|
||||
) as T
|
||||
}
|
||||
|
||||
fun nip04Encrypt(
|
||||
decryptedContent: String,
|
||||
toPublicKey: HexKey,
|
||||
): String? {
|
||||
if (keyPair.privKey == null) return null
|
||||
|
||||
return CryptoUtils.encryptNIP04(
|
||||
decryptedContent,
|
||||
keyPair.privKey,
|
||||
toPublicKey.hexToByteArray(),
|
||||
)
|
||||
}
|
||||
|
||||
fun nip04Decrypt(
|
||||
encryptedContent: String,
|
||||
fromPublicKey: HexKey,
|
||||
): String? {
|
||||
if (keyPair.privKey == null) return null
|
||||
|
||||
return try {
|
||||
val sharedSecret =
|
||||
CryptoUtils.getSharedSecretNIP04(keyPair.privKey, fromPublicKey.hexToByteArray())
|
||||
|
||||
CryptoUtils.decryptNIP04(encryptedContent, sharedSecret)
|
||||
} catch (e: Exception) {
|
||||
Log.w("NIP04Decrypt", "Error decrypting the message ${e.message} on $encryptedContent")
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
fun nip44Encrypt(
|
||||
decryptedContent: String,
|
||||
toPublicKey: HexKey,
|
||||
): String? {
|
||||
if (keyPair.privKey == null) return null
|
||||
|
||||
return CryptoUtils
|
||||
.encryptNIP44(
|
||||
decryptedContent,
|
||||
keyPair.privKey,
|
||||
toPublicKey.hexToByteArray(),
|
||||
).encodePayload()
|
||||
}
|
||||
|
||||
fun nip44Decrypt(
|
||||
encryptedContent: String,
|
||||
fromPublicKey: HexKey,
|
||||
): String? {
|
||||
if (keyPair.privKey == null) return null
|
||||
|
||||
return CryptoUtils
|
||||
.decryptNIP44(
|
||||
payload = encryptedContent,
|
||||
privateKey = keyPair.privKey,
|
||||
pubKey = fromPublicKey.hexToByteArray(),
|
||||
)
|
||||
}
|
||||
|
||||
private fun <T> signPrivateZap(
|
||||
createdAt: Long,
|
||||
kind: Int,
|
||||
tags: Array<Array<String>>,
|
||||
content: String,
|
||||
): T? {
|
||||
if (keyPair.privKey == null) return null
|
||||
|
||||
val zappedEvent = tags.firstOrNull { it.size > 1 && it[0] == "e" }?.let { it[1] }
|
||||
val userHex = tags.firstOrNull { it.size > 1 && it[0] == "p" }?.let { it[1] } ?: return null
|
||||
|
||||
// if it is a Zap for an Event, use event.id if not, use the user's pubkey
|
||||
val idToGeneratePrivateKey = zappedEvent ?: userHex
|
||||
|
||||
val encryptionPrivateKey =
|
||||
LnZapRequestEvent.createEncryptionPrivateKey(
|
||||
keyPair.privKey.toHexKey(),
|
||||
idToGeneratePrivateKey,
|
||||
createdAt,
|
||||
)
|
||||
|
||||
val fullTagsNoAnon = tags.filter { t -> t.getOrNull(0) != "anon" }.toTypedArray()
|
||||
|
||||
val privateEvent = LnZapPrivateEvent.create(this, fullTagsNoAnon, content) ?: return null
|
||||
|
||||
val noteJson = privateEvent.toJson()
|
||||
val encryptedContent =
|
||||
LnZapRequestEvent.encryptPrivateZapMessage(
|
||||
noteJson,
|
||||
encryptionPrivateKey,
|
||||
userHex.hexToByteArray(),
|
||||
)
|
||||
|
||||
val newTags =
|
||||
tags.filter { t -> t.getOrNull(0) != "anon" } + listOf(arrayOf("anon", encryptedContent))
|
||||
val newContent = ""
|
||||
|
||||
return NostrSignerSync(KeyPair(encryptionPrivateKey)).signNormal(createdAt, kind, newTags.toTypedArray(), newContent)
|
||||
}
|
||||
|
||||
fun decryptZapEvent(event: LnZapRequestEvent): LnZapPrivateEvent? {
|
||||
if (keyPair.privKey == null) return null
|
||||
|
||||
val recipientPK = event.zappedAuthor().firstOrNull()
|
||||
val recipientPost = event.zappedPost().firstOrNull()
|
||||
val privateEvent =
|
||||
if (recipientPK == pubKey) {
|
||||
// if the receiver is logged in, these are the params.
|
||||
val privateKeyToUse = keyPair.privKey
|
||||
val pubkeyToUse = event.pubKey
|
||||
|
||||
event.getPrivateZapEvent(privateKeyToUse, pubkeyToUse)
|
||||
} else {
|
||||
// if the sender is logged in, these are the params
|
||||
val altPubkeyToUse = recipientPK
|
||||
val altPrivateKeyToUse =
|
||||
if (recipientPost != null) {
|
||||
LnZapRequestEvent.createEncryptionPrivateKey(
|
||||
keyPair.privKey.toHexKey(),
|
||||
recipientPost,
|
||||
event.createdAt,
|
||||
)
|
||||
} else if (recipientPK != null) {
|
||||
LnZapRequestEvent.createEncryptionPrivateKey(
|
||||
keyPair.privKey.toHexKey(),
|
||||
recipientPK,
|
||||
event.createdAt,
|
||||
)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
try {
|
||||
if (altPrivateKeyToUse != null && altPubkeyToUse != null) {
|
||||
val altPubKeyFromPrivate = CryptoUtils.pubkeyCreate(altPrivateKeyToUse).toHexKey()
|
||||
|
||||
if (altPubKeyFromPrivate == event.pubKey) {
|
||||
val result = event.getPrivateZapEvent(altPrivateKeyToUse, altPubkeyToUse)
|
||||
|
||||
if (result == null) {
|
||||
Log.w(
|
||||
"Private ZAP Decrypt",
|
||||
"Fail to decrypt Zap from ${event.id}",
|
||||
)
|
||||
}
|
||||
result
|
||||
} else {
|
||||
null
|
||||
}
|
||||
} else {
|
||||
null
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e("Account", "Failed to create pubkey for ZapRequest ${event.id}", e)
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
return privateEvent
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user