NIPs nostr improvement proposals

NIP-55 - Android Signer Application

Table of Contents

Android Signer Application

draft optional

This NIP describes a method for 2-way communication between an Android signer and any Nostr client on Android. The Android signer is an Android Application and the client can be a web client or an Android application.

Usage for Android applications

The Android signer uses Intents and Content Resolvers to communicate between applications.

To be able to use the Android signer in your application you should add this to your AndroidManifest.xml:

<queries>
<intent>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="nostrsigner" />
</intent>
</queries>

Then you can use this function to check if there's a signer application installed:

fun isExternalSignerInstalled(context: Context): Boolean {
val intent =
Intent().apply {
action = Intent.ACTION_VIEW
data = Uri.parse("nostrsigner:")
}
val infos = context.packageManager.queryIntentActivities(intent, 0)
return infos.size > 0
}

Using Intents

To get the result back from the Signer Application you should use registerForActivityResult or rememberLauncherForActivityResult in Kotlin. If you are using another framework check the documentation of your framework or a third party library to get the result.

val launcher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.StartActivityForResult(),
onResult = { result ->
if (result.resultCode != Activity.RESULT_OK) {
Toast.makeText(
context,
"Sign request rejected",
Toast.LENGTH_SHORT
).show()
} else {
val result = activityResult.data?.getStringExtra("result")
// Do something with result ...
}
}
)

Create the Intent using the nostrsigner scheme:

val intent = Intent(Intent.ACTION_VIEW, Uri.parse("nostrsigner:$content"))

Set the Signer package name:

intent.`package` = "com.example.signer"

If you are sending multiple intents without awaiting you can add some intent flags to sign all events without opening multiple times the signer

intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_CLEAR_TOP)

If you are developing a signer application them you need to add this to your AndroidManifest.xml so clients can use the intent flags above

android:launchMode="singleTop"

Signer MUST answer multiple permissions with an array of results

val results = listOf(
Result(
package = signerPackageName,
result = eventSignture,
id = intentId
)
)
val json = results.toJson()
intent.putExtra("results", json)

Send the Intent:

launcher.launch(intent)

Methods

Using Content Resolver

To get the result back from Signer Application you should use contentResolver.query in Kotlin. If you are using another framework check the documentation of your framework or a third party library to get the result.

If the user did not check the "remember my choice" option, the pubkey is not in Signer Application or the signer type is not recognized the contentResolver will return null

For the SIGN_EVENT type Signer Application returns two columns "result" and "event". The column event is the signed event json

For the other types Signer Application returns the column "result"

If the user chose to always reject the event, signer application will return the column "rejected" and you should not open signer application

Methods

Usage for Web Applications

Since web applications can't receive a result from the intent, you should add a modal to paste the signature or the event json or create a callback url.

If you send the callback url parameter, Signer Application will send the result to the url.

If you don't send a callback url, Signer Application will copy the result to the clipboard.

You can configure the returnType to be signature or event.

Android intents and browser urls have limitations, so if you are using the returnType of event consider using the parameter compressionType=gzip that will return "Signer1" + Base64 gzip encoded event json

Methods

Example

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h1>Test</h1>
<script>
window.onload = function() {
var url = new URL(window.location.href);
var params = url.searchParams;
if (params) {
var param1 = params.get("event");
if (param1) alert(param1)
}
let json = {
kind: 1,
content: "test"
}
let encodedJson = encodeURIComponent(JSON.stringify(json))
var newAnchor = document.createElement("a");
newAnchor.href = `nostrsigner:${encodedJson}?compressionType=none&returnType=signature&type=sign_event&callbackUrl=https://example.com/?event=`;
newAnchor.textContent = "Open External Signer";
document.body.appendChild(newAnchor)
}
</script>
</body>
</html>