nips/55.md

607 lines
18 KiB
Markdown
Raw Normal View History

NIP-55
======
2023-11-08 08:22:43 -05:00
Android Signer Application
--------------------------
2023-11-08 08:22:43 -05:00
2024-03-08 05:59:54 -05:00
`draft` `optional`
2023-11-08 08:22:43 -05:00
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.
2023-11-08 08:22:43 -05:00
# 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:
2023-11-08 08:22:43 -05:00
```xml
<queries>
<intent>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="nostrsigner" />
</intent>
2023-11-08 08:22:43 -05:00
</queries>
```
Then you can use this function to check if there's a signer application installed:
```kotlin
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
}
```
2023-11-08 08:22:43 -05:00
## 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.
2023-11-08 08:22:43 -05:00
```kotlin
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 ...
}
}
)
```
2023-11-08 08:22:43 -05:00
Create the Intent using the **nostrsigner** scheme:
```kotlin
val intent = Intent(Intent.ACTION_VIEW, Uri.parse("nostrsigner:$content"))
```
Set the Signer package name:
2023-11-08 08:22:43 -05:00
```kotlin
intent.`package` = "com.example.signer"
```
2024-11-01 07:03:10 -04:00
If you are sending multiple intents without awaiting you can add some intent flags to sign all events without opening multiple times the signer
```kotlin
intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_CLEAR_TOP)
```
2024-11-01 07:16:06 -04:00
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
```kotlin
android:launchMode="singleTop"
```
Send the Intent:
```kotlin
launcher.launch(intent)
```
2023-11-08 08:22:43 -05:00
### Methods
- **get_public_key**
- params:
```kotlin
val intent = Intent(Intent.ACTION_VIEW, Uri.parse("nostrsigner:"))
intent.`package` = "com.example.signer"
intent.putExtra("type", "get_public_key")
// You can send some default permissions for the user to authorize for ever
2023-11-29 09:22:26 -05:00
val permissions = listOf(
Permission(
2024-03-18 14:01:03 -04:00
type = "sign_event",
kind = 22242
2023-11-29 09:22:26 -05:00
),
Permission(
type = "nip44_decrypt"
2023-11-29 09:22:26 -05:00
)
)
intent.putExtra("permissions", permissions.toJson())
2023-11-08 08:22:43 -05:00
context.startActivity(intent)
```
- result:
- If the user approved intent it will return the **pubkey** in the result field
2023-11-08 08:22:43 -05:00
```kotlin
val pubkey = intent.data?.getStringExtra("result")
// The package name of the signer application
val packageName = intent.data?.getStringExtra("package")
2023-11-08 08:22:43 -05:00
```
- **sign_event**
- params:
```kotlin
val intent = Intent(Intent.ACTION_VIEW, Uri.parse("nostrsigner:$eventJson"))
intent.`package` = "com.example.signer"
intent.putExtra("type", "sign_event")
// To handle results when not waiting between intents
2023-11-08 08:22:43 -05:00
intent.putExtra("id", event.id)
// Send the current logged in user pubkey
intent.putExtra("current_user", pubkey)
2024-06-05 14:24:43 -04:00
2023-11-08 08:22:43 -05:00
context.startActivity(intent)
```
- result:
- If the user approved intent it will return the **result**, **id** and **event** fields
2023-11-08 08:22:43 -05:00
```kotlin
val signature = intent.data?.getStringExtra("result")
// The id you sent
2023-11-08 08:22:43 -05:00
val id = intent.data?.getStringExtra("id")
val signedEventJson = intent.data?.getStringExtra("event")
```
- **nip04_encrypt**
- params:
```kotlin
val intent = Intent(Intent.ACTION_VIEW, Uri.parse("nostrsigner:$plaintext"))
intent.`package` = "com.example.signer"
intent.putExtra("type", "nip04_encrypt")
// to control the result in your application in case you are not waiting the result before sending another intent
intent.putExtra("id", "some_id")
// Send the current logged in user pubkey
intent.putExtra("current_user", account.keyPair.pubkey)
// Send the hex pubkey that will be used for encrypting the data
intent.putExtra("pubkey", pubkey)
2024-06-05 14:24:43 -04:00
2023-11-08 08:22:43 -05:00
context.startActivity(intent)
```
- result:
- If the user approved intent it will return the **result** and **id** fields
2023-11-08 08:22:43 -05:00
```kotlin
val encryptedText = intent.data?.getStringExtra("result")
2023-11-08 08:22:43 -05:00
// the id you sent
val id = intent.data?.getStringExtra("id")
```
- **nip44_encrypt**
- params:
```kotlin
val intent = Intent(Intent.ACTION_VIEW, Uri.parse("nostrsigner:$plaintext"))
intent.`package` = "com.example.signer"
intent.putExtra("type", "nip44_encrypt")
// to control the result in your application in case you are not waiting the result before sending another intent
intent.putExtra("id", "some_id")
// Send the current logged in user pubkey
intent.putExtra("current_user", account.keyPair.pubkey)
// Send the hex pubkey that will be used for encrypting the data
intent.putExtra("pubkey", pubkey)
2024-06-05 14:24:43 -04:00
2023-11-08 08:22:43 -05:00
context.startActivity(intent)
```
- result:
- If the user approved intent it will return the **signature** and **id** fields
```kotlin
val encryptedText = intent.data?.getStringExtra("signature")
// the id you sent
val id = intent.data?.getStringExtra("id")
2024-06-05 14:24:43 -04:00
```
2023-11-08 08:22:43 -05:00
- **nip04_decrypt**
- params:
```kotlin
val intent = Intent(Intent.ACTION_VIEW, Uri.parse("nostrsigner:$encryptedText"))
intent.`package` = "com.example.signer"
intent.putExtra("type", "nip04_decrypt")
// to control the result in your application in case you are not waiting the result before sending another intent
intent.putExtra("id", "some_id")
// Send the current logged in user pubkey
intent.putExtra("current_user", account.keyPair.pubkey)
// Send the hex pubkey that will be used for decrypting the data
intent.putExtra("pubkey", pubkey)
2024-06-05 14:24:43 -04:00
2023-11-08 08:22:43 -05:00
context.startActivity(intent)
```
- result:
- If the user approved intent it will return the **result** and **id** fields
2023-11-08 08:22:43 -05:00
```kotlin
val plainText = intent.data?.getStringExtra("result")
2023-11-08 08:22:43 -05:00
// the id you sent
val id = intent.data?.getStringExtra("id")
2024-06-05 14:24:43 -04:00
```
2023-11-08 08:22:43 -05:00
- **nip44_decrypt**
- params:
```kotlin
val intent = Intent(Intent.ACTION_VIEW, Uri.parse("nostrsigner:$encryptedText"))
intent.`package` = "com.example.signer"
intent.putExtra("type", "nip04_decrypt")
// to control the result in your application in case you are not waiting the result before sending another intent
intent.putExtra("id", "some_id")
// Send the current logged in user pubkey
intent.putExtra("current_user", account.keyPair.pubkey)
// Send the hex pubkey that will be used for decrypting the data
intent.putExtra("pubkey", pubkey)
2024-06-05 14:24:43 -04:00
2023-11-08 08:22:43 -05:00
context.startActivity(intent)
```
- result:
- If the user approved intent it will return the **result** and **id** fields
2023-11-08 08:22:43 -05:00
```kotlin
val plainText = intent.data?.getStringExtra("result")
2023-11-08 08:22:43 -05:00
// the id you sent
val id = intent.data?.getStringExtra("id")
2024-06-05 14:24:43 -04:00
```
2023-11-08 08:22:43 -05:00
- **get_relays**
- params:
```kotlin
val intent = Intent(Intent.ACTION_VIEW, Uri.parse("nostrsigner:"))
intent.`package` = "com.example.signer"
intent.putExtra("type", "get_relays")
// to control the result in your application in case you are not waiting the result before sending another intent
intent.putExtra("id", "some_id")
// Send the current logged in user pubkey
intent.putExtra("current_user", account.keyPair.pubkey)
context.startActivity(intent)
```
- result:
- If the user approved intent it will return the **result** and **id** fields
```kotlin
val relayJsonText = intent.data?.getStringExtra("result")
// the id you sent
val id = intent.data?.getStringExtra("id")
```
2023-11-08 08:22:43 -05:00
- **decrypt_zap_event**
- params:
```kotlin
val intent = Intent(Intent.ACTION_VIEW, Uri.parse("nostrsigner:$eventJson"))
intent.`package` = "com.example.signer"
intent.putExtra("type", "decrypt_zap_event")
// to control the result in your application in case you are not waiting the result before sending another intent
intent.putExtra("id", "some_id")
// Send the current logged in user pubkey
intent.putExtra("current_user", account.keyPair.pubkey)
2023-11-08 08:22:43 -05:00
context.startActivity(intent)
```
- result:
- If the user approved intent it will return the **result** and **id** fields
2023-11-08 08:22:43 -05:00
```kotlin
val eventJson = intent.data?.getStringExtra("result")
2023-11-08 08:22:43 -05:00
// the id you sent
val id = intent.data?.getStringExtra("id")
2024-06-05 14:24:43 -04:00
```
2023-11-08 08:22:43 -05:00
## 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
2023-11-08 08:22:43 -05:00
For the SIGN_EVENT type Signer Application returns two columns "result" and "event". The column event is the signed event json
2023-11-08 08:22:43 -05:00
For the other types Signer Application returns the column "result"
2023-11-08 08:22:43 -05:00
If the user chose to always reject the event, signer application will return the column "rejected" and you should not open signer application
2023-11-08 08:22:43 -05:00
### Methods
- **get_public_key**
- params:
```kotlin
val result = context.contentResolver.query(
Uri.parse("content://com.example.signer.GET_PUBLIC_KEY"),
listOf("login"),
null,
null,
null
)
```
- result:
- Will return the **pubkey** in the result column
2023-11-08 08:22:43 -05:00
```kotlin
if (result == null) return
if (result.moveToFirst()) {
val index = it.getColumnIndex("result")
2023-11-08 08:22:43 -05:00
if (index < 0) return
val pubkey = it.getString(index)
2023-11-08 08:22:43 -05:00
}
```
- **sign_event**
- params:
```kotlin
val result = context.contentResolver.query(
Uri.parse("content://com.example.signer.SIGN_EVENT"),
listOf("$eventJson", "", "${logged_in_user_pubkey}"),
2023-11-08 08:22:43 -05:00
null,
null,
null
)
```
- result:
- Will return the **result** and the **event** columns
2023-11-08 08:22:43 -05:00
```kotlin
if (result == null) return
if (result.moveToFirst()) {
val index = it.getColumnIndex("result")
2023-11-08 08:22:43 -05:00
val indexJson = it.getColumnIndex("event")
val signature = it.getString(index)
val eventJson = it.getString(indexJson)
}
```
- **nip04_encrypt**
- params:
```kotlin
val result = context.contentResolver.query(
Uri.parse("content://com.example.signer.NIP04_ENCRYPT"),
listOf("$plainText", "${hex_pub_key}", "${logged_in_user_pubkey}"),
2023-11-08 08:22:43 -05:00
null,
null,
null
)
```
- result:
- Will return the **result** column
2023-11-08 08:22:43 -05:00
```kotlin
if (result == null) return
if (result.moveToFirst()) {
val index = it.getColumnIndex("result")
2023-11-08 08:22:43 -05:00
val encryptedText = it.getString(index)
}
```
- **nip44_encrypt**
- params:
```kotlin
val result = context.contentResolver.query(
Uri.parse("content://com.example.signer.NIP44_ENCRYPT"),
listOf("$plainText", "${hex_pub_key}", "${logged_in_user_pubkey}"),
2023-11-08 08:22:43 -05:00
null,
null,
null
)
```
- result:
- Will return the **result** column
2023-11-08 08:22:43 -05:00
```kotlin
if (result == null) return
if (result.moveToFirst()) {
val index = it.getColumnIndex("result")
2023-11-08 08:22:43 -05:00
val encryptedText = it.getString(index)
}
2024-06-05 14:24:43 -04:00
```
2023-11-08 08:22:43 -05:00
- **nip04_decrypt**
- params:
```kotlin
val result = context.contentResolver.query(
Uri.parse("content://com.example.signer.NIP04_DECRYPT"),
listOf("$encryptedText", "${hex_pub_key}", "${logged_in_user_pubkey}"),
2023-11-08 08:22:43 -05:00
null,
null,
null
)
```
- result:
- Will return the **result** column
2023-11-08 08:22:43 -05:00
```kotlin
if (result == null) return
if (result.moveToFirst()) {
val index = it.getColumnIndex("result")
2023-11-08 08:22:43 -05:00
val encryptedText = it.getString(index)
}
2024-06-05 14:24:43 -04:00
```
2023-11-08 08:22:43 -05:00
- **nip44_decrypt**
- params:
```kotlin
val result = context.contentResolver.query(
Uri.parse("content://com.example.signer.NIP44_DECRYPT"),
listOf("$encryptedText", "${hex_pub_key}", "${logged_in_user_pubkey}"),
2023-11-08 08:22:43 -05:00
null,
null,
null
)
```
- result:
- Will return the **result** column
2023-11-08 08:22:43 -05:00
```kotlin
if (result == null) return
if (result.moveToFirst()) {
val index = it.getColumnIndex("result")
2023-11-08 08:22:43 -05:00
val encryptedText = it.getString(index)
}
2024-06-05 14:24:43 -04:00
```
2023-11-08 08:22:43 -05:00
- **get_relays**
- params:
```kotlin
val result = context.contentResolver.query(
Uri.parse("content://com.example.signer.GET_RELAYS"),
listOf("${logged_in_user_pubkey}"),
null,
null,
null
)
```
- result:
- Will return the **result** column
```kotlin
if (result == null) return
if (result.moveToFirst()) {
val index = it.getColumnIndex("result")
val relayJsonText = it.getString(index)
}
```
2023-11-08 08:22:43 -05:00
- **decrypt_zap_event**
- params:
```kotlin
val result = context.contentResolver.query(
Uri.parse("content://com.example.signer.DECRYPT_ZAP_EVENT"),
listOf("$eventJson", "", "${logged_in_user_pubkey}"),
2023-11-08 08:22:43 -05:00
null,
null,
null
)
```
- result:
- Will return the **result** column
2023-11-08 08:22:43 -05:00
```kotlin
if (result == null) return
if (result.moveToFirst()) {
val index = it.getColumnIndex("result")
2023-11-08 08:22:43 -05:00
val eventJson = it.getString(index)
}
2024-06-05 14:24:43 -04:00
```
2023-11-08 08:22:43 -05:00
# 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.
2023-11-08 08:22:43 -05:00
If you send the callback url parameter, Signer Application will send the result to the url.
2023-11-08 08:22:43 -05:00
If you don't send a callback url, Signer Application will copy the result to the clipboard.
2023-11-08 08:22:43 -05:00
You can configure the `returnType` to be **signature** or **event**.
2023-11-08 08:22:43 -05:00
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
2023-11-08 08:22:43 -05:00
## Methods
- **get_public_key**
- params:
```js
2024-04-29 08:25:04 -04:00
window.href = `nostrsigner:?compressionType=none&returnType=signature&type=get_public_key&callbackUrl=https://example.com/?event=`;
2023-11-08 08:22:43 -05:00
```
- **sign_event**
- params:
```js
2024-04-29 08:25:04 -04:00
window.href = `nostrsigner:${eventJson}?compressionType=none&returnType=signature&type=sign_event&callbackUrl=https://example.com/?event=`;
2024-06-05 14:24:43 -04:00
```
2023-11-08 08:22:43 -05:00
- **nip04_encrypt**
- params:
```js
window.href = `nostrsigner:${plainText}?pubkey=${hex_pub_key}&compressionType=none&returnType=signature&type=nip04_encrypt&callbackUrl=https://example.com/?event=`;
2024-06-05 14:24:43 -04:00
```
2023-11-08 08:22:43 -05:00
- **nip44_encrypt**
- params:
```js
window.href = `nostrsigner:${plainText}?pubkey=${hex_pub_key}&compressionType=none&returnType=signature&type=nip44_encrypt&callbackUrl=https://example.com/?event=`;
2024-06-05 14:24:43 -04:00
```
2023-11-08 08:22:43 -05:00
- **nip04_decrypt**
- params:
```js
window.href = `nostrsigner:${encryptedText}?pubkey=${hex_pub_key}&compressionType=none&returnType=signature&type=nip04_decrypt&callbackUrl=https://example.com/?event=`;
2024-06-05 14:24:43 -04:00
```
2023-11-08 08:22:43 -05:00
- **nip44_decrypt**
- params:
```js
window.href = `nostrsigner:${encryptedText}?pubkey=${hex_pub_key}&compressionType=none&returnType=signature&type=nip44_decrypt&callbackUrl=https://example.com/?event=`;
```
- **get_relays**
- params:
```js
window.href = `nostrsigner:?compressionType=none&returnType=signature&type=get_relays&callbackUrl=https://example.com/?event=`;
2024-06-05 14:24:43 -04:00
```
2023-11-08 08:22:43 -05:00
- **decrypt_zap_event**
- params:
```js
2024-04-29 08:25:04 -04:00
window.href = `nostrsigner:${eventJson}?compressionType=none&returnType=signature&type=decrypt_zap_event&callbackUrl=https://example.com/?event=`;
2024-06-05 14:24:43 -04:00
```
2023-11-08 08:22:43 -05:00
## Example
```js
<!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>
2024-06-05 14:24:43 -04:00
2023-11-08 08:22:43 -05:00
<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");
2024-04-29 08:25:04 -04:00
newAnchor.href = `nostrsigner:${encodedJson}?compressionType=none&returnType=signature&type=sign_event&callbackUrl=https://example.com/?event=`;
2023-11-08 08:22:43 -05:00
newAnchor.textContent = "Open External Signer";
document.body.appendChild(newAnchor)
}
</script>
</body>
</html>
2024-03-18 14:01:03 -04:00
```