From 145ff51559f0e07b6ec7853c833a567ed47255ef Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 13 Mar 2026 14:59:01 +0000 Subject: [PATCH] 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 --- .../greenart7c3/nostrsigner/SignerProvider.kt | 80 ++++++++++++++----- .../nostrsigner/models/BasicPermissions.kt | 10 ++- .../nostrsigner/models/EncryptedDataKind.kt | 36 +++++++++ .../nostrsigner/models/Permission.kt | 24 ++++++ .../nostrsigner/service/AmberUtils.kt | 20 +++-- .../nostrsigner/service/BunkerRequestUtils.kt | 2 + .../nostrsigner/service/IntentUtils.kt | 1 + .../components/BunkerSingleEventHomeScreen.kt | 37 ++++++++- .../components/IntentSingleEventHomeScreen.kt | 11 ++- app/src/main/res/values/strings.xml | 6 ++ 10 files changed, 192 insertions(+), 35 deletions(-) diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/SignerProvider.kt b/app/src/main/java/com/greenart7c3/nostrsigner/SignerProvider.kt index 2f1e13b0..da3de199 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/SignerProvider.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/SignerProvider.kt @@ -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(result, result, result)) + cursor.addRow(arrayOf(finalResult, finalResult, finalResult)) return cursor } diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/models/BasicPermissions.kt b/app/src/main/java/com/greenart7c3/nostrsigner/models/BasicPermissions.kt index 710b19de..d6cd67e2 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/models/BasicPermissions.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/models/BasicPermissions.kt @@ -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), diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/models/EncryptedDataKind.kt b/app/src/main/java/com/greenart7c3/nostrsigner/models/EncryptedDataKind.kt index 1ff84116..91962683 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/models/EncryptedDataKind.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/models/EncryptedDataKind.kt @@ -13,3 +13,39 @@ class TagArrayEncryptedDataKind(val tagArray: Array>, 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" +} diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/models/Permission.kt b/app/src/main/java/com/greenart7c3/nostrsigner/models/Permission.kt index 0813a1eb..1847e503 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/models/Permission.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/models/Permission.kt @@ -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), ) diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/service/AmberUtils.kt b/app/src/main/java/com/greenart7c3/nostrsigner/service/AmberUtils.kt index 7e28e66f..c779e9c1 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/service/AmberUtils.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/service/AmberUtils.kt @@ -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, diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/service/BunkerRequestUtils.kt b/app/src/main/java/com/greenart7c3/nostrsigner/service/BunkerRequestUtils.kt index e64f5f2b..da376d50 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/service/BunkerRequestUtils.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/service/BunkerRequestUtils.kt @@ -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, ) } diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/service/IntentUtils.kt b/app/src/main/java/com/greenart7c3/nostrsigner/service/IntentUtils.kt index a8444639..b4010e7e 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/service/IntentUtils.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/service/IntentUtils.kt @@ -734,6 +734,7 @@ object IntentUtils { kind = kind, rememberType = rememberType, relay = relay, + encryptedData = intentData.encryptedData, ) } diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/BunkerSingleEventHomeScreen.kt b/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/BunkerSingleEventHomeScreen.kt index 884cdc92..17811c97 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/BunkerSingleEventHomeScreen.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/BunkerSingleEventHomeScreen.kt @@ -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 { diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/IntentSingleEventHomeScreen.kt b/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/IntentSingleEventHomeScreen.kt index 2c8151bb..366f95db 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/IntentSingleEventHomeScreen.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/components/IntentSingleEventHomeScreen.kt @@ -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 { diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 6ca303e0..3e4b7ba8 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -134,6 +134,12 @@ Decrypt data using nip 44 Encrypt data using nip 44 Decrypt private zaps + Encrypt text messages + Decrypt text messages + Encrypt events + Decrypt events + Encrypt tag arrays + Decrypt tag arrays Metadata Short text note Follows