From 6db031b208614a09618e60c13fc7ff9130360de9 Mon Sep 17 00:00:00 2001 From: greenart7c3 Date: Mon, 20 Nov 2023 14:47:46 -0300 Subject: [PATCH 1/2] fix sending zaps using amber, send some default permissions on login, save the signer package name --- .../amethyst/LocalPreferences.kt | 7 ++- .../amethyst/ui/components/InvoiceRequest.kt | 2 + .../ui/screen/AccountStateViewModel.kt | 11 +++- .../ui/screen/loggedOff/LoginScreen.kt | 38 +++---------- .../quartz/signers/ExternalSignerLauncher.kt | 55 ++++++++++++++++++- .../quartz/signers/NostrSignerExternal.kt | 36 ++++++++---- 6 files changed, 102 insertions(+), 47 deletions(-) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/LocalPreferences.kt b/app/src/main/java/com/vitorpamplona/amethyst/LocalPreferences.kt index b22fced76..9a560cb9b 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/LocalPreferences.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/LocalPreferences.kt @@ -26,9 +26,11 @@ import com.vitorpamplona.amethyst.service.checkNotInMainThread import com.vitorpamplona.quartz.crypto.KeyPair import com.vitorpamplona.quartz.encoders.hexToByteArray import com.vitorpamplona.quartz.encoders.toHexKey +import com.vitorpamplona.quartz.encoders.toNpub import com.vitorpamplona.quartz.events.ContactListEvent import com.vitorpamplona.quartz.events.Event import com.vitorpamplona.quartz.events.LnZapEvent +import com.vitorpamplona.quartz.signers.ExternalSignerLauncher import com.vitorpamplona.quartz.signers.NostrSignerExternal import com.vitorpamplona.quartz.signers.NostrSignerInternal import kotlinx.coroutines.Dispatchers @@ -89,6 +91,7 @@ private object PrefKeys { const val AUTOMATICALLY_HIDE_NAV_BARS = "automatically_hide_nav_bars" const val LOGIN_WITH_EXTERNAL_SIGNER = "login_with_external_signer" const val AUTOMATICALLY_SHOW_PROFILE_PICTURE = "automatically_show_profile_picture" + const val SIGNER_PACKAGE_NAME = "signer_package_name" const val ALL_ACCOUNT_INFO = "all_saved_accounts_info" const val SHARED_SETTINGS = "shared_settings" @@ -249,6 +252,7 @@ object LocalPreferences { putBoolean(PrefKeys.LOGIN_WITH_EXTERNAL_SIGNER, account.signer is NostrSignerExternal) if (account.signer is NostrSignerExternal) { remove(PrefKeys.NOSTR_PRIVKEY) + putString(PrefKeys.SIGNER_PACKAGE_NAME, account.signer.launcher.signerPackageName) } else { account.keyPair.privKey?.let { putString(PrefKeys.NOSTR_PRIVKEY, it.toHexKey()) } } @@ -475,7 +479,8 @@ object LocalPreferences { val keyPair = KeyPair(privKey = privKey?.hexToByteArray(), pubKey = pubKey.hexToByteArray()) val signer = if (loginWithExternalSigner) { - NostrSignerExternal(pubKey) + val packageName = getString(PrefKeys.SIGNER_PACKAGE_NAME, null) ?: "com.greenart7c3.nostrsigner" + NostrSignerExternal(pubKey, ExternalSignerLauncher(pubKey.hexToByteArray().toNpub(), packageName)) } else { NostrSignerInternal(keyPair) } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/InvoiceRequest.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/InvoiceRequest.kt index 9980a01c0..1d4e3229f 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/InvoiceRequest.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/InvoiceRequest.kt @@ -34,6 +34,7 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.model.Account +import com.vitorpamplona.amethyst.model.LocalCache import com.vitorpamplona.amethyst.service.lnurl.LightningAddressResolver import com.vitorpamplona.amethyst.ui.theme.QuoteBorder import com.vitorpamplona.amethyst.ui.theme.placeholderText @@ -170,6 +171,7 @@ fun InvoiceRequest( ) } else { account.createZapRequestFor(toUserPubKeyHex, message, account.defaultZapType) { zapRequest -> + LocalCache.justConsume(zapRequest, null) LightningAddressResolver().lnAddressInvoice( lud16, amount * 1000, diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/AccountStateViewModel.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/AccountStateViewModel.kt index 979e7fb47..541d1a8c4 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/AccountStateViewModel.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/AccountStateViewModel.kt @@ -14,6 +14,8 @@ import com.vitorpamplona.quartz.encoders.Nip19 import com.vitorpamplona.quartz.encoders.bechToBytes import com.vitorpamplona.quartz.encoders.hexToByteArray import com.vitorpamplona.quartz.encoders.toHexKey +import com.vitorpamplona.quartz.encoders.toNpub +import com.vitorpamplona.quartz.signers.ExternalSignerLauncher import com.vitorpamplona.quartz.signers.NostrSignerExternal import com.vitorpamplona.quartz.signers.NostrSignerInternal import kotlinx.coroutines.DelicateCoroutinesApi @@ -56,7 +58,8 @@ class AccountStateViewModel() : ViewModel() { key: String, useProxy: Boolean, proxyPort: Int, - loginWithExternalSigner: Boolean = false + loginWithExternalSigner: Boolean = false, + packageName: String = "" ) = withContext(Dispatchers.IO) { val parsed = Nip19.uriToRoute(key) val pubKeyParsed = parsed?.hex?.hexToByteArray() @@ -69,7 +72,8 @@ class AccountStateViewModel() : ViewModel() { val account = if (loginWithExternalSigner) { val keyPair = KeyPair(pubKey = pubKeyParsed) - Account(keyPair, proxy = proxy, proxyPort = proxyPort, signer = NostrSignerExternal(keyPair.pubKey.toHexKey())) + val localPackageName = packageName.ifBlank { "com.greenart7c3.nostrsigner" } + Account(keyPair, proxy = proxy, proxyPort = proxyPort, signer = NostrSignerExternal(keyPair.pubKey.toHexKey(), ExternalSignerLauncher(keyPair.pubKey.toNpub(), localPackageName))) } else if (key.startsWith("nsec")) { val keyPair = KeyPair(privKey = key.bechToBytes()) Account(keyPair, proxy = proxy, proxyPort = proxyPort, signer = NostrSignerInternal(keyPair)) @@ -130,11 +134,12 @@ class AccountStateViewModel() : ViewModel() { useProxy: Boolean, proxyPort: Int, loginWithExternalSigner: Boolean = false, + packageName: String = "", onError: () -> Unit ) { viewModelScope.launch(Dispatchers.IO) { try { - loginAndStartUI(key, useProxy, proxyPort, loginWithExternalSigner) + loginAndStartUI(key, useProxy, proxyPort, loginWithExternalSigner, packageName) } catch (e: Exception) { Log.e("Login", "Could not sign in", e) onError() diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedOff/LoginScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedOff/LoginScreen.kt index 0db615da9..a7c8f8307 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedOff/LoginScreen.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedOff/LoginScreen.kt @@ -108,7 +108,7 @@ fun LoginPage( var loginWithExternalSigner by remember { mutableStateOf(false) } if (loginWithExternalSigner) { - val externalSignerLauncher = remember { ExternalSignerLauncher("") } + val externalSignerLauncher = remember { ExternalSignerLauncher("", signerPackageName = "") } val id = remember { UUID.randomUUID().toString() } val launcher = rememberLauncherForActivityResult( @@ -162,8 +162,11 @@ fun LoginPage( SignerType.GET_PUBLIC_KEY, "", id - ) { pubkey -> + ) { result -> println("AAAA- COME BACK") + val split = result.split("-") + val pubkey = split.first() + val packageName = if (split.size > 1) split[1] else "" key.value = TextFieldValue(pubkey) if (!acceptedTerms.value) { termsAcceptanceIsRequired = @@ -175,7 +178,7 @@ fun LoginPage( } if (acceptedTerms.value && key.value.text.isNotBlank()) { - accountViewModel.login(key.value.text, useProxy.value, proxyPort.value.toInt(), true) { + accountViewModel.login(key.value.text, useProxy.value, proxyPort.value.toInt(), true, packageName) { errorMessage = context.getString(R.string.invalid_key) } } @@ -432,33 +435,8 @@ fun LoginPage( return@Button } - val result = ExternalSignerLauncher("").getDataFromResolver( - SignerType.GET_PUBLIC_KEY, - arrayOf("login"), - contentResolver = Amethyst.instance.contentResolver - ) - - if (result == null) { - loginWithExternalSigner = true - return@Button - } else { - key.value = TextFieldValue(result) - if (key.value.text.isBlank()) { - errorMessage = context.getString(R.string.key_is_required) - return@Button - } - - if (acceptedTerms.value && key.value.text.isNotBlank()) { - accountViewModel.login( - key.value.text, - useProxy.value, - proxyPort.value.toInt(), - true - ) { - errorMessage = context.getString(R.string.invalid_key) - } - } - } + loginWithExternalSigner = true + return@Button }, shape = RoundedCornerShape(Size35dp), modifier = Modifier diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/signers/ExternalSignerLauncher.kt b/quartz/src/main/java/com/vitorpamplona/quartz/signers/ExternalSignerLauncher.kt index f0292ad37..4e0d38ed4 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/signers/ExternalSignerLauncher.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/signers/ExternalSignerLauncher.kt @@ -19,9 +19,18 @@ enum class SignerType { DECRYPT_ZAP_EVENT } +class Permission( + val type: String, + val kind: Int? = null +) { + fun toJson(): String { + return "{\"type\":\"${type}\",\"kind\":${kind}}" + } +} + class ExternalSignerLauncher( private val npub: String, - private val signerPackageName: String = "com.greenart7c3.nostrsigner" + val signerPackageName: String = "com.greenart7c3.nostrsigner" ) { private val contentCache = LruCache Unit>(20) @@ -49,9 +58,11 @@ class ExternalSignerLauncher( fun newResult(data: Intent) { val signature = data.getStringExtra("signature") ?: "" + val packageName = data.getStringExtra("package") ?: "" val id = data.getStringExtra("id") ?: "" if (id.isNotBlank()) { - contentCache.get(id)?.invoke(signature) + val result = if (packageName.isNotBlank()) "$signature-$packageName" else signature + contentCache.get(id)?.invoke(result) } } @@ -70,6 +81,40 @@ class ExternalSignerLauncher( } } + private fun defaultPermissions(): String { + val permissions = listOf( + Permission( + "sign_event", + 22242 + ), + Permission( + "nip04_encrypt" + ), + Permission( + "nip04_decrypt" + ), + Permission( + "nip44_encrypt" + ), + Permission( + "nip44_decrypt" + ), + Permission( + "decrypt_zap_event" + ), + ) + val jsonArray = StringBuilder("[") + permissions.forEachIndexed { index, permission -> + jsonArray.append(permission.toJson()) + if (index < permissions.size - 1) { + jsonArray.append(",") + } + } + jsonArray.append("]") + + return jsonArray.toString() + } + private fun openSignerApp( data: String, type: SignerType, @@ -93,8 +138,12 @@ class ExternalSignerLauncher( intent.putExtra("id", id) if (type !== SignerType.GET_PUBLIC_KEY) { intent.putExtra("current_user", npub) + } else { + intent.putExtra("permissions", defaultPermissions()) + } + if (signerPackageName.isNotBlank()) { + intent.`package` = signerPackageName } - intent.`package` = signerPackageName contentCache.put(id, onReady) diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/signers/NostrSignerExternal.kt b/quartz/src/main/java/com/vitorpamplona/quartz/signers/NostrSignerExternal.kt index b159e0742..1078ceef0 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/signers/NostrSignerExternal.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/signers/NostrSignerExternal.kt @@ -34,17 +34,33 @@ class NostrSignerExternal( ) launcher.openSigner(event) { signature -> - (EventFactory.create( - event.id, - event.pubKey, - event.createdAt, - event.kind, - event.tags, - event.content, - signature - ) as? T?)?.let { - onReady(it) + if (signature.startsWith("{")) { + val localEvent = Event.fromJson(signature) + (EventFactory.create( + localEvent.id, + localEvent.pubKey, + localEvent.createdAt, + localEvent.kind, + localEvent.tags, + localEvent.content, + localEvent.sig + ) as? T?)?.let { + onReady(it) + } + } else { + (EventFactory.create( + event.id, + event.pubKey, + event.createdAt, + event.kind, + event.tags, + event.content, + signature + ) as? T?)?.let { + onReady(it) + } } + } } From 9fc0c378459d2cea6b244be9f8db74659c1e6f17 Mon Sep 17 00:00:00 2001 From: greenart7c3 Date: Mon, 20 Nov 2023 17:35:35 -0300 Subject: [PATCH 2/2] fix relay reconnecting when returning from external signer --- .../vitorpamplona/amethyst/ui/MainActivity.kt | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/MainActivity.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/MainActivity.kt index 5919c1f0d..11d4698f2 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/MainActivity.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/MainActivity.kt @@ -49,6 +49,8 @@ import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch import java.net.URLEncoder import java.nio.charset.StandardCharsets +import java.util.Timer +import kotlin.concurrent.schedule class MainActivity : AppCompatActivity() { private val isOnMobileDataState = mutableStateOf(false) @@ -108,6 +110,7 @@ class MainActivity : AppCompatActivity() { DefaultMutedSetting.value = true // Keep connection alive if it's calling the signer app + Log.d("shouldPauseService", "shouldPauseService onResume: $shouldPauseService") if (shouldPauseService) { GlobalScope.launch(Dispatchers.IO) { serviceManager.justStart() @@ -121,7 +124,9 @@ class MainActivity : AppCompatActivity() { (getSystemService(ConnectivityManager::class.java) as ConnectivityManager).registerDefaultNetworkCallback(networkCallback) // resets state until next External Signer Call - shouldPauseService = true + Timer().schedule(350) { + shouldPauseService = true + } } override fun onPause() { @@ -134,6 +139,7 @@ class MainActivity : AppCompatActivity() { } // } + Log.d("shouldPauseService", "shouldPauseService onPause: $shouldPauseService") if (shouldPauseService) { GlobalScope.launch(Dispatchers.IO) { serviceManager.pauseForGood() @@ -171,8 +177,11 @@ class MainActivity : AppCompatActivity() { override fun onAvailable(network: Network) { super.onAvailable(network) - GlobalScope.launch(Dispatchers.IO) { - serviceManager.forceRestart() + Log.d("shouldPauseService", "shouldPauseService onAvailable: $shouldPauseService") + if (shouldPauseService) { + GlobalScope.launch(Dispatchers.IO) { + serviceManager.forceRestart() + } } } @@ -202,7 +211,8 @@ class MainActivity : AppCompatActivity() { changedNetwork = true } - if (changedNetwork) { + Log.d("shouldPauseService", "shouldPauseService onCapabilitiesChanged: $shouldPauseService") + if (changedNetwork && shouldPauseService) { GlobalScope.launch(Dispatchers.IO) { serviceManager.forceRestart() }