mirror of
https://github.com/nostr-protocol/nips.git
synced 2026-07-22 07:48:25 +00:00
Simplify nip 55 (#2363)
This commit is contained in:
667
55.md
667
55.md
@@ -6,13 +6,31 @@ Android Signer Application
|
|||||||
|
|
||||||
`draft` `optional`
|
`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.
|
## Rationale
|
||||||
|
|
||||||
## Usage for Android applications
|
This NIP describes a method for 2-way communication between an Android signer and any Nostr client running on the same device, so that the client never needs to handle the user's private key. The signer is an Android application; the client may be another Android application or a web page.
|
||||||
|
|
||||||
The Android signer uses Intents (to accept/reject permissions manually) and Content Resolvers (to accept/reject permissions automatically in background if the user allowed it) to communicate between applications.
|
## Terminology
|
||||||
|
|
||||||
To be able to use the Android signer in your application you should add this to your AndroidManifest.xml:
|
- **user**: A person trying to use Nostr.
|
||||||
|
- **client**: A user-facing application that sends signing requests.
|
||||||
|
- **signer**: An Android application that holds the user's private key and answers requests from _client_.
|
||||||
|
- **user-pubkey**: The public key representing _user_, returned by `get_public_key`.
|
||||||
|
- **package name**: The Android package name of the _signer_ (e.g. `com.example.signer`), used by _client_ to address subsequent requests.
|
||||||
|
|
||||||
|
All pubkeys in this NIP are in hex format.
|
||||||
|
|
||||||
|
## Communication methods
|
||||||
|
|
||||||
|
A _client_ can talk to the _signer_ in three ways:
|
||||||
|
|
||||||
|
- **Intents** — used by Android clients. The _signer_ opens, the _user_ approves or rejects the request manually, and the result is returned to _client_.
|
||||||
|
- **Content Resolver** — used by Android clients. Runs in the background without opening the _signer_, but only works for permissions the _user_ has previously chosen to remember.
|
||||||
|
- **Web** — used by web clients. The result is returned via a `callbackUrl` or copied to the clipboard.
|
||||||
|
|
||||||
|
## Setup
|
||||||
|
|
||||||
|
To detect and call the _signer_, an Android _client_ must declare the `nostrsigner` scheme in its `AndroidManifest.xml`:
|
||||||
|
|
||||||
```xml
|
```xml
|
||||||
<queries>
|
<queries>
|
||||||
@@ -24,514 +42,148 @@ To be able to use the Android signer in your application you should add this to
|
|||||||
</queries>
|
</queries>
|
||||||
```
|
```
|
||||||
|
|
||||||
Then you can use this function to check if there's a signer application installed:
|
It can then check whether a _signer_ is installed:
|
||||||
|
|
||||||
```kotlin
|
```kotlin
|
||||||
fun isExternalSignerInstalled(context: Context): Boolean {
|
fun isExternalSignerInstalled(context: Context): Boolean {
|
||||||
val intent =
|
val intent = Intent(Intent.ACTION_VIEW, Uri.parse("nostrsigner:"))
|
||||||
Intent().apply {
|
return context.packageManager.queryIntentActivities(intent, 0).isNotEmpty()
|
||||||
action = Intent.ACTION_VIEW
|
|
||||||
data = Uri.parse("nostrsigner:")
|
|
||||||
}
|
|
||||||
val infos = context.packageManager.queryIntentActivities(intent, 0)
|
|
||||||
return infos.size > 0
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Using Intents
|
## Initiating a connection
|
||||||
|
|
||||||
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.
|
1. _client_ sends a `get_public_key` request.
|
||||||
|
2. _signer_ responds with the `user-pubkey` and its `package name`.
|
||||||
|
3. _client_ stores both, and SHOULD NOT call `get_public_key` again while the _user_ stays logged in.
|
||||||
|
4. _client_ addresses all further requests to that `package name`.
|
||||||
|
|
||||||
```kotlin
|
A _client_ MAY pass a list of `permissions` with `get_public_key` so the _user_ can pre-authorize methods (used by the Content Resolver to answer in the background). Each permission is an object with a `type` (a method name) and, for `sign_event`, an optional `kind`:
|
||||||
val launcher = rememberLauncherForActivityResult(
|
|
||||||
contract = ActivityResultContracts.StartActivityForResult(),
|
```json
|
||||||
onResult = { result ->
|
[{ "type": "sign_event", "kind": 22242 }, { "type": "nip44_decrypt" }]
|
||||||
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:
|
## Methods
|
||||||
|
|
||||||
```kotlin
|
Every request has a `type` (the method name) and a payload. The payload is passed differently per transport (see below) but its meaning is shared:
|
||||||
val intent = Intent(Intent.ACTION_VIEW, Uri.parse("nostrsigner:$content"))
|
|
||||||
```
|
| Method | Payload | Extra params | Result |
|
||||||
|
| ------------------- | --------------- | ------------------------- | ------------------------------- |
|
||||||
Set the Signer package name after you receive the response from **get_public_key** method:
|
| `get_public_key` | _(empty)_ | `permissions` | `user-pubkey` |
|
||||||
|
| `sign_event` | event JSON | `current_user` | the signature, and signed event |
|
||||||
```kotlin
|
| `nip04_encrypt` | plaintext | `pubkey`, `current_user` | the ciphertext |
|
||||||
intent.`package` = "com.example.signer"
|
| `nip44_encrypt` | plaintext | `pubkey`, `current_user` | the ciphertext |
|
||||||
```
|
| `nip04_decrypt` | ciphertext | `pubkey`, `current_user` | the plaintext |
|
||||||
|
| `nip44_decrypt` | ciphertext | `pubkey`, `current_user` | the plaintext |
|
||||||
If you are sending multiple intents without awaiting you can add some intent flags to sign all events without opening multiple times the signer
|
| `decrypt_zap_event` | event JSON | `current_user` | the decrypted event JSON |
|
||||||
|
|
||||||
```kotlin
|
- `current_user` is the `user-pubkey` currently logged in to _client_, in hex format.
|
||||||
intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_CLEAR_TOP)
|
- `pubkey` is the public key of the other party used for encryption/decryption, in hex format.
|
||||||
```
|
- `id` is an optional client-chosen string echoed back in the result, used to match responses when several requests are sent without waiting.
|
||||||
|
|
||||||
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
|
## Using Intents
|
||||||
|
|
||||||
```kotlin
|
The payload is the `nostrsigner:` URI data; all other params are intent extras. The result is returned via `registerForActivityResult` / `rememberLauncherForActivityResult`.
|
||||||
android:launchMode="singleTop"
|
|
||||||
```
|
|
||||||
|
|
||||||
Signer MUST answer multiple permissions with an array of results
|
|
||||||
|
|
||||||
```kotlin
|
|
||||||
|
|
||||||
val results = listOf(
|
|
||||||
Result(
|
|
||||||
package = signerPackageName,
|
|
||||||
result = eventSignature,
|
|
||||||
id = intentId
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
val json = results.toJson()
|
|
||||||
|
|
||||||
intent.putExtra("results", json)
|
|
||||||
```
|
|
||||||
|
|
||||||
Send the Intent:
|
|
||||||
|
|
||||||
```kotlin
|
```kotlin
|
||||||
|
val intent = Intent(Intent.ACTION_VIEW, Uri.parse("nostrsigner:$payload")).apply {
|
||||||
|
`package` = signerPackageName // omit only for get_public_key
|
||||||
|
putExtra("type", "sign_event")
|
||||||
|
putExtra("id", event.id) // optional, echoed back
|
||||||
|
putExtra("current_user", userPubkey)
|
||||||
|
}
|
||||||
launcher.launch(intent)
|
launcher.launch(intent)
|
||||||
```
|
```
|
||||||
|
|
||||||
### Initiating a connection
|
The result is read from the returned intent's extras:
|
||||||
|
|
||||||
- Client send a get_public_key `Intent`
|
| Extra | Description |
|
||||||
- Signer responds with the user `pubkey` and it's `packageName`
|
| ---------- | -------------------------------------------------------- |
|
||||||
- Client saves the `pubkey` and `packageName` somewhere and NEVER calls `get_public_key` again
|
| `result` | the method result (pubkey, signature, ciphertext, etc.) |
|
||||||
- Client now can send `content resolvers` and `Intents` for the other methods
|
| `id` | the `id` sent in the request |
|
||||||
|
| `event` | the signed event JSON (`sign_event` only) |
|
||||||
#### Methods
|
| `package` | the _signer_ package name (`get_public_key` only) |
|
||||||
|
| `rejected` | `true` when the _user_ rejected the request |
|
||||||
- **get_public_key**
|
|
||||||
- params:
|
A `resultCode` other than `Activity.RESULT_OK` means the _signer_ failed (e.g. it crashed), not that the _user_ rejected the request. When the _user_ rejects, the _signer_ returns `RESULT_OK` with a `rejected` extra set to `true`.
|
||||||
|
|
||||||
```kotlin
|
```kotlin
|
||||||
val intent = Intent(Intent.ACTION_VIEW, Uri.parse("nostrsigner:"))
|
onResult = { result ->
|
||||||
intent.putExtra("type", "get_public_key")
|
if (result.resultCode != Activity.RESULT_OK) {
|
||||||
// You can send some default permissions for the user to authorize for ever
|
// signer error (e.g. crash)
|
||||||
val permissions = listOf(
|
} else if (result.data?.getBooleanExtra("rejected", false) == true) {
|
||||||
Permission(
|
// user rejected the request
|
||||||
type = "sign_event",
|
} else {
|
||||||
kind = 22242
|
val value = result.data?.getStringExtra("result")
|
||||||
),
|
}
|
||||||
Permission(
|
}
|
||||||
type = "nip44_decrypt"
|
```
|
||||||
)
|
|
||||||
)
|
To sign multiple events without reopening the _signer_ for each one, _client_ adds the flags below and the _signer_ returns an array of results. The _signer_ must declare `android:launchMode="singleTop"` for this to work.
|
||||||
intent.putExtra("permissions", permissions.toJson())
|
|
||||||
context.startActivity(intent)
|
```kotlin
|
||||||
```
|
intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_CLEAR_TOP)
|
||||||
- result:
|
|
||||||
- If the user approved the intent it will return the **pubkey** in the result field and the signer packageName in the **package** field
|
// signer answers with:
|
||||||
|
intent.putExtra("results", listOf(Result(package = signerPackageName, result = signature, id = intentId)).toJson())
|
||||||
```kotlin
|
```
|
||||||
val pubkey = intent.data?.getStringExtra("result")
|
|
||||||
// The package name of the signer application
|
## Using Content Resolver
|
||||||
val packageName = intent.data?.getStringExtra("package")
|
|
||||||
```
|
The _client_ queries `content://<package-name>.<TYPE>` (e.g. `SIGN_EVENT`, `NIP44_ENCRYPT`). The query's `selectionArgs` carry the payload and params in the order `[payload, pubkey, current_user]`:
|
||||||
|
|
||||||
- **sign_event**
|
```kotlin
|
||||||
- params:
|
val result = context.contentResolver.query(
|
||||||
|
Uri.parse("content://com.example.signer.SIGN_EVENT"),
|
||||||
```kotlin
|
listOf(eventJson, "", loggedInUserPubkey),
|
||||||
val intent = Intent(Intent.ACTION_VIEW, Uri.parse("nostrsigner:$eventJson"))
|
null, null, null,
|
||||||
intent.`package` = "com.example.signer"
|
)
|
||||||
intent.putExtra("type", "sign_event")
|
```
|
||||||
// To handle results when not waiting between intents
|
|
||||||
intent.putExtra("id", event.id)
|
The cursor returns a `result` column (and, for `sign_event`, an `event` column with the signed event JSON). The query returns `null`, or a `rejected` column is present, when:
|
||||||
// Send the current logged in user pubkey
|
|
||||||
intent.putExtra("current_user", pubkey)
|
- the _user_ did not enable "remember my choice" for this request,
|
||||||
|
- the `user-pubkey` is not present in the _signer_,
|
||||||
context.startActivity(intent)
|
- the request `type` is not recognized, or
|
||||||
```
|
- the _user_ chose to always reject this request (a `rejected` column is returned — _client_ SHOULD NOT then fall back to an Intent).
|
||||||
- result:
|
|
||||||
- If the user approved intent it will return the **result**, **id** and **event** fields
|
```kotlin
|
||||||
|
if (result == null) return
|
||||||
```kotlin
|
if (result.getColumnIndex("rejected") > -1) return
|
||||||
val signature = intent.data?.getStringExtra("result")
|
if (result.moveToFirst()) {
|
||||||
// The id you sent
|
val value = result.getString(result.getColumnIndex("result"))
|
||||||
val id = intent.data?.getStringExtra("id")
|
// for sign_event also: result.getColumnIndex("event")
|
||||||
val signedEventJson = intent.data?.getStringExtra("event")
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
- **nip04_encrypt**
|
Clients SHOULD store the `user-pubkey` locally and avoid calling `get_public_key` after the user is logged in.
|
||||||
- params:
|
|
||||||
|
## Using Web Applications
|
||||||
```kotlin
|
|
||||||
val intent = Intent(Intent.ACTION_VIEW, Uri.parse("nostrsigner:$plaintext"))
|
Web clients can't receive a result from an intent, so the _signer_ returns the result via a `callbackUrl` (appended to the URL) or, if none is given, by copying it to the clipboard. The request is a `nostrsigner:` URL whose path is the payload and whose query string carries the params:
|
||||||
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
|
nostrsigner:<payload>?type=<method>&pubkey=<hex_pubkey>&callbackUrl=https://example.com/?result=
|
||||||
intent.putExtra("id", "some_id")
|
```
|
||||||
// Send the current logged in user pubkey
|
|
||||||
intent.putExtra("current_user", account.keyPair.pubkey)
|
Query parameters:
|
||||||
// Send the hex pubkey that will be used for encrypting the data
|
|
||||||
intent.putExtra("pubkey", pubkey)
|
- `type` — the method name.
|
||||||
|
- `pubkey` — the other party's hex pubkey (encryption/decryption methods).
|
||||||
context.startActivity(intent)
|
- `callbackUrl` — where the _signer_ appends the result. If omitted, the result is copied to the clipboard.
|
||||||
```
|
- `returnType` — `signature` or `event`.
|
||||||
- result:
|
- `compressionType` — `none` (default) or `gzip`. With `gzip` the returned event is `"Signer1"` + Base64(gzip(event json)); useful because intents and URLs have length limits.
|
||||||
- If the user approved intent it will return the **result** and **id** fields
|
|
||||||
|
```js
|
||||||
```kotlin
|
window.href = `nostrsigner:${eventJson}?compressionType=none&returnType=signature&type=sign_event&callbackUrl=https://example.com/?event=`;
|
||||||
val encryptedText = intent.data?.getStringExtra("result")
|
```
|
||||||
// the id you sent
|
|
||||||
val id = intent.data?.getStringExtra("id")
|
> Consider using [NIP-46: Nostr Remote Signing](46.md) for web applications. With the approach here the web client can't call the _signer_ in the background, so the _user_ sees a popup for every request.
|
||||||
```
|
|
||||||
|
|
||||||
- **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)
|
|
||||||
|
|
||||||
context.startActivity(intent)
|
|
||||||
```
|
|
||||||
- result:
|
|
||||||
- If the user approved intent it will return the **result** and **id** fields
|
|
||||||
|
|
||||||
```kotlin
|
|
||||||
val encryptedText = intent.data?.getStringExtra("result")
|
|
||||||
// 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 pubkey
|
|
||||||
intent.putExtra("current_user", account.keyPair.pubkey)
|
|
||||||
// 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 **result** and **id** fields
|
|
||||||
|
|
||||||
```kotlin
|
|
||||||
val plainText = intent.data?.getStringExtra("result")
|
|
||||||
// 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", "nip44_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)
|
|
||||||
|
|
||||||
context.startActivity(intent)
|
|
||||||
```
|
|
||||||
- result:
|
|
||||||
- If the user approved intent it will return the **result** and **id** fields
|
|
||||||
|
|
||||||
```kotlin
|
|
||||||
val plainText = intent.data?.getStringExtra("result")
|
|
||||||
// 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 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 eventJson = intent.data?.getStringExtra("result")
|
|
||||||
// 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 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
|
|
||||||
|
|
||||||
Clients SHOULD save the user pubkey locally and avoid calling the `get_public_key` after the user is logged in to the Client
|
|
||||||
|
|
||||||
#### Methods
|
|
||||||
|
|
||||||
- **sign_event**
|
|
||||||
- params:
|
|
||||||
|
|
||||||
```kotlin
|
|
||||||
val result = context.contentResolver.query(
|
|
||||||
Uri.parse("content://com.example.signer.SIGN_EVENT"),
|
|
||||||
listOf("$eventJson", "", "${logged_in_user_pubkey}"),
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null
|
|
||||||
)
|
|
||||||
```
|
|
||||||
- result:
|
|
||||||
- Will return the **result** and the **event** columns
|
|
||||||
|
|
||||||
```kotlin
|
|
||||||
if (result == null) return
|
|
||||||
|
|
||||||
if (it.getColumnIndex("rejected") > -1) return
|
|
||||||
|
|
||||||
if (result.moveToFirst()) {
|
|
||||||
val index = it.getColumnIndex("result")
|
|
||||||
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}"),
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null
|
|
||||||
)
|
|
||||||
```
|
|
||||||
- result:
|
|
||||||
- Will return the **result** column
|
|
||||||
|
|
||||||
```kotlin
|
|
||||||
if (result == null) return
|
|
||||||
|
|
||||||
if (it.getColumnIndex("rejected") > -1) return
|
|
||||||
|
|
||||||
if (result.moveToFirst()) {
|
|
||||||
val index = it.getColumnIndex("result")
|
|
||||||
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}"),
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null
|
|
||||||
)
|
|
||||||
```
|
|
||||||
- result:
|
|
||||||
- Will return the **result** column
|
|
||||||
|
|
||||||
```kotlin
|
|
||||||
if (result == null) return
|
|
||||||
|
|
||||||
if (it.getColumnIndex("rejected") > -1) return
|
|
||||||
|
|
||||||
if (result.moveToFirst()) {
|
|
||||||
val index = it.getColumnIndex("result")
|
|
||||||
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_pubkey}"),
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null
|
|
||||||
)
|
|
||||||
```
|
|
||||||
- result:
|
|
||||||
- Will return the **result** column
|
|
||||||
|
|
||||||
```kotlin
|
|
||||||
if (result == null) return
|
|
||||||
|
|
||||||
if (it.getColumnIndex("rejected") > -1) return
|
|
||||||
|
|
||||||
if (result.moveToFirst()) {
|
|
||||||
val index = it.getColumnIndex("result")
|
|
||||||
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_pubkey}"),
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null
|
|
||||||
)
|
|
||||||
```
|
|
||||||
- result:
|
|
||||||
- Will return the **result** column
|
|
||||||
|
|
||||||
```kotlin
|
|
||||||
if (result == null) return
|
|
||||||
|
|
||||||
if (it.getColumnIndex("rejected") > -1) return
|
|
||||||
|
|
||||||
if (result.moveToFirst()) {
|
|
||||||
val index = it.getColumnIndex("result")
|
|
||||||
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_pubkey}"),
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null
|
|
||||||
)
|
|
||||||
```
|
|
||||||
- result:
|
|
||||||
- Will return the **result** column
|
|
||||||
|
|
||||||
```kotlin
|
|
||||||
if (result == null) return
|
|
||||||
|
|
||||||
if (it.getColumnIndex("rejected") > -1) return
|
|
||||||
|
|
||||||
if (result.moveToFirst()) {
|
|
||||||
val index = it.getColumnIndex("result")
|
|
||||||
val eventJson = it.getString(index)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Usage for Web Applications
|
|
||||||
|
|
||||||
You should consider using [NIP-46: Nostr Connect](46.md) for a better experience for web applications. When using this approach, the web app can't call the signer in the background, so the user will see a popup for every event you try to sign.
|
|
||||||
|
|
||||||
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
|
### Example
|
||||||
|
|
||||||
```js
|
```html
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
@@ -544,23 +196,18 @@ Android intents and browser urls have limitations, so if you are using the `retu
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
window.onload = function() {
|
window.onload = function() {
|
||||||
var url = new URL(window.location.href);
|
var params = new URL(window.location.href).searchParams;
|
||||||
var params = url.searchParams;
|
var result = params.get("event");
|
||||||
if (params) {
|
if (result) alert(result);
|
||||||
var param1 = params.get("event");
|
|
||||||
if (param1) alert(param1)
|
var json = { kind: 1, content: "test" };
|
||||||
}
|
var encodedJson = encodeURIComponent(JSON.stringify(json));
|
||||||
let json = {
|
var anchor = document.createElement("a");
|
||||||
kind: 1,
|
anchor.href = `nostrsigner:${encodedJson}?compressionType=none&returnType=signature&type=sign_event&callbackUrl=https://example.com/?event=`;
|
||||||
content: "test"
|
anchor.textContent = "Open External Signer";
|
||||||
}
|
document.body.appendChild(anchor);
|
||||||
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>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
```
|
```
|
||||||
Reference in New Issue
Block a user