support for sending and receiving nip 44 dms

This commit is contained in:
greenart7c3
2023-09-06 18:53:11 -03:00
parent a7aa3705ef
commit 6303573dd9
16 changed files with 399 additions and 85 deletions
@@ -16,6 +16,7 @@ import com.vitorpamplona.amethyst.service.relays.Constants
import com.vitorpamplona.amethyst.service.relays.FeedType import com.vitorpamplona.amethyst.service.relays.FeedType
import com.vitorpamplona.amethyst.service.relays.Relay import com.vitorpamplona.amethyst.service.relays.Relay
import com.vitorpamplona.amethyst.service.relays.RelayPool import com.vitorpamplona.amethyst.service.relays.RelayPool
import com.vitorpamplona.amethyst.ui.actions.SignerType
import com.vitorpamplona.amethyst.ui.components.BundledUpdate import com.vitorpamplona.amethyst.ui.components.BundledUpdate
import com.vitorpamplona.amethyst.ui.note.combineWith import com.vitorpamplona.amethyst.ui.note.combineWith
import com.vitorpamplona.quartz.crypto.CryptoUtils import com.vitorpamplona.quartz.crypto.CryptoUtils
@@ -114,7 +115,8 @@ class Account(
AmberUtils.content = "" AmberUtils.content = ""
AmberUtils.decrypt( AmberUtils.decrypt(
content, content,
keyPair.pubKey.toHexKey() keyPair.pubKey.toHexKey(),
blockList?.id() ?: ""
) )
blockList?.decryptedContent = AmberUtils.content blockList?.decryptedContent = AmberUtils.content
live.invalidateData() live.invalidateData()
@@ -282,7 +284,7 @@ class Account(
return return
} }
if (note.event is ChatMessageEvent && !loginWithAmber) { if (note.event is ChatMessageEvent) {
val event = note.event as ChatMessageEvent val event = note.event as ChatMessageEvent
val users = event.recipientsPubKey().plus(event.pubKey).toSet().toList() val users = event.recipientsPubKey().plus(event.pubKey).toSet().toList()
@@ -290,21 +292,91 @@ class Account(
val emojiUrl = EmojiUrl.decode(reaction) val emojiUrl = EmojiUrl.decode(reaction)
if (emojiUrl != null) { if (emojiUrl != null) {
note.event?.let { note.event?.let {
if (loginWithAmber) {
val senderPublicKey = keyPair.pubKey.toHexKey()
var senderReaction = ReactionEvent.create(
emojiUrl,
it,
keyPair
)
AmberUtils.openAmber(senderReaction)
if (AmberUtils.content.isBlank()) return
senderReaction = ReactionEvent.create(senderReaction, AmberUtils.content)
val giftWraps = users.plus(senderPublicKey).map {
val gossip = Gossip.create(senderReaction)
val content = Gossip.toJson(gossip)
AmberUtils.encrypt(content, it, SignerType.NIP44_ENCRYPT)
var sealedEvent = SealedGossipEvent.create(
encryptedContent = AmberUtils.content,
pubKey = senderPublicKey
)
AmberUtils.openAmber(sealedEvent)
if (AmberUtils.content.isBlank()) return
sealedEvent = SealedGossipEvent.create(sealedEvent, AmberUtils.content)
GiftWrapEvent.create(
event = sealedEvent,
recipientPubKey = it
)
}
broadcastPrivately(giftWraps)
} else {
val giftWraps = NIP24Factory().createReactionWithinGroup( val giftWraps = NIP24Factory().createReactionWithinGroup(
emojiUrl = emojiUrl, emojiUrl = emojiUrl,
originalNote = it, originalNote = it,
to = users, to = users,
from = keyPair from = keyPair
) )
broadcastPrivately(giftWraps) broadcastPrivately(giftWraps)
} }
}
return return
} }
} }
note.event?.let { note.event?.let {
if (loginWithAmber) {
val senderPublicKey = keyPair.pubKey.toHexKey()
var senderReaction = ReactionEvent.create(
reaction,
it,
keyPair
)
AmberUtils.openAmber(senderReaction)
if (AmberUtils.content.isBlank()) return
senderReaction = ReactionEvent.create(senderReaction, AmberUtils.content)
val newUsers = users.plus(senderPublicKey)
newUsers.forEach {
val gossip = Gossip.create(senderReaction)
val content = Gossip.toJson(gossip)
AmberUtils.content = ""
AmberUtils.encrypt(content, it, SignerType.NIP44_ENCRYPT)
var sealedEvent = SealedGossipEvent.create(
encryptedContent = AmberUtils.content,
pubKey = senderPublicKey
)
AmberUtils.openAmber(sealedEvent)
if (AmberUtils.content.isBlank()) return
sealedEvent = SealedGossipEvent.create(sealedEvent, AmberUtils.content)
val giftWraps = GiftWrapEvent.create(
event = sealedEvent,
recipientPubKey = it
)
broadcastPrivately(listOf(giftWraps))
}
} else {
val giftWraps = NIP24Factory().createReactionWithinGroup( val giftWraps = NIP24Factory().createReactionWithinGroup(
content = reaction, content = reaction,
originalNote = it, originalNote = it,
@@ -314,6 +386,7 @@ class Account(
broadcastPrivately(giftWraps) broadcastPrivately(giftWraps)
} }
}
return return
} else { } else {
if (reaction.startsWith(":")) { if (reaction.startsWith(":")) {
@@ -1373,13 +1446,52 @@ class Account(
wantsToMarkAsSensitive: Boolean, wantsToMarkAsSensitive: Boolean,
zapRaiserAmount: Long? = null, zapRaiserAmount: Long? = null,
geohash: String? = null geohash: String? = null
): List<GiftWrapEvent>? { ) {
// TODO: add support for amber if (!isWriteable() && !loginWithAmber) return
if (!isWriteable() && !loginWithAmber) return null
val repliesToHex = listOfNotNull(replyingTo?.idHex).ifEmpty { null } val repliesToHex = listOfNotNull(replyingTo?.idHex).ifEmpty { null }
val mentionsHex = mentions?.map { it.pubkeyHex } val mentionsHex = mentions?.map { it.pubkeyHex }
if (loginWithAmber) {
var chatMessageEvent = ChatMessageEvent.create(
msg = message,
to = toUsers,
keyPair = keyPair,
subject = subject,
replyTos = repliesToHex,
mentions = mentionsHex,
zapReceiver = zapReceiver,
markAsSensitive = wantsToMarkAsSensitive,
zapRaiserAmount = zapRaiserAmount,
geohash = geohash
)
AmberUtils.openAmber(chatMessageEvent)
if (AmberUtils.content.isBlank()) return
chatMessageEvent = ChatMessageEvent.create(chatMessageEvent, AmberUtils.content)
val senderPublicKey = keyPair.pubKey.toHexKey()
toUsers.plus(senderPublicKey).toSet().forEach {
val gossip = Gossip.create(chatMessageEvent)
val content = Gossip.toJson(gossip)
AmberUtils.content = ""
AmberUtils.encrypt(content, it, SignerType.NIP44_ENCRYPT)
if (AmberUtils.content.isNotBlank()) {
var sealedEvent = SealedGossipEvent.create(
encryptedContent = AmberUtils.content,
pubKey = senderPublicKey
)
AmberUtils.openAmber(sealedEvent)
if (AmberUtils.content.isBlank()) return
sealedEvent = SealedGossipEvent.create(sealedEvent, AmberUtils.content)
val giftWraps = GiftWrapEvent.create(
event = sealedEvent,
recipientPubKey = it
)
broadcastPrivately(listOf(giftWraps))
}
}
} else {
val signedEvents = NIP24Factory().createMsgNIP24( val signedEvents = NIP24Factory().createMsgNIP24(
msg = message, msg = message,
to = toUsers, to = toUsers,
@@ -1390,15 +1502,11 @@ class Account(
markAsSensitive = wantsToMarkAsSensitive, markAsSensitive = wantsToMarkAsSensitive,
zapRaiserAmount = zapRaiserAmount, zapRaiserAmount = zapRaiserAmount,
geohash = geohash, geohash = geohash,
from = keyPair.privKey!! keyPair = keyPair
) )
if (loginWithAmber) {
return signedEvents
}
broadcastPrivately(signedEvents) broadcastPrivately(signedEvents)
return null }
} }
fun broadcastPrivately(signedEvents: List<GiftWrapEvent>) { fun broadcastPrivately(signedEvents: List<GiftWrapEvent>) {
@@ -1407,6 +1515,25 @@ class Account(
// Only keep in cache the GiftWrap for the account. // Only keep in cache the GiftWrap for the account.
if (it.recipientPubKey() == keyPair.pubKey.toHexKey()) { if (it.recipientPubKey() == keyPair.pubKey.toHexKey()) {
if (loginWithAmber) {
AmberUtils.content = ""
AmberUtils.decrypt(it.content, it.pubKey, it.id, SignerType.NIP44_DECRYPT)
val decryptedContent = AmberUtils.cachedDecryptedContent[it.id] ?: ""
if (decryptedContent.isEmpty()) return
it.cachedGift(keyPair.pubKey, decryptedContent)?.let { cached ->
if (cached is SealedGossipEvent) {
AmberUtils.content = ""
AmberUtils.decrypt(cached.content, cached.pubKey, cached.id, SignerType.NIP44_DECRYPT)
val localDecryptedContent = AmberUtils.cachedDecryptedContent[cached.id] ?: ""
if (localDecryptedContent.isEmpty()) return
cached.cachedGossip(keyPair.pubKey, localDecryptedContent)?.let { gossip ->
LocalCache.justConsume(gossip, null)
}
} else {
LocalCache.justConsume(it, null)
}
}
} else {
it.cachedGift(keyPair.privKey!!)?.let { it.cachedGift(keyPair.privKey!!)?.let {
if (it is SealedGossipEvent) { if (it is SealedGossipEvent) {
it.cachedGossip(keyPair.privKey!!)?.let { it.cachedGossip(keyPair.privKey!!)?.let {
@@ -1416,6 +1543,7 @@ class Account(
LocalCache.justConsume(it, null) LocalCache.justConsume(it, null)
} }
} }
}
LocalCache.consume(it, null) LocalCache.consume(it, null)
} }
@@ -2026,7 +2154,7 @@ class Account(
AmberUtils.content AmberUtils.content
} else { } else {
AmberUtils.content = "" AmberUtils.content = ""
AmberUtils.decrypt(content, keyPair.pubKey.toHexKey()) AmberUtils.decrypt(content, keyPair.pubKey.toHexKey(), blockList?.id ?: "")
if (AmberUtils.content.isBlank()) return if (AmberUtils.content.isBlank()) return
val decryptedContent = AmberUtils.content val decryptedContent = AmberUtils.content
AmberUtils.content = "" AmberUtils.content = ""
@@ -2110,7 +2238,7 @@ class Account(
AmberUtils.content AmberUtils.content
} else { } else {
AmberUtils.content = "" AmberUtils.content = ""
AmberUtils.decrypt(content, keyPair.pubKey.toHexKey()) AmberUtils.decrypt(content, keyPair.pubKey.toHexKey(), blockList.id)
if (AmberUtils.content.isBlank()) return if (AmberUtils.content.isBlank()) return
val decryptedContent = AmberUtils.content val decryptedContent = AmberUtils.content
AmberUtils.content = "" AmberUtils.content = ""
@@ -2356,12 +2484,30 @@ class Account(
} }
fun unwrap(event: GiftWrapEvent): Event? { fun unwrap(event: GiftWrapEvent): Event? {
if (!isWriteable()) return null if (!isWriteable() && !loginWithAmber) return null
if (loginWithAmber) {
AmberUtils.content = ""
AmberUtils.decrypt(event.content, event.pubKey, event.id, SignerType.NIP44_DECRYPT)
val decryptedContent = AmberUtils.cachedDecryptedContent[event.id] ?: ""
if (decryptedContent.isEmpty()) return null
return event.cachedGift(keyPair.pubKey, decryptedContent)
}
return event.cachedGift(keyPair.privKey!!) return event.cachedGift(keyPair.privKey!!)
} }
fun unseal(event: SealedGossipEvent): Event? { fun unseal(event: SealedGossipEvent): Event? {
if (!isWriteable()) return null if (!isWriteable() && !loginWithAmber) return null
if (loginWithAmber) {
AmberUtils.content = ""
AmberUtils.decrypt(event.content, event.pubKey, event.id, SignerType.NIP44_DECRYPT)
val decryptedContent = AmberUtils.cachedDecryptedContent[event.id] ?: ""
if (decryptedContent.isEmpty()) return null
return event.cachedGossip(keyPair.pubKey, decryptedContent)
}
return event.cachedGossip(keyPair.privKey!!) return event.cachedGossip(keyPair.privKey!!)
} }
@@ -17,7 +17,8 @@ object AmberUtils {
data: String, data: String,
type: SignerType, type: SignerType,
intentResult: ActivityResultLauncher<Intent>, intentResult: ActivityResultLauncher<Intent>,
pubKey: HexKey pubKey: HexKey,
id: String
) { ) {
ServiceManager.shouldPauseService = false ServiceManager.shouldPauseService = false
val intent = Intent(Intent.ACTION_VIEW, Uri.parse("nostrsigner:$data")) val intent = Intent(Intent.ACTION_VIEW, Uri.parse("nostrsigner:$data"))
@@ -31,6 +32,7 @@ object AmberUtils {
} }
intent.putExtra("type", signerType) intent.putExtra("type", signerType)
intent.putExtra("pubKey", pubKey) intent.putExtra("pubKey", pubKey)
intent.putExtra("id", id)
intent.`package` = "com.greenart7c3.nostrsigner.debug" intent.`package` = "com.greenart7c3.nostrsigner.debug"
intentResult.launch(intent) intentResult.launch(intent)
} }
@@ -44,7 +46,8 @@ object AmberUtils {
event.toJson(), event.toJson(),
SignerType.SIGN_EVENT, SignerType.SIGN_EVENT,
IntentUtils.activityResultLauncher, IntentUtils.activityResultLauncher,
"" "",
event.id()
) )
while (isActivityRunning) { while (isActivityRunning) {
// do nothing // do nothing
@@ -59,6 +62,7 @@ object AmberUtils {
"", "",
SignerType.GET_PUBLIC_KEY, SignerType.GET_PUBLIC_KEY,
IntentUtils.activityResultLauncher, IntentUtils.activityResultLauncher,
"",
"" ""
) )
while (isActivityRunning) { while (isActivityRunning) {
@@ -66,14 +70,15 @@ object AmberUtils {
} }
} }
fun decrypt(encryptedContent: String, pubKey: HexKey) { fun decrypt(encryptedContent: String, pubKey: HexKey, id: String, signerType: SignerType = SignerType.NIP04_DECRYPT) {
if (content.isBlank()) { if (content.isBlank()) {
isActivityRunning = true isActivityRunning = true
openAmber( openAmber(
encryptedContent, encryptedContent,
SignerType.NIP04_DECRYPT, signerType,
IntentUtils.activityResultLauncher, IntentUtils.activityResultLauncher,
pubKey pubKey,
id
) )
while (isActivityRunning) { while (isActivityRunning) {
// do nothing // do nothing
@@ -81,14 +86,15 @@ object AmberUtils {
} }
} }
fun encrypt(decryptedContent: String, pubKey: HexKey) { fun encrypt(decryptedContent: String, pubKey: HexKey, signerType: SignerType = SignerType.NIP04_ENCRYPT) {
if (content.isBlank()) { if (content.isBlank()) {
isActivityRunning = true isActivityRunning = true
openAmber( openAmber(
decryptedContent, decryptedContent,
SignerType.NIP04_ENCRYPT, signerType,
IntentUtils.activityResultLauncher, IntentUtils.activityResultLauncher,
pubKey pubKey,
""
) )
while (isActivityRunning) { while (isActivityRunning) {
// do nothing // do nothing
@@ -34,6 +34,10 @@ object IntentUtils {
} }
val event = it.data?.getStringExtra("signature") ?: "" val event = it.data?.getStringExtra("signature") ?: ""
val id = it.data?.getStringExtra("id") ?: ""
if (id.isNotBlank()) {
AmberUtils.cachedDecryptedContent[id] = event
}
AmberUtils.content = event AmberUtils.content = event
AmberUtils.isActivityRunning = false AmberUtils.isActivityRunning = false
ServiceManager.shouldPauseService = true ServiceManager.shouldPauseService = true
@@ -8,6 +8,7 @@ import com.vitorpamplona.amethyst.service.relays.EOSEAccount
import com.vitorpamplona.amethyst.service.relays.JsonFilter import com.vitorpamplona.amethyst.service.relays.JsonFilter
import com.vitorpamplona.amethyst.service.relays.Relay import com.vitorpamplona.amethyst.service.relays.Relay
import com.vitorpamplona.amethyst.service.relays.TypedFilter import com.vitorpamplona.amethyst.service.relays.TypedFilter
import com.vitorpamplona.amethyst.ui.actions.SignerType
import com.vitorpamplona.quartz.events.AdvertisedRelayListEvent import com.vitorpamplona.quartz.events.AdvertisedRelayListEvent
import com.vitorpamplona.quartz.events.BadgeAwardEvent import com.vitorpamplona.quartz.events.BadgeAwardEvent
import com.vitorpamplona.quartz.events.BadgeProfilesEvent import com.vitorpamplona.quartz.events.BadgeProfilesEvent
@@ -29,6 +30,10 @@ import com.vitorpamplona.quartz.events.RepostEvent
import com.vitorpamplona.quartz.events.SealedGossipEvent import com.vitorpamplona.quartz.events.SealedGossipEvent
import com.vitorpamplona.quartz.events.StatusEvent import com.vitorpamplona.quartz.events.StatusEvent
import com.vitorpamplona.quartz.events.TextNoteEvent import com.vitorpamplona.quartz.events.TextNoteEvent
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
object NostrAccountDataSource : NostrDataSource("AccountData") { object NostrAccountDataSource : NostrDataSource("AccountData") {
lateinit var account: Account lateinit var account: Account
@@ -144,6 +149,7 @@ object NostrAccountDataSource : NostrDataSource("AccountData") {
latestEOSEs.addOrUpdate(account.userProfile(), account.defaultNotificationFollowList, relayUrl, time) latestEOSEs.addOrUpdate(account.userProfile(), account.defaultNotificationFollowList, relayUrl, time)
} }
@OptIn(DelicateCoroutinesApi::class)
override fun consume(event: Event, relay: Relay) { override fun consume(event: Event, relay: Relay) {
if (LocalCache.justVerify(event)) { if (LocalCache.justVerify(event)) {
if (event is GiftWrapEvent) { if (event is GiftWrapEvent) {
@@ -152,6 +158,17 @@ object NostrAccountDataSource : NostrDataSource("AccountData") {
event.cachedGift(privateKey)?.let { event.cachedGift(privateKey)?.let {
this.consume(it, relay) this.consume(it, relay)
} }
} else if (account.loginWithAmber) {
GlobalScope.launch(Dispatchers.IO) {
AmberUtils.content = ""
AmberUtils.decrypt(event.content, event.pubKey, event.id, SignerType.NIP44_DECRYPT)
val decryptedContent = AmberUtils.cachedDecryptedContent[event.id] ?: ""
if (decryptedContent.isNotBlank()) {
event.cachedGift(account.keyPair.pubKey, decryptedContent)?.let {
consume(it, relay)
}
}
}
} }
} }
@@ -161,6 +178,22 @@ object NostrAccountDataSource : NostrDataSource("AccountData") {
event.cachedGossip(privateKey)?.let { event.cachedGossip(privateKey)?.let {
LocalCache.justConsume(it, relay) LocalCache.justConsume(it, relay)
} }
} else if (account.loginWithAmber) {
GlobalScope.launch(Dispatchers.IO) {
AmberUtils.content = ""
AmberUtils.decrypt(
event.content,
event.pubKey,
event.id,
SignerType.NIP44_DECRYPT
)
val decryptedContent = AmberUtils.cachedDecryptedContent[event.id] ?: ""
if (decryptedContent.isNotBlank()) {
event.cachedGossip(account.keyPair.pubKey, decryptedContent)?.let {
LocalCache.justConsume(it, relay)
}
}
}
} }
// Don't store sealed gossips to avoid rebroadcasting by mistake. // Don't store sealed gossips to avoid rebroadcasting by mistake.
@@ -7,8 +7,10 @@ import com.vitorpamplona.amethyst.LocalPreferences
import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.model.Account import com.vitorpamplona.amethyst.model.Account
import com.vitorpamplona.amethyst.model.LocalCache import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.service.AmberUtils
import com.vitorpamplona.amethyst.service.notifications.NotificationUtils.sendDMNotification import com.vitorpamplona.amethyst.service.notifications.NotificationUtils.sendDMNotification
import com.vitorpamplona.amethyst.service.notifications.NotificationUtils.sendZapNotification import com.vitorpamplona.amethyst.service.notifications.NotificationUtils.sendZapNotification
import com.vitorpamplona.amethyst.ui.actions.SignerType
import com.vitorpamplona.amethyst.ui.note.showAmount import com.vitorpamplona.amethyst.ui.note.showAmount
import com.vitorpamplona.quartz.encoders.toHexKey import com.vitorpamplona.quartz.encoders.toHexKey
import com.vitorpamplona.quartz.events.ChatMessageEvent import com.vitorpamplona.quartz.events.ChatMessageEvent
@@ -47,18 +49,60 @@ class EventNotificationConsumer(private val applicationContext: Context) {
return when (event) { return when (event) {
is GiftWrapEvent -> { is GiftWrapEvent -> {
val key = account.keyPair.privKey ?: return null val key = account.keyPair.privKey
if (key != null) {
event.cachedGift(key)?.let { event.cachedGift(key)?.let {
unwrapAndConsume(it, account) unwrapAndConsume(it, account)
} }
} else if (account.loginWithAmber) {
AmberUtils.content = ""
AmberUtils.decrypt(
event.content,
event.pubKey,
event.id,
SignerType.NIP44_DECRYPT
)
val decryptedContent = AmberUtils.cachedDecryptedContent[event.id] ?: ""
if (decryptedContent.isNotBlank()) {
event.cachedGift(account.keyPair.pubKey, decryptedContent)?.let {
LocalCache.justConsume(it, null)
it
}
} else {
null
}
} else {
null
}
} }
is SealedGossipEvent -> { is SealedGossipEvent -> {
val key = account.keyPair.privKey ?: return null val key = account.keyPair.privKey
if (key != null) {
event.cachedGossip(key)?.let { event.cachedGossip(key)?.let {
// this is not verifiable // this is not verifiable
LocalCache.justConsume(it, null) LocalCache.justConsume(it, null)
it it
} }
} else if (account.loginWithAmber) {
AmberUtils.content = ""
AmberUtils.decrypt(
event.content,
event.pubKey,
event.id,
SignerType.NIP44_DECRYPT
)
val decryptedContent = AmberUtils.cachedDecryptedContent[event.id] ?: ""
if (decryptedContent.isNotBlank()) {
event.cachedGossip(account.keyPair.pubKey, decryptedContent)?.let {
LocalCache.justConsume(it, null)
it
}
} else {
null
}
} else {
null
}
} }
else -> { else -> {
LocalCache.justConsume(event, null) LocalCache.justConsume(event, null)
@@ -87,7 +87,7 @@ fun SignerDialog(
) )
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
AmberUtils.openAmber(data, type, intentResult, pubKey) AmberUtils.openAmber(data, type, intentResult, pubKey, "")
} }
Dialog( Dialog(
@@ -178,7 +178,7 @@ fun SignerDialog(
) )
Button( Button(
shape = ButtonBorder, shape = ButtonBorder,
onClick = { AmberUtils.openAmber(data, type, intentResult, pubKey) } onClick = { AmberUtils.openAmber(data, type, intentResult, pubKey, "") }
) { ) {
Text("Open Amber") Text("Open Amber")
} }
@@ -18,7 +18,7 @@ object BookmarkPrivateFeedFilter : FeedFilter<Note>() {
if (account.loginWithAmber) { if (account.loginWithAmber) {
if (AmberUtils.content.isBlank()) { if (AmberUtils.content.isBlank()) {
AmberUtils.decrypt(bookmarks?.content ?: "", account.keyPair.pubKey.toHexKey()) AmberUtils.decrypt(bookmarks?.content ?: "", account.keyPair.pubKey.toHexKey(), "")
bookmarks?.decryptedContent = AmberUtils.content bookmarks?.decryptedContent = AmberUtils.content
} }
@@ -301,7 +301,8 @@ private fun UserRoomCompose(
event?.content() ?: "", event?.content() ?: "",
SignerType.NIP04_DECRYPT, SignerType.NIP04_DECRYPT,
activityLauncher, activityLauncher,
(event as PrivateDmEvent).talkingWith(accountViewModel.userProfile().pubkeyHex) (event as PrivateDmEvent).talkingWith(accountViewModel.userProfile().pubkeyHex),
event.id
) )
} }
} }
@@ -668,7 +668,8 @@ private fun RenderRegularTextNote(
event?.content() ?: "", event?.content() ?: "",
SignerType.NIP04_DECRYPT, SignerType.NIP04_DECRYPT,
activityLauncher, activityLauncher,
(event as PrivateDmEvent).talkingWith(accountViewModel.userProfile().pubkeyHex) (event as PrivateDmEvent).talkingWith(accountViewModel.userProfile().pubkeyHex),
event.id
) )
} }
} }
@@ -1596,7 +1596,8 @@ private fun RenderPrivateMessage(
event?.content() ?: "", event?.content() ?: "",
SignerType.NIP04_DECRYPT, SignerType.NIP04_DECRYPT,
activityLauncher, activityLauncher,
(event as PrivateDmEvent).talkingWith(accountViewModel.userProfile().pubkeyHex) (event as PrivateDmEvent).talkingWith(accountViewModel.userProfile().pubkeyHex),
event.id
) )
} }
} }
@@ -452,7 +452,8 @@ fun NoteDropDownMenu(note: Note, popupExpanded: MutableState<Boolean>, accountVi
val bookmarks = accountViewModel.userProfile().latestBookmarkList val bookmarks = accountViewModel.userProfile().latestBookmarkList
AmberUtils.decrypt( AmberUtils.decrypt(
bookmarks?.content ?: "", bookmarks?.content ?: "",
accountViewModel.account.keyPair.pubKey.toHexKey() accountViewModel.account.keyPair.pubKey.toHexKey(),
bookmarks?.id ?: ""
) )
bookmarks?.decryptedContent = AmberUtils.content bookmarks?.decryptedContent = AmberUtils.content
AmberUtils.content = "" AmberUtils.content = ""
@@ -474,7 +475,8 @@ fun NoteDropDownMenu(note: Note, popupExpanded: MutableState<Boolean>, accountVi
val bookmarks = accountViewModel.userProfile().latestBookmarkList val bookmarks = accountViewModel.userProfile().latestBookmarkList
AmberUtils.decrypt( AmberUtils.decrypt(
bookmarks?.content ?: "", bookmarks?.content ?: "",
accountViewModel.account.keyPair.pubKey.toHexKey() accountViewModel.account.keyPair.pubKey.toHexKey(),
bookmarks?.id ?: ""
) )
bookmarks?.decryptedContent = AmberUtils.content bookmarks?.decryptedContent = AmberUtils.content
AmberUtils.content = "" AmberUtils.content = ""
@@ -497,7 +499,8 @@ fun NoteDropDownMenu(note: Note, popupExpanded: MutableState<Boolean>, accountVi
val bookmarks = accountViewModel.userProfile().latestBookmarkList val bookmarks = accountViewModel.userProfile().latestBookmarkList
AmberUtils.decrypt( AmberUtils.decrypt(
bookmarks?.content ?: "", bookmarks?.content ?: "",
accountViewModel.account.keyPair.pubKey.toHexKey() accountViewModel.account.keyPair.pubKey.toHexKey(),
bookmarks?.id ?: ""
) )
bookmarks?.decryptedContent = AmberUtils.content bookmarks?.decryptedContent = AmberUtils.content
AmberUtils.content = "" AmberUtils.content = ""
@@ -522,7 +525,8 @@ fun NoteDropDownMenu(note: Note, popupExpanded: MutableState<Boolean>, accountVi
val bookmarks = accountViewModel.userProfile().latestBookmarkList val bookmarks = accountViewModel.userProfile().latestBookmarkList
AmberUtils.decrypt( AmberUtils.decrypt(
bookmarks?.content ?: "", bookmarks?.content ?: "",
accountViewModel.account.keyPair.pubKey.toHexKey() accountViewModel.account.keyPair.pubKey.toHexKey(),
bookmarks?.id ?: ""
) )
bookmarks?.decryptedContent = AmberUtils.content bookmarks?.decryptedContent = AmberUtils.content
AmberUtils.content = "" AmberUtils.content = ""
@@ -296,7 +296,6 @@ fun ChatroomScreen(
PrivateMessageEditFieldRow(newPostModel, isPrivate = true, accountViewModel) { PrivateMessageEditFieldRow(newPostModel, isPrivate = true, accountViewModel) {
scope.launch(Dispatchers.IO) { scope.launch(Dispatchers.IO) {
if (newPostModel.nip24 || room.users.size > 1 || replyTo.value?.event is ChatMessageEvent) { if (newPostModel.nip24 || room.users.size > 1 || replyTo.value?.event is ChatMessageEvent) {
// TODO: add support for amber
accountViewModel.account.sendNIP24PrivateMessage( accountViewModel.account.sendNIP24PrivateMessage(
message = newPostModel.message.text, message = newPostModel.message.text,
toUsers = room.users.toList(), toUsers = room.users.toList(),
@@ -639,7 +638,6 @@ fun NewSubjectView(onClose: () -> Unit, accountViewModel: AccountViewModel, room
PostButton( PostButton(
onPost = { onPost = {
scope.launch(Dispatchers.IO) { scope.launch(Dispatchers.IO) {
// TODO: add support for amber
accountViewModel.account.sendNIP24PrivateMessage( accountViewModel.account.sendNIP24PrivateMessage(
message = message.value, message = message.value,
toUsers = room.users.toList(), toUsers = room.users.toList(),
@@ -5,6 +5,7 @@ import androidx.compose.runtime.Stable
import com.vitorpamplona.quartz.utils.TimeUtils import com.vitorpamplona.quartz.utils.TimeUtils
import com.vitorpamplona.quartz.encoders.toHexKey import com.vitorpamplona.quartz.encoders.toHexKey
import com.vitorpamplona.quartz.crypto.CryptoUtils import com.vitorpamplona.quartz.crypto.CryptoUtils
import com.vitorpamplona.quartz.crypto.KeyPair
import com.vitorpamplona.quartz.encoders.HexKey import com.vitorpamplona.quartz.encoders.HexKey
import kotlinx.collections.immutable.ImmutableSet import kotlinx.collections.immutable.ImmutableSet
import kotlinx.collections.immutable.toImmutableSet import kotlinx.collections.immutable.toImmutableSet
@@ -61,7 +62,7 @@ class ChatMessageEvent(
markAsSensitive: Boolean = false, markAsSensitive: Boolean = false,
zapRaiserAmount: Long? = null, zapRaiserAmount: Long? = null,
geohash: String? = null, geohash: String? = null,
privateKey: ByteArray, keyPair: KeyPair,
createdAt: Long = TimeUtils.now() createdAt: Long = TimeUtils.now()
): ChatMessageEvent { ): ChatMessageEvent {
val content = msg val content = msg
@@ -91,10 +92,17 @@ class ChatMessageEvent(
tags.add(listOf("subject", it)) tags.add(listOf("subject", it))
} }
val pubKey = CryptoUtils.pubkeyCreate(privateKey).toHexKey() val pubKey = keyPair.pubKey.toHexKey()
val id = generateId(pubKey, createdAt, ClassifiedsEvent.kind, tags, content) val id = generateId(pubKey, createdAt, ClassifiedsEvent.kind, tags, content)
val sig = CryptoUtils.sign(id, privateKey) val sig = if (keyPair.privKey == null) null else CryptoUtils.sign(id, keyPair.privKey)
return ChatMessageEvent(id.toHexKey(), pubKey, createdAt, tags, content, sig.toHexKey()) return ChatMessageEvent(id.toHexKey(), pubKey, createdAt, tags, content, sig?.toHexKey() ?: "")
}
fun create(
unsignedEvent: ChatMessageEvent,
signature: String
): ChatMessageEvent {
return ChatMessageEvent(unsignedEvent.id, unsignedEvent.pubKey, unsignedEvent.createdAt, unsignedEvent.tags, unsignedEvent.content, signature)
} }
} }
} }
@@ -36,6 +36,19 @@ class GiftWrapEvent(
return myInnerEvent return myInnerEvent
} }
fun cachedGift(pubKey: ByteArray, decryptedContent: String): Event? {
val hex = pubKey.toHexKey()
if (cachedInnerEvent.contains(hex)) return cachedInnerEvent[hex]
val myInnerEvent = unwrap(decryptedContent)
if (myInnerEvent is WrappedEvent) {
myInnerEvent.host = this
}
cachedInnerEvent = cachedInnerEvent + Pair(hex, myInnerEvent)
return myInnerEvent
}
fun unwrap(privKey: ByteArray) = try { fun unwrap(privKey: ByteArray) = try {
plainContent(privKey)?.let { fromJson(it) } plainContent(privKey)?.let { fromJson(it) }
} catch (e: Exception) { } catch (e: Exception) {
@@ -43,6 +56,13 @@ class GiftWrapEvent(
null null
} }
fun unwrap(decryptedContent: String) = try {
plainContent(decryptedContent)?.let { fromJson(it) }
} catch (e: Exception) {
// Log.e("UnwrapError", "Couldn't Decrypt the content", e)
null
}
private fun plainContent(privKey: ByteArray): String? { private fun plainContent(privKey: ByteArray): String? {
if (content.isEmpty()) return null if (content.isEmpty()) return null
@@ -60,6 +80,11 @@ class GiftWrapEvent(
} }
} }
private fun plainContent(decryptedContent: String): String? {
if (decryptedContent.isEmpty()) return null
return decryptedContent
}
fun recipientPubKey() = tags.firstOrNull { it.size > 1 && it[0] == "p" }?.get(1) fun recipientPubKey() = tags.firstOrNull { it.size > 1 && it[0] == "p" }?.get(1)
companion object { companion object {
@@ -9,7 +9,7 @@ class NIP24Factory {
fun createMsgNIP24( fun createMsgNIP24(
msg: String, msg: String,
to: List<HexKey>, to: List<HexKey>,
from: ByteArray, keyPair: KeyPair,
subject: String? = null, subject: String? = null,
replyTos: List<String>? = null, replyTos: List<String>? = null,
mentions: List<String>? = null, mentions: List<String>? = null,
@@ -18,12 +18,12 @@ class NIP24Factory {
zapRaiserAmount: Long? = null, zapRaiserAmount: Long? = null,
geohash: String? = null geohash: String? = null
): List<GiftWrapEvent> { ): List<GiftWrapEvent> {
val senderPublicKey = CryptoUtils.pubkeyCreate(from).toHexKey() val senderPublicKey = keyPair.pubKey.toHexKey()
val senderMessage = ChatMessageEvent.create( val senderMessage = ChatMessageEvent.create(
msg = msg, msg = msg,
to = to, to = to,
privateKey = from, keyPair = keyPair,
subject = subject, subject = subject,
replyTos = replyTos, replyTos = replyTos,
mentions = mentions, mentions = mentions,
@@ -38,7 +38,7 @@ class NIP24Factory {
event = SealedGossipEvent.create( event = SealedGossipEvent.create(
event = senderMessage, event = senderMessage,
encryptTo = it, encryptTo = it,
privateKey = from privateKey = keyPair.privKey!!
), ),
recipientPubKey = it recipientPubKey = it
) )
@@ -46,7 +46,7 @@ class NIP24Factory {
} }
fun createReactionWithinGroup(content: String, originalNote: EventInterface, to: List<HexKey>, from: KeyPair): List<GiftWrapEvent> { fun createReactionWithinGroup(content: String, originalNote: EventInterface, to: List<HexKey>, from: KeyPair): List<GiftWrapEvent> {
val senderPublicKey = CryptoUtils.pubkeyCreate(from.privKey!!).toHexKey() val senderPublicKey = from.pubKey.toHexKey()
val senderReaction = ReactionEvent.create( val senderReaction = ReactionEvent.create(
content, content,
@@ -59,7 +59,7 @@ class NIP24Factory {
event = SealedGossipEvent.create( event = SealedGossipEvent.create(
event = senderReaction, event = senderReaction,
encryptTo = it, encryptTo = it,
privateKey = from.privKey privateKey = from.privKey!!
), ),
recipientPubKey = it recipientPubKey = it
) )
@@ -67,7 +67,7 @@ class NIP24Factory {
} }
fun createReactionWithinGroup(emojiUrl: EmojiUrl, originalNote: EventInterface, to: List<HexKey>, from: KeyPair): List<GiftWrapEvent> { fun createReactionWithinGroup(emojiUrl: EmojiUrl, originalNote: EventInterface, to: List<HexKey>, from: KeyPair): List<GiftWrapEvent> {
val senderPublicKey = CryptoUtils.pubkeyCreate(from.privKey!!).toHexKey() val senderPublicKey = from.pubKey.toHexKey()
val senderReaction = ReactionEvent.create( val senderReaction = ReactionEvent.create(
emojiUrl, emojiUrl,
@@ -80,7 +80,7 @@ class NIP24Factory {
event = SealedGossipEvent.create( event = SealedGossipEvent.create(
event = senderReaction, event = senderReaction,
encryptTo = it, encryptTo = it,
privateKey = from.privKey privateKey = from.privKey!!
), ),
recipientPubKey = it recipientPubKey = it
) )
@@ -38,6 +38,20 @@ class SealedGossipEvent(
return event return event
} }
fun cachedGossip(pubKey: ByteArray, decryptedContent: String): Event? {
val hex = pubKey.toHexKey()
if (cachedInnerEvent.contains(hex)) return cachedInnerEvent[hex]
val gossip = unseal(decryptedContent)
val event = gossip?.mergeWith(this)
if (event is WrappedEvent) {
event.host = host ?: this
}
cachedInnerEvent = cachedInnerEvent + Pair(hex, event)
return event
}
fun unseal(privKey: ByteArray): Gossip? = try { fun unseal(privKey: ByteArray): Gossip? = try {
plainContent(privKey)?.let { Gossip.fromJson(it) } plainContent(privKey)?.let { Gossip.fromJson(it) }
} catch (e: Exception) { } catch (e: Exception) {
@@ -45,6 +59,18 @@ class SealedGossipEvent(
null null
} }
fun unseal(decryptedContent: String): Gossip? = try {
plainContent(decryptedContent)?.let { Gossip.fromJson(it) }
} catch (e: Exception) {
Log.w("GossipEvent", "Fail to decrypt or parse Gossip", e)
null
}
private fun plainContent(decryptedContent: String): String? {
if (decryptedContent.isEmpty()) return null
return decryptedContent
}
private fun plainContent(privKey: ByteArray): String? { private fun plainContent(privKey: ByteArray): String? {
if (content.isEmpty()) return null if (content.isEmpty()) return null
@@ -95,6 +121,23 @@ class SealedGossipEvent(
val sig = CryptoUtils.sign(id, privateKey) val sig = CryptoUtils.sign(id, privateKey)
return SealedGossipEvent(id.toHexKey(), pubKey, createdAt, tags, content, sig.toHexKey()) return SealedGossipEvent(id.toHexKey(), pubKey, createdAt, tags, content, sig.toHexKey())
} }
fun create(
encryptedContent: String,
pubKey: HexKey,
createdAt: Long = TimeUtils.randomWithinAWeek()
): SealedGossipEvent {
val tags = listOf<List<String>>()
val id = generateId(pubKey, createdAt, kind, tags, encryptedContent)
return SealedGossipEvent(id.toHexKey(), pubKey, createdAt, tags, encryptedContent, "")
}
fun create(
unsignedEvent: SealedGossipEvent,
signature: String
): SealedGossipEvent {
return SealedGossipEvent(unsignedEvent.id, unsignedEvent.pubKey, unsignedEvent.createdAt, unsignedEvent.tags, unsignedEvent.content, signature)
}
} }
} }