mirror of
https://github.com/greenart7c3/Amber.git
synced 2026-10-05 10:58:23 +00:00
Add AIDL signer service as alternative to SignerProvider
Introduce an ISignerService AIDL bound-service interface so external apps can sign, encrypt/decrypt, sign PSBTs, and ping the signer over a direct binder connection instead of issuing content:// ContentProvider queries. The per-operation logic (permission lookups, history recording, error logging, NIP-42 relay whitelist handling, encrypted-data classification) is extracted into a shared SignerCore object so the ContentProvider and the new SignerService stay in sync. SignerProvider is refactored to delegate to SignerCore and map results to its existing cursor columns (PING keeps its legacy two-column shape for backwards compatibility). The service returns a Bundle mirroring the legacy cursor columns (rejected / signature / event / result); a null return signals an error, matching the ContentProvider's null-cursor behaviour. AIDL builds are enabled via the aidl build feature.
This commit is contained in:
@@ -156,6 +156,7 @@ android {
|
||||
compose = true
|
||||
buildConfig = true
|
||||
resValues = true
|
||||
aidl = true
|
||||
}
|
||||
|
||||
packaging {
|
||||
|
||||
@@ -63,6 +63,15 @@
|
||||
android:exported="true"
|
||||
tools:ignore="ExportedContentProvider" />
|
||||
|
||||
<service
|
||||
android:name=".SignerService"
|
||||
android:enabled="true"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="com.greenart7c3.nostrsigner.SIGNER_SERVICE" />
|
||||
</intent-filter>
|
||||
</service>
|
||||
|
||||
<provider
|
||||
android:name="androidx.core.content.FileProvider"
|
||||
android:authorities="${applicationId}.fileprovider"
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
// ISignerService.aidl
|
||||
package com.greenart7c3.nostrsigner;
|
||||
|
||||
/**
|
||||
* Bound-service signing interface — a synchronous alternative to the
|
||||
* {@code SignerProvider} ContentProvider.
|
||||
*
|
||||
* Clients bind to {@code com.greenart7c3.nostrsigner.SignerService} (action
|
||||
* {@code com.greenart7c3.nostrsigner.SIGNER_SERVICE}) and call these methods
|
||||
* directly instead of issuing {@code content://} queries.
|
||||
*
|
||||
* Every method returns a {@link android.os.Bundle} that mirrors the columns the
|
||||
* ContentProvider used to return:
|
||||
* - {@code "rejected"} (boolean) — present and {@code true} when no auto-accept
|
||||
* rule matched, meaning the request must be confirmed by the user through the
|
||||
* normal {@code nostrsigner:} intent flow.
|
||||
* - {@code "signature"}, {@code "event"}, {@code "result"} (String) — the
|
||||
* operation output, populated on success.
|
||||
*
|
||||
* A {@code null} return signals an error (unknown account, malformed input,
|
||||
* missing permission record, etc.), matching the ContentProvider's {@code null}
|
||||
* cursor behaviour.
|
||||
*
|
||||
* The {@code npub} parameter selects the signing account (hex pubkey or npub).
|
||||
*/
|
||||
interface ISignerService {
|
||||
Bundle signMessage(String message, String npub);
|
||||
|
||||
Bundle signEvent(String eventJson, String npub);
|
||||
|
||||
Bundle nip04Encrypt(String plaintext, String pubKey, String npub);
|
||||
|
||||
Bundle nip04Decrypt(String ciphertext, String pubKey, String npub);
|
||||
|
||||
Bundle nip44Encrypt(String plaintext, String pubKey, String npub);
|
||||
|
||||
Bundle nip44Decrypt(String ciphertext, String pubKey, String npub);
|
||||
|
||||
Bundle decryptZapEvent(String eventJson, String pubKey, String npub);
|
||||
|
||||
Bundle signPsbt(String psbtHex, String npub);
|
||||
|
||||
Bundle ping(String npub);
|
||||
}
|
||||
@@ -0,0 +1,363 @@
|
||||
package com.greenart7c3.nostrsigner
|
||||
|
||||
import android.content.Context
|
||||
import android.util.Log
|
||||
import com.greenart7c3.nostrsigner.database.HistoryDatabase
|
||||
import com.greenart7c3.nostrsigner.database.HistoryEntity
|
||||
import com.greenart7c3.nostrsigner.database.LogDatabase
|
||||
import com.greenart7c3.nostrsigner.database.LogEntity
|
||||
import com.greenart7c3.nostrsigner.models.SignerType
|
||||
import com.greenart7c3.nostrsigner.models.kindToNip
|
||||
import com.greenart7c3.nostrsigner.models.permissionTypeFromContent
|
||||
import com.greenart7c3.nostrsigner.service.AmberUtils
|
||||
import com.greenart7c3.nostrsigner.service.IntentUtils
|
||||
import com.greenart7c3.nostrsigner.service.RelayUrlUtils
|
||||
import com.greenart7c3.nostrsigner.service.model.AmberEvent
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
import com.vitorpamplona.quartz.nip57Zaps.LnZapRequestEvent
|
||||
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
|
||||
/**
|
||||
* Shared signing logic backing both request paths that reach the signer
|
||||
* synchronously: the [SignerProvider] ContentProvider and the [SignerService]
|
||||
* AIDL bound service.
|
||||
*
|
||||
* Each operation queries the per-account permission database, records history,
|
||||
* and either performs the operation or reports that explicit user approval is
|
||||
* still required. The two transports differ only in how they marshal the
|
||||
* returned [SignResult] (cursor columns vs. a [android.os.Bundle]).
|
||||
*/
|
||||
object SignerCore {
|
||||
private val scope get() = Amber.instance.applicationIOScope
|
||||
|
||||
/** Outcome of a synchronous signer operation. `null` (not modelled here) means error. */
|
||||
sealed interface SignResult {
|
||||
/** No auto-accept rule matched; the request must be confirmed by the user. */
|
||||
data object Rejected : SignResult
|
||||
|
||||
/** Operation completed. Field meanings mirror the legacy cursor columns. */
|
||||
data class Reply(
|
||||
val signature: String?,
|
||||
val event: String?,
|
||||
val result: String?,
|
||||
) : SignResult
|
||||
}
|
||||
|
||||
fun signMessage(
|
||||
context: Context,
|
||||
packageName: String,
|
||||
message: String,
|
||||
npubInput: String,
|
||||
): SignResult? {
|
||||
val npub = IntentUtils.parsePubKey(npubInput) ?: run {
|
||||
Log.d(Amber.TAG, "No npub")
|
||||
return null
|
||||
}
|
||||
val account = LocalPreferences.loadFromEncryptedStorageSync(context, npub) ?: run {
|
||||
Log.d(Amber.TAG, "No account from storage")
|
||||
return null
|
||||
}
|
||||
val historyDatabase = Amber.instance.getHistoryDatabase(account.npub)
|
||||
val permDao = Amber.instance.dao(account.npub)
|
||||
val permission = permDao.getPermission(packageName, "SIGN_MESSAGE")
|
||||
val signPolicy = permDao.getSignPolicy(packageName)
|
||||
val isRemembered = IntentUtils.isRemembered(signPolicy, permission) ?: return null
|
||||
if (!isRemembered) {
|
||||
recordHistory(historyDatabase, account.npub, packageName, "SIGN_MESSAGE", null, false, message)
|
||||
return SignResult.Rejected
|
||||
}
|
||||
|
||||
val result = runBlocking { account.signString(message) }
|
||||
recordHistory(historyDatabase, account.npub, packageName, "SIGN_MESSAGE", null, true, message)
|
||||
return SignResult.Reply(result, result, result)
|
||||
}
|
||||
|
||||
fun signEvent(
|
||||
context: Context,
|
||||
packageName: String,
|
||||
json: String,
|
||||
npubInput: String,
|
||||
): SignResult? {
|
||||
val npub = IntentUtils.parsePubKey(npubInput) ?: run {
|
||||
Log.d(Amber.TAG, "No npub")
|
||||
return null
|
||||
}
|
||||
val account = LocalPreferences.loadFromEncryptedStorageSync(context, npub) ?: run {
|
||||
Log.d(Amber.TAG, "No account from storage")
|
||||
return null
|
||||
}
|
||||
val event = try {
|
||||
IntentUtils.getUnsignedEvent(json, account)
|
||||
} catch (e: Exception) {
|
||||
Log.d(Amber.TAG, "Failed to parse event from $packageName", e)
|
||||
return null
|
||||
}
|
||||
|
||||
val historyDatabase = Amber.instance.getHistoryDatabase(account.npub)
|
||||
val permDao = Amber.instance.dao(account.npub)
|
||||
|
||||
// For kind 22242 (NIP-42 relay auth), extract relay host once for both whitelist and permission checks
|
||||
val relayHost = if (event.kind == 22242) {
|
||||
RelayUrlUtils.extractHostAndPort(AmberEvent.relay(event))
|
||||
} else {
|
||||
""
|
||||
}
|
||||
|
||||
val whitelistAutoAccept = if (event.kind == 22242) {
|
||||
val authWhitelist = Amber.instance.settings.authWhitelist
|
||||
when {
|
||||
authWhitelist.isEmpty() -> false
|
||||
relayHost in authWhitelist -> true
|
||||
else -> {
|
||||
recordHistory(historyDatabase, account.npub, packageName, "SIGN_EVENT", event.kind, false, event.toJson())
|
||||
return SignResult.Rejected
|
||||
}
|
||||
}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
|
||||
var permission = if (event.kind == 22242) {
|
||||
// Kind 22242 = relay client auth (NIP-42): check relay-specific permission first
|
||||
permDao.getPermissionForRelay(packageName, "SIGN_EVENT", 22242, relayHost)
|
||||
?: permDao.getWildcardRelayPermission(packageName, "SIGN_EVENT", 22242)
|
||||
} else {
|
||||
permDao.getPermission(packageName, "SIGN_EVENT", event.kind)
|
||||
}
|
||||
if (permission == null && event.kind != 22242) {
|
||||
event.kind.kindToNip()?.let {
|
||||
val nipNumber = it.toIntOrNull()
|
||||
permission = if (nipNumber == null) {
|
||||
null
|
||||
} else {
|
||||
permDao.getPermission(packageName, "NIP", nipNumber)
|
||||
}
|
||||
}
|
||||
}
|
||||
val signPolicy = permDao.getSignPolicy(packageName)
|
||||
val isRemembered = whitelistAutoAccept || IntentUtils.isRemembered(signPolicy, permission) ?: return null
|
||||
if (!isRemembered) {
|
||||
recordHistory(historyDatabase, account.npub, packageName, "SIGN_EVENT", event.kind, false, event.toJson())
|
||||
return SignResult.Rejected
|
||||
}
|
||||
|
||||
val signedEvent = account.signSync<Event>(event.createdAt, event.kind, event.tags, event.content)
|
||||
recordHistory(historyDatabase, account.npub, packageName, "SIGN_EVENT", event.kind, true, signedEvent.toJson())
|
||||
|
||||
val signature =
|
||||
if (event.kind == LnZapRequestEvent.KIND &&
|
||||
event.tags.any { tag -> tag.any { t -> t == "anon" } }
|
||||
) {
|
||||
signedEvent.toJson()
|
||||
} else {
|
||||
signedEvent.sig
|
||||
}
|
||||
return SignResult.Reply(signature, signedEvent.toJson(), signature)
|
||||
}
|
||||
|
||||
fun encryptOrDecrypt(
|
||||
context: Context,
|
||||
packageName: String,
|
||||
type: SignerType,
|
||||
content: String,
|
||||
pubKey: String,
|
||||
npubInput: String,
|
||||
): SignResult? {
|
||||
val npub = IntentUtils.parsePubKey(npubInput) ?: return null
|
||||
val stringType = type.toString()
|
||||
val account = LocalPreferences.loadFromEncryptedStorageSync(context, npub) ?: return null
|
||||
val logDatabase = Amber.instance.getLogDatabase(account.npub)
|
||||
val historyDatabase = Amber.instance.getHistoryDatabase(account.npub)
|
||||
val permDao = Amber.instance.dao(account.npub)
|
||||
|
||||
val isEncrypt = type == SignerType.NIP04_ENCRYPT || type == SignerType.NIP44_ENCRYPT
|
||||
|
||||
// For ENCRYPT: classify plaintext input; for DECRYPT: perform operation first then classify result
|
||||
val result =
|
||||
if (isEncrypt) {
|
||||
null // defer until after permission check
|
||||
} else {
|
||||
try {
|
||||
runBlocking {
|
||||
AmberUtils.encryptOrDecryptData(content, type, account, pubKey)
|
||||
?: "Could not decrypt the message"
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
recordLog(logDatabase, packageName, stringType, e.message ?: "Could not decrypt the message")
|
||||
"Could not decrypt the message"
|
||||
}
|
||||
}
|
||||
|
||||
// Classify the content to determine EncryptedDataKind-based permission type
|
||||
val classifyContent = if (isEncrypt) content else (result ?: content)
|
||||
val permType = permissionTypeFromContent(classifyContent, isEncrypt, type)
|
||||
|
||||
var permission = permDao.getPermission(packageName, permType)
|
||||
if (permission == null) {
|
||||
permission = permDao.getPermission(packageName, type.toString())
|
||||
}
|
||||
if (permission == null) {
|
||||
val nip = when (type) {
|
||||
SignerType.NIP04_DECRYPT, SignerType.NIP04_ENCRYPT -> 4
|
||||
SignerType.NIP44_DECRYPT, SignerType.NIP44_ENCRYPT -> 44
|
||||
else -> null
|
||||
}
|
||||
nip?.let {
|
||||
permission = permDao.getPermission(packageName, "NIP", it)
|
||||
}
|
||||
}
|
||||
val signPolicy = permDao.getSignPolicy(packageName)
|
||||
val isRemembered = IntentUtils.isRemembered(signPolicy, permission) ?: return null
|
||||
if (!isRemembered) {
|
||||
recordHistory(historyDatabase, account.npub, packageName, stringType, null, false, content)
|
||||
return SignResult.Rejected
|
||||
}
|
||||
|
||||
// For encrypt: perform the operation now after permission is confirmed
|
||||
val finalResult =
|
||||
result ?: try {
|
||||
runBlocking {
|
||||
AmberUtils.encryptOrDecryptData(content, type, account, pubKey)
|
||||
?: "Could not decrypt the message"
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
recordLog(logDatabase, packageName, stringType, e.message ?: "Could not decrypt the message")
|
||||
"Could not decrypt the message"
|
||||
}
|
||||
|
||||
recordHistory(historyDatabase, account.npub, packageName, stringType, null, true, if (!isEncrypt) finalResult else content)
|
||||
return SignResult.Reply(finalResult, finalResult, finalResult)
|
||||
}
|
||||
|
||||
fun signPsbt(
|
||||
context: Context,
|
||||
packageName: String,
|
||||
psbtHex: String,
|
||||
npubInput: String,
|
||||
): SignResult? {
|
||||
val npub = IntentUtils.parsePubKey(npubInput) ?: run {
|
||||
Log.d(Amber.TAG, "No npub")
|
||||
return null
|
||||
}
|
||||
val account = LocalPreferences.loadFromEncryptedStorageSync(context, npub) ?: run {
|
||||
Log.d(Amber.TAG, "No account from storage")
|
||||
return null
|
||||
}
|
||||
val historyDatabase = Amber.instance.getHistoryDatabase(account.npub)
|
||||
val permDao = Amber.instance.dao(account.npub)
|
||||
val permission = permDao.getPermission(packageName, "SIGN_PSBT")
|
||||
val signPolicy = permDao.getSignPolicy(packageName)
|
||||
val isRemembered = IntentUtils.isRemembered(signPolicy, permission) ?: return null
|
||||
if (!isRemembered) {
|
||||
recordHistory(historyDatabase, account.npub, packageName, "SIGN_PSBT", null, false, psbtHex)
|
||||
return SignResult.Rejected
|
||||
}
|
||||
|
||||
val result = try {
|
||||
runBlocking { account.signPsbt(psbtHex) }
|
||||
} catch (e: Exception) {
|
||||
Log.d(Amber.TAG, "Failed to sign psbt", e)
|
||||
recordLog(Amber.instance.getLogDatabase(account.npub), packageName, "SIGN_PSBT", e.message ?: "Could not sign the psbt")
|
||||
return null
|
||||
}
|
||||
|
||||
recordHistory(historyDatabase, account.npub, packageName, "SIGN_PSBT", null, true, psbtHex)
|
||||
return SignResult.Reply(result, result, result)
|
||||
}
|
||||
|
||||
fun ping(
|
||||
context: Context,
|
||||
packageName: String,
|
||||
npubInput: String?,
|
||||
): SignResult? {
|
||||
val npub = if (!npubInput.isNullOrBlank()) IntentUtils.parsePubKey(npubInput) else null
|
||||
val account = if (npub != null) {
|
||||
LocalPreferences.loadFromEncryptedStorageSync(context, npub)
|
||||
} else {
|
||||
LocalPreferences.allSavedAccounts(context).firstNotNullOfOrNull { accountInfo ->
|
||||
val localDatabase = Amber.instance.getDatabase(accountInfo.npub)
|
||||
val hasAccount = localDatabase.dao().getByKeySync(packageName) != null
|
||||
if (hasAccount) {
|
||||
LocalPreferences.loadFromEncryptedStorageSync(context, accountInfo.npub)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
} ?: return null
|
||||
|
||||
val historyDatabase = Amber.instance.getHistoryDatabase(account.npub)
|
||||
val permDao = Amber.instance.dao(account.npub)
|
||||
val permission = permDao.getPermission(packageName, "PING")
|
||||
val signPolicy = permDao.getSignPolicy(packageName)
|
||||
val isRemembered = IntentUtils.isRemembered(signPolicy, permission) ?: return null
|
||||
if (!isRemembered) {
|
||||
recordHistory(historyDatabase, account.npub, packageName, "PING", null, false)
|
||||
return SignResult.Rejected
|
||||
}
|
||||
|
||||
recordHistory(historyDatabase, account.npub, packageName, "PING", null, true)
|
||||
return SignResult.Reply("pong", null, "pong")
|
||||
}
|
||||
|
||||
/** Logs an unexpected error against every saved account, matching the legacy catch-all. */
|
||||
fun logError(
|
||||
context: Context,
|
||||
packageName: String,
|
||||
type: String,
|
||||
message: String,
|
||||
) {
|
||||
scope.launch {
|
||||
LocalPreferences.allSavedAccounts(context).forEach { accInfo ->
|
||||
recordLog(Amber.instance.getLogDatabase(accInfo.npub), packageName, type, message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun recordHistory(
|
||||
historyDatabase: HistoryDatabase,
|
||||
npub: String,
|
||||
packageName: String,
|
||||
type: String,
|
||||
kind: Int?,
|
||||
accepted: Boolean,
|
||||
content: String = "",
|
||||
) {
|
||||
scope.launch {
|
||||
historyDatabase.dao().addHistory(
|
||||
listOf(
|
||||
HistoryEntity(
|
||||
0,
|
||||
packageName,
|
||||
type,
|
||||
kind,
|
||||
TimeUtils.now(),
|
||||
accepted,
|
||||
content = content,
|
||||
),
|
||||
),
|
||||
npub,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun recordLog(
|
||||
logDatabase: LogDatabase,
|
||||
packageName: String,
|
||||
type: String,
|
||||
message: String,
|
||||
) {
|
||||
scope.launch {
|
||||
logDatabase.dao().insertLog(
|
||||
LogEntity(
|
||||
0,
|
||||
packageName,
|
||||
type,
|
||||
message,
|
||||
System.currentTimeMillis(),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,25 +6,10 @@ import android.database.Cursor
|
||||
import android.database.MatrixCursor
|
||||
import android.net.Uri
|
||||
import android.util.Log
|
||||
import com.greenart7c3.nostrsigner.database.HistoryEntity
|
||||
import com.greenart7c3.nostrsigner.database.LogEntity
|
||||
import com.greenart7c3.nostrsigner.models.SignerType
|
||||
import com.greenart7c3.nostrsigner.models.kindToNip
|
||||
import com.greenart7c3.nostrsigner.models.permissionTypeFromContent
|
||||
import com.greenart7c3.nostrsigner.service.AmberUtils
|
||||
import com.greenart7c3.nostrsigner.service.IntentUtils
|
||||
import com.greenart7c3.nostrsigner.service.RelayUrlUtils
|
||||
import com.greenart7c3.nostrsigner.service.model.AmberEvent
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
import com.vitorpamplona.quartz.nip57Zaps.LnZapRequestEvent
|
||||
import com.vitorpamplona.quartz.utils.Hex
|
||||
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
|
||||
class SignerProvider : ContentProvider() {
|
||||
private val scope get() = Amber.instance.applicationIOScope
|
||||
|
||||
override fun delete(
|
||||
uri: Uri,
|
||||
@@ -65,235 +50,22 @@ class SignerProvider : ContentProvider() {
|
||||
Log.d(Amber.TAG, "No package name")
|
||||
return null
|
||||
}
|
||||
val localContext = context ?: return null
|
||||
return try {
|
||||
when (uriString) {
|
||||
"content://$appId.SIGN_MESSAGE" -> {
|
||||
val message = projection?.first()
|
||||
if (message == null) {
|
||||
val message = projection?.first() ?: run {
|
||||
Log.d(Amber.TAG, "No message")
|
||||
return null
|
||||
}
|
||||
val npub = IntentUtils.parsePubKey(projection[2])
|
||||
if (npub == null) {
|
||||
Log.d(Amber.TAG, "No npub")
|
||||
return null
|
||||
}
|
||||
val account = LocalPreferences.loadFromEncryptedStorageSync(context!!, npub)
|
||||
if (account == null) {
|
||||
Log.d(Amber.TAG, "No account from storage")
|
||||
return null
|
||||
}
|
||||
val historyDatabase = Amber.instance.getHistoryDatabase(account.npub)
|
||||
val permDao = Amber.instance.dao(account.npub)
|
||||
val permission =
|
||||
permDao
|
||||
.getPermission(
|
||||
packageName,
|
||||
"SIGN_MESSAGE",
|
||||
)
|
||||
val signPolicy = permDao.getSignPolicy(packageName)
|
||||
val isRemembered = IntentUtils.isRemembered(signPolicy, permission) ?: return null
|
||||
if (!isRemembered) {
|
||||
scope.launch {
|
||||
historyDatabase.dao().addHistory(
|
||||
listOf(
|
||||
HistoryEntity(
|
||||
0,
|
||||
packageName,
|
||||
uriString.replace("content://$appId.", ""),
|
||||
null,
|
||||
TimeUtils.now(),
|
||||
false,
|
||||
content = message,
|
||||
),
|
||||
),
|
||||
account.npub,
|
||||
)
|
||||
}
|
||||
val cursor =
|
||||
MatrixCursor(arrayOf("rejected")).also {
|
||||
it.addRow(arrayOf("true"))
|
||||
}
|
||||
|
||||
return cursor
|
||||
}
|
||||
|
||||
val result = runBlocking { account.signString(message) }
|
||||
scope.launch {
|
||||
historyDatabase.dao().addHistory(
|
||||
listOf(
|
||||
HistoryEntity(
|
||||
0,
|
||||
packageName,
|
||||
"SIGN_MESSAGE",
|
||||
null,
|
||||
TimeUtils.now(),
|
||||
true,
|
||||
content = message,
|
||||
),
|
||||
),
|
||||
account.npub,
|
||||
)
|
||||
}
|
||||
|
||||
val localCursor = MatrixCursor(arrayOf("signature", "event", "result")).also {
|
||||
it.addRow(arrayOf(result, result, result))
|
||||
}
|
||||
|
||||
return localCursor
|
||||
SignerCore.signMessage(localContext, packageName, message, projection[2])?.toCursor()
|
||||
}
|
||||
"content://$appId.SIGN_EVENT" -> {
|
||||
val json = projection?.first()
|
||||
if (json == null) {
|
||||
val json = projection?.first() ?: run {
|
||||
Log.d(Amber.TAG, "No json")
|
||||
return null
|
||||
}
|
||||
val npub = IntentUtils.parsePubKey(projection[2])
|
||||
if (npub == null) {
|
||||
Log.d(Amber.TAG, "No npub")
|
||||
return null
|
||||
}
|
||||
val account = LocalPreferences.loadFromEncryptedStorageSync(context!!, npub)
|
||||
if (account == null) {
|
||||
Log.d(Amber.TAG, "No account from storage")
|
||||
return null
|
||||
}
|
||||
val event = try {
|
||||
IntentUtils.getUnsignedEvent(json, account)
|
||||
} catch (e: Exception) {
|
||||
Log.d(Amber.TAG, "Failed to parse event from $packageName", e)
|
||||
return null
|
||||
}
|
||||
|
||||
val historyDatabase = Amber.instance.getHistoryDatabase(account.npub)
|
||||
val permDao = Amber.instance.dao(account.npub)
|
||||
|
||||
// For kind 22242 (NIP-42 relay auth), extract relay host once for both whitelist and permission checks
|
||||
val relayHost = if (event.kind == 22242) {
|
||||
RelayUrlUtils.extractHostAndPort(AmberEvent.relay(event))
|
||||
} else {
|
||||
""
|
||||
}
|
||||
|
||||
val whitelistAutoAccept = if (event.kind == 22242) {
|
||||
val authWhitelist = Amber.instance.settings.authWhitelist
|
||||
when {
|
||||
authWhitelist.isEmpty() -> false
|
||||
relayHost in authWhitelist -> true
|
||||
else -> {
|
||||
scope.launch {
|
||||
historyDatabase.dao().addHistory(
|
||||
listOf(
|
||||
HistoryEntity(
|
||||
0,
|
||||
packageName,
|
||||
uriString.replace("content://$appId.", ""),
|
||||
event.kind,
|
||||
TimeUtils.now(),
|
||||
false,
|
||||
content = event.toJson(),
|
||||
),
|
||||
),
|
||||
account.npub,
|
||||
)
|
||||
}
|
||||
return MatrixCursor(arrayOf("rejected")).also { it.addRow(arrayOf("true")) }
|
||||
}
|
||||
}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
|
||||
var permission = if (event.kind == 22242) {
|
||||
// Kind 22242 = relay client auth (NIP-42): check relay-specific permission first
|
||||
permDao.getPermissionForRelay(packageName, "SIGN_EVENT", 22242, relayHost)
|
||||
?: permDao.getWildcardRelayPermission(packageName, "SIGN_EVENT", 22242)
|
||||
} else {
|
||||
permDao
|
||||
.getPermission(
|
||||
packageName,
|
||||
"SIGN_EVENT",
|
||||
event.kind,
|
||||
)
|
||||
}
|
||||
if (permission == null && event.kind != 22242) {
|
||||
event.kind.kindToNip()?.let {
|
||||
val nipNumber = it.toIntOrNull()
|
||||
permission = if (nipNumber == null) {
|
||||
null
|
||||
} else {
|
||||
permDao
|
||||
.getPermission(
|
||||
packageName,
|
||||
"NIP",
|
||||
nipNumber,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
val signPolicy = permDao.getSignPolicy(packageName)
|
||||
val isRemembered = whitelistAutoAccept || IntentUtils.isRemembered(signPolicy, permission) ?: return null
|
||||
if (!isRemembered) {
|
||||
scope.launch {
|
||||
historyDatabase.dao().addHistory(
|
||||
listOf(
|
||||
HistoryEntity(
|
||||
0,
|
||||
packageName,
|
||||
uriString.replace("content://$appId.", ""),
|
||||
event.kind,
|
||||
TimeUtils.now(),
|
||||
false,
|
||||
content = event.toJson(),
|
||||
),
|
||||
),
|
||||
account.npub,
|
||||
)
|
||||
}
|
||||
|
||||
val cursor =
|
||||
MatrixCursor(arrayOf("rejected")).also {
|
||||
it.addRow(arrayOf("true"))
|
||||
}
|
||||
|
||||
return cursor
|
||||
}
|
||||
|
||||
val signedEvent = account.signSync<Event>(event.createdAt, event.kind, event.tags, event.content)
|
||||
|
||||
scope.launch {
|
||||
historyDatabase.dao().addHistory(
|
||||
listOf(
|
||||
HistoryEntity(
|
||||
0,
|
||||
packageName,
|
||||
"SIGN_EVENT",
|
||||
event.kind,
|
||||
TimeUtils.now(),
|
||||
true,
|
||||
content = signedEvent.toJson(),
|
||||
),
|
||||
),
|
||||
account.npub,
|
||||
)
|
||||
}
|
||||
|
||||
val cursor =
|
||||
MatrixCursor(arrayOf("signature", "event", "result")).also {
|
||||
val signature =
|
||||
if (event.kind == LnZapRequestEvent.KIND &&
|
||||
event.tags.any { tag ->
|
||||
tag.any { t -> t == "anon" }
|
||||
}
|
||||
) {
|
||||
signedEvent.toJson()
|
||||
} else {
|
||||
signedEvent.sig
|
||||
}
|
||||
it.addRow(arrayOf(signature, signedEvent.toJson(), signature))
|
||||
}
|
||||
|
||||
return cursor
|
||||
SignerCore.signEvent(localContext, packageName, json, projection[2])?.toCursor()
|
||||
}
|
||||
"content://$appId.NIP04_DECRYPT",
|
||||
"content://$appId.NIP44_DECRYPT",
|
||||
@@ -302,15 +74,8 @@ class SignerProvider : ContentProvider() {
|
||||
"content://$appId.DECRYPT_ZAP_EVENT",
|
||||
-> {
|
||||
val content = projection?.first() ?: return null
|
||||
val npub = IntentUtils.parsePubKey(projection[2]) ?: return null
|
||||
val stringType = uriString.replace("content://$appId.", "")
|
||||
val pubkey = projection[1]
|
||||
val account = LocalPreferences.loadFromEncryptedStorageSync(context!!, npub) ?: return null
|
||||
val logDatabase = Amber.instance.getLogDatabase(account.npub)
|
||||
val historyDatabase = Amber.instance.getHistoryDatabase(account.npub)
|
||||
val permDao = Amber.instance.dao(account.npub)
|
||||
val type =
|
||||
when (stringType) {
|
||||
val type = when (uriString.replace("content://$appId.", "")) {
|
||||
"NIP04_DECRYPT" -> SignerType.NIP04_DECRYPT
|
||||
"NIP44_DECRYPT" -> SignerType.NIP44_DECRYPT
|
||||
"NIP04_ENCRYPT" -> SignerType.NIP04_ENCRYPT
|
||||
@@ -318,333 +83,45 @@ class SignerProvider : ContentProvider() {
|
||||
"DECRYPT_ZAP_EVENT" -> SignerType.DECRYPT_ZAP_EVENT
|
||||
else -> null
|
||||
} ?: return null
|
||||
|
||||
val isEncrypt = type == SignerType.NIP04_ENCRYPT || type == SignerType.NIP44_ENCRYPT
|
||||
|
||||
// For ENCRYPT: classify plaintext input; for DECRYPT: perform operation first then classify result
|
||||
val result =
|
||||
if (isEncrypt) {
|
||||
null // defer until after permission check
|
||||
} else {
|
||||
try {
|
||||
runBlocking {
|
||||
AmberUtils.encryptOrDecryptData(
|
||||
content,
|
||||
type,
|
||||
account,
|
||||
pubkey,
|
||||
) ?: "Could not decrypt the message"
|
||||
SignerCore.encryptOrDecrypt(localContext, packageName, type, content, pubkey, projection[2])?.toCursor()
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
scope.launch {
|
||||
logDatabase.dao().insertLog(
|
||||
LogEntity(
|
||||
0,
|
||||
packageName,
|
||||
uriString.replace("content://$appId.", ""),
|
||||
e.message ?: "Could not decrypt the message",
|
||||
System.currentTimeMillis(),
|
||||
),
|
||||
)
|
||||
}
|
||||
"Could not decrypt the message"
|
||||
}
|
||||
}
|
||||
|
||||
// Classify the content to determine EncryptedDataKind-based permission type
|
||||
val classifyContent = if (isEncrypt) content else (result ?: content)
|
||||
val permType = permissionTypeFromContent(classifyContent, isEncrypt, type)
|
||||
|
||||
var permission = permDao.getPermission(packageName, permType)
|
||||
if (permission == null) {
|
||||
permission = permDao.getPermission(
|
||||
packageName,
|
||||
type.toString(),
|
||||
)
|
||||
}
|
||||
if (permission == null) {
|
||||
val nip = when (stringType) {
|
||||
"NIP04_DECRYPT" -> 4
|
||||
"NIP44_DECRYPT" -> 44
|
||||
"NIP04_ENCRYPT" -> 4
|
||||
"NIP44_ENCRYPT" -> 44
|
||||
"DECRYPT_ZAP_EVENT" -> null
|
||||
else -> null
|
||||
}
|
||||
nip?.let {
|
||||
permission =
|
||||
permDao
|
||||
.getPermission(
|
||||
packageName,
|
||||
"NIP",
|
||||
it,
|
||||
)
|
||||
}
|
||||
}
|
||||
val signPolicy = permDao.getSignPolicy(packageName)
|
||||
val isRemembered = IntentUtils.isRemembered(signPolicy, permission) ?: return null
|
||||
if (!isRemembered) {
|
||||
scope.launch {
|
||||
historyDatabase.dao().addHistory(
|
||||
listOf(
|
||||
HistoryEntity(
|
||||
0,
|
||||
packageName,
|
||||
uriString.replace("content://$appId.", ""),
|
||||
null,
|
||||
TimeUtils.now(),
|
||||
false,
|
||||
content = content,
|
||||
),
|
||||
),
|
||||
account.npub,
|
||||
)
|
||||
}
|
||||
|
||||
val cursor =
|
||||
MatrixCursor(arrayOf("rejected")).also {
|
||||
it.addRow(arrayOf("true"))
|
||||
}
|
||||
|
||||
return cursor
|
||||
}
|
||||
|
||||
// For encrypt: perform the operation now after permission is confirmed
|
||||
val finalResult =
|
||||
result ?: try {
|
||||
runBlocking {
|
||||
AmberUtils.encryptOrDecryptData(
|
||||
content,
|
||||
type,
|
||||
account,
|
||||
pubkey,
|
||||
) ?: "Could not decrypt the message"
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
scope.launch {
|
||||
logDatabase.dao().insertLog(
|
||||
LogEntity(
|
||||
0,
|
||||
packageName,
|
||||
uriString.replace("content://$appId.", ""),
|
||||
e.message ?: "Could not decrypt the message",
|
||||
System.currentTimeMillis(),
|
||||
),
|
||||
)
|
||||
}
|
||||
"Could not decrypt the message"
|
||||
}
|
||||
|
||||
scope.launch {
|
||||
historyDatabase.dao().addHistory(
|
||||
listOf(
|
||||
HistoryEntity(
|
||||
0,
|
||||
packageName,
|
||||
uriString.replace("content://$appId.", ""),
|
||||
null,
|
||||
TimeUtils.now(),
|
||||
true,
|
||||
content = if (!isEncrypt) finalResult else content,
|
||||
),
|
||||
),
|
||||
account.npub,
|
||||
)
|
||||
}
|
||||
|
||||
val cursor = MatrixCursor(arrayOf("signature", "event", "result"))
|
||||
cursor.addRow(arrayOf<Any>(finalResult, finalResult, finalResult))
|
||||
return cursor
|
||||
}
|
||||
|
||||
"content://$appId.SIGN_PSBT" -> {
|
||||
val psbtHex = projection?.first()
|
||||
if (psbtHex == null) {
|
||||
val psbtHex = projection?.first() ?: run {
|
||||
Log.d(Amber.TAG, "No psbt")
|
||||
return null
|
||||
}
|
||||
val npub = IntentUtils.parsePubKey(projection[2])
|
||||
if (npub == null) {
|
||||
Log.d(Amber.TAG, "No npub")
|
||||
return null
|
||||
}
|
||||
val account = LocalPreferences.loadFromEncryptedStorageSync(context!!, npub)
|
||||
if (account == null) {
|
||||
Log.d(Amber.TAG, "No account from storage")
|
||||
return null
|
||||
}
|
||||
val historyDatabase = Amber.instance.getHistoryDatabase(account.npub)
|
||||
val permDao = Amber.instance.dao(account.npub)
|
||||
val permission =
|
||||
permDao
|
||||
.getPermission(
|
||||
packageName,
|
||||
"SIGN_PSBT",
|
||||
)
|
||||
val signPolicy = permDao.getSignPolicy(packageName)
|
||||
val isRemembered = IntentUtils.isRemembered(signPolicy, permission) ?: return null
|
||||
if (!isRemembered) {
|
||||
scope.launch {
|
||||
historyDatabase.dao().addHistory(
|
||||
listOf(
|
||||
HistoryEntity(
|
||||
0,
|
||||
packageName,
|
||||
uriString.replace("content://$appId.", ""),
|
||||
null,
|
||||
TimeUtils.now(),
|
||||
false,
|
||||
content = psbtHex,
|
||||
),
|
||||
),
|
||||
account.npub,
|
||||
)
|
||||
}
|
||||
val cursor =
|
||||
MatrixCursor(arrayOf("rejected")).also {
|
||||
it.addRow(arrayOf("true"))
|
||||
}
|
||||
|
||||
return cursor
|
||||
}
|
||||
|
||||
val result = try {
|
||||
runBlocking { account.signPsbt(psbtHex) }
|
||||
} catch (e: Exception) {
|
||||
Log.d(Amber.TAG, "Failed to sign psbt", e)
|
||||
scope.launch {
|
||||
val logDb = Amber.instance.getLogDatabase(account.npub)
|
||||
logDb.dao().insertLog(
|
||||
LogEntity(
|
||||
0,
|
||||
packageName,
|
||||
"SIGN_PSBT",
|
||||
e.message ?: "Could not sign the psbt",
|
||||
System.currentTimeMillis(),
|
||||
),
|
||||
)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
scope.launch {
|
||||
historyDatabase.dao().addHistory(
|
||||
listOf(
|
||||
HistoryEntity(
|
||||
0,
|
||||
packageName,
|
||||
"SIGN_PSBT",
|
||||
null,
|
||||
TimeUtils.now(),
|
||||
true,
|
||||
content = psbtHex,
|
||||
),
|
||||
),
|
||||
account.npub,
|
||||
)
|
||||
}
|
||||
|
||||
val localCursor = MatrixCursor(arrayOf("signature", "event", "result")).also {
|
||||
it.addRow(arrayOf(result, result, result))
|
||||
}
|
||||
|
||||
return localCursor
|
||||
SignerCore.signPsbt(localContext, packageName, psbtHex, projection[2])?.toCursor()
|
||||
}
|
||||
"content://$appId.PING" -> {
|
||||
val npub = if (projection != null && projection.isNotEmpty()) IntentUtils.parsePubKey(projection[0]) else null
|
||||
val account = if (npub != null) {
|
||||
LocalPreferences.loadFromEncryptedStorageSync(context!!, npub)
|
||||
} else {
|
||||
LocalPreferences.allSavedAccounts(context!!).firstNotNullOfOrNull { accountInfo ->
|
||||
val localDatabase = Amber.instance.getDatabase(accountInfo.npub)
|
||||
val hasAccount = localDatabase.dao().getByKeySync(packageName) != null
|
||||
if (hasAccount) {
|
||||
LocalPreferences.loadFromEncryptedStorageSync(context!!, accountInfo.npub)
|
||||
} else {
|
||||
null
|
||||
val npub = if (projection != null && projection.isNotEmpty()) projection[0] else null
|
||||
when (val result = SignerCore.ping(localContext, packageName, npub)) {
|
||||
null -> null
|
||||
is SignerCore.SignResult.Rejected -> result.toCursor()
|
||||
// PING keeps its legacy two-column shape for backwards compatibility.
|
||||
is SignerCore.SignResult.Reply ->
|
||||
MatrixCursor(arrayOf("signature", "result")).also {
|
||||
it.addRow(arrayOf<Any?>(result.result, result.result))
|
||||
}
|
||||
}
|
||||
}
|
||||
if (account == null) {
|
||||
return null
|
||||
}
|
||||
val historyDatabase = Amber.instance.getHistoryDatabase(account.npub)
|
||||
val permDao = Amber.instance.dao(account.npub)
|
||||
val permission =
|
||||
permDao
|
||||
.getPermission(
|
||||
packageName,
|
||||
"PING",
|
||||
)
|
||||
|
||||
val signPolicy = permDao.getSignPolicy(packageName)
|
||||
val isRemembered = IntentUtils.isRemembered(signPolicy, permission) ?: return null
|
||||
if (!isRemembered) {
|
||||
scope.launch {
|
||||
historyDatabase.dao().addHistory(
|
||||
listOf(
|
||||
HistoryEntity(
|
||||
0,
|
||||
packageName,
|
||||
uriString.replace("content://$appId.", ""),
|
||||
null,
|
||||
TimeUtils.now(),
|
||||
false,
|
||||
),
|
||||
),
|
||||
account.npub,
|
||||
)
|
||||
}
|
||||
|
||||
val cursor =
|
||||
MatrixCursor(arrayOf("rejected")).also {
|
||||
it.addRow(arrayOf("true"))
|
||||
}
|
||||
|
||||
return cursor
|
||||
}
|
||||
|
||||
scope.launch {
|
||||
historyDatabase.dao().addHistory(
|
||||
listOf(
|
||||
HistoryEntity(
|
||||
0,
|
||||
packageName,
|
||||
uriString.replace("content://$appId.", ""),
|
||||
null,
|
||||
TimeUtils.now(),
|
||||
true,
|
||||
),
|
||||
),
|
||||
account.npub,
|
||||
)
|
||||
}
|
||||
|
||||
val cursor = MatrixCursor(arrayOf("signature", "result"))
|
||||
cursor.addRow(arrayOf<Any>("pong", "pong"))
|
||||
return cursor
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
scope.launch {
|
||||
LocalPreferences.allSavedAccounts(context!!).forEach { accInfo ->
|
||||
val database = Amber.instance.getLogDatabase(accInfo.npub)
|
||||
database.dao().insertLog(
|
||||
LogEntity(
|
||||
0,
|
||||
packageName,
|
||||
uriString.replace("content://$appId.", ""),
|
||||
e.message ?: "Error from $callingPackage $uri",
|
||||
System.currentTimeMillis(),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
SignerCore.logError(localContext, packageName, uriString.replace("content://$appId.", ""), e.message ?: "Error from $callingPackage $uri")
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
/** Maps a [SignerCore.SignResult] onto the standard cursor columns shared by most operations. */
|
||||
private fun SignerCore.SignResult.toCursor(): Cursor = when (this) {
|
||||
is SignerCore.SignResult.Rejected ->
|
||||
MatrixCursor(arrayOf("rejected")).also { it.addRow(arrayOf("true")) }
|
||||
is SignerCore.SignResult.Reply ->
|
||||
MatrixCursor(arrayOf("signature", "event", "result")).also {
|
||||
it.addRow(arrayOf<Any?>(signature, event, result))
|
||||
}
|
||||
}
|
||||
|
||||
override fun update(
|
||||
uri: Uri,
|
||||
values: ContentValues?,
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
package com.greenart7c3.nostrsigner
|
||||
|
||||
import android.app.Service
|
||||
import android.content.Intent
|
||||
import android.os.Binder
|
||||
import android.os.Bundle
|
||||
import android.os.IBinder
|
||||
import android.util.Log
|
||||
import com.greenart7c3.nostrsigner.models.SignerType
|
||||
|
||||
/**
|
||||
* Bound service exposing [ISignerService] — a synchronous alternative to the
|
||||
* [SignerProvider] ContentProvider.
|
||||
*
|
||||
* Both transports share their business logic in [SignerCore]; this class only
|
||||
* resolves the calling package from the binder UID and marshals
|
||||
* [SignerCore.SignResult] into the [Bundle] contract documented in the AIDL.
|
||||
*/
|
||||
class SignerService : Service() {
|
||||
private val binder = object : ISignerService.Stub() {
|
||||
override fun signMessage(message: String?, npub: String?): Bundle? = run("SIGN_MESSAGE") { pkg ->
|
||||
SignerCore.signMessage(applicationContext, pkg, message ?: return@run null, npub ?: return@run null)
|
||||
}
|
||||
|
||||
override fun signEvent(eventJson: String?, npub: String?): Bundle? = run("SIGN_EVENT") { pkg ->
|
||||
SignerCore.signEvent(applicationContext, pkg, eventJson ?: return@run null, npub ?: return@run null)
|
||||
}
|
||||
|
||||
override fun nip04Encrypt(plaintext: String?, pubKey: String?, npub: String?): Bundle? = encryptOrDecrypt(SignerType.NIP04_ENCRYPT, plaintext, pubKey, npub)
|
||||
|
||||
override fun nip04Decrypt(ciphertext: String?, pubKey: String?, npub: String?): Bundle? = encryptOrDecrypt(SignerType.NIP04_DECRYPT, ciphertext, pubKey, npub)
|
||||
|
||||
override fun nip44Encrypt(plaintext: String?, pubKey: String?, npub: String?): Bundle? = encryptOrDecrypt(SignerType.NIP44_ENCRYPT, plaintext, pubKey, npub)
|
||||
|
||||
override fun nip44Decrypt(ciphertext: String?, pubKey: String?, npub: String?): Bundle? = encryptOrDecrypt(SignerType.NIP44_DECRYPT, ciphertext, pubKey, npub)
|
||||
|
||||
override fun decryptZapEvent(eventJson: String?, pubKey: String?, npub: String?): Bundle? = encryptOrDecrypt(SignerType.DECRYPT_ZAP_EVENT, eventJson, pubKey, npub)
|
||||
|
||||
override fun signPsbt(psbtHex: String?, npub: String?): Bundle? = run("SIGN_PSBT") { pkg ->
|
||||
SignerCore.signPsbt(applicationContext, pkg, psbtHex ?: return@run null, npub ?: return@run null)
|
||||
}
|
||||
|
||||
override fun ping(npub: String?): Bundle? = run("PING") { pkg ->
|
||||
SignerCore.ping(applicationContext, pkg, npub)
|
||||
}
|
||||
|
||||
private fun encryptOrDecrypt(
|
||||
type: SignerType,
|
||||
content: String?,
|
||||
pubKey: String?,
|
||||
npub: String?,
|
||||
): Bundle? = run(type.toString()) { pkg ->
|
||||
SignerCore.encryptOrDecrypt(
|
||||
applicationContext,
|
||||
pkg,
|
||||
type,
|
||||
content ?: return@run null,
|
||||
pubKey ?: return@run null,
|
||||
npub ?: return@run null,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves the caller's package, runs [block], and converts the result into
|
||||
* the [Bundle] contract. Returns `null` on any error or missing caller,
|
||||
* matching the ContentProvider's `null` cursor behaviour.
|
||||
*/
|
||||
private inline fun run(
|
||||
type: String,
|
||||
block: (packageName: String) -> SignerCore.SignResult?,
|
||||
): Bundle? {
|
||||
val packageName = callingPackageName() ?: run {
|
||||
Log.d(Amber.TAG, "No package name")
|
||||
return null
|
||||
}
|
||||
return try {
|
||||
block(packageName)?.toBundle()
|
||||
} catch (e: Exception) {
|
||||
SignerCore.logError(applicationContext, packageName, type, e.message ?: "Error from $packageName")
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private fun callingPackageName(): String? {
|
||||
val uid = Binder.getCallingUid()
|
||||
return packageManager.getPackagesForUid(uid)?.firstOrNull()
|
||||
}
|
||||
|
||||
override fun onBind(intent: Intent?): IBinder = binder
|
||||
|
||||
companion object {
|
||||
const val KEY_REJECTED = "rejected"
|
||||
const val KEY_SIGNATURE = "signature"
|
||||
const val KEY_EVENT = "event"
|
||||
const val KEY_RESULT = "result"
|
||||
|
||||
fun SignerCore.SignResult.toBundle(): Bundle = when (this) {
|
||||
is SignerCore.SignResult.Rejected -> Bundle().apply { putBoolean(KEY_REJECTED, true) }
|
||||
is SignerCore.SignResult.Reply -> Bundle().apply {
|
||||
putString(KEY_SIGNATURE, signature)
|
||||
putString(KEY_EVENT, event)
|
||||
putString(KEY_RESULT, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user