Rewrites NIP-55 to start offering better error handling.

This commit is contained in:
Vitor Pamplona
2025-07-14 11:17:59 -04:00
parent f93b08a272
commit 510583e72e
78 changed files with 3809 additions and 1555 deletions
@@ -20,6 +20,7 @@
*/
package com.vitorpamplona.quartz.nip01Core.core
import android.util.Log
import androidx.compose.runtime.Immutable
import com.vitorpamplona.quartz.nip01Core.jackson.EventManualSerializer
import com.vitorpamplona.quartz.nip01Core.jackson.JsonMapper
@@ -58,6 +59,14 @@ open class Event(
companion object {
fun fromJson(json: String): Event = JsonMapper.fromJson(json)
fun fromJsonOrNull(json: String) =
try {
fromJson(json)
} catch (e: Exception) {
Log.e("Event", "Unable to parse event JSON: $json", e)
null
}
fun build(
kind: Int,
content: String = "",
@@ -35,9 +35,13 @@ import com.vitorpamplona.quartz.nip47WalletConnect.Request
import com.vitorpamplona.quartz.nip47WalletConnect.RequestDeserializer
import com.vitorpamplona.quartz.nip47WalletConnect.Response
import com.vitorpamplona.quartz.nip47WalletConnect.ResponseDeserializer
import com.vitorpamplona.quartz.nip55AndroidSigner.api.permission.Permission
import com.vitorpamplona.quartz.nip55AndroidSigner.api.permission.PermissionDeserializer
import com.vitorpamplona.quartz.nip55AndroidSigner.api.permission.PermissionSerializer
import com.vitorpamplona.quartz.nip59Giftwrap.rumors.Rumor
import com.vitorpamplona.quartz.nip59Giftwrap.rumors.RumorDeserializer
import com.vitorpamplona.quartz.nip59Giftwrap.rumors.RumorSerializer
import kotlin.jvm.java
class JsonMapper {
companion object Companion {
@@ -60,7 +64,9 @@ class JsonMapper {
.addSerializer(BunkerRequest::class.java, BunkerRequest.BunkerRequestSerializer())
.addDeserializer(BunkerRequest::class.java, BunkerRequest.BunkerRequestDeserializer())
.addSerializer(BunkerResponse::class.java, BunkerResponse.BunkerResponseSerializer())
.addDeserializer(BunkerResponse::class.java, BunkerResponse.BunkerResponseDeserializer()),
.addDeserializer(BunkerResponse::class.java, BunkerResponse.BunkerResponseDeserializer())
.addDeserializer(Permission::class.java, PermissionDeserializer())
.addSerializer(Permission::class.java, PermissionSerializer()),
)
fun fromJson(json: String): Event = mapper.readValue(json, Event::class.java)
@@ -47,25 +47,25 @@ abstract class NostrSigner(
)
abstract fun nip04Encrypt(
decryptedContent: String,
plaintext: String,
toPublicKey: HexKey,
onReady: (String) -> Unit,
)
abstract fun nip04Decrypt(
encryptedContent: String,
ciphertext: String,
fromPublicKey: HexKey,
onReady: (String) -> Unit,
)
abstract fun nip44Encrypt(
decryptedContent: String,
plaintext: String,
toPublicKey: HexKey,
onReady: (String) -> Unit,
)
abstract fun nip44Decrypt(
encryptedContent: String,
ciphertext: String,
fromPublicKey: HexKey,
onReady: (String) -> Unit,
)
@@ -45,35 +45,35 @@ class NostrSignerInternal(
}
override fun nip04Encrypt(
decryptedContent: String,
plaintext: String,
toPublicKey: HexKey,
onReady: (String) -> Unit,
) {
signerSync.nip04Encrypt(decryptedContent, toPublicKey)?.let { onReady(it) }
signerSync.nip04Encrypt(plaintext, toPublicKey)?.let { onReady(it) }
}
override fun nip04Decrypt(
encryptedContent: String,
ciphertext: String,
fromPublicKey: HexKey,
onReady: (String) -> Unit,
) {
signerSync.nip04Decrypt(encryptedContent, fromPublicKey)?.let { onReady(it) }
signerSync.nip04Decrypt(ciphertext, fromPublicKey)?.let { onReady(it) }
}
override fun nip44Encrypt(
decryptedContent: String,
plaintext: String,
toPublicKey: HexKey,
onReady: (String) -> Unit,
) {
signerSync.nip44Encrypt(decryptedContent, toPublicKey)?.let { onReady(it) }
signerSync.nip44Encrypt(plaintext, toPublicKey)?.let { onReady(it) }
}
override fun nip44Decrypt(
encryptedContent: String,
ciphertext: String,
fromPublicKey: HexKey,
onReady: (String) -> Unit,
) {
signerSync.nip44Decrypt(encryptedContent, fromPublicKey)?.let { onReady(it) }
signerSync.nip44Decrypt(ciphertext, fromPublicKey)?.let { onReady(it) }
}
override fun decryptZapEvent(
@@ -1,438 +0,0 @@
/**
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.nip55AndroidSigner
import android.content.ContentResolver
import android.content.Intent
import android.util.Log
import android.util.LruCache
import androidx.core.net.toUri
import com.fasterxml.jackson.annotation.JsonProperty
import com.fasterxml.jackson.core.JsonParser
import com.fasterxml.jackson.databind.DeserializationContext
import com.fasterxml.jackson.databind.DeserializationFeature
import com.fasterxml.jackson.databind.JsonNode
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.databind.deser.std.StdDeserializer
import com.fasterxml.jackson.databind.module.SimpleModule
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import com.fasterxml.jackson.module.kotlin.readValue
import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.nip01Core.core.HexKey
import com.vitorpamplona.quartz.nip57Zaps.LnZapRequestEvent
enum class SignerType {
SIGN_EVENT,
NIP04_ENCRYPT,
NIP04_DECRYPT,
NIP44_ENCRYPT,
NIP44_DECRYPT,
GET_PUBLIC_KEY,
DECRYPT_ZAP_EVENT,
DERIVE_KEY,
}
class Permission(
val type: String,
val kind: Int? = null,
) {
fun toJson(): String = "{\"type\":\"${type}\",\"kind\":$kind}"
}
class Result(
@JsonProperty("package") val `package`: String?,
@JsonProperty("signature") val signature: String?,
@JsonProperty("result") val result: String?,
@JsonProperty("id") val id: String?,
) {
companion object {
val mapper: ObjectMapper =
jacksonObjectMapper()
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
.registerModule(
SimpleModule().addDeserializer(Result::class.java, ResultDeserializer()),
)
private class ResultDeserializer : StdDeserializer<Result>(Result::class.java) {
override fun deserialize(
jp: JsonParser,
ctxt: DeserializationContext,
): Result {
val jsonObject: JsonNode = jp.codec.readTree(jp)
return Result(
jsonObject.get("package").asText().intern(),
jsonObject.get("signature")?.asText()?.intern(),
jsonObject.get("result")?.asText()?.intern(),
jsonObject.get("id").asText().intern(),
)
}
}
fun fromJson(json: String): Result = mapper.readValue(json, Result::class.java)
/**
* Parses the json with a string of events to an Array of Event objects.
*/
fun fromJsonArray(json: String): Array<Result> = mapper.readValue(json)
}
}
class ExternalSignerLauncher(
private val currentUserPubKeyHex: String,
val signerPackageName: String,
) {
private val contentCache = LruCache<String, (String) -> Unit>(50)
private var signerAppLauncher: ((Intent) -> Unit)? = null
private var contentResolver: (() -> ContentResolver)? = null
/** Call this function when the launcher becomes available on activity, fragment or compose */
fun registerLauncher(
launcher: ((Intent) -> Unit),
contentResolver: (() -> ContentResolver),
) {
this.signerAppLauncher = launcher
this.contentResolver = contentResolver
}
/** Call this function when the activity is destroyed or is about to be replaced. */
fun clearLauncherIf(launcher: ((Intent) -> Unit)) {
if (signerAppLauncher == launcher) {
this.signerAppLauncher = null
this.contentResolver = null
}
}
fun newResult(data: Intent) {
val results = data.getStringExtra("results")
if (results != null) {
val localResults: Array<Result> = Result.fromJsonArray(results)
localResults.forEach {
val signature = it.result ?: it.signature ?: ""
val packageName = it.`package`?.let { "-$it" } ?: ""
val id = it.id ?: ""
if (id.isNotBlank()) {
val result = if (packageName.isNotBlank()) "$signature$packageName" else signature
val contentCache = contentCache.get(id)
contentCache?.invoke(result)
}
}
} else {
val signature = data.getStringExtra("result") ?: data.getStringExtra("signature") ?: ""
val packageName = data.getStringExtra("package")?.let { "-$it" } ?: ""
val id = data.getStringExtra("id") ?: ""
if (id.isNotBlank()) {
val result = if (packageName.isNotBlank()) "$signature$packageName" else signature
val contentCache = contentCache.get(id)
contentCache?.invoke(result)
}
}
}
fun openSignerApp(
data: String,
type: SignerType,
pubKey: HexKey,
id: String,
onReady: (String) -> Unit,
) {
signerAppLauncher?.let {
openSignerApp(
data,
type,
it,
pubKey,
id,
onReady,
)
}
}
private fun defaultPermissions(): String {
val permissions =
listOf(
Permission(
"sign_event",
22242,
),
Permission(
"sign_event",
31234,
),
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,
intentLauncher: (Intent) -> Unit,
pubKey: HexKey,
id: String,
onReady: (String) -> Unit,
) {
val intent = Intent(Intent.ACTION_VIEW, "nostrsigner:$data".toUri())
val signerType =
when (type) {
SignerType.SIGN_EVENT -> "sign_event"
SignerType.NIP04_ENCRYPT -> "nip04_encrypt"
SignerType.NIP04_DECRYPT -> "nip04_decrypt"
SignerType.NIP44_ENCRYPT -> "nip44_encrypt"
SignerType.NIP44_DECRYPT -> "nip44_decrypt"
SignerType.GET_PUBLIC_KEY -> "get_public_key"
SignerType.DECRYPT_ZAP_EVENT -> "decrypt_zap_event"
SignerType.DERIVE_KEY -> "derive_key"
}
intent.putExtra("type", signerType)
intent.putExtra("pubKey", pubKey)
intent.putExtra("id", id)
if (type !== SignerType.GET_PUBLIC_KEY) {
intent.putExtra("current_user", currentUserPubKeyHex)
} else {
intent.putExtra("permissions", defaultPermissions())
}
if (signerPackageName.isNotBlank()) {
intent.`package` = signerPackageName
}
intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_CLEAR_TOP)
contentCache.put(id, onReady)
intentLauncher(intent)
}
fun openSigner(
event: Event,
onReady: (String) -> Unit,
) {
getDataFromResolver(
SignerType.SIGN_EVENT,
arrayOf(event.toJson(), event.pubKey),
).fold(
onFailure = { },
onSuccess = {
if (it == null) {
openSignerApp(
event.toJson(),
SignerType.SIGN_EVENT,
"",
event.id,
onReady,
)
} else {
onReady(it)
}
},
)
}
private fun getDataFromResolver(
signerType: SignerType,
data: Array<out String>,
): kotlin.Result<String?> = getDataFromResolver(signerType, data, contentResolver)
private fun getDataFromResolver(
signerType: SignerType,
data: Array<out String>,
contentResolver: (() -> ContentResolver)? = null,
): kotlin.Result<String?> {
val localData =
if (signerType !== SignerType.GET_PUBLIC_KEY) {
arrayOf(*data, currentUserPubKeyHex)
} else {
data
}
try {
contentResolver
?.let { it() }
?.query(
"content://$signerPackageName.$signerType".toUri(),
localData,
"1",
null,
null,
).use {
if (it == null) {
return kotlin.Result.success(null)
}
if (it.moveToFirst()) {
if (it.getColumnIndex("rejected") > -1) {
Log.d("getDataFromResolver", "Permission denied")
return kotlin.Result.failure(Exception("Permission denied"))
}
var index = it.getColumnIndex("result")
if (index < 0) {
index = it.getColumnIndex("signature")
}
if (index < 0) {
Log.d("getDataFromResolver", "column 'signature' not found")
return kotlin.Result.success(null)
}
return kotlin.Result.success(it.getString(index))
}
}
} catch (e: Exception) {
Log.e("ExternalSignerLauncher", "Failed to query the Signer app in the background", e)
return kotlin.Result.success(null)
}
return kotlin.Result.success(null)
}
fun hashCodeFields(
str1: String,
onReady: (String) -> Unit,
): Int {
var result = str1.hashCode()
result = 31 * result + onReady.hashCode()
return result
}
fun hashCodeFields(
str1: String,
str2: String,
onReady: (String) -> Unit,
): Int {
var result = str1.hashCode()
result = 31 * result + str2.hashCode()
result = 31 * result + onReady.hashCode()
return result
}
fun decrypt(
encryptedContent: String,
pubKey: HexKey,
signerType: SignerType = SignerType.NIP04_DECRYPT,
onReady: (String) -> Unit,
) {
getDataFromResolver(signerType, arrayOf(encryptedContent, pubKey)).fold(
onFailure = { },
onSuccess = {
if (it == null) {
openSignerApp(
encryptedContent,
signerType,
pubKey,
hashCodeFields(encryptedContent, pubKey, onReady).toString(),
onReady,
)
} else {
onReady(it)
}
},
)
}
fun encrypt(
decryptedContent: String,
pubKey: HexKey,
signerType: SignerType = SignerType.NIP04_ENCRYPT,
onReady: (String) -> Unit,
) {
getDataFromResolver(signerType, arrayOf(decryptedContent, pubKey)).fold(
onFailure = { },
onSuccess = {
if (it == null) {
openSignerApp(
decryptedContent,
signerType,
pubKey,
hashCodeFields(decryptedContent, pubKey, onReady).toString(),
onReady,
)
} else {
onReady(it)
}
},
)
}
fun decryptZapEvent(
event: LnZapRequestEvent,
onReady: (String) -> Unit,
) {
getDataFromResolver(SignerType.DECRYPT_ZAP_EVENT, arrayOf(event.toJson(), event.pubKey)).fold(
onFailure = { },
onSuccess = {
if (it == null) {
openSignerApp(
event.toJson(),
SignerType.DECRYPT_ZAP_EVENT,
event.pubKey,
event.id,
onReady,
)
} else {
onReady(it)
}
},
)
}
fun deriveKey(
nonce: HexKey,
signerType: SignerType = SignerType.DERIVE_KEY,
onReady: (String) -> Unit,
) {
getDataFromResolver(signerType, arrayOf(nonce)).fold(
onFailure = { },
onSuccess = {
if (it == null) {
openSignerApp(
nonce,
signerType,
"",
hashCodeFields(nonce, onReady).toString(),
onReady,
)
} else {
onReady(it)
}
},
)
}
}
@@ -0,0 +1,50 @@
/**
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.nip55AndroidSigner.api
enum class CommandType(
val code: String,
) {
SIGN_EVENT("sign_event"),
NIP04_ENCRYPT("nip04_encrypt"),
NIP04_DECRYPT("nip04_decrypt"),
NIP44_ENCRYPT("nip44_encrypt"),
NIP44_DECRYPT("nip44_decrypt"),
GET_PUBLIC_KEY("get_public_key"),
DECRYPT_ZAP_EVENT("decrypt_zap_event"),
DERIVE_KEY("derive_key"),
;
companion object Companion {
fun parse(code: String): CommandType? =
when (code) {
SIGN_EVENT.code -> SIGN_EVENT
NIP04_ENCRYPT.code -> NIP04_ENCRYPT
NIP04_DECRYPT.code -> NIP04_DECRYPT
NIP44_ENCRYPT.code -> NIP44_ENCRYPT
NIP44_DECRYPT.code -> NIP44_DECRYPT
GET_PUBLIC_KEY.code -> GET_PUBLIC_KEY
DECRYPT_ZAP_EVENT.code -> DECRYPT_ZAP_EVENT
DERIVE_KEY.code -> DERIVE_KEY
else -> null
}
}
}
@@ -0,0 +1,86 @@
/**
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.nip55AndroidSigner.api
import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.nip01Core.core.HexKey
import com.vitorpamplona.quartz.nip57Zaps.LnZapPrivateEvent
interface RequestAddressed
sealed class SignerResult<T : IResult> {
class Successful<T : IResult>(
val result: T,
) : SignerResult<T>(),
RequestAddressed
class Rejected<T : IResult> :
SignerResult<T>(),
RequestAddressed
class ReceivedButCouldNotPerform<T : IResult>(
message: String? = null,
) : SignerResult<T>(),
RequestAddressed
class ReceivedButCouldNotParseEventFromResult<T : IResult>(
val eventJson: String,
) : SignerResult<T>(),
RequestAddressed
class ReceivedButCouldNotVerifyResultingEvent<T : IResult>(
val invalidEvent: Event,
) : SignerResult<T>(),
RequestAddressed
class ErrorExceptionCallingContentResolver<T : IResult>(
val e: Exception? = null,
) : SignerResult<T>()
class RequiresManualApproval<T : IResult> : SignerResult<T>()
}
interface IResult
data class PubKeyResult(
val pubkey: HexKey,
val packageName: String,
) : IResult
data class SignResult(
val event: Event,
) : IResult
data class EncryptionResult(
val ciphertext: String,
) : IResult
data class DecryptionResult(
val plaintext: String,
) : IResult
data class ZapEventDecryptionResult(
val privateEvent: LnZapPrivateEvent,
) : IResult
data class DerivationResult(
val newPrivKey: HexKey,
) : IResult
@@ -0,0 +1,66 @@
/**
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.nip55AndroidSigner.api.background.queries
import android.content.ContentResolver
import androidx.core.net.toUri
import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.nip01Core.core.HexKey
import com.vitorpamplona.quartz.nip01Core.verify
import com.vitorpamplona.quartz.nip55AndroidSigner.api.CommandType
import com.vitorpamplona.quartz.nip55AndroidSigner.api.SignerResult
import com.vitorpamplona.quartz.nip55AndroidSigner.api.ZapEventDecryptionResult
import com.vitorpamplona.quartz.nip55AndroidSigner.api.background.utils.getStringByName
import com.vitorpamplona.quartz.nip55AndroidSigner.api.background.utils.query
import com.vitorpamplona.quartz.nip57Zaps.LnZapPrivateEvent
import kotlin.text.isNullOrBlank
class DecryptZapQuery(
val loggedInUser: HexKey,
val packageName: String,
val contentResolver: ContentResolver,
) {
fun query(event: Event): SignerResult<ZapEventDecryptionResult> =
contentResolver.query(
"content://$packageName.${CommandType.DECRYPT_ZAP_EVENT}".toUri(),
arrayOf(event.toJson(), event.pubKey, loggedInUser),
) { cursor ->
val decryptedEventAsJson = cursor.getStringByName("result")
if (!decryptedEventAsJson.isNullOrBlank()) {
if (decryptedEventAsJson.startsWith("{")) {
val event = Event.fromJsonOrNull(decryptedEventAsJson) as? LnZapPrivateEvent
if (event != null) {
if (event.verify()) {
SignerResult.Successful(ZapEventDecryptionResult(event))
} else {
SignerResult.ReceivedButCouldNotVerifyResultingEvent(event)
}
} else {
SignerResult.ReceivedButCouldNotParseEventFromResult(decryptedEventAsJson)
}
} else {
SignerResult.ReceivedButCouldNotPerform(decryptedEventAsJson)
}
} else {
SignerResult.ReceivedButCouldNotPerform(decryptedEventAsJson)
}
}
}
@@ -0,0 +1,51 @@
/**
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.nip55AndroidSigner.api.background.queries
import android.content.ContentResolver
import androidx.core.net.toUri
import com.vitorpamplona.quartz.nip01Core.core.HexKey
import com.vitorpamplona.quartz.nip55AndroidSigner.api.CommandType
import com.vitorpamplona.quartz.nip55AndroidSigner.api.DerivationResult
import com.vitorpamplona.quartz.nip55AndroidSigner.api.SignerResult
import com.vitorpamplona.quartz.nip55AndroidSigner.api.background.utils.getStringByName
import com.vitorpamplona.quartz.nip55AndroidSigner.api.background.utils.query
class DeriveKeyQuery(
val loggedInUser: HexKey,
val packageName: String,
val contentResolver: ContentResolver,
) {
val uri = "content://$packageName.${CommandType.DERIVE_KEY}".toUri()
fun query(nonce: HexKey): SignerResult<DerivationResult> =
contentResolver.query(
uri,
arrayOf(nonce, loggedInUser),
) { cursor ->
val newPrivateKey = cursor.getStringByName("result")
if (!newPrivateKey.isNullOrBlank()) {
SignerResult.Successful(DerivationResult(newPrivateKey))
} else {
SignerResult.ReceivedButCouldNotPerform()
}
}
}
@@ -0,0 +1,53 @@
/**
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.nip55AndroidSigner.api.background.queries
import android.content.ContentResolver
import androidx.core.net.toUri
import com.vitorpamplona.quartz.nip55AndroidSigner.api.CommandType
import com.vitorpamplona.quartz.nip55AndroidSigner.api.PubKeyResult
import com.vitorpamplona.quartz.nip55AndroidSigner.api.SignerResult
import com.vitorpamplona.quartz.nip55AndroidSigner.api.background.utils.getStringByName
import com.vitorpamplona.quartz.nip55AndroidSigner.api.background.utils.query
class LoginQuery(
val packageName: String,
val contentResolver: ContentResolver,
) {
companion object {
val LOGIN = arrayOf("login")
}
val uri = "content://$packageName.${CommandType.GET_PUBLIC_KEY}".toUri()
fun query(): SignerResult<PubKeyResult> =
contentResolver.query(
uri,
LOGIN,
) { cursor ->
val pubkeyHex = cursor.getStringByName("result")
if (!pubkeyHex.isNullOrBlank()) {
SignerResult.Successful(PubKeyResult(pubkeyHex, packageName))
} else {
SignerResult.ReceivedButCouldNotPerform()
}
}
}
@@ -0,0 +1,54 @@
/**
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.nip55AndroidSigner.api.background.queries
import android.content.ContentResolver
import androidx.core.net.toUri
import com.vitorpamplona.quartz.nip01Core.core.HexKey
import com.vitorpamplona.quartz.nip55AndroidSigner.api.CommandType
import com.vitorpamplona.quartz.nip55AndroidSigner.api.DecryptionResult
import com.vitorpamplona.quartz.nip55AndroidSigner.api.SignerResult
import com.vitorpamplona.quartz.nip55AndroidSigner.api.background.utils.getStringByName
import com.vitorpamplona.quartz.nip55AndroidSigner.api.background.utils.query
class Nip04DecryptQuery(
val loggedInUser: HexKey,
val packageName: String,
val contentResolver: ContentResolver,
) {
val uri = "content://$packageName.${CommandType.NIP04_DECRYPT}".toUri()
fun query(
ciphertext: String,
fromPubKey: HexKey,
): SignerResult<DecryptionResult> =
contentResolver.query(
uri,
arrayOf(ciphertext, fromPubKey, loggedInUser),
) { cursor ->
val plaintext = cursor.getStringByName("result")
if (!plaintext.isNullOrBlank()) {
SignerResult.Successful(DecryptionResult(plaintext))
} else {
SignerResult.ReceivedButCouldNotPerform()
}
}
}
@@ -0,0 +1,54 @@
/**
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.nip55AndroidSigner.api.background.queries
import android.content.ContentResolver
import androidx.core.net.toUri
import com.vitorpamplona.quartz.nip01Core.core.HexKey
import com.vitorpamplona.quartz.nip55AndroidSigner.api.CommandType
import com.vitorpamplona.quartz.nip55AndroidSigner.api.EncryptionResult
import com.vitorpamplona.quartz.nip55AndroidSigner.api.SignerResult
import com.vitorpamplona.quartz.nip55AndroidSigner.api.background.utils.getStringByName
import com.vitorpamplona.quartz.nip55AndroidSigner.api.background.utils.query
class Nip04EncryptQuery(
val loggedInUser: HexKey,
val packageName: String,
val contentResolver: ContentResolver,
) {
val uri = "content://$packageName.${CommandType.NIP04_ENCRYPT}".toUri()
fun query(
plaintext: String,
toPubKey: HexKey,
): SignerResult<EncryptionResult> =
contentResolver.query(
uri,
arrayOf(plaintext, toPubKey, loggedInUser),
) { cursor ->
val ciphertext = cursor.getStringByName("result")
if (!ciphertext.isNullOrBlank()) {
SignerResult.Successful(EncryptionResult(ciphertext))
} else {
SignerResult.ReceivedButCouldNotPerform()
}
}
}
@@ -0,0 +1,54 @@
/**
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.nip55AndroidSigner.api.background.queries
import android.content.ContentResolver
import androidx.core.net.toUri
import com.vitorpamplona.quartz.nip01Core.core.HexKey
import com.vitorpamplona.quartz.nip55AndroidSigner.api.CommandType
import com.vitorpamplona.quartz.nip55AndroidSigner.api.DecryptionResult
import com.vitorpamplona.quartz.nip55AndroidSigner.api.SignerResult
import com.vitorpamplona.quartz.nip55AndroidSigner.api.background.utils.getStringByName
import com.vitorpamplona.quartz.nip55AndroidSigner.api.background.utils.query
class Nip44DecryptQuery(
val loggedInUser: HexKey,
val packageName: String,
val contentResolver: ContentResolver,
) {
val uri = "content://$packageName.${CommandType.NIP44_DECRYPT}".toUri()
fun query(
ciphertext: String,
fromPubKey: HexKey,
): SignerResult<DecryptionResult> =
contentResolver.query(
uri,
arrayOf(ciphertext, fromPubKey, loggedInUser),
) { cursor ->
val plaintext = cursor.getStringByName("result")
if (!plaintext.isNullOrBlank()) {
SignerResult.Successful(DecryptionResult(plaintext))
} else {
SignerResult.ReceivedButCouldNotPerform()
}
}
}
@@ -0,0 +1,54 @@
/**
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.nip55AndroidSigner.api.background.queries
import android.content.ContentResolver
import androidx.core.net.toUri
import com.vitorpamplona.quartz.nip01Core.core.HexKey
import com.vitorpamplona.quartz.nip55AndroidSigner.api.CommandType
import com.vitorpamplona.quartz.nip55AndroidSigner.api.EncryptionResult
import com.vitorpamplona.quartz.nip55AndroidSigner.api.SignerResult
import com.vitorpamplona.quartz.nip55AndroidSigner.api.background.utils.getStringByName
import com.vitorpamplona.quartz.nip55AndroidSigner.api.background.utils.query
class Nip44EncryptQuery(
val loggedInUser: HexKey,
val packageName: String,
val contentResolver: ContentResolver,
) {
val uri = "content://$packageName.${CommandType.NIP44_ENCRYPT}".toUri()
fun query(
plaintext: String,
toPubKey: HexKey,
): SignerResult<EncryptionResult> =
contentResolver.query(
uri,
arrayOf(plaintext, toPubKey, loggedInUser),
) { cursor ->
val ciphertext = cursor.getStringByName("result")
if (!ciphertext.isNullOrBlank()) {
SignerResult.Successful(EncryptionResult(ciphertext))
} else {
SignerResult.ReceivedButCouldNotPerform()
}
}
}
@@ -0,0 +1,86 @@
/**
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.nip55AndroidSigner.api.background.queries
import android.content.ContentResolver
import androidx.core.net.toUri
import com.vitorpamplona.quartz.EventFactory
import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.nip01Core.core.HexKey
import com.vitorpamplona.quartz.nip01Core.verify
import com.vitorpamplona.quartz.nip55AndroidSigner.api.CommandType
import com.vitorpamplona.quartz.nip55AndroidSigner.api.SignResult
import com.vitorpamplona.quartz.nip55AndroidSigner.api.SignerResult
import com.vitorpamplona.quartz.nip55AndroidSigner.api.background.utils.getStringByName
import com.vitorpamplona.quartz.nip55AndroidSigner.api.background.utils.query
class SignQuery(
val loggedInUser: HexKey,
val packageName: String,
val contentResolver: ContentResolver,
) {
val uri = "content://$packageName.${CommandType.SIGN_EVENT}".toUri()
fun query(unsignedEvent: Event): SignerResult<SignResult> =
contentResolver.query(
uri,
arrayOf(unsignedEvent.toJson(), unsignedEvent.pubKey, loggedInUser),
) { cursor ->
val eventJson = cursor.getStringByName("event")
if (!eventJson.isNullOrBlank()) {
if (eventJson.startsWith("{")) {
val event = Event.fromJsonOrNull(eventJson)
if (event != null) {
if (event.verify()) {
SignerResult.Successful(SignResult(event))
} else {
SignerResult.ReceivedButCouldNotVerifyResultingEvent(event)
}
} else {
SignerResult.ReceivedButCouldNotParseEventFromResult(eventJson)
}
} else {
SignerResult.ReceivedButCouldNotParseEventFromResult(eventJson)
}
} else {
val signature = cursor.getStringByName("result")
if (!signature.isNullOrBlank()) {
val event =
EventFactory.create(
id = unsignedEvent.id,
pubKey = unsignedEvent.pubKey,
createdAt = unsignedEvent.createdAt,
kind = unsignedEvent.kind,
tags = unsignedEvent.tags,
content = unsignedEvent.content,
sig = signature,
)
if (event.verify()) {
SignerResult.Successful(SignResult(event))
} else {
SignerResult.ReceivedButCouldNotVerifyResultingEvent(event)
}
} else {
SignerResult.ReceivedButCouldNotPerform()
}
}
}
}
@@ -0,0 +1,56 @@
/**
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.nip55AndroidSigner.api.background.utils
import android.content.ContentResolver
import android.database.Cursor
import android.net.Uri
import android.util.Log
import com.vitorpamplona.quartz.nip55AndroidSigner.api.IResult
import com.vitorpamplona.quartz.nip55AndroidSigner.api.SignerResult
fun <T : IResult> ContentResolver.query(
uri: Uri,
projection: Array<out String>,
map: (cursor: Cursor) -> SignerResult<T>,
): SignerResult<T> =
try {
query(
uri,
projection,
null,
null,
null,
).use {
if (it != null && it.moveToFirst()) {
if (it.getColumnIndex("rejected") > -1) {
SignerResult.Rejected()
} else {
map(it)
}
} else {
SignerResult.RequiresManualApproval()
}
}
} catch (e: Exception) {
Log.e("ExternalSignerLauncher", "Failed to query the Signer app in the background", e)
SignerResult.ErrorExceptionCallingContentResolver(e)
}
@@ -0,0 +1,33 @@
/**
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.nip55AndroidSigner.api.background.utils
import android.database.Cursor
fun Cursor.getStringByName(name: String): String? {
val index = getColumnIndex(name)
return if (index >= 0) {
val result = getString(index)
result.ifBlank { null }
} else {
null
}
}
@@ -0,0 +1,65 @@
/**
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.nip55AndroidSigner.api.foreground
import android.content.Intent
import androidx.collection.LruCache
import com.vitorpamplona.quartz.nip55AndroidSigner.api.foreground.NewResultProcessor
import com.vitorpamplona.quartz.utils.RandomInstance
class IntentRequestDatabase {
private val awaitingRequests = LruCache<String, NewResultProcessor>(2000)
private var appLauncher: ((Intent) -> Unit)? = null
/** Call this function when the launcher becomes available on activity, fragment or compose */
fun registerForegroundLauncher(launcher: ((Intent) -> Unit)) {
this.appLauncher = launcher
}
/** Call this function when the activity is destroyed or is about to be replaced. */
fun unregisterForegroundLauncher(launcher: ((Intent) -> Unit)) {
if (this.appLauncher == launcher) {
this.appLauncher = null
}
}
fun newResponse(data: Intent) {
val callId = data.getStringExtra("id")
if (callId != null) {
awaitingRequests[callId]?.process(data)
awaitingRequests.remove(callId)
}
}
fun launch(
requestIntent: Intent,
responseProcessor: NewResultProcessor,
) {
appLauncher?.let {
val callId = RandomInstance.randomChars(32)
awaitingRequests.put(callId, responseProcessor)
requestIntent.putExtra("id", callId)
requestIntent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_CLEAR_TOP)
it.invoke(requestIntent)
}
}
}
@@ -0,0 +1,47 @@
/**
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.nip55AndroidSigner.api.foreground
import android.content.Intent
/**
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
interface NewResultProcessor {
fun process(intent: Intent)
}
@@ -0,0 +1,43 @@
/**
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.nip55AndroidSigner.api.foreground.intents.requests
import android.content.Intent
import androidx.core.net.toUri
import com.vitorpamplona.quartz.nip01Core.core.HexKey
import com.vitorpamplona.quartz.nip55AndroidSigner.api.CommandType
import com.vitorpamplona.quartz.nip57Zaps.LnZapRequestEvent
class DecryptZapRequest {
companion object {
fun assemble(
event: LnZapRequestEvent,
loggedInUser: HexKey,
packageName: String,
): Intent {
val intent = Intent(Intent.ACTION_VIEW, "nostrsigner:${event.toJson()}".toUri())
intent.`package` = packageName
intent.putExtra("type", CommandType.NIP44_DECRYPT.code)
intent.putExtra("current_user", loggedInUser)
return intent
}
}
}
@@ -0,0 +1,42 @@
/**
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.nip55AndroidSigner.api.foreground.intents.requests
import android.content.Intent
import androidx.core.net.toUri
import com.vitorpamplona.quartz.nip01Core.core.HexKey
import com.vitorpamplona.quartz.nip55AndroidSigner.api.CommandType
class DeriveKeyRequest {
companion object {
fun assemble(
nonce: HexKey,
loggedInUser: HexKey,
packageName: String,
): Intent {
val intent = Intent(Intent.ACTION_VIEW, "nostrsigner:$nonce".toUri())
intent.`package` = packageName
intent.putExtra("type", CommandType.DERIVE_KEY.code)
intent.putExtra("current_user", loggedInUser)
return intent
}
}
}
@@ -0,0 +1,49 @@
/**
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.nip55AndroidSigner.api.foreground.intents.requests
import android.content.Intent
import androidx.core.net.toUri
import com.vitorpamplona.quartz.nip01Core.jackson.JsonMapper
import com.vitorpamplona.quartz.nip42RelayAuth.RelayAuthEvent
import com.vitorpamplona.quartz.nip55AndroidSigner.api.CommandType
import com.vitorpamplona.quartz.nip55AndroidSigner.api.permission.Permission
class LoginRequest {
companion object {
val DefaultPermissions =
listOf(
Permission(CommandType.SIGN_EVENT, RelayAuthEvent.KIND),
Permission(CommandType.NIP04_ENCRYPT),
Permission(CommandType.NIP04_DECRYPT),
Permission(CommandType.NIP44_DECRYPT),
Permission(CommandType.NIP44_DECRYPT),
Permission(CommandType.DECRYPT_ZAP_EVENT),
)
fun assemble(permissions: List<Permission> = DefaultPermissions): Intent {
val intent = Intent(Intent.ACTION_VIEW, "nostrsigner:".toUri())
intent.putExtra("type", CommandType.GET_PUBLIC_KEY.code)
intent.putExtra("permissions", JsonMapper.mapper.writeValueAsString(permissions))
return intent
}
}
}
@@ -0,0 +1,44 @@
/**
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.nip55AndroidSigner.api.foreground.intents.requests
import android.content.Intent
import androidx.core.net.toUri
import com.vitorpamplona.quartz.nip01Core.core.HexKey
import com.vitorpamplona.quartz.nip55AndroidSigner.api.CommandType
class Nip04DecryptRequest {
companion object {
fun assemble(
ciphertext: String,
fromPubKey: HexKey,
loggedInUser: HexKey,
packageName: String,
): Intent {
val intent = Intent(Intent.ACTION_VIEW, "nostrsigner:$ciphertext".toUri())
intent.`package` = packageName
intent.putExtra("type", CommandType.NIP04_DECRYPT.code)
intent.putExtra("pubKey", fromPubKey)
intent.putExtra("current_user", loggedInUser)
return intent
}
}
}
@@ -0,0 +1,44 @@
/**
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.nip55AndroidSigner.api.foreground.intents.requests
import android.content.Intent
import androidx.core.net.toUri
import com.vitorpamplona.quartz.nip01Core.core.HexKey
import com.vitorpamplona.quartz.nip55AndroidSigner.api.CommandType
class Nip04EncryptRequest {
companion object {
fun assemble(
plaintext: String,
toPubKey: HexKey,
loggedInUser: HexKey,
packageName: String,
): Intent {
val intent = Intent(Intent.ACTION_VIEW, "nostrsigner:$plaintext".toUri())
intent.`package` = packageName
intent.putExtra("type", CommandType.NIP04_ENCRYPT.code)
intent.putExtra("pubKey", toPubKey)
intent.putExtra("current_user", loggedInUser)
return intent
}
}
}
@@ -0,0 +1,44 @@
/**
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.nip55AndroidSigner.api.foreground.intents.requests
import android.content.Intent
import androidx.core.net.toUri
import com.vitorpamplona.quartz.nip01Core.core.HexKey
import com.vitorpamplona.quartz.nip55AndroidSigner.api.CommandType
class Nip44DecryptRequest {
companion object {
fun assemble(
ciphertext: String,
fromPubKey: HexKey,
loggedInUser: HexKey,
packageName: String,
): Intent {
val intent = Intent(Intent.ACTION_VIEW, "nostrsigner:$ciphertext".toUri())
intent.`package` = packageName
intent.putExtra("type", CommandType.NIP44_DECRYPT.code)
intent.putExtra("pubKey", fromPubKey)
intent.putExtra("current_user", loggedInUser)
return intent
}
}
}
@@ -0,0 +1,44 @@
/**
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.nip55AndroidSigner.api.foreground.intents.requests
import android.content.Intent
import androidx.core.net.toUri
import com.vitorpamplona.quartz.nip01Core.core.HexKey
import com.vitorpamplona.quartz.nip55AndroidSigner.api.CommandType
class Nip44EncryptRequest {
companion object {
fun assemble(
plaintext: String,
toPubKey: HexKey,
loggedInUser: HexKey,
packageName: String,
): Intent {
val intent = Intent(Intent.ACTION_VIEW, "nostrsigner:$plaintext".toUri())
intent.`package` = packageName
intent.putExtra("type", CommandType.NIP44_ENCRYPT.code)
intent.putExtra("pubKey", toPubKey)
intent.putExtra("current_user", loggedInUser)
return intent
}
}
}
@@ -0,0 +1,43 @@
/**
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.nip55AndroidSigner.api.foreground.intents.requests
import android.content.Intent
import androidx.core.net.toUri
import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.nip01Core.core.HexKey
import com.vitorpamplona.quartz.nip55AndroidSigner.api.CommandType
class SignRequest {
companion object {
fun assemble(
event: Event,
loggedInUser: HexKey,
packageName: String,
): Intent {
val intent = Intent(Intent.ACTION_VIEW, "nostrsigner:${event.toJson()}".toUri())
intent.`package` = packageName
intent.putExtra("type", CommandType.SIGN_EVENT.code)
intent.putExtra("current_user", loggedInUser)
return intent
}
}
}
@@ -0,0 +1,55 @@
/**
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.nip55AndroidSigner.api.foreground.intents.responses
import android.content.Intent
import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.nip55AndroidSigner.api.SignerResult
import com.vitorpamplona.quartz.nip55AndroidSigner.api.ZapEventDecryptionResult
import com.vitorpamplona.quartz.nip57Zaps.LnZapPrivateEvent
class DecryptZapResponse {
companion object {
fun assemble(event: LnZapPrivateEvent): Intent {
val intent = Intent()
intent.putExtra("result", event.toJson())
return intent
}
fun parse(intent: Intent): SignerResult<ZapEventDecryptionResult> {
val eventJson = intent.getStringExtra("result")
return if (!eventJson.isNullOrBlank()) {
if (eventJson.startsWith("{")) {
val event = Event.fromJsonOrNull(eventJson) as? LnZapPrivateEvent
if (event != null) {
SignerResult.Successful(ZapEventDecryptionResult(event))
} else {
SignerResult.ReceivedButCouldNotParseEventFromResult(eventJson)
}
} else {
SignerResult.ReceivedButCouldNotPerform()
}
} else {
SignerResult.ReceivedButCouldNotPerform()
}
}
}
}
@@ -0,0 +1,38 @@
/**
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.nip55AndroidSigner.api.foreground.intents.responses
import android.content.Intent
import com.vitorpamplona.quartz.nip55AndroidSigner.api.DerivationResult
import com.vitorpamplona.quartz.nip55AndroidSigner.api.SignerResult
class DeriveKeyResponse {
companion object {
fun parse(intent: Intent): SignerResult<DerivationResult> {
val newPrivateKey = intent.getStringExtra("result")
return if (newPrivateKey != null) {
SignerResult.Successful(DerivationResult(newPrivateKey))
} else {
SignerResult.ReceivedButCouldNotPerform()
}
}
}
}
@@ -0,0 +1,45 @@
/**
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.nip55AndroidSigner.api.foreground.intents.responses
import android.content.Intent
import com.vitorpamplona.quartz.nip55AndroidSigner.api.PubKeyResult
import com.vitorpamplona.quartz.nip55AndroidSigner.api.SignerResult
class LoginResponse {
companion object {
fun parse(intent: Intent): SignerResult<PubKeyResult> {
val pubkey = intent.getStringExtra("result")
val packageName = intent.getStringExtra("package")
return if (pubkey != null && packageName != null) {
SignerResult.Successful(
PubKeyResult(
pubkey = pubkey,
packageName = packageName,
),
)
} else {
SignerResult.ReceivedButCouldNotPerform()
}
}
}
}
@@ -0,0 +1,38 @@
/**
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.nip55AndroidSigner.api.foreground.intents.responses
import android.content.Intent
import com.vitorpamplona.quartz.nip55AndroidSigner.api.DecryptionResult
import com.vitorpamplona.quartz.nip55AndroidSigner.api.SignerResult
class Nip04DecryptResponse {
companion object {
fun parse(intent: Intent): SignerResult<DecryptionResult> {
val ciphertext = intent.getStringExtra("result")
return if (ciphertext != null) {
SignerResult.Successful(DecryptionResult(ciphertext))
} else {
SignerResult.ReceivedButCouldNotPerform()
}
}
}
}
@@ -0,0 +1,38 @@
/**
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.nip55AndroidSigner.api.foreground.intents.responses
import android.content.Intent
import com.vitorpamplona.quartz.nip55AndroidSigner.api.EncryptionResult
import com.vitorpamplona.quartz.nip55AndroidSigner.api.SignerResult
class Nip04EncryptResponse {
companion object {
fun parse(intent: Intent): SignerResult<EncryptionResult> {
val ciphertext = intent.getStringExtra("result")
return if (ciphertext != null) {
SignerResult.Successful(EncryptionResult(ciphertext))
} else {
SignerResult.ReceivedButCouldNotPerform()
}
}
}
}
@@ -0,0 +1,38 @@
/**
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.nip55AndroidSigner.api.foreground.intents.responses
import android.content.Intent
import com.vitorpamplona.quartz.nip55AndroidSigner.api.DecryptionResult
import com.vitorpamplona.quartz.nip55AndroidSigner.api.SignerResult
class Nip44DecryptResponse {
companion object {
fun parse(intent: Intent): SignerResult<DecryptionResult> {
val ciphertext = intent.getStringExtra("result")
return if (ciphertext != null) {
SignerResult.Successful(DecryptionResult(ciphertext))
} else {
SignerResult.ReceivedButCouldNotPerform()
}
}
}
}
@@ -0,0 +1,38 @@
/**
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.nip55AndroidSigner.api.foreground.intents.responses
import android.content.Intent
import com.vitorpamplona.quartz.nip55AndroidSigner.api.EncryptionResult
import com.vitorpamplona.quartz.nip55AndroidSigner.api.SignerResult
class Nip44EncryptResponse {
companion object {
fun parse(intent: Intent): SignerResult<EncryptionResult> {
val ciphertext = intent.getStringExtra("result")
return if (ciphertext != null) {
SignerResult.Successful(EncryptionResult(ciphertext))
} else {
SignerResult.ReceivedButCouldNotPerform()
}
}
}
}
@@ -0,0 +1,76 @@
/**
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.nip55AndroidSigner.api.foreground.intents.responses
import android.content.Intent
import com.vitorpamplona.quartz.EventFactory
import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.nip01Core.verify
import com.vitorpamplona.quartz.nip55AndroidSigner.api.SignResult
import com.vitorpamplona.quartz.nip55AndroidSigner.api.SignerResult
class SignResponse {
companion object {
fun parse(
intent: Intent,
unsignedEvent: Event,
): SignerResult<SignResult> {
val eventJson = intent.getStringExtra("event")
return if (eventJson != null) {
if (eventJson.startsWith("{")) {
val event = Event.fromJsonOrNull(eventJson)
if (event != null) {
if (event.verify()) {
SignerResult.Successful(SignResult(event))
} else {
SignerResult.ReceivedButCouldNotVerifyResultingEvent(event)
}
} else {
SignerResult.ReceivedButCouldNotParseEventFromResult(eventJson)
}
} else {
SignerResult.ReceivedButCouldNotPerform(eventJson)
}
} else {
val signature = intent.getStringExtra("result")
if (signature != null && signature.length == 128) {
val event =
EventFactory.create(
id = unsignedEvent.id,
pubKey = unsignedEvent.pubKey,
createdAt = unsignedEvent.createdAt,
kind = unsignedEvent.kind,
tags = unsignedEvent.tags,
content = unsignedEvent.content,
sig = signature,
)
if (event.verify()) {
SignerResult.Successful(SignResult(event))
} else {
SignerResult.ReceivedButCouldNotVerifyResultingEvent(event)
}
} else {
SignerResult.ReceivedButCouldNotPerform(signature)
}
}
}
}
}
@@ -0,0 +1,40 @@
/**
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.nip55AndroidSigner.api.foreground.processors
import android.content.Intent
import com.vitorpamplona.quartz.nip55AndroidSigner.api.SignerResult
import com.vitorpamplona.quartz.nip55AndroidSigner.api.ZapEventDecryptionResult
import com.vitorpamplona.quartz.nip55AndroidSigner.api.foreground.NewResultProcessor
import com.vitorpamplona.quartz.nip55AndroidSigner.api.foreground.intents.responses.DecryptZapResponse
import com.vitorpamplona.quartz.nip57Zaps.LnZapPrivateEvent
class DecryptZapResultProcessor(
val onReady: (LnZapPrivateEvent) -> Unit,
) : NewResultProcessor {
override fun process(intent: Intent) {
val foregroundResult = DecryptZapResponse.parse(intent)
if (foregroundResult is SignerResult.Successful<ZapEventDecryptionResult>) {
onReady(foregroundResult.result.privateEvent)
}
}
}
@@ -0,0 +1,39 @@
/**
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.nip55AndroidSigner.api.foreground.processors
import android.content.Intent
import com.vitorpamplona.quartz.nip55AndroidSigner.api.DerivationResult
import com.vitorpamplona.quartz.nip55AndroidSigner.api.SignerResult
import com.vitorpamplona.quartz.nip55AndroidSigner.api.foreground.NewResultProcessor
import com.vitorpamplona.quartz.nip55AndroidSigner.api.foreground.intents.responses.DeriveKeyResponse
class DeriveKeyResultProcessor(
val onReady: (String) -> Unit,
) : NewResultProcessor {
override fun process(intent: Intent) {
val foregroundResult = DeriveKeyResponse.parse(intent)
if (foregroundResult is SignerResult.Successful<DerivationResult>) {
onReady(foregroundResult.result.newPrivKey)
}
}
}
@@ -0,0 +1,40 @@
/**
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.nip55AndroidSigner.api.foreground.processors
import android.content.Intent
import com.vitorpamplona.quartz.nip01Core.core.HexKey
import com.vitorpamplona.quartz.nip55AndroidSigner.api.PubKeyResult
import com.vitorpamplona.quartz.nip55AndroidSigner.api.SignerResult
import com.vitorpamplona.quartz.nip55AndroidSigner.api.foreground.NewResultProcessor
import com.vitorpamplona.quartz.nip55AndroidSigner.api.foreground.intents.responses.LoginResponse
class LoginResultProcessor(
val onReady: (HexKey, String) -> Unit,
) : NewResultProcessor {
override fun process(intent: Intent) {
val foregroundResult = LoginResponse.parse(intent)
if (foregroundResult is SignerResult.Successful<PubKeyResult>) {
onReady(foregroundResult.result.pubkey, foregroundResult.result.packageName)
}
}
}
@@ -0,0 +1,39 @@
/**
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.nip55AndroidSigner.api.foreground.processors
import android.content.Intent
import com.vitorpamplona.quartz.nip55AndroidSigner.api.DecryptionResult
import com.vitorpamplona.quartz.nip55AndroidSigner.api.SignerResult
import com.vitorpamplona.quartz.nip55AndroidSigner.api.foreground.NewResultProcessor
import com.vitorpamplona.quartz.nip55AndroidSigner.api.foreground.intents.responses.Nip04DecryptResponse
class Nip04DecryptResultProcessor(
val onReady: (String) -> Unit,
) : NewResultProcessor {
override fun process(intent: Intent) {
val foregroundResult = Nip04DecryptResponse.parse(intent)
if (foregroundResult is SignerResult.Successful<DecryptionResult>) {
onReady(foregroundResult.result.plaintext)
}
}
}
@@ -0,0 +1,39 @@
/**
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.nip55AndroidSigner.api.foreground.processors
import android.content.Intent
import com.vitorpamplona.quartz.nip55AndroidSigner.api.EncryptionResult
import com.vitorpamplona.quartz.nip55AndroidSigner.api.SignerResult
import com.vitorpamplona.quartz.nip55AndroidSigner.api.foreground.NewResultProcessor
import com.vitorpamplona.quartz.nip55AndroidSigner.api.foreground.intents.responses.Nip04EncryptResponse
class Nip04EncryptResultProcessor(
val onReady: (String) -> Unit,
) : NewResultProcessor {
override fun process(intent: Intent) {
val foregroundResult = Nip04EncryptResponse.parse(intent)
if (foregroundResult is SignerResult.Successful<EncryptionResult>) {
onReady(foregroundResult.result.ciphertext)
}
}
}
@@ -0,0 +1,39 @@
/**
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.nip55AndroidSigner.api.foreground.processors
import android.content.Intent
import com.vitorpamplona.quartz.nip55AndroidSigner.api.DecryptionResult
import com.vitorpamplona.quartz.nip55AndroidSigner.api.SignerResult
import com.vitorpamplona.quartz.nip55AndroidSigner.api.foreground.NewResultProcessor
import com.vitorpamplona.quartz.nip55AndroidSigner.api.foreground.intents.responses.Nip44DecryptResponse
class Nip44DecryptResultProcessor(
val onReady: (String) -> Unit,
) : NewResultProcessor {
override fun process(intent: Intent) {
val foregroundResult = Nip44DecryptResponse.parse(intent)
if (foregroundResult is SignerResult.Successful<DecryptionResult>) {
onReady(foregroundResult.result.plaintext)
}
}
}
@@ -0,0 +1,38 @@
/**
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.nip55AndroidSigner.api.foreground.processors
import android.content.Intent
import com.vitorpamplona.quartz.nip55AndroidSigner.api.EncryptionResult
import com.vitorpamplona.quartz.nip55AndroidSigner.api.SignerResult
import com.vitorpamplona.quartz.nip55AndroidSigner.api.foreground.NewResultProcessor
import com.vitorpamplona.quartz.nip55AndroidSigner.api.foreground.intents.responses.Nip44EncryptResponse
class Nip44EncryptResultProcessor(
val onReady: (String) -> Unit,
) : NewResultProcessor {
override fun process(intent: Intent) {
val foregroundResult = Nip44EncryptResponse.parse(intent)
if (foregroundResult is SignerResult.Successful<EncryptionResult>) {
onReady(foregroundResult.result.ciphertext)
}
}
}
@@ -0,0 +1,41 @@
/**
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.nip55AndroidSigner.api.foreground.processors
import android.content.Intent
import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.nip55AndroidSigner.api.SignResult
import com.vitorpamplona.quartz.nip55AndroidSigner.api.SignerResult
import com.vitorpamplona.quartz.nip55AndroidSigner.api.foreground.NewResultProcessor
import com.vitorpamplona.quartz.nip55AndroidSigner.api.foreground.intents.responses.SignResponse
class SignResultProcessor(
val unsignedEvent: Event,
val onReady: (Event) -> Unit,
) : NewResultProcessor {
override fun process(intent: Intent) {
val foregroundResult = SignResponse.parse(intent, unsignedEvent)
if (foregroundResult is SignerResult.Successful<SignResult>) {
onReady(foregroundResult.result.event)
}
}
}
@@ -0,0 +1,37 @@
/**
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.nip55AndroidSigner.api.permission
import com.vitorpamplona.quartz.nip01Core.jackson.JsonMapper
import com.vitorpamplona.quartz.nip55AndroidSigner.api.CommandType
class Permission(
val type: CommandType?,
val kind: Int? = null,
) {
fun toJson(): String = JsonMapper.mapper.writeValueAsString(this)
companion object {
fun fromJson(json: String): Permission = JsonMapper.mapper.readValue(json, Permission::class.java)
fun fromJsonArray(json: String): Array<Permission> = JsonMapper.mapper.readValue(json, Array<Permission>::class.java)
}
}
@@ -0,0 +1,40 @@
/**
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.nip55AndroidSigner.api.permission
import com.fasterxml.jackson.core.JsonParser
import com.fasterxml.jackson.databind.DeserializationContext
import com.fasterxml.jackson.databind.JsonNode
import com.fasterxml.jackson.databind.deser.std.StdDeserializer
import com.vitorpamplona.quartz.nip55AndroidSigner.api.CommandType
class PermissionDeserializer : StdDeserializer<Permission>(Permission::class.java) {
override fun deserialize(
jp: JsonParser,
ctxt: DeserializationContext,
): Permission {
val jsonObject: JsonNode = jp.codec.readTree(jp)
return Permission(
type = CommandType.parse(jsonObject.get("type").asText()),
kind = jsonObject.get("kind")?.asInt(),
)
}
}
@@ -0,0 +1,42 @@
/**
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.nip55AndroidSigner.api.permission
import com.fasterxml.jackson.core.JsonGenerator
import com.fasterxml.jackson.databind.SerializerProvider
import com.fasterxml.jackson.databind.ser.std.StdSerializer
class PermissionSerializer : StdSerializer<Permission>(Permission::class.java) {
override fun serialize(
permission: Permission,
gen: JsonGenerator,
provider: SerializerProvider,
) {
gen.writeStartObject()
permission.type.let { gen.writeStringField("type", it?.code) }
if (permission.kind != null) {
gen.writeNumberField("kind", permission.kind)
} else {
gen.writeNullField("kind")
}
gen.writeEndObject()
}
}
@@ -0,0 +1,38 @@
/**
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.nip55AndroidSigner.client
import android.content.Intent
import com.vitorpamplona.quartz.nip55AndroidSigner.api.PubKeyResult
import com.vitorpamplona.quartz.nip55AndroidSigner.api.SignerResult
import com.vitorpamplona.quartz.nip55AndroidSigner.api.foreground.intents.requests.LoginRequest
import com.vitorpamplona.quartz.nip55AndroidSigner.api.foreground.intents.responses.LoginResponse
import com.vitorpamplona.quartz.nip55AndroidSigner.api.permission.Permission
object ExternalSignerLogin {
fun createIntent(permissions: List<Permission> = LoginRequest.DefaultPermissions): Intent {
val intent = LoginRequest.assemble(permissions)
intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_CLEAR_TOP)
return intent
}
fun parseResult(data: Intent): SignerResult<PubKeyResult> = LoginResponse.parse(data)
}
@@ -0,0 +1,31 @@
/**
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.nip55AndroidSigner.client
import android.content.Intent
interface IActivityLauncher {
fun registerForegroundLauncher(launcher: ((Intent) -> Unit))
fun unregisterForegroundLauncher(launcher: ((Intent) -> Unit))
fun newResponse(data: Intent)
}
@@ -0,0 +1,37 @@
/**
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.nip55AndroidSigner.client
import android.annotation.SuppressLint
import android.content.Context
import android.content.Intent
import androidx.core.net.toUri
@SuppressLint("QueryPermissionsNeeded")
fun isExternalSignerInstalled(context: Context): Boolean =
context.packageManager
.queryIntentActivities(
Intent().apply {
action = Intent.ACTION_VIEW
data = "nostrsigner:".toUri()
},
0,
).isNotEmpty()
@@ -18,23 +18,42 @@
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.nip55AndroidSigner
package com.vitorpamplona.quartz.nip55AndroidSigner.client
import android.util.Log
import com.vitorpamplona.quartz.EventFactory
import android.content.ContentResolver
import android.content.Intent
import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.nip01Core.core.HexKey
import com.vitorpamplona.quartz.nip01Core.crypto.EventHasher
import com.vitorpamplona.quartz.nip01Core.signers.NostrSigner
import com.vitorpamplona.quartz.nip55AndroidSigner.client.handlers.BackgroundRequestHandler
import com.vitorpamplona.quartz.nip55AndroidSigner.client.handlers.ForegroundRequestHandler
import com.vitorpamplona.quartz.nip57Zaps.LnZapPrivateEvent
import com.vitorpamplona.quartz.nip57Zaps.LnZapRequestEvent
class NostrSignerExternal(
pubKey: HexKey,
val launcher: ExternalSignerLauncher,
) : NostrSigner(pubKey) {
packageName: String,
contentResolver: ContentResolver,
) : NostrSigner(pubKey),
IActivityLauncher {
override fun isWriteable(): Boolean = true
val backgroundQuery = BackgroundRequestHandler(pubKey, packageName, contentResolver)
val foregroundQuery = ForegroundRequestHandler(pubKey, packageName)
override fun registerForegroundLauncher(launcher: ((Intent) -> Unit)) {
this.foregroundQuery.launcher.registerForegroundLauncher(launcher)
}
override fun unregisterForegroundLauncher(launcher: ((Intent) -> Unit)) {
this.foregroundQuery.launcher.unregisterForegroundLauncher(launcher)
}
override fun newResponse(data: Intent) {
this.foregroundQuery.launcher.newResponse(data)
}
override fun <T : Event> sign(
createdAt: Long,
kind: Int,
@@ -42,7 +61,7 @@ class NostrSignerExternal(
content: String,
onReady: (T) -> Unit,
) {
val event =
val unsignedEvent =
Event(
id = EventHasher.hashId(pubKey, createdAt, kind, tags, content),
pubKey = pubKey,
@@ -53,109 +72,70 @@ class NostrSignerExternal(
sig = "",
)
launcher.openSigner(event) { signature ->
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.split("-")[0],
) as? T?
)?.let { onReady(it) }
}
val newOnReady: (Event) -> Unit = { result ->
(result as? T)?.let(onReady)
}
if (!backgroundQuery.sign(unsignedEvent, newOnReady)) {
foregroundQuery.sign(unsignedEvent, newOnReady)
}
}
override fun nip04Encrypt(
decryptedContent: String,
plaintext: String,
toPublicKey: HexKey,
onReady: (String) -> Unit,
) {
launcher.encrypt(
decryptedContent,
toPublicKey,
SignerType.NIP04_ENCRYPT,
onReady,
)
if (!backgroundQuery.nip04Encrypt(plaintext, toPublicKey, onReady)) {
foregroundQuery.nip04Encrypt(plaintext, toPublicKey, onReady)
}
}
override fun nip04Decrypt(
encryptedContent: String,
ciphertext: String,
fromPublicKey: HexKey,
onReady: (String) -> Unit,
) {
launcher.decrypt(
encryptedContent,
fromPublicKey,
SignerType.NIP04_DECRYPT,
onReady,
)
if (!backgroundQuery.nip04Decrypt(ciphertext, fromPublicKey, onReady)) {
foregroundQuery.nip04Decrypt(ciphertext, fromPublicKey, onReady)
}
}
override fun nip44Encrypt(
decryptedContent: String,
plaintext: String,
toPublicKey: HexKey,
onReady: (String) -> Unit,
) {
launcher.encrypt(
decryptedContent,
toPublicKey,
SignerType.NIP44_ENCRYPT,
onReady,
)
if (!backgroundQuery.nip44Encrypt(plaintext, toPublicKey, onReady)) {
foregroundQuery.nip44Encrypt(plaintext, toPublicKey, onReady)
}
}
override fun nip44Decrypt(
encryptedContent: String,
ciphertext: String,
fromPublicKey: HexKey,
onReady: (String) -> Unit,
) {
launcher.decrypt(
encryptedContent,
fromPublicKey,
SignerType.NIP44_DECRYPT,
onReady,
)
if (!backgroundQuery.nip44Decrypt(ciphertext, fromPublicKey, onReady)) {
foregroundQuery.nip44Decrypt(ciphertext, fromPublicKey, onReady)
}
}
override fun deriveKey(
nonce: HexKey,
onReady: (HexKey) -> Unit,
) {
launcher.deriveKey(
nonce,
SignerType.DERIVE_KEY,
onReady,
)
if (!backgroundQuery.deriveKey(nonce, onReady)) {
foregroundQuery.deriveKey(nonce, onReady)
}
}
override fun decryptZapEvent(
event: LnZapRequestEvent,
onReady: (LnZapPrivateEvent) -> Unit,
) {
launcher.decryptZapEvent(event) { jsonEvent ->
try {
(Event.fromJson(jsonEvent) as? LnZapPrivateEvent)?.let { onReady(it) }
} catch (e: Exception) {
Log.e("NostrExternalSigner", "Unable to parse returned decrypted Zap: $jsonEvent")
}
if (!backgroundQuery.decryptZapEvent(event, onReady)) {
foregroundQuery.decryptZapEvent(event, onReady)
}
}
}
@@ -0,0 +1,163 @@
/**
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.nip55AndroidSigner.client.handlers
import android.content.ContentResolver
import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.nip01Core.core.HexKey
import com.vitorpamplona.quartz.nip55AndroidSigner.api.DecryptionResult
import com.vitorpamplona.quartz.nip55AndroidSigner.api.DerivationResult
import com.vitorpamplona.quartz.nip55AndroidSigner.api.EncryptionResult
import com.vitorpamplona.quartz.nip55AndroidSigner.api.PubKeyResult
import com.vitorpamplona.quartz.nip55AndroidSigner.api.RequestAddressed
import com.vitorpamplona.quartz.nip55AndroidSigner.api.SignResult
import com.vitorpamplona.quartz.nip55AndroidSigner.api.SignerResult
import com.vitorpamplona.quartz.nip55AndroidSigner.api.ZapEventDecryptionResult
import com.vitorpamplona.quartz.nip55AndroidSigner.api.background.queries.DecryptZapQuery
import com.vitorpamplona.quartz.nip55AndroidSigner.api.background.queries.DeriveKeyQuery
import com.vitorpamplona.quartz.nip55AndroidSigner.api.background.queries.LoginQuery
import com.vitorpamplona.quartz.nip55AndroidSigner.api.background.queries.Nip04DecryptQuery
import com.vitorpamplona.quartz.nip55AndroidSigner.api.background.queries.Nip04EncryptQuery
import com.vitorpamplona.quartz.nip55AndroidSigner.api.background.queries.Nip44DecryptQuery
import com.vitorpamplona.quartz.nip55AndroidSigner.api.background.queries.Nip44EncryptQuery
import com.vitorpamplona.quartz.nip55AndroidSigner.api.background.queries.SignQuery
import com.vitorpamplona.quartz.nip57Zaps.LnZapPrivateEvent
import com.vitorpamplona.quartz.nip57Zaps.LnZapRequestEvent
class BackgroundRequestHandler(
loggedInUser: HexKey,
packageName: String,
contentResolver: ContentResolver,
) {
val login = LoginQuery(packageName, contentResolver)
val sign = SignQuery(loggedInUser, packageName, contentResolver)
val nip04Encrypt = Nip04EncryptQuery(loggedInUser, packageName, contentResolver)
val nip04Decrypt = Nip04DecryptQuery(loggedInUser, packageName, contentResolver)
val nip44Encrypt = Nip44EncryptQuery(loggedInUser, packageName, contentResolver)
val nip44Decrypt = Nip44DecryptQuery(loggedInUser, packageName, contentResolver)
val decryptZap = DecryptZapQuery(loggedInUser, packageName, contentResolver)
val deriveKey = DeriveKeyQuery(loggedInUser, packageName, contentResolver)
fun login(onReady: (HexKey) -> Unit): Boolean {
val backgroundResult = login.query()
if (backgroundResult is SignerResult.Successful<PubKeyResult>) {
onReady(backgroundResult.result.pubkey)
}
return backgroundResult is RequestAddressed
}
fun sign(
unsignedEvent: Event,
onReady: (Event) -> Unit,
): Boolean {
val backgroundResult = sign.query(unsignedEvent)
if (backgroundResult is SignerResult.Successful<SignResult>) {
onReady(backgroundResult.result.event)
}
return backgroundResult is RequestAddressed
}
fun nip04Encrypt(
plaintext: String,
toPubKey: HexKey,
onReady: (String) -> Unit,
): Boolean {
val backgroundResult = nip04Encrypt.query(plaintext, toPubKey)
if (backgroundResult is SignerResult.Successful<EncryptionResult>) {
onReady(backgroundResult.result.ciphertext)
}
return backgroundResult is RequestAddressed
}
fun nip04Decrypt(
ciphertext: String,
fromPubKey: HexKey,
onReady: (String) -> Unit,
): Boolean {
val backgroundResult = nip04Decrypt.query(ciphertext, fromPubKey)
if (backgroundResult is SignerResult.Successful<DecryptionResult>) {
onReady(backgroundResult.result.plaintext)
}
return backgroundResult is RequestAddressed
}
fun nip44Encrypt(
plaintext: String,
toPubKey: HexKey,
onReady: (String) -> Unit,
): Boolean {
val backgroundResult = nip44Encrypt.query(plaintext, toPubKey)
if (backgroundResult is SignerResult.Successful<EncryptionResult>) {
onReady(backgroundResult.result.ciphertext)
}
return backgroundResult is RequestAddressed
}
fun nip44Decrypt(
ciphertext: String,
fromPubKey: HexKey,
onReady: (String) -> Unit,
): Boolean {
val backgroundResult = nip44Decrypt.query(ciphertext, fromPubKey)
if (backgroundResult is SignerResult.Successful<DecryptionResult>) {
onReady(backgroundResult.result.plaintext)
}
return backgroundResult is RequestAddressed
}
fun decryptZapEvent(
event: LnZapRequestEvent,
onReady: (LnZapPrivateEvent) -> Unit,
): Boolean {
val backgroundResult = decryptZap.query(event)
if (backgroundResult is SignerResult.Successful<ZapEventDecryptionResult>) {
onReady(backgroundResult.result.privateEvent)
}
return backgroundResult is RequestAddressed
}
fun deriveKey(
nonce: HexKey,
onReady: (HexKey) -> Unit,
): Boolean {
val backgroundResult = deriveKey.query(nonce)
if (backgroundResult is SignerResult.Successful<DerivationResult>) {
onReady(backgroundResult.result.newPrivKey)
}
return backgroundResult is RequestAddressed
}
}
@@ -0,0 +1,108 @@
/**
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.nip55AndroidSigner.client.handlers
import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.nip01Core.core.HexKey
import com.vitorpamplona.quartz.nip55AndroidSigner.api.foreground.IntentRequestDatabase
import com.vitorpamplona.quartz.nip55AndroidSigner.api.foreground.intents.requests.DecryptZapRequest
import com.vitorpamplona.quartz.nip55AndroidSigner.api.foreground.intents.requests.DeriveKeyRequest
import com.vitorpamplona.quartz.nip55AndroidSigner.api.foreground.intents.requests.Nip04DecryptRequest
import com.vitorpamplona.quartz.nip55AndroidSigner.api.foreground.intents.requests.Nip04EncryptRequest
import com.vitorpamplona.quartz.nip55AndroidSigner.api.foreground.intents.requests.Nip44DecryptRequest
import com.vitorpamplona.quartz.nip55AndroidSigner.api.foreground.intents.requests.Nip44EncryptRequest
import com.vitorpamplona.quartz.nip55AndroidSigner.api.foreground.intents.requests.SignRequest
import com.vitorpamplona.quartz.nip55AndroidSigner.api.foreground.processors.DecryptZapResultProcessor
import com.vitorpamplona.quartz.nip55AndroidSigner.api.foreground.processors.DeriveKeyResultProcessor
import com.vitorpamplona.quartz.nip55AndroidSigner.api.foreground.processors.Nip04DecryptResultProcessor
import com.vitorpamplona.quartz.nip55AndroidSigner.api.foreground.processors.Nip04EncryptResultProcessor
import com.vitorpamplona.quartz.nip55AndroidSigner.api.foreground.processors.Nip44DecryptResultProcessor
import com.vitorpamplona.quartz.nip55AndroidSigner.api.foreground.processors.Nip44EncryptResultProcessor
import com.vitorpamplona.quartz.nip55AndroidSigner.api.foreground.processors.SignResultProcessor
import com.vitorpamplona.quartz.nip57Zaps.LnZapPrivateEvent
import com.vitorpamplona.quartz.nip57Zaps.LnZapRequestEvent
class ForegroundRequestHandler(
val loggedInUser: HexKey,
val packageName: String,
) {
val launcher = IntentRequestDatabase()
fun sign(
unsignedEvent: Event,
onReady: (Event) -> Unit,
) = launcher.launch(
SignRequest.assemble(unsignedEvent, loggedInUser, packageName),
SignResultProcessor(unsignedEvent, onReady),
)
fun nip04Encrypt(
plaintext: String,
toPubKey: HexKey,
onReady: (String) -> Unit,
) = launcher.launch(
Nip04EncryptRequest.assemble(plaintext, toPubKey, loggedInUser, packageName),
Nip04EncryptResultProcessor(onReady),
)
fun nip04Decrypt(
ciphertext: String,
fromPubKey: HexKey,
onReady: (String) -> Unit,
) = launcher.launch(
Nip04DecryptRequest.assemble(ciphertext, fromPubKey, loggedInUser, packageName),
Nip04DecryptResultProcessor(onReady),
)
fun nip44Encrypt(
plaintext: String,
toPubKey: HexKey,
onReady: (String) -> Unit,
) = launcher.launch(
Nip44EncryptRequest.assemble(plaintext, toPubKey, loggedInUser, packageName),
Nip44EncryptResultProcessor(onReady),
)
fun nip44Decrypt(
ciphertext: String,
fromPubKey: HexKey,
onReady: (String) -> Unit,
) = launcher.launch(
Nip44DecryptRequest.assemble(ciphertext, fromPubKey, loggedInUser, packageName),
Nip44DecryptResultProcessor(onReady),
)
fun decryptZapEvent(
event: LnZapRequestEvent,
onReady: (LnZapPrivateEvent) -> Unit,
) = launcher.launch(
DecryptZapRequest.assemble(event, loggedInUser, packageName),
DecryptZapResultProcessor(onReady),
)
fun deriveKey(
nonce: HexKey,
onReady: (HexKey) -> Unit,
) = launcher.launch(
DeriveKeyRequest.assemble(nonce, loggedInUser, packageName),
DeriveKeyResultProcessor(onReady),
)
}