Refactor encrypt/decrypt permissions to be by EncryptedDataKind

Instead of storing permissions as nip04_encrypt, nip44_encrypt, etc.
(grouped by encryption algorithm), permissions are now stored based on
the type of data being encrypted or decrypted:

- ENCRYPT_CLEAR_TEXT / DECRYPT_CLEAR_TEXT (text messages)
- ENCRYPT_EVENT / DECRYPT_EVENT (nostr events)
- ENCRYPT_TAG_ARRAY / DECRYPT_TAG_ARRAY (tag arrays)
- DECRYPT_ZAP_EVENT (private zap events, unchanged)

This allows more granular permission control based on what data is
being processed rather than which NIP algorithm is used. Backward
compatibility is maintained by falling back to old NIP-based
permission types during lookup.

Key changes:
- EncryptedDataKind.kt: add toPermissionType(), toPermissionTypeString(),
  permissionTypeFromContent() helpers
- AmberUtils.acceptPermission(): use EncryptedDataKind-based type when storing
- BunkerRequestUtils/IntentUtils: pass encryptedData to acceptPermission
- BunkerSingleEventHomeScreen/IntentSingleEventHomeScreen: look up permissions
  by EncryptedDataKind type with NIP-based fallbacks
- SignerProvider: classify content to determine permission type before lookup
- BasicPermissions/Permission: add new permission types and string resources

https://claude.ai/code/session_01Q7XBgjPSfeVdiQ3smLw7YC
This commit is contained in:
Claude
2026-03-13 14:59:01 +00:00
parent 0114167eb4
commit 145ff51559
10 changed files with 192 additions and 35 deletions
@@ -10,6 +10,7 @@ import com.greenart7c3.nostrsigner.database.HistoryEntity
import com.greenart7c3.nostrsigner.database.LogEntity
import com.greenart7c3.nostrsigner.models.SignerType
import com.greenart7c3.nostrsigner.models.kindToNip
import com.greenart7c3.nostrsigner.models.permissionTypeFromContent
import com.greenart7c3.nostrsigner.service.AmberUtils
import com.greenart7c3.nostrsigner.service.IntentUtils
import com.greenart7c3.nostrsigner.service.model.AmberEvent
@@ -287,13 +288,59 @@ class SignerProvider : ContentProvider() {
val database = Amber.instance.getDatabase(account.npub)
val logDatabase = Amber.instance.getLogDatabase(account.npub)
val historyDatabase = Amber.instance.getHistoryDatabase(account.npub)
var permission =
database
.dao()
.getPermission(
packageName,
uri.toString().replace("content://$appId.", ""),
)
val type =
when (stringType) {
"NIP04_DECRYPT" -> SignerType.NIP04_DECRYPT
"NIP44_DECRYPT" -> SignerType.NIP44_DECRYPT
"NIP04_ENCRYPT" -> SignerType.NIP04_ENCRYPT
"NIP44_ENCRYPT" -> SignerType.NIP44_ENCRYPT
"DECRYPT_ZAP_EVENT" -> SignerType.DECRYPT_ZAP_EVENT
else -> null
} ?: return null
val isEncrypt = type == SignerType.NIP04_ENCRYPT || type == SignerType.NIP44_ENCRYPT
// For ENCRYPT: classify plaintext input; for DECRYPT: perform operation first then classify result
val result =
if (isEncrypt) {
null // defer until after permission check
} else {
try {
runBlocking {
AmberUtils.encryptOrDecryptData(
content,
type,
account,
pubkey,
) ?: "Could not decrypt the message"
}
} catch (e: Exception) {
scope.launch {
logDatabase.dao().insertLog(
LogEntity(
0,
packageName,
uri.toString().replace("content://$appId.", ""),
e.message ?: "Could not decrypt the message",
System.currentTimeMillis(),
),
)
}
"Could not decrypt the message"
}
}
// Classify the content to determine EncryptedDataKind-based permission type
val classifyContent = if (isEncrypt) content else (result ?: content)
val permType = permissionTypeFromContent(classifyContent, isEncrypt, type)
var permission = database.dao().getPermission(packageName, permType)
if (permission == null) {
permission = database.dao().getPermission(
packageName,
type.toString(),
)
}
if (permission == null) {
val nip = when (stringType) {
"NIP04_DECRYPT" -> 4
@@ -340,18 +387,9 @@ class SignerProvider : ContentProvider() {
return cursor
}
val type =
when (stringType) {
"NIP04_DECRYPT" -> SignerType.NIP04_DECRYPT
"NIP44_DECRYPT" -> SignerType.NIP44_DECRYPT
"NIP04_ENCRYPT" -> SignerType.NIP04_ENCRYPT
"NIP44_ENCRYPT" -> SignerType.NIP44_ENCRYPT
"DECRYPT_ZAP_EVENT" -> SignerType.DECRYPT_ZAP_EVENT
else -> null
} ?: return null
val result =
try {
// For encrypt: perform the operation now after permission is confirmed
val finalResult =
result ?: try {
runBlocking {
AmberUtils.encryptOrDecryptData(
content,
@@ -384,14 +422,14 @@ class SignerProvider : ContentProvider() {
null,
TimeUtils.now(),
true,
content = if (type == SignerType.NIP04_DECRYPT || type == SignerType.NIP44_DECRYPT || type == SignerType.DECRYPT_ZAP_EVENT) result else content,
content = if (!isEncrypt) finalResult else content,
),
account.npub,
)
}
val cursor = MatrixCursor(arrayOf("signature", "event", "result"))
cursor.addRow(arrayOf<Any>(result, result, result))
cursor.addRow(arrayOf<Any>(finalResult, finalResult, finalResult))
return cursor
}
@@ -2,10 +2,12 @@ package com.greenart7c3.nostrsigner.models
val basicPermissions = listOf(
Permission("get_public_key", null),
Permission("nip04_encrypt", null),
Permission("nip04_decrypt", null),
Permission("nip44_decrypt", null),
Permission("nip44_encrypt", null),
Permission("encrypt_clear_text", null),
Permission("decrypt_clear_text", null),
Permission("encrypt_event", null),
Permission("decrypt_event", null),
Permission("encrypt_tag_array", null),
Permission("decrypt_tag_array", null),
Permission("decrypt_zap_event", null),
Permission("sign_event", 0),
Permission("sign_event", 1),
@@ -13,3 +13,39 @@ class TagArrayEncryptedDataKind(val tagArray: Array<Array<String>>, override val
class EventEncryptedDataKind(val event: AmberEvent, val sealEncryptedDataKind: EncryptedDataKind?, override val result: String) : EncryptedDataKind
class PrivateZapEncryptedDataKind(override val result: String) : EncryptedDataKind
fun EncryptedDataKind?.toPermissionType(isEncrypt: Boolean): String = when (this) {
is ClearTextEncryptedDataKind -> if (isEncrypt) "ENCRYPT_CLEAR_TEXT" else "DECRYPT_CLEAR_TEXT"
is TagArrayEncryptedDataKind -> if (isEncrypt) "ENCRYPT_TAG_ARRAY" else "DECRYPT_TAG_ARRAY"
is EventEncryptedDataKind -> if (isEncrypt) "ENCRYPT_EVENT" else "DECRYPT_EVENT"
is PrivateZapEncryptedDataKind -> "DECRYPT_ZAP_EVENT"
null -> if (isEncrypt) "ENCRYPT_CLEAR_TEXT" else "DECRYPT_CLEAR_TEXT"
else -> if (isEncrypt) "ENCRYPT_CLEAR_TEXT" else "DECRYPT_CLEAR_TEXT"
}
fun SignerType.toPermissionTypeString(encryptedData: EncryptedDataKind?): String = when (this) {
SignerType.NIP04_ENCRYPT, SignerType.NIP44_ENCRYPT -> encryptedData.toPermissionType(isEncrypt = true)
SignerType.NIP04_DECRYPT, SignerType.NIP44_DECRYPT -> encryptedData.toPermissionType(isEncrypt = false)
SignerType.DECRYPT_ZAP_EVENT -> "DECRYPT_ZAP_EVENT"
else -> this.toString()
}
val encryptDecryptSignerTypes = setOf(
SignerType.NIP04_ENCRYPT,
SignerType.NIP44_ENCRYPT,
SignerType.NIP04_DECRYPT,
SignerType.NIP44_DECRYPT,
SignerType.DECRYPT_ZAP_EVENT,
)
/**
* Determines the permission type string based on content string and operation direction.
* For ENCRYPT: pass the plaintext to classify what kind of data is being encrypted.
* For DECRYPT: pass the decrypted plaintext to classify what was decrypted.
*/
fun permissionTypeFromContent(content: String, isEncrypt: Boolean, signerType: SignerType): String = when {
signerType == SignerType.DECRYPT_ZAP_EVENT -> "DECRYPT_ZAP_EVENT"
content.startsWith("{") -> if (isEncrypt) "ENCRYPT_EVENT" else "DECRYPT_EVENT"
content.startsWith("[") -> if (isEncrypt) "ENCRYPT_TAG_ARRAY" else "DECRYPT_TAG_ARRAY"
else -> if (isEncrypt) "ENCRYPT_CLEAR_TEXT" else "DECRYPT_CLEAR_TEXT"
}
@@ -84,6 +84,24 @@ data class Permission(
"decrypt_zap_event" -> {
context.getString(R.string.decrypt_private_zaps)
}
"encrypt_clear_text" -> {
context.getString(R.string.encrypt_clear_text)
}
"decrypt_clear_text" -> {
context.getString(R.string.decrypt_clear_text)
}
"encrypt_event" -> {
context.getString(R.string.encrypt_event)
}
"decrypt_event" -> {
context.getString(R.string.decrypt_event)
}
"encrypt_tag_array" -> {
context.getString(R.string.encrypt_tag_array)
}
"decrypt_tag_array" -> {
context.getString(R.string.decrypt_tag_array)
}
"sign_event" -> {
when (kind) {
0 -> context.getString(R.string.event_kind_0)
@@ -577,5 +595,11 @@ val supportedKindNumbers = listOf(
Permission("nip44_decrypt", null),
Permission("nip44_encrypt", null),
Permission("decrypt_zap_event", null),
Permission("encrypt_clear_text", null),
Permission("decrypt_clear_text", null),
Permission("encrypt_event", null),
Permission("decrypt_event", null),
Permission("encrypt_tag_array", null),
Permission("decrypt_tag_array", null),
Permission("get_public_key", null),
)
@@ -8,9 +8,12 @@ import com.greenart7c3.nostrsigner.database.ApplicationPermissionsEntity
import com.greenart7c3.nostrsigner.database.ApplicationWithPermissions
import com.greenart7c3.nostrsigner.models.Account
import com.greenart7c3.nostrsigner.models.AmberBunkerRequest
import com.greenart7c3.nostrsigner.models.EncryptedDataKind
import com.greenart7c3.nostrsigner.models.Permission
import com.greenart7c3.nostrsigner.models.SignerType
import com.greenart7c3.nostrsigner.models.basicPermissions
import com.greenart7c3.nostrsigner.models.encryptDecryptSignerTypes
import com.greenart7c3.nostrsigner.models.toPermissionTypeString
import com.greenart7c3.nostrsigner.ui.RememberType
import com.vitorpamplona.quartz.nip01Core.core.HexKey
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
@@ -186,6 +189,7 @@ object AmberUtils {
kind: Int?,
rememberType: RememberType,
relay: String = "",
encryptedData: EncryptedDataKind? = null,
) {
val until = when (rememberType) {
RememberType.ALWAYS -> Long.MAX_VALUE / 1000
@@ -195,25 +199,31 @@ object AmberUtils {
else -> 0L
}
val permissionTypeStr = type.toPermissionTypeString(encryptedData)
if (kind != null) {
if (relay.isNotEmpty()) {
if (relay == "*") {
application.permissions.removeIf { it.kind == kind && it.type == type.toString() }
application.permissions.removeIf { it.kind == kind && it.type == permissionTypeStr }
} else {
application.permissions.removeIf { it.kind == kind && it.type == type.toString() && it.relay == relay }
application.permissions.removeIf { it.kind == kind && it.type == permissionTypeStr && it.relay == relay }
}
} else {
application.permissions.removeIf { it.kind == kind && it.type == type.toString() && it.relay.isEmpty() }
application.permissions.removeIf { it.kind == kind && it.type == permissionTypeStr && it.relay.isEmpty() }
}
} else {
application.permissions.removeIf { it.type == type.toString() && it.type != "SIGN_EVENT" }
application.permissions.removeIf { it.type == permissionTypeStr && it.type != "SIGN_EVENT" }
// Also remove any old NIP-based permission entries for this operation type
if (type in encryptDecryptSignerTypes) {
application.permissions.removeIf { it.type == type.toString() }
}
}
application.permissions.add(
ApplicationPermissionsEntity(
null,
key,
type.toString(),
permissionTypeStr,
kind,
true,
rememberType.screenCode,
@@ -241,6 +241,7 @@ object BunkerRequestUtils {
oldKey: String = "",
deleteAfter: Long = 0L,
relay: String = "",
encryptedData: com.greenart7c3.nostrsigner.models.EncryptedDataKind? = null,
) {
onLoading(true)
Amber.instance.applicationIOScope.launch {
@@ -321,6 +322,7 @@ object BunkerRequestUtils {
kind = kind,
rememberType = rememberType,
relay = relay,
encryptedData = encryptedData ?: bunkerRequest.encryptedData,
)
}
@@ -734,6 +734,7 @@ object IntentUtils {
kind = kind,
rememberType = rememberType,
relay = relay,
encryptedData = intentData.encryptedData,
)
}
@@ -33,6 +33,7 @@ import com.greenart7c3.nostrsigner.models.AmberBunkerRequest
import com.greenart7c3.nostrsigner.models.Permission
import com.greenart7c3.nostrsigner.models.SignerType
import com.greenart7c3.nostrsigner.models.kindToNip
import com.greenart7c3.nostrsigner.models.toPermissionType
import com.greenart7c3.nostrsigner.service.BunkerRequestUtils
import com.greenart7c3.nostrsigner.service.isPrivateEvent
import com.greenart7c3.nostrsigner.service.model.AmberEvent
@@ -336,10 +337,17 @@ fun BunkerSingleEventHomeScreen(
is BunkerRequestNip04Encrypt -> {
val nip = 4
val permType = bunkerRequest.encryptedData.toPermissionType(isEncrypt = true)
var permission =
applicationEntity?.permissions?.firstOrNull {
it.pkKey == key && it.type == type.toString()
it.pkKey == key && it.type == permType
}
if (permission == null) {
permission =
applicationEntity?.permissions?.firstOrNull {
it.pkKey == key && it.type == type.toString()
}
}
if (permission == null) {
permission =
applicationEntity?.permissions?.firstOrNull {
@@ -400,10 +408,17 @@ fun BunkerSingleEventHomeScreen(
is BunkerRequestNip04Decrypt -> {
val nip = 4
val permType = bunkerRequest.encryptedData.toPermissionType(isEncrypt = false)
var permission =
applicationEntity?.permissions?.firstOrNull {
it.pkKey == key && it.type == type.toString()
it.pkKey == key && it.type == permType
}
if (permission == null) {
permission =
applicationEntity?.permissions?.firstOrNull {
it.pkKey == key && it.type == type.toString()
}
}
if (permission == null) {
permission =
applicationEntity?.permissions?.firstOrNull {
@@ -466,10 +481,17 @@ fun BunkerSingleEventHomeScreen(
is BunkerRequestNip44Encrypt -> {
val nip = 44
val permType = bunkerRequest.encryptedData.toPermissionType(isEncrypt = true)
var permission =
applicationEntity?.permissions?.firstOrNull {
it.pkKey == key && it.type == type.toString()
it.pkKey == key && it.type == permType
}
if (permission == null) {
permission =
applicationEntity?.permissions?.firstOrNull {
it.pkKey == key && it.type == type.toString()
}
}
if (permission == null) {
permission =
applicationEntity?.permissions?.firstOrNull {
@@ -532,10 +554,17 @@ fun BunkerSingleEventHomeScreen(
is BunkerRequestNip44Decrypt -> {
val nip = 44
val permType = bunkerRequest.encryptedData.toPermissionType(isEncrypt = false)
var permission =
applicationEntity?.permissions?.firstOrNull {
it.pkKey == key && it.type == type.toString()
it.pkKey == key && it.type == permType
}
if (permission == null) {
permission =
applicationEntity?.permissions?.firstOrNull {
it.pkKey == key && it.type == type.toString()
}
}
if (permission == null) {
permission =
applicationEntity?.permissions?.firstOrNull {
@@ -28,6 +28,7 @@ import com.greenart7c3.nostrsigner.models.IntentData
import com.greenart7c3.nostrsigner.models.IntentResultType
import com.greenart7c3.nostrsigner.models.SignerType
import com.greenart7c3.nostrsigner.models.kindToNip
import com.greenart7c3.nostrsigner.models.toPermissionType
import com.greenart7c3.nostrsigner.service.IntentUtils
import com.greenart7c3.nostrsigner.service.isPrivateEvent
import com.greenart7c3.nostrsigner.service.model.AmberEvent
@@ -164,10 +165,18 @@ fun IntentSingleEventHomeScreen(
SignerType.DECRYPT_ZAP_EVENT -> null
else -> null
}
val isEncrypt = intentData.type == SignerType.NIP04_ENCRYPT || intentData.type == SignerType.NIP44_ENCRYPT
val permType = intentData.encryptedData.toPermissionType(isEncrypt = isEncrypt)
var permission =
applicationEntity?.permissions?.firstOrNull {
it.pkKey == key && it.type == intentData.type.toString()
it.pkKey == key && it.type == permType
}
if (permission == null) {
permission =
applicationEntity?.permissions?.firstOrNull {
it.pkKey == key && it.type == intentData.type.toString()
}
}
if (permission == null && nip != null) {
permission =
applicationEntity?.permissions?.firstOrNull {
+6
View File
@@ -134,6 +134,12 @@
<string name="decrypt_data_using_nip_44">Decrypt data using nip 44</string>
<string name="encrypt_data_using_nip_44">Encrypt data using nip 44</string>
<string name="decrypt_private_zaps">Decrypt private zaps</string>
<string name="encrypt_clear_text">Encrypt text messages</string>
<string name="decrypt_clear_text">Decrypt text messages</string>
<string name="encrypt_event">Encrypt events</string>
<string name="decrypt_event">Decrypt events</string>
<string name="encrypt_tag_array">Encrypt tag arrays</string>
<string name="decrypt_tag_array">Decrypt tag arrays</string>
<string name="event_kind_0">Metadata</string>
<string name="event_kind_1">Short text note</string>
<string name="event_kind_3">Follows</string>