New interface for encryption/decryption events

This commit is contained in:
greenart7c3
2025-10-31 05:24:50 -03:00
parent b1a2449b58
commit dcc5391077
10 changed files with 297 additions and 79 deletions
@@ -0,0 +1,15 @@
package com.greenart7c3.nostrsigner.models
import com.greenart7c3.nostrsigner.service.model.AmberEvent
interface EncryptedDataKind {
val result: String
}
class ClearTextEncryptedDataKind(val text: String, override val result: String) : EncryptedDataKind
class TagArrayEncryptedDataKind(val tagArray: Array<Array<String>>, override val result: String) : EncryptedDataKind
class EventEncryptedDataKind(val event: AmberEvent, override val result: String) : EncryptedDataKind
class PrivateZapEncryptedDataKind(override val result: String) : EncryptedDataKind
@@ -20,7 +20,7 @@ data class IntentData(
val rememberType: MutableState<RememberType>,
val route: String?,
val event: Event?,
val encryptedData: String?,
val encryptedData: EncryptedDataKind?,
val isNostrConnectURI: Boolean = false,
val unsignedEventKey: HexKey = "",
)
@@ -101,6 +101,7 @@ data class Permission(
11 -> context.getString(R.string.event_kind_11)
12 -> context.getString(R.string.event_kind_12)
13 -> context.getString(R.string.event_kind_13)
14 -> context.getString(R.string.event_kind_14)
16 -> context.getString(R.string.event_kind_16)
17 -> context.getString(R.string.event_kind_17)
20 -> context.getString(R.string.event_kind_20)
@@ -25,17 +25,22 @@ import com.greenart7c3.nostrsigner.database.HistoryEntity
import com.greenart7c3.nostrsigner.database.LogEntity
import com.greenart7c3.nostrsigner.models.Account
import com.greenart7c3.nostrsigner.models.AmberBunkerRequest
import com.greenart7c3.nostrsigner.models.ClearTextEncryptedDataKind
import com.greenart7c3.nostrsigner.models.CompressionType
import com.greenart7c3.nostrsigner.models.EventEncryptedDataKind
import com.greenart7c3.nostrsigner.models.IntentData
import com.greenart7c3.nostrsigner.models.IntentResultType
import com.greenart7c3.nostrsigner.models.Permission
import com.greenart7c3.nostrsigner.models.PrivateZapEncryptedDataKind
import com.greenart7c3.nostrsigner.models.ReturnType
import com.greenart7c3.nostrsigner.models.SignerType
import com.greenart7c3.nostrsigner.models.TagArrayEncryptedDataKind
import com.greenart7c3.nostrsigner.models.containsNip
import com.greenart7c3.nostrsigner.service.model.AmberEvent
import com.greenart7c3.nostrsigner.ui.RememberType
import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.nip01Core.crypto.EventHasher
import com.vitorpamplona.quartz.nip01Core.jackson.JacksonMapper
import com.vitorpamplona.quartz.nip19Bech32.toNpub
import com.vitorpamplona.quartz.utils.Hex
import com.vitorpamplona.quartz.utils.TimeUtils
@@ -190,6 +195,32 @@ object IntentUtils {
"Could not decrypt the message"
}
val encryptedDataKind = when (type) {
else -> {
if (type.name.contains("ENCRYPT")) {
if (localData.startsWith("{")) {
val event = AmberEvent.fromJson(localData)
EventEncryptedDataKind(event, result)
} else if (localData.startsWith("[")) {
val tagArray = JacksonMapper.fromJsonToTagArray(localData)
TagArrayEncryptedDataKind(tagArray, result)
} else {
ClearTextEncryptedDataKind(localData, result)
}
} else {
if (result.startsWith("{")) {
val event = AmberEvent.fromJson(result)
EventEncryptedDataKind(event, result)
} else if (result.startsWith("[")) {
val tagArray = JacksonMapper.fromJsonToTagArray(result)
TagArrayEncryptedDataKind(tagArray, result)
} else {
ClearTextEncryptedDataKind(localData, result)
}
}
}
}
IntentData(
data = localData,
name = appName,
@@ -205,7 +236,7 @@ object IntentUtils {
rememberType = mutableStateOf(RememberType.NEVER),
route = route,
event = null,
encryptedData = result,
encryptedData = encryptedDataKind,
)
}
SignerType.GET_PUBLIC_KEY -> {
@@ -378,6 +409,35 @@ object IntentUtils {
npub = parsePubKey(npub)
}
val encryptedDataKind = when (type) {
SignerType.DECRYPT_ZAP_EVENT -> {
PrivateZapEncryptedDataKind(result)
}
else -> {
if (type.name.contains("ENCRYPT")) {
if (data.startsWith("{")) {
val event = AmberEvent.fromJson(data)
EventEncryptedDataKind(event, result)
} else if (data.startsWith("[")) {
val tagArray = JacksonMapper.fromJsonToTagArray(data)
TagArrayEncryptedDataKind(tagArray, result)
} else {
ClearTextEncryptedDataKind(data, result)
}
} else {
if (result.startsWith("{")) {
val event = AmberEvent.fromJson(result)
EventEncryptedDataKind(event, result)
} else if (result.startsWith("[")) {
val tagArray = JacksonMapper.fromJsonToTagArray(result)
TagArrayEncryptedDataKind(tagArray, result)
} else {
ClearTextEncryptedDataKind(data, result)
}
}
}
}
IntentData(
data = data,
name = name,
@@ -393,7 +453,7 @@ object IntentUtils {
rememberType = mutableStateOf(RememberType.NEVER),
route = route,
event = null,
encryptedData = result,
encryptedData = encryptedDataKind,
)
}
SignerType.GET_PUBLIC_KEY -> {
@@ -17,6 +17,7 @@ import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.nip01Core.core.HexKey
import com.vitorpamplona.quartz.nip01Core.jackson.JacksonMapper
import com.vitorpamplona.quartz.nip01Core.jackson.toTypedArray
import com.vitorpamplona.quartz.nip02FollowList.tags.ContactTag
import com.vitorpamplona.quartz.utils.TimeUtils
@Immutable
@@ -60,7 +61,7 @@ open class AmberEvent(
companion object {
fun fromJson(jsonObject: JsonNode): AmberEvent =
AmberEvent(
id = jsonObject.get("id")?.asText()?.intern() ?: "",
id = jsonObject.get("id")?.asText() ?: "",
pubKey = jsonObject.get("pubkey")?.asText()?.intern() ?: "",
createdAt = jsonObject.get("created_at")?.asLong() ?: TimeUtils.now(),
kind = jsonObject.get("kind").asInt(),
@@ -73,6 +74,12 @@ open class AmberEvent(
}
}
fun relay(): String {
return tags.filter { it.size > 1 && it[0] == "relay" }.map { it[1] }.first()
}
fun verifiedFollowKeySet(): Set<HexKey> = tags.mapNotNullTo(HashSet(), ContactTag::parseValidKey)
companion object {
val mapper: ObjectMapper =
jacksonObjectMapper()
@@ -26,6 +26,8 @@ import androidx.compose.ui.text.intl.Locale
import androidx.compose.ui.text.toLowerCase
import androidx.compose.ui.unit.dp
import com.greenart7c3.nostrsigner.R
import com.greenart7c3.nostrsigner.models.ClearTextEncryptedDataKind
import com.greenart7c3.nostrsigner.models.EventEncryptedDataKind
import com.greenart7c3.nostrsigner.models.Permission
import com.greenart7c3.nostrsigner.models.SignerType
import com.greenart7c3.nostrsigner.service.BunkerRequestUtils
@@ -122,7 +124,7 @@ fun SeeDetailsScreen(
val event = intent.event!!
if (event.kind == 22242) AmberEvent.relay(event) else event.content
} else {
intent.encryptedData ?: intent.data
if (type.name.contains("ENCRYPT") && intent.encryptedData is ClearTextEncryptedDataKind) intent.encryptedData.text else if (intent.encryptedData is EventEncryptedDataKind) intent.encryptedData.event.content else intent.encryptedData?.result ?: ""
}
Text(
@@ -23,24 +23,32 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.capitalize
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.intl.Locale
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.text.withStyle
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.greenart7c3.nostrsigner.Amber
import com.greenart7c3.nostrsigner.R
import com.greenart7c3.nostrsigner.models.Account
import com.greenart7c3.nostrsigner.models.ClearTextEncryptedDataKind
import com.greenart7c3.nostrsigner.models.EncryptedDataKind
import com.greenart7c3.nostrsigner.models.EventEncryptedDataKind
import com.greenart7c3.nostrsigner.models.Permission
import com.greenart7c3.nostrsigner.models.SignerType
import com.greenart7c3.nostrsigner.models.TagArrayEncryptedDataKind
import com.greenart7c3.nostrsigner.ui.RememberType
import com.vitorpamplona.quartz.nip02FollowList.ContactListEvent
import kotlinx.coroutines.launch
@Composable
fun EncryptDecryptData(
account: Account,
modifier: Modifier,
content: String,
encryptedData: String,
encryptedData: EncryptedDataKind?,
shouldRunOnAccept: Boolean?,
packageName: String?,
applicationName: String?,
@@ -49,12 +57,6 @@ fun EncryptDecryptData(
onAccept: (RememberType) -> Unit,
onReject: (RememberType) -> Unit,
) {
var showMore by androidx.compose.runtime.remember {
mutableStateOf(false)
}
val clipboardManager = LocalClipboard.current
val coroutineScope = rememberCoroutineScope()
val context = LocalContext.current
var rememberType by remember {
mutableStateOf(RememberType.NEVER)
}
@@ -64,16 +66,6 @@ fun EncryptDecryptData(
) {
ProfilePicture(account)
val message =
when (type) {
SignerType.NIP44_ENCRYPT -> stringResource(R.string.encrypt_nip44)
SignerType.NIP04_ENCRYPT -> stringResource(R.string.encrypt_nip04)
SignerType.NIP44_DECRYPT -> stringResource(R.string.decrypt_nip44)
SignerType.NIP04_DECRYPT -> stringResource(R.string.decrypt_nip04)
SignerType.DECRYPT_ZAP_EVENT -> stringResource(R.string.decrypt_zap_event)
else -> stringResource(R.string.encrypt_decrypt)
}
packageName?.let {
Text(
modifier = Modifier
@@ -91,26 +83,68 @@ fun EncryptDecryptData(
withStyle(style = SpanStyle(fontWeight = FontWeight.Bold)) {
append(applicationName ?: appName)
}
append(" requests $message")
if (type.name.contains("ENCRYPT")) {
when (encryptedData) {
is EventEncryptedDataKind -> {
val permission = Permission("sign_event", encryptedData.event.kind)
append(stringResource(R.string.wants_to_encrypt_with, permission.toLocalizedString(Amber.instance), type.name.split("_").first()))
}
is TagArrayEncryptedDataKind -> {
append(stringResource(R.string.wants_to_encrypt_this_list_of_tags_with, type.name.split("_").first()))
}
else -> append(stringResource(R.string.wants_to_encrypt_this_text_with, type.name.split("_").first()))
}
} else {
when (encryptedData) {
is EventEncryptedDataKind -> {
val permission = Permission("sign_event", encryptedData.event.kind)
append(stringResource(R.string.wants_to_read_from_encrypted_content, permission.toLocalizedString(Amber.instance), type.name.split("_").first()))
}
is TagArrayEncryptedDataKind -> {
append(stringResource(R.string.wants_to_read_this_list_of_tags_from_encrypted_content, type.name.split("_").first()))
}
else -> append(stringResource(R.string.wants_to_read_this_text_from_encrypted_content, type.name.split("_").first()))
}
}
},
fontSize = 18.sp,
)
Spacer(Modifier.size(4.dp))
if (!type.toString().contains("DECRYPT")) {
Card(
modifier = Modifier
.fillMaxWidth(),
) {
Column(Modifier.padding(6.dp)) {
Text(
"Event content",
fontWeight = FontWeight.Bold,
)
Card(
modifier = Modifier
.fillMaxWidth(),
) {
Column(Modifier.padding(6.dp)) {
Text(
stringResource(R.string.content).capitalize(Locale.current),
fontWeight = FontWeight.Bold,
)
if (encryptedData is EventEncryptedDataKind) {
val content = if (encryptedData.event.kind == 22242) encryptedData.event.relay() else encryptedData.event.content
if (encryptedData.event.kind == ContactListEvent.KIND) {
ContactListDetail(
title = stringResource(R.string.following),
text = "${encryptedData.event.verifiedFollowKeySet().size}",
)
} else {
Text(
content,
modifier = Modifier
.fillMaxWidth()
.padding(top = 8.dp),
)
}
} else {
val content = if (type.name.contains("ENCRYPT") && encryptedData is ClearTextEncryptedDataKind) encryptedData.text else encryptedData?.result ?: ""
Text(
content,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
modifier = Modifier
.fillMaxWidth()
.padding(top = 8.dp),
@@ -118,37 +152,8 @@ fun EncryptDecryptData(
}
}
}
RawJsonButton(
onCLick = {
showMore = !showMore
},
if (!showMore) stringResource(R.string.show_details) else stringResource(R.string.hide_details),
)
if (showMore) {
RawJson(
content,
encryptedData,
Modifier.height(200.dp),
stringResource(R.string.encrypted_decrypted_data),
type,
) {
coroutineScope.launch {
clipboardManager.setClipEntry(
ClipEntry(
ClipData.newPlainText("", encryptedData),
),
)
Toast.makeText(
context,
context.getString(R.string.data_copied_to_the_clipboard),
Toast.LENGTH_SHORT,
).show()
}
}
} else {
Spacer(modifier = Modifier.weight(1f))
}
Spacer(modifier = Modifier.weight(1f))
RememberMyChoice(
shouldRunOnAccept,
@@ -188,8 +188,7 @@ fun IntentSingleEventHomeScreen(
EncryptDecryptData(
account = account,
modifier = modifier,
content = intentData.data,
encryptedData = intentData.encryptedData ?: "",
encryptedData = intentData.encryptedData,
shouldRunOnAccept = acceptOrReject,
packageName = packageName,
applicationName = applicationName,
@@ -197,10 +196,10 @@ fun IntentSingleEventHomeScreen(
type = intentData.type,
onAccept = {
val result =
if (intentData.encryptedData == "Could not decrypt the message" && (intentData.type == SignerType.DECRYPT_ZAP_EVENT)) {
if (intentData.encryptedData?.result == "Could not decrypt the message" && (intentData.type == SignerType.DECRYPT_ZAP_EVENT)) {
""
} else {
intentData.encryptedData ?: ""
intentData.encryptedData?.result ?: ""
}
IntentUtils.sendResult(
@@ -48,10 +48,14 @@ import com.greenart7c3.nostrsigner.database.ApplicationWithPermissions
import com.greenart7c3.nostrsigner.database.HistoryEntity
import com.greenart7c3.nostrsigner.models.Account
import com.greenart7c3.nostrsigner.models.AmberBunkerRequest
import com.greenart7c3.nostrsigner.models.ClearTextEncryptedDataKind
import com.greenart7c3.nostrsigner.models.EventEncryptedDataKind
import com.greenart7c3.nostrsigner.models.IntentData
import com.greenart7c3.nostrsigner.models.IntentResultType
import com.greenart7c3.nostrsigner.models.Permission
import com.greenart7c3.nostrsigner.models.PrivateZapEncryptedDataKind
import com.greenart7c3.nostrsigner.models.SignerType
import com.greenart7c3.nostrsigner.models.TagArrayEncryptedDataKind
import com.greenart7c3.nostrsigner.service.AmberUtils
import com.greenart7c3.nostrsigner.service.ApplicationNameCache
import com.greenart7c3.nostrsigner.service.BunkerRequestUtils
@@ -116,14 +120,41 @@ fun IntentMultiEventHomeScreen(
onLoading: (Boolean) -> Unit,
) {
val context = LocalContext.current
val grouped = intents.groupBy { it.type }.filter { it.key != SignerType.SIGN_EVENT }
val grouped2 = intents.filter { it.type == SignerType.SIGN_EVENT }.groupBy { it.event?.kind }
val acceptEventsGroup1 = grouped.map {
val groupedEventEncryptedData = intents.filter { it.encryptedData is EventEncryptedDataKind }.groupBy { it.type }
val groupedTextEncryptedData = intents.filter { it.encryptedData is ClearTextEncryptedDataKind }.groupBy { it.type }
val groupedTagArrayEncryptedData = intents.filter { it.encryptedData is TagArrayEncryptedDataKind }.groupBy { it.type }
val groupedPrivateZapEncryptedDataKind = intents.filter { it.encryptedData is PrivateZapEncryptedDataKind }.groupBy { it.type }
val groupedOthers = intents.filter { it.encryptedData == null && it.type != SignerType.SIGN_EVENT }.groupBy { it.type }
val groupedEvents = intents.filter { it.type == SignerType.SIGN_EVENT }.groupBy { it.event?.kind }
val acceptedGroupedEventEncryptedData = groupedEventEncryptedData.map {
remember {
mutableStateOf(true)
}
}
val acceptEventsGroup2 = grouped2.map {
val acceptedGroupedTextEncryptedData = groupedTextEncryptedData.map {
remember {
mutableStateOf(true)
}
}
val acceptedGroupedTagArrayEncryptedData = groupedTagArrayEncryptedData.map {
remember {
mutableStateOf(true)
}
}
val acceptedGroupedPrivateZapEncryptedDataKind = groupedPrivateZapEncryptedDataKind.map {
remember {
mutableStateOf(true)
}
}
val acceptEventsGroupOthers = groupedOthers.map {
remember {
mutableStateOf(true)
}
}
val acceptEventsGroup2 = groupedEvents.map {
remember {
mutableStateOf(true)
}
@@ -153,10 +184,10 @@ fun IntentMultiEventHomeScreen(
.padding(bottom = 20.dp),
)
grouped.toList().forEachIndexed { index, it ->
groupedEventEncryptedData.toList().forEachIndexed { index, it ->
PermissionCard(
context = context,
acceptEventsGroup = acceptEventsGroup1,
acceptEventsGroup = acceptedGroupedEventEncryptedData,
index = index,
item = it,
onDetailsClick = {
@@ -166,7 +197,59 @@ fun IntentMultiEventHomeScreen(
},
)
}
grouped2.toList().forEachIndexed { index, it ->
groupedTextEncryptedData.toList().forEachIndexed { index, it ->
PermissionCard(
context = context,
acceptEventsGroup = acceptedGroupedTextEncryptedData,
index = index,
item = it,
onDetailsClick = {
MultiEventScreenIntents.intents = it
MultiEventScreenIntents.appName = appName
navController.navigate(Route.SeeDetails.route)
},
)
}
groupedTagArrayEncryptedData.toList().forEachIndexed { index, it ->
PermissionCard(
context = context,
acceptEventsGroup = acceptedGroupedTagArrayEncryptedData,
index = index,
item = it,
onDetailsClick = {
MultiEventScreenIntents.intents = it
MultiEventScreenIntents.appName = appName
navController.navigate(Route.SeeDetails.route)
},
)
}
groupedPrivateZapEncryptedDataKind.toList().forEachIndexed { index, it ->
PermissionCard(
context = context,
acceptEventsGroup = acceptedGroupedPrivateZapEncryptedDataKind,
index = index,
item = it,
onDetailsClick = {
MultiEventScreenIntents.intents = it
MultiEventScreenIntents.appName = appName
navController.navigate(Route.SeeDetails.route)
},
)
}
groupedOthers.toList().forEachIndexed { index, it ->
PermissionCard(
context = context,
acceptEventsGroup = acceptEventsGroupOthers,
index = index,
item = it,
onDetailsClick = {
MultiEventScreenIntents.intents = it
MultiEventScreenIntents.appName = appName
navController.navigate(Route.SeeDetails.route)
},
)
}
groupedEvents.toList().forEachIndexed { index, it ->
PermissionCard(
context = context,
acceptEventsGroup = acceptEventsGroup2,
@@ -373,7 +456,7 @@ fun IntentMultiEventHomeScreen(
thisAccount.npub,
)
val signature = intentData.encryptedData ?: continue
val signature = intentData.encryptedData?.result ?: continue
if (intentData.checked.value) {
results.add(
Result(
@@ -1004,7 +1087,38 @@ fun PermissionCard(
val message = if (first == SignerType.CONNECT) {
stringResource(R.string.connect)
} else {
permission.toLocalizedString(context)
val encryptedData = item.second.first().encryptedData
if (first.toString().contains("ENCRYPT")) {
when (encryptedData) {
is EventEncryptedDataKind -> {
val permission = Permission("sign_event", encryptedData.event.kind)
stringResource(R.string.encrypt_with, permission.toLocalizedString(context), first.toString().split("_").first())
}
is TagArrayEncryptedDataKind -> {
stringResource(R.string.encrypt_this_list_of_tags_with, first.toString().split("_").first())
}
else -> stringResource(R.string.encrypt_this_text_with, first.toString().split("_").first())
}
} else {
if (first.toString().contains("DECRYPT")) {
when (encryptedData) {
is EventEncryptedDataKind -> {
val permission = Permission("sign_event", encryptedData.event.kind)
stringResource(R.string.read_from_encrypted_content, permission.toLocalizedString(context), first.toString().split("_").first())
}
is TagArrayEncryptedDataKind -> {
stringResource(R.string.read_this_list_of_tags_from_encrypted_content, first.toString().split("_").first())
}
else -> stringResource(R.string.read_this_text_from_encrypted_content, first.toString().split("_").first())
}
} else {
permission.toLocalizedString(context)
}
}
}
Text(
+15
View File
@@ -579,4 +579,19 @@
<string name="would_you_like_to_send_the_recent_crash_report_to_amber_in_a_dm_no_personal_information_will_be_shared">Would you like to send the recent crash report to Amber in a DM? No personal information will be shared</string>
<string name="copy_crash_report_to_clipboard">Would you like to copy the crash report to your clipboard and open a Client to send it to Amber?</string>
<string name="crashreport_found_send">Send it</string>
<string name="event_content">Event content</string>
<string name="event_kind_14">Direct Message</string>
<string name="wants_to_encrypt_with">" wants to encrypt %1$s with %2$s"</string>
<string name="wants_to_encrypt_this_list_of_tags_with">" wants to encrypt this list of tags with %1$s"</string>
<string name="wants_to_encrypt_this_text_with">" wants to encrypt this text with %1$s"</string>
<string name="wants_to_read_from_encrypted_content">" wants to read %1$s from %2$s encrypted content"</string>
<string name="wants_to_read_this_list_of_tags_from_encrypted_content">" wants to read this list of tags from %1$s encrypted content"</string>
<string name="wants_to_read_this_text_from_encrypted_content">" wants to read this text from %1$s encrypted content"</string>
<string name="encrypt_with">Encrypt %1$s with %2$s</string>
<string name="encrypt_this_list_of_tags_with">Encrypt this list of tags with %1$s</string>
<string name="encrypt_this_text_with">Encrypt this text with %1$s</string>
<string name="read_from_encrypted_content">Read %1$s from %2$s encrypted content</string>
<string name="read_this_list_of_tags_from_encrypted_content">Read this list of tags from %1$s encrypted content</string>
<string name="read_this_text_from_encrypted_content">Read this text from %1$s encrypted content</string>
</resources>