2024-09-02 13:03:45 -04:00
NIP-55
======
2023-11-08 08:22:43 -05:00
2024-09-02 13:03:45 -04: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
2024-03-08 07:20:19 -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.
2024-03-08 07:20:19 -05:00
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 >
2024-01-22 09:25:25 -05:00
< 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 >
```
2024-01-22 09:25:25 -05:00
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
2024-03-08 07:20:19 -05:00
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
2024-03-18 14:10:25 -04: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 signature = activityResult.data?.getStringExtra("signature")
// Do something with signature ...
}
}
)
```
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"))
```
2024-03-18 14:00:30 -04:00
Set the Signer package name:
2023-11-08 08:22:43 -05:00
```kotlin
intent.`package` = "com.example.signer"
```
2024-03-18 14:10:25 -04:00
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")
2024-03-18 14:00:30 -04:00
// 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(
2024-03-18 14:00:30 -04:00
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 **npub** in the signature field
```kotlin
val npub = intent.data?.getStringExtra("signature")
2023-11-29 09:23:14 -05:00
// 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")
2024-03-18 14:00:30 -04:00
// 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 npub
2024-03-18 14:00:30 -04:00
intent.putExtra("current_user", npub)
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** , **id** and **event** fields
```kotlin
val signature = intent.data?.getStringExtra("signature")
2024-03-18 14:00:30 -04:00
// 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 npub
intent.putExtra("current_user", account.keyPair.pubKey.toNpub())
// 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")
```
- **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 npub
intent.putExtra("current_user", account.keyPair.pubKey.toNpub())
// 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 npub
intent.putExtra("current_user", account.keyPair.pubKey.toNpub())
// 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 **signature** and **id** fields
```kotlin
val plainText = 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
- **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 npub
intent.putExtra("current_user", account.keyPair.pubKey.toNpub())
// 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 **signature** and **id** fields
```kotlin
val plainText = 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
- **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 npub
intent.putExtra("current_user", account.keyPair.pubKey.toNpub())
context.startActivity(intent)
```
- result:
- If the user approved intent it will return the **signature** and **id** fields
```kotlin
val eventJson = 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
## 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.
2024-03-18 14:00:30 -04:00
If the user did not check the "remember my choice" option, the npub 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 "signature" and "event". The column event is the signed event json
For the other types Signer Application returns the column "signature"
2024-03-18 14:00:30 -04:00
If the user chose to always reject the event, signer application will return the column "rejected" and you should not open signer application
2024-01-22 09:25:25 -05:00
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 **npub** in the signature column
```kotlin
if (result == null) return
if (result.moveToFirst()) {
val index = it.getColumnIndex("signature")
if (index < 0 ) return
val npub = it.getString(index)
}
```
- **sign_event**
- params:
```kotlin
val result = context.contentResolver.query(
Uri.parse("content://com.example.signer.SIGN_EVENT"),
listOf("$eventJson", "", "${logged_in_user_npub}"),
null,
null,
null
)
```
- result:
- Will return the **signature** and the **event** columns
```kotlin
if (result == null) return
if (result.moveToFirst()) {
val index = it.getColumnIndex("signature")
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_npub}"),
null,
null,
null
)
```
- result:
- Will return the **signature** column
```kotlin
if (result == null) return
if (result.moveToFirst()) {
val index = it.getColumnIndex("signature")
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_npub}"),
null,
null,
null
)
```
- result:
- Will return the **signature** column
```kotlin
if (result == null) return
if (result.moveToFirst()) {
val index = it.getColumnIndex("signature")
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_npub}"),
null,
null,
null
)
```
- result:
- Will return the **signature** column
```kotlin
if (result == null) return
if (result.moveToFirst()) {
val index = it.getColumnIndex("signature")
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_npub}"),
null,
null,
null
)
```
- result:
- Will return the **signature** column
```kotlin
if (result == null) return
if (result.moveToFirst()) {
val index = it.getColumnIndex("signature")
val encryptedText = it.getString(index)
}
2024-06-05 14:24:43 -04:00
```
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_npub}"),
null,
null,
null
)
```
- result:
- Will return the **signature** column
```kotlin
if (result == null) return
if (result.moveToFirst()) {
val index = it.getColumnIndex("signature")
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
2024-08-30 04:55:22 -04:00
You should consider using [NIP-46: Nostr Connect ](46.md ), [Nostr login ](https://github.com/nostrband/nostr-login ) or (window.nostr.js)[https://github.com/fiatjaf/window.nostr.js] for a better experience for web applications. When using this approach the web app can't call the signer in background so the user will see a popup for every event you try to sign.
2024-03-18 14:00:30 -04:00
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
2024-03-18 14:00:30 -04: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
2024-03-18 14:00:30 -04: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
2024-03-18 14:00:30 -04:00
You can configure the `returnType` to be **signature** or **event** .
2023-11-08 08:22:43 -05:00
2024-03-18 14:00:30 -04: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
2024-04-29 08:25:04 -04:00
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
2024-04-29 08:25:04 -04:00
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
2024-04-29 08:25:04 -04:00
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
2024-04-29 08:25:04 -04:00
window.href = `nostrsigner:${encryptedText}?pubKey=${hex_pub_key}&compressionType=none&returnType=signature&type=nip44_decrypt&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
```