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.Relay
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.note.combineWith
import com.vitorpamplona.quartz.crypto.CryptoUtils
@@ -114,7 +115,8 @@ class Account(
AmberUtils.content = ""
AmberUtils.decrypt(
content,
keyPair.pubKey.toHexKey()
keyPair.pubKey.toHexKey(),
blockList?.id() ?: ""
)
blockList?.decryptedContent = AmberUtils.content
live.invalidateData()
@@ -282,7 +284,7 @@ class Account(
return
}
if (note.event is ChatMessageEvent && !loginWithAmber) {
if (note.event is ChatMessageEvent) {
val event = note.event as ChatMessageEvent
val users = event.recipientsPubKey().plus(event.pubKey).toSet().toList()
@@ -290,14 +292,48 @@ class Account(
val emojiUrl = EmojiUrl.decode(reaction)
if (emojiUrl != null) {
note.event?.let {
val giftWraps = NIP24Factory().createReactionWithinGroup(
emojiUrl = emojiUrl,
originalNote = it,
to = users,
from = keyPair
)
if (loginWithAmber) {
val senderPublicKey = keyPair.pubKey.toHexKey()
broadcastPrivately(giftWraps)
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(
emojiUrl = emojiUrl,
originalNote = it,
to = users,
from = keyPair
)
broadcastPrivately(giftWraps)
}
}
return
@@ -305,14 +341,51 @@ class Account(
}
note.event?.let {
val giftWraps = NIP24Factory().createReactionWithinGroup(
content = reaction,
originalNote = it,
to = users,
from = keyPair
)
if (loginWithAmber) {
val senderPublicKey = keyPair.pubKey.toHexKey()
broadcastPrivately(giftWraps)
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(
content = reaction,
originalNote = it,
to = users,
from = keyPair
)
broadcastPrivately(giftWraps)
}
}
return
} else {
@@ -1373,32 +1446,67 @@ class Account(
wantsToMarkAsSensitive: Boolean,
zapRaiserAmount: Long? = null,
geohash: String? = null
): List<GiftWrapEvent>? {
// TODO: add support for amber
if (!isWriteable() && !loginWithAmber) return null
) {
if (!isWriteable() && !loginWithAmber) return
val repliesToHex = listOfNotNull(replyingTo?.idHex).ifEmpty { null }
val mentionsHex = mentions?.map { it.pubkeyHex }
val signedEvents = NIP24Factory().createMsgNIP24(
msg = message,
to = toUsers,
subject = subject,
replyTos = repliesToHex,
mentions = mentionsHex,
zapReceiver = zapReceiver,
markAsSensitive = wantsToMarkAsSensitive,
zapRaiserAmount = zapRaiserAmount,
geohash = geohash,
from = keyPair.privKey!!
)
if (loginWithAmber) {
return signedEvents
}
var chatMessageEvent = ChatMessageEvent.create(
msg = message,
to = toUsers,
keyPair = keyPair,
subject = subject,
replyTos = repliesToHex,
mentions = mentionsHex,
zapReceiver = zapReceiver,
markAsSensitive = wantsToMarkAsSensitive,
zapRaiserAmount = zapRaiserAmount,
geohash = geohash
)
broadcastPrivately(signedEvents)
return null
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(
msg = message,
to = toUsers,
subject = subject,
replyTos = repliesToHex,
mentions = mentionsHex,
zapReceiver = zapReceiver,
markAsSensitive = wantsToMarkAsSensitive,
zapRaiserAmount = zapRaiserAmount,
geohash = geohash,
keyPair = keyPair
)
broadcastPrivately(signedEvents)
}
}
fun broadcastPrivately(signedEvents: List<GiftWrapEvent>) {
@@ -1407,13 +1515,33 @@ class Account(
// Only keep in cache the GiftWrap for the account.
if (it.recipientPubKey() == keyPair.pubKey.toHexKey()) {
it.cachedGift(keyPair.privKey!!)?.let {
if (it is SealedGossipEvent) {
it.cachedGossip(keyPair.privKey!!)?.let {
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 {
if (it is SealedGossipEvent) {
it.cachedGossip(keyPair.privKey!!)?.let {
LocalCache.justConsume(it, null)
}
} else {
LocalCache.justConsume(it, null)
}
} else {
LocalCache.justConsume(it, null)
}
}
@@ -2026,7 +2154,7 @@ class Account(
AmberUtils.content
} else {
AmberUtils.content = ""
AmberUtils.decrypt(content, keyPair.pubKey.toHexKey())
AmberUtils.decrypt(content, keyPair.pubKey.toHexKey(), blockList?.id ?: "")
if (AmberUtils.content.isBlank()) return
val decryptedContent = AmberUtils.content
AmberUtils.content = ""
@@ -2110,7 +2238,7 @@ class Account(
AmberUtils.content
} else {
AmberUtils.content = ""
AmberUtils.decrypt(content, keyPair.pubKey.toHexKey())
AmberUtils.decrypt(content, keyPair.pubKey.toHexKey(), blockList.id)
if (AmberUtils.content.isBlank()) return
val decryptedContent = AmberUtils.content
AmberUtils.content = ""
@@ -2356,12 +2484,30 @@ class Account(
}
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!!)
}
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!!)
}
@@ -17,7 +17,8 @@ object AmberUtils {
data: String,
type: SignerType,
intentResult: ActivityResultLauncher<Intent>,
pubKey: HexKey
pubKey: HexKey,
id: String
) {
ServiceManager.shouldPauseService = false
val intent = Intent(Intent.ACTION_VIEW, Uri.parse("nostrsigner:$data"))
@@ -31,6 +32,7 @@ object AmberUtils {
}
intent.putExtra("type", signerType)
intent.putExtra("pubKey", pubKey)
intent.putExtra("id", id)
intent.`package` = "com.greenart7c3.nostrsigner.debug"
intentResult.launch(intent)
}
@@ -44,7 +46,8 @@ object AmberUtils {
event.toJson(),
SignerType.SIGN_EVENT,
IntentUtils.activityResultLauncher,
""
"",
event.id()
)
while (isActivityRunning) {
// do nothing
@@ -59,6 +62,7 @@ object AmberUtils {
"",
SignerType.GET_PUBLIC_KEY,
IntentUtils.activityResultLauncher,
"",
""
)
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()) {
isActivityRunning = true
openAmber(
encryptedContent,
SignerType.NIP04_DECRYPT,
signerType,
IntentUtils.activityResultLauncher,
pubKey
pubKey,
id
)
while (isActivityRunning) {
// 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()) {
isActivityRunning = true
openAmber(
decryptedContent,
SignerType.NIP04_ENCRYPT,
signerType,
IntentUtils.activityResultLauncher,
pubKey
pubKey,
""
)
while (isActivityRunning) {
// do nothing
@@ -34,6 +34,10 @@ object IntentUtils {
}
val event = it.data?.getStringExtra("signature") ?: ""
val id = it.data?.getStringExtra("id") ?: ""
if (id.isNotBlank()) {
AmberUtils.cachedDecryptedContent[id] = event
}
AmberUtils.content = event
AmberUtils.isActivityRunning = false
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.Relay
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.BadgeAwardEvent
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.StatusEvent
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") {
lateinit var account: Account
@@ -144,6 +149,7 @@ object NostrAccountDataSource : NostrDataSource("AccountData") {
latestEOSEs.addOrUpdate(account.userProfile(), account.defaultNotificationFollowList, relayUrl, time)
}
@OptIn(DelicateCoroutinesApi::class)
override fun consume(event: Event, relay: Relay) {
if (LocalCache.justVerify(event)) {
if (event is GiftWrapEvent) {
@@ -152,6 +158,17 @@ object NostrAccountDataSource : NostrDataSource("AccountData") {
event.cachedGift(privateKey)?.let {
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 {
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.
@@ -7,8 +7,10 @@ import com.vitorpamplona.amethyst.LocalPreferences
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.model.Account
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.sendZapNotification
import com.vitorpamplona.amethyst.ui.actions.SignerType
import com.vitorpamplona.amethyst.ui.note.showAmount
import com.vitorpamplona.quartz.encoders.toHexKey
import com.vitorpamplona.quartz.events.ChatMessageEvent
@@ -47,17 +49,59 @@ class EventNotificationConsumer(private val applicationContext: Context) {
return when (event) {
is GiftWrapEvent -> {
val key = account.keyPair.privKey ?: return null
event.cachedGift(key)?.let {
unwrapAndConsume(it, account)
val key = account.keyPair.privKey
if (key != null) {
event.cachedGift(key)?.let {
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 -> {
val key = account.keyPair.privKey ?: return null
event.cachedGossip(key)?.let {
// this is not verifiable
LocalCache.justConsume(it, null)
it
val key = account.keyPair.privKey
if (key != null) {
event.cachedGossip(key)?.let {
// this is not verifiable
LocalCache.justConsume(it, null)
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 -> {
@@ -87,7 +87,7 @@ fun SignerDialog(
)
LaunchedEffect(Unit) {
AmberUtils.openAmber(data, type, intentResult, pubKey)
AmberUtils.openAmber(data, type, intentResult, pubKey, "")
}
Dialog(
@@ -178,7 +178,7 @@ fun SignerDialog(
)
Button(
shape = ButtonBorder,
onClick = { AmberUtils.openAmber(data, type, intentResult, pubKey) }
onClick = { AmberUtils.openAmber(data, type, intentResult, pubKey, "") }
) {
Text("Open Amber")
}
@@ -18,7 +18,7 @@ object BookmarkPrivateFeedFilter : FeedFilter<Note>() {
if (account.loginWithAmber) {
if (AmberUtils.content.isBlank()) {
AmberUtils.decrypt(bookmarks?.content ?: "", account.keyPair.pubKey.toHexKey())
AmberUtils.decrypt(bookmarks?.content ?: "", account.keyPair.pubKey.toHexKey(), "")
bookmarks?.decryptedContent = AmberUtils.content
}
@@ -301,7 +301,8 @@ private fun UserRoomCompose(
event?.content() ?: "",
SignerType.NIP04_DECRYPT,
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() ?: "",
SignerType.NIP04_DECRYPT,
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() ?: "",
SignerType.NIP04_DECRYPT,
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
AmberUtils.decrypt(
bookmarks?.content ?: "",
accountViewModel.account.keyPair.pubKey.toHexKey()
accountViewModel.account.keyPair.pubKey.toHexKey(),
bookmarks?.id ?: ""
)
bookmarks?.decryptedContent = AmberUtils.content
AmberUtils.content = ""
@@ -474,7 +475,8 @@ fun NoteDropDownMenu(note: Note, popupExpanded: MutableState<Boolean>, accountVi
val bookmarks = accountViewModel.userProfile().latestBookmarkList
AmberUtils.decrypt(
bookmarks?.content ?: "",
accountViewModel.account.keyPair.pubKey.toHexKey()
accountViewModel.account.keyPair.pubKey.toHexKey(),
bookmarks?.id ?: ""
)
bookmarks?.decryptedContent = AmberUtils.content
AmberUtils.content = ""
@@ -497,7 +499,8 @@ fun NoteDropDownMenu(note: Note, popupExpanded: MutableState<Boolean>, accountVi
val bookmarks = accountViewModel.userProfile().latestBookmarkList
AmberUtils.decrypt(
bookmarks?.content ?: "",
accountViewModel.account.keyPair.pubKey.toHexKey()
accountViewModel.account.keyPair.pubKey.toHexKey(),
bookmarks?.id ?: ""
)
bookmarks?.decryptedContent = AmberUtils.content
AmberUtils.content = ""
@@ -522,7 +525,8 @@ fun NoteDropDownMenu(note: Note, popupExpanded: MutableState<Boolean>, accountVi
val bookmarks = accountViewModel.userProfile().latestBookmarkList
AmberUtils.decrypt(
bookmarks?.content ?: "",
accountViewModel.account.keyPair.pubKey.toHexKey()
accountViewModel.account.keyPair.pubKey.toHexKey(),
bookmarks?.id ?: ""
)
bookmarks?.decryptedContent = AmberUtils.content
AmberUtils.content = ""
@@ -296,7 +296,6 @@ fun ChatroomScreen(
PrivateMessageEditFieldRow(newPostModel, isPrivate = true, accountViewModel) {
scope.launch(Dispatchers.IO) {
if (newPostModel.nip24 || room.users.size > 1 || replyTo.value?.event is ChatMessageEvent) {
// TODO: add support for amber
accountViewModel.account.sendNIP24PrivateMessage(
message = newPostModel.message.text,
toUsers = room.users.toList(),
@@ -639,7 +638,6 @@ fun NewSubjectView(onClose: () -> Unit, accountViewModel: AccountViewModel, room
PostButton(
onPost = {
scope.launch(Dispatchers.IO) {
// TODO: add support for amber
accountViewModel.account.sendNIP24PrivateMessage(
message = message.value,
toUsers = room.users.toList(),