add support for sending nips as permissions

This commit is contained in:
greenart7c3
2024-07-24 08:35:33 -03:00
parent d4cd24cb42
commit fd8fd142b9
8 changed files with 344 additions and 11 deletions
@@ -8,6 +8,7 @@ import android.net.Uri
import com.greenart7c3.nostrsigner.database.HistoryEntity
import com.greenart7c3.nostrsigner.models.SignerType
import com.greenart7c3.nostrsigner.models.TimeUtils
import com.greenart7c3.nostrsigner.models.kindToNip
import com.greenart7c3.nostrsigner.service.AmberUtils
import com.greenart7c3.nostrsigner.service.IntentUtils
import com.vitorpamplona.quartz.crypto.CryptoUtils
@@ -127,7 +128,7 @@ class SignerProvider : ContentProvider() {
val event = Event.fromJson(json)
val currentSelection = selection ?: "0"
val database = NostrSigner.getInstance().getDatabase(account.keyPair.pubKey.toNpub())
val permission =
var permission =
database
.applicationDao()
.getPermission(
@@ -135,6 +136,18 @@ class SignerProvider : ContentProvider() {
"SIGN_EVENT",
event.kind,
)
if (permission == null) {
event.kind.kindToNip()?.let {
permission =
database
.applicationDao()
.getPermission(
sortOrder ?: packageName,
"NIP",
it.toInt(),
)
}
}
if (currentSelection == "1") {
val isRemembered = permission?.acceptable ?: return null
if (!isRemembered) {
@@ -224,13 +237,33 @@ class SignerProvider : ContentProvider() {
val account = LocalPreferences.loadFromEncryptedStorage(context!!, npub) ?: return null
val currentSelection = selection ?: "0"
val database = NostrSigner.getInstance().getDatabase(account.keyPair.pubKey.toNpub())
val permission =
var permission =
database
.applicationDao()
.getPermission(
sortOrder ?: packageName,
uri.toString().replace("content://$appId.", ""),
)
if (permission == null) {
val nip = when (stringType) {
"NIP04_DECRYPT" -> 4
"NIP44_DECRYPT" -> 44
"NIP04_ENCRYPT" -> 4
"NIP44_ENCRYPT" -> 44
"DECRYPT_ZAP_EVENT" -> null
else -> null
}
nip?.let {
permission =
database
.applicationDao()
.getPermission(
sortOrder ?: packageName,
"NIP",
it,
)
}
}
if (currentSelection == "1") {
val isRemembered = permission?.acceptable ?: return null
@@ -131,7 +131,159 @@ data class Permission(
}
}
"connect" -> context.getString(R.string.would_like_your_permission_to_read_your_public_key_and_sign_events_on_your_behalf)
"nip" -> {
when (kind) {
1 -> context.getString(R.string.nip_01)
2 -> context.getString(R.string.nip_02)
3 -> context.getString(R.string.nip_03)
4 -> context.getString(R.string.nip_04)
5 -> context.getString(R.string.nip_05)
6 -> context.getString(R.string.nip_06)
7 -> context.getString(R.string.nip_07)
8 -> context.getString(R.string.nip_08)
9 -> context.getString(R.string.nip_09)
10 -> context.getString(R.string.nip_10)
11 -> context.getString(R.string.nip_11)
13 -> context.getString(R.string.nip_13)
14 -> context.getString(R.string.nip_14)
15 -> context.getString(R.string.nip_15)
17 -> context.getString(R.string.nip_17)
18 -> context.getString(R.string.nip_18)
19 -> context.getString(R.string.nip_19)
21 -> context.getString(R.string.nip_21)
23 -> context.getString(R.string.nip_23)
24 -> context.getString(R.string.nip_24)
25 -> context.getString(R.string.nip_25)
26 -> context.getString(R.string.nip_26)
27 -> context.getString(R.string.nip_27)
28 -> context.getString(R.string.nip_28)
29 -> context.getString(R.string.nip_29)
30 -> context.getString(R.string.nip_30)
31 -> context.getString(R.string.nip_31)
32 -> context.getString(R.string.nip_32)
34 -> context.getString(R.string.nip_34)
35 -> context.getString(R.string.nip_35)
36 -> context.getString(R.string.nip_36)
38 -> context.getString(R.string.nip_38)
39 -> context.getString(R.string.nip_39)
40 -> context.getString(R.string.nip_40)
42 -> context.getString(R.string.nip_42)
44 -> context.getString(R.string.nip_44)
45 -> context.getString(R.string.nip_45)
46 -> context.getString(R.string.nip_46)
47 -> context.getString(R.string.nip_47)
48 -> context.getString(R.string.nip_48)
49 -> context.getString(R.string.nip_49)
50 -> context.getString(R.string.nip_50)
51 -> context.getString(R.string.nip_51)
52 -> context.getString(R.string.nip_52)
53 -> context.getString(R.string.nip_53)
54 -> context.getString(R.string.nip_54)
55 -> context.getString(R.string.nip_55)
56 -> context.getString(R.string.nip_56)
57 -> context.getString(R.string.nip_57)
58 -> context.getString(R.string.nip_58)
59 -> context.getString(R.string.nip_59)
65 -> context.getString(R.string.nip_65)
70 -> context.getString(R.string.nip_70)
71 -> context.getString(R.string.nip_71)
72 -> context.getString(R.string.nip_72)
75 -> context.getString(R.string.nip_75)
78 -> context.getString(R.string.nip_78)
84 -> context.getString(R.string.nip_84)
89 -> context.getString(R.string.nip_89)
90 -> context.getString(R.string.nip_90)
92 -> context.getString(R.string.nip_92)
94 -> context.getString(R.string.nip_94)
96 -> context.getString(R.string.nip_96)
98 -> context.getString(R.string.nip_98)
99 -> context.getString(R.string.nip_99)
else -> context.getString(R.string.nip_kind, kind.toString())
}
}
else -> type
}
}
}
data class KindNip(val kind: IntRange, val nip: String)
fun Int.kindToNipUrl(): String? =
kindsByNip.firstOrNull { it.kind.contains(this) }?.let {
return if (it.nip.startsWith("http")) it.nip else "https://github.com/nostr-protocol/nips/blob/master/${it.nip}.md"
}
fun Int.containsNip(): Boolean = kindsByNip.any {
val nip = if (this < 10) {
this.toString().padStart(2, '0')
} else {
this.toString()
}
it.nip.contains(nip)
}
fun Int.kindToNip(): String? = kindsByNip.firstOrNull { it.kind.contains(this) }?.nip
fun Int.nipToUrl(): String? = kindsByNip.firstOrNull { it.nip.toIntOrNull() == this }
?.nip?.let { nip -> if (nip.startsWith("http")) nip else "https://github.com/nostr-protocol/nips/blob/master/$nip.md" }
val kindsByNip = listOf(
KindNip(0..2, "01"),
KindNip(3..3, "02"),
KindNip(4..4, "04"),
KindNip(5..5, "09"),
KindNip(6..6, "18"),
KindNip(16..16, "18"),
KindNip(7..7, "25"),
KindNip(8..8, "58"),
KindNip(9..12, "29"),
KindNip(13..13, "59"),
KindNip(14..14, "17"),
KindNip(40..44, "28"),
KindNip(818..818, "54"),
KindNip(1021..1022, "15"),
KindNip(1040..1040, "03"),
KindNip(1059..1059, "59"),
KindNip(1063..1063, "94"),
KindNip(1311..1311, "53"),
KindNip(1617..1633, "34"),
KindNip(1971..1971, "https://github.com/nostrocket/NIPS/blob/main/Problems.md"),
KindNip(1984..1985, "56"),
KindNip(2003..2004, "35"),
KindNip(2022..2022, "https://gitlab.com/1440000bytes/joinstr/-/blob/main/NIP.md"),
KindNip(4550..4550, "72"),
KindNip(5000..5999, "90"),
KindNip(6000..6999, "90"),
KindNip(7000..7000, "90"),
KindNip(9000..9030, "29"),
KindNip(9041..9041, "75"),
KindNip(9734..9735, "57"),
KindNip(9802..9802, "84"),
KindNip(10009..10009, "29"),
KindNip(10000..10030, "51"),
KindNip(10050..10050, "17"),
KindNip(10096..10096, "96"),
KindNip(13194..13194, "47"),
KindNip(23194..23195, "47"),
KindNip(21000..21000, "https://github.com/shocknet/Lightning.Pub/blob/master/proto/autogenerated/client.md"),
KindNip(22242..22242, "42"),
KindNip(24133..24133, "46"),
KindNip(27235..27235, "98"),
KindNip(30000..30005, "51"),
KindNip(30008..30009, "58"),
KindNip(30017..30020, "15"),
KindNip(30023..30024, "23"),
KindNip(30078..30078, "78"),
KindNip(30311..30311, "53"),
KindNip(30315..30315, "38"),
KindNip(30402..30403, "99"),
KindNip(30617..30617, "34"),
KindNip(30818..30819, "54"),
KindNip(31890..31890, "https://wikifreedia.xyz/cip-01/97c70a44366a6535c1"),
KindNip(31922..31925, "52"),
KindNip(31989..31990, "89"),
KindNip(34235..34237, "71"),
KindNip(34550..34550, "72"),
KindNip(39000..39009, "29"),
KindNip(31234..31234, "https://github.com/nostr-protocol/nips/pull/1124"),
)
@@ -11,6 +11,7 @@ import com.greenart7c3.nostrsigner.models.BunkerRequest
import com.greenart7c3.nostrsigner.models.BunkerResponse
import com.greenart7c3.nostrsigner.models.IntentData
import com.greenart7c3.nostrsigner.models.SignerType
import com.greenart7c3.nostrsigner.models.kindToNip
import com.vitorpamplona.ammolite.relays.RelaySetupInfo
import com.vitorpamplona.quartz.crypto.CryptoUtils
import com.vitorpamplona.quartz.encoders.HexKey
@@ -169,7 +170,11 @@ object AmberUtils {
permissions = mutableListOf(),
)
if (application.permissions.none { it.type == intentData.type.toString() && it.kind == kind }) {
val noPermission = application.permissions.none {
val nip = it.kind?.kindToNip()?.toIntOrNull()
(it.type == intentData.type.toString() && it.kind == kind) || (nip != null && it.type == "NIP" && it.kind == nip)
}
if (noPermission) {
application.permissions.add(
ApplicationPermissionsEntity(
null,
@@ -30,6 +30,7 @@ import com.greenart7c3.nostrsigner.models.Permission
import com.greenart7c3.nostrsigner.models.ReturnType
import com.greenart7c3.nostrsigner.models.SignerType
import com.greenart7c3.nostrsigner.models.TimeUtils
import com.greenart7c3.nostrsigner.models.containsNip
import com.greenart7c3.nostrsigner.relays.AmberListenerSingleton
import com.greenart7c3.nostrsigner.service.model.AmberEvent
import com.greenart7c3.nostrsigner.ui.NotificationType
@@ -433,6 +434,9 @@ object IntentUtils {
}
}
permissions.removeIf { it.kind == null && (it.type == "sign_event" || it.type == "nip") }
permissions.removeIf { it.type == "nip" && (it.kind == null || !it.kind.containsNip()) }
when (type) {
SignerType.CONNECT -> {
onReady(
@@ -611,7 +615,8 @@ object IntentUtils {
permissions?.forEach {
it.checked = true
}
permissions?.removeIf { it.kind == null && it.type == "sign_event" }
permissions?.removeIf { it.kind == null && (it.type == "sign_event" || it.type == "nip") }
permissions?.removeIf { it.type == "nip" && (it.kind == null || !it.kind.containsNip()) }
when (type) {
SignerType.SIGN_EVENT -> {
@@ -1,5 +1,6 @@
package com.greenart7c3.nostrsigner.ui
import android.widget.Toast
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
@@ -13,6 +14,7 @@ import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ContentPaste
import androidx.compose.material.icons.filled.Delete
import androidx.compose.material.icons.filled.Info
import androidx.compose.material.icons.filled.QrCode
import androidx.compose.material3.Button
import androidx.compose.material3.Icon
@@ -33,6 +35,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalClipboardManager
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.font.FontWeight
@@ -42,6 +45,7 @@ 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.toLowerCase
import androidx.compose.ui.text.toUpperCase
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.navigation.NavController
@@ -54,6 +58,8 @@ import com.greenart7c3.nostrsigner.database.ApplicationPermissionsEntity
import com.greenart7c3.nostrsigner.database.ApplicationWithPermissions
import com.greenart7c3.nostrsigner.models.Account
import com.greenart7c3.nostrsigner.models.Permission
import com.greenart7c3.nostrsigner.models.kindToNipUrl
import com.greenart7c3.nostrsigner.models.nipToUrl
import com.greenart7c3.nostrsigner.ui.actions.ActivityDialog
import com.greenart7c3.nostrsigner.ui.actions.DeleteDialog
import com.greenart7c3.nostrsigner.ui.actions.EditRelaysDialog
@@ -73,6 +79,7 @@ fun EditPermission(
navController: NavController,
database: AppDatabase,
) {
val uriHandler = LocalUriHandler.current
val clipboardManager = LocalClipboardManager.current
val context = LocalContext.current
val localAccount = LocalPreferences.loadFromEncryptedStorage(context, account.keyPair.pubKey.toNpub())!!
@@ -309,7 +316,7 @@ fun EditPermission(
)
val message =
if (permission.type == "SIGN_EVENT") {
if (permission.type == "SIGN_EVENT" || permission.type == "NIP") {
stringResource(R.string.sign, localPermission.toLocalizedString(context))
} else {
localPermission.toLocalizedString(context)
@@ -374,6 +381,53 @@ fun EditPermission(
permissions.remove(permission)
},
)
IconButton(
content = {
Icon(
Icons.Default.Info,
stringResource(R.string.more_info),
modifier = Modifier
.fillMaxHeight(),
)
},
onClick = {
if (permission.type.toLowerCase(Locale.current) == "sign_event") {
val nip = permission.kind?.kindToNipUrl()
if (nip == null) {
Toast.makeText(
context,
context.getString(R.string.nip_not_found_for_the_event_kind, permission.kind.toString()),
Toast.LENGTH_SHORT,
).show()
return@IconButton
}
uriHandler.openUri(nip)
} else if (permission.type.toLowerCase(Locale.current) == "nip") {
val nip = permission.kind?.nipToUrl()
if (nip == null) {
Toast.makeText(
context,
context.getString(R.string.nip_not_found, permission.kind.toString()),
Toast.LENGTH_SHORT,
).show()
return@IconButton
}
uriHandler.openUri(nip)
} else if ((permission.type.toUpperCase(Locale.current) == "NIP04_ENCRYPT") || (permission.type.toUpperCase(Locale.current) == "NIP04_DECRYPT")) {
uriHandler.openUri("https://github.com/nostr-protocol/nips/blob/master/04.md")
} else if ((permission.type.toUpperCase(Locale.current) == "NIP44_ENCRYPT") || (permission.type.toUpperCase(Locale.current) == "NIP44_DECRYPT")) {
uriHandler.openUri("https://github.com/nostr-protocol/nips/blob/master/44.md")
} else if (permission.type.toLowerCase(Locale.current) == "decrypt_zap_event") {
uriHandler.openUri("https://github.com/nostr-protocol/nips/blob/master/57.md")
} else {
Toast.makeText(
context,
context.getString(R.string.no_information_available_for, localPermission.toLocalizedString(context)),
Toast.LENGTH_SHORT,
).show()
}
},
)
}
}
}
@@ -31,6 +31,7 @@ import com.greenart7c3.nostrsigner.models.Account
import com.greenart7c3.nostrsigner.models.IntentData
import com.greenart7c3.nostrsigner.models.SignerType
import com.greenart7c3.nostrsigner.models.TimeUtils
import com.greenart7c3.nostrsigner.models.kindToNip
import com.greenart7c3.nostrsigner.service.AmberUtils
import com.greenart7c3.nostrsigner.service.getAppCompatActivity
import com.greenart7c3.nostrsigner.service.toShortenHex
@@ -253,10 +254,22 @@ fun SingleEventHomeScreen(
}
SignerType.NIP04_DECRYPT, SignerType.NIP04_ENCRYPT, SignerType.NIP44_ENCRYPT, SignerType.NIP44_DECRYPT, SignerType.DECRYPT_ZAP_EVENT -> {
val permission =
val nip = when (intentData.type) {
SignerType.NIP04_DECRYPT, SignerType.NIP04_ENCRYPT -> 4
SignerType.NIP44_ENCRYPT, SignerType.NIP44_DECRYPT -> 44
SignerType.DECRYPT_ZAP_EVENT -> null
else -> null
}
var permission =
applicationEntity?.permissions?.firstOrNull {
it.pkKey == key && it.type == intentData.type.toString()
}
if (permission == null && nip != null) {
permission =
applicationEntity?.permissions?.firstOrNull {
it.pkKey == key && it.type == "NIP" && it.kind == nip
}
}
val remember =
remember {
mutableStateOf(permission?.acceptable != null)
@@ -393,7 +406,8 @@ fun SingleEventHomeScreen(
} else {
val permission =
applicationEntity?.permissions?.firstOrNull {
it.pkKey == key && it.type == intentData.type.toString() && it.kind == event.kind
val nip = event.kind.kindToNip()?.toIntOrNull()
it.pkKey == key && ((it.type == intentData.type.toString() && it.kind == event.kind) || (nip != null && it.type == "NIP" && it.kind == nip))
}
val remember =
remember {
+70
View File
@@ -265,4 +265,74 @@
<string name="event_kind_39000">Group metadata</string>
<string name="event_kind_39001">Group admins</string>
<string name="event_kind_39002">Group members</string>
<string name="more_info">More information about this permission</string>
<string name="nip_kind">Nip %1$s</string>
<string name="nip_01">Basic protocol flow description</string>
<string name="nip_02">Follow List</string>
<string name="nip_03">OpenTimestamps Attestations for Events</string>
<string name="nip_04">Encrypted Direct Message</string>
<string name="nip_05">Mapping Nostr keys to DNS-based internet identifiers</string>
<string name="nip_06">Basic key derivation from mnemonic seed phrase</string>
<string name="nip_07">window.nostr capability for web browsers</string>
<string name="nip_08">Handling Mentions</string>
<string name="nip_09">Event Deletion</string>
<string name="nip_10">Conventions for clients</string>
<string name="nip_11">Relay Information Document</string>
<string name="nip_13">Proof of Work</string>
<string name="nip_14">Subject tag in text events</string>
<string name="nip_15">Nostr Marketplace (for resilient marketplaces)</string>
<string name="nip_17">Private Direct Messages</string>
<string name="nip_18">Reposts</string>
<string name="nip_19">bech32-encoded entities</string>
<string name="nip_21">nostr: URI scheme</string>
<string name="nip_23">Long-form Content</string>
<string name="nip_24">Extra metadata fields and tags</string>
<string name="nip_25">Reactions</string>
<string name="nip_26">Delegated Event Signing</string>
<string name="nip_27">Text Note References</string>
<string name="nip_28">Public Chat</string>
<string name="nip_29">Relay-based Groups</string>
<string name="nip_30">Custom Emoji</string>
<string name="nip_31">Dealing with Unknown Events</string>
<string name="nip_32">Labeling</string>
<string name="nip_34">git stuff</string>
<string name="nip_35">Torrents</string>
<string name="nip_36">Sensitive Content</string>
<string name="nip_38">User Statuses</string>
<string name="nip_39">External Identities in Profiles</string>
<string name="nip_40">Expiration Timestamp</string>
<string name="nip_42">Authentication of clients to relays</string>
<string name="nip_44">Versioned Encryption</string>
<string name="nip_45">Counting results</string>
<string name="nip_46">Nostr Connect</string>
<string name="nip_47">Wallet Connect</string>
<string name="nip_48">Proxy Tags</string>
<string name="nip_49">Private Key Encryption</string>
<string name="nip_50">Search Capability</string>
<string name="nip_51">Lists</string>
<string name="nip_52">Calendar Events</string>
<string name="nip_53">Live Activities</string>
<string name="nip_54">Wiki</string>
<string name="nip_55">Android Signer Application</string>
<string name="nip_56">Reporting</string>
<string name="nip_57">Lightning Zaps</string>
<string name="nip_58">Badges</string>
<string name="nip_59">Gift Wrap</string>
<string name="nip_65">Relay List Metadata</string>
<string name="nip_70">Protected Events</string>
<string name="nip_71">Video Events</string>
<string name="nip_72">Moderated Communities</string>
<string name="nip_75">Zap Goals</string>
<string name="nip_78">Application-specific data</string>
<string name="nip_84">Highlights</string>
<string name="nip_89">Recommended Application Handlers</string>
<string name="nip_90">Data Vending Machines</string>
<string name="nip_92">Media Attachments</string>
<string name="nip_94">File Metadata</string>
<string name="nip_96">HTTP File Storage Integration</string>
<string name="nip_98">HTTP Auth</string>
<string name="nip_99">Classified Listings</string>
<string name="no_information_available_for">No information available for %1$s</string>
<string name="nip_not_found">Nip %1$s not found</string>
<string name="nip_not_found_for_the_event_kind">Nip not found for the event kind %1$s</string>
</resources>
+4 -4
View File
@@ -15,9 +15,9 @@ junit = "4.13.2"
junitVersion = "1.2.1"
lifecycle_version = "2.8.3"
material3 = "1.2.1"
mockk = "1.13.11"
mockk = "1.13.12"
nav_version = "2.7.7"
quartz = "e764ebbcb2" # TODO: change back to amethyst before releasing a new version
quartz = "0.89.7"
compose_ui = "1.6.8"
richtextUi = "077a2cde64"
roomKtx = "2.6.1"
@@ -53,8 +53,8 @@ material-icons-extended = { module = "androidx.compose.material:material-icons-e
material3 = { module = "androidx.compose.material3:material3", version.ref = "material3" }
mockk = { module = "io.mockk:mockk", version.ref = "mockk" }
navigation-compose = { module = "androidx.navigation:navigation-compose", version.ref = "nav_version" }
quartz = { module = "com.github.greenart7c3.amethyst:quartz", version.ref = "quartz" }
ammolite = { module = "com.github.greenart7c3.amethyst:ammolite", version.ref = "quartz" }
quartz = { module = "com.github.vitorpamplona.amethyst:quartz", version.ref = "quartz" }
ammolite = { module = "com.github.vitorpamplona.amethyst:ammolite", version.ref = "quartz" }
richtext-commonmark = { module = "com.github.vitorpamplona.compose-richtext:richtext-commonmark", version.ref = "richtextUi" }
richtext-ui = { module = "com.github.vitorpamplona.compose-richtext:richtext-ui", version.ref = "richtextUi" }
richtext-ui-material3 = { module = "com.github.vitorpamplona.compose-richtext:richtext-ui-material3", version.ref = "richtextUi" }