mirror of
https://github.com/nostr-protocol/nips.git
synced 2024-11-09 22:09:06 -05:00
Merge pull request #868 from greenart7c3/master
Android Signer Application
This commit is contained in:
commit
81a0e0036e
538
100.md
Normal file
538
100.md
Normal file
|
@ -0,0 +1,538 @@
|
||||||
|
# NIP-100
|
||||||
|
|
||||||
|
## 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:
|
||||||
|
|
||||||
|
```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:
|
||||||
|
|
||||||
|
```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
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 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.
|
||||||
|
|
||||||
|
```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 ...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
Create the Intent using the **nostrsigner** scheme:
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
val intent = Intent(Intent.ACTION_VIEW, Uri.parse("nostrsigner:$content"))
|
||||||
|
```
|
||||||
|
|
||||||
|
Set the Signer package name:
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
intent.`package` = "com.example.signer"
|
||||||
|
```
|
||||||
|
|
||||||
|
Send the Intent:
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
launcher.launch(intent)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 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
|
||||||
|
val permissions = listOf(
|
||||||
|
Permission(
|
||||||
|
type = "sign_event",
|
||||||
|
kind = 22242
|
||||||
|
),
|
||||||
|
Permission(
|
||||||
|
type = "nip44_decrypt"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
intent.putExtra("permissions", permissions.toJson())
|
||||||
|
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")
|
||||||
|
// The package name of the signer application
|
||||||
|
val packageName = intent.data?.getStringExtra("package")
|
||||||
|
```
|
||||||
|
|
||||||
|
- **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
|
||||||
|
intent.putExtra("id", event.id)
|
||||||
|
// Send the current logged in user npub
|
||||||
|
intent.putExtra("current_user", npub)
|
||||||
|
|
||||||
|
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")
|
||||||
|
// The id you sent
|
||||||
|
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)
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
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")
|
||||||
|
```
|
||||||
|
|
||||||
|
- **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)
|
||||||
|
|
||||||
|
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")
|
||||||
|
```
|
||||||
|
|
||||||
|
- **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)
|
||||||
|
|
||||||
|
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")
|
||||||
|
```
|
||||||
|
|
||||||
|
- **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")
|
||||||
|
```
|
||||||
|
|
||||||
|
## 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 npub 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 "signature" and "event". The column event is the signed event json
|
||||||
|
|
||||||
|
For the other types Signer Application returns the column "signature"
|
||||||
|
|
||||||
|
If the user chose to always reject the event, signer application will return the column "rejected" and you should not open signer application
|
||||||
|
|
||||||
|
### 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)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- **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)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- **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)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- **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)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
# 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
|
||||||
|
|
||||||
|
- **get_public_key**
|
||||||
|
- params:
|
||||||
|
|
||||||
|
```js
|
||||||
|
window.href = `nostrsigner:?compressionType=none&returnType=signature&type=get_public_key&callbackUrl=https://example.com/?event=`;
|
||||||
|
```
|
||||||
|
|
||||||
|
- **sign_event**
|
||||||
|
- params:
|
||||||
|
|
||||||
|
```js
|
||||||
|
window.href = `nostrsigner:${eventJson}?compressionType=none&returnType=signature&type=sign_event&callbackUrl=https://example.com/?event=`;
|
||||||
|
```
|
||||||
|
|
||||||
|
- **nip04_encrypt**
|
||||||
|
- params:
|
||||||
|
|
||||||
|
```js
|
||||||
|
window.href = `nostrsigner:${plainText}?pubKey=${hex_pub_key}&compressionType=none&returnType=signature&type=nip04_encrypt&callbackUrl=https://example.com/?event=`;
|
||||||
|
```
|
||||||
|
|
||||||
|
- **nip44_encrypt**
|
||||||
|
- params:
|
||||||
|
|
||||||
|
```js
|
||||||
|
window.href = `nostrsigner:${plainText}?pubKey=${hex_pub_key}&compressionType=none&returnType=signature&type=nip44_encrypt&callbackUrl=https://example.com/?event=`;
|
||||||
|
```
|
||||||
|
|
||||||
|
- **nip04_decrypt**
|
||||||
|
- params:
|
||||||
|
|
||||||
|
```js
|
||||||
|
window.href = `nostrsigner:${encryptedText}?pubKey=${hex_pub_key}&compressionType=none&returnType=signature&type=nip04_decrypt&callbackUrl=https://example.com/?event=`;
|
||||||
|
```
|
||||||
|
|
||||||
|
- **nip44_decrypt**
|
||||||
|
- params:
|
||||||
|
|
||||||
|
```js
|
||||||
|
window.href = `nostrsigner:${encryptedText}?pubKey=${hex_pub_key}&compressionType=none&returnType=signature&type=nip44_decrypt&callbackUrl=https://example.com/?event=`;
|
||||||
|
```
|
||||||
|
|
||||||
|
- **decrypt_zap_event**
|
||||||
|
- params:
|
||||||
|
|
||||||
|
```js
|
||||||
|
window.href = `nostrsigner:${eventJson}?compressionType=none&returnType=signature&type=decrypt_zap_event&callbackUrl=https://example.com/?event=`;
|
||||||
|
```
|
||||||
|
|
||||||
|
## 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>
|
||||||
|
|
||||||
|
<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>
|
||||||
|
```
|
Loading…
Reference in New Issue
Block a user