upleb.uk

Public git repos — served from a NIP-34 GRASP relay at git.upleb.uk

summaryrefslogtreecommitdiff
path: root/46.md
diff options
context:
space:
mode:
Diffstat (limited to '46.md')
-rw-r--r--46.md257
1 files changed, 161 insertions, 96 deletions
diff --git a/46.md b/46.md
index 90fa1a0..1528116 100644
--- a/46.md
+++ b/46.md
@@ -1,162 +1,227 @@
1NIP-46 1# NIP-46 - Nostr Remote Signing
2======
3
4Nostr Connect
5------------------------
6
7`draft` `optional` `author:tiero` `author:giowe` `author:vforvalerio87`
8 2
9## Rationale 3## Rationale
10 4
11Private keys should be exposed to as few systems - apps, operating systems, devices - as possible as each system adds to the attack surface. 5Private keys should be exposed to as few systems - apps, operating systems, devices - as possible as each system adds to the attack surface.
12 6
13Entering private keys can also be annoying and requires exposing them to even more systems such as the operating system's clipboard that might be monitored by malicious apps. 7This NIP describes a method for 2-way communication between a remote signer and a Nostr client. The remote signer could be, for example, a hardware device dedicated to signing Nostr events, while the client is a normal Nostr client.
8
9## Terminology
10
11- **Local keypair**: A local public and private key-pair used to encrypt content and communicate with the remote signer. Usually created by the client application.
12- **Remote user pubkey**: The public key that the user wants to sign as. The remote signer has control of the private key that matches this public key.
13- **Remote signer pubkey**: This is the public key of the remote signer itself. This is needed in both `create_account` command because you don't yet have a remote user pubkey.
14
15All pubkeys specified in this NIP are in hex format.
14 16
17## Initiating a connection
15 18
16## Terms 19To initiate a connection between a client and a remote signer there are a few different options.
17 20
18* **App**: Nostr app on any platform that *requires* to act on behalf of a nostr account. 21### Direct connection initiated by remote signer
19* **Signer**: Nostr app that holds the private key of a nostr account and *can sign* on its behalf. 22
23This is most common in a situation where you have your own nsecbunker or other type of remote signer and want to connect through a client that supports remote signing.
24
25The remote signer would provide a connection token in the form:
26
27```
28bunker://<remote-user-pubkey>?relay=<wss://relay-to-connect-on>&relay=<wss://another-relay-to-connect-on>&secret=<optional-secret-value>
29```
20 30
31This token is pasted into the client by the user and the client then uses the details to connect to the remote signer via the specified relay(s).
21 32
22## `TL;DR` 33### Direct connection initiated by the client
23 34
35In this case, basically the opposite direction of the first case, the client provides a connection token (or encodes the token in a QR code) and the signer initiates a connection to the client via the specified relay(s).
24 36
25**App** and **Signer** sends ephemeral encrypted messages to each other using kind `24133`, using a relay of choice. 37```
38nostrconnect://<local-keypair-pubkey>?relay=<wss://relay-to-connect-on>&metadata=<json metadata in the form: {"name":"...", "url": "...", "description": "..."}>
39```
26 40
27App prompts the Signer to do things such as fetching the public key or signing events. 41## The flow
28 42
29The `content` field must be an encrypted JSONRPC-ish **request** or **response**. 431. Client creates a local keypair. This keypair doesn't need to be communicated to the user since it's largely disposable (i.e. the user doesn't need to see this pubkey). Clients might choose to store it locally and they should delete it when the user logs out.
442. Client gets the remote user pubkey (either via a `bunker://` connection string or a NIP-05 login-flow; shown below)
453. Clients use the local keypair to send requests to the remote signer by `p`-tagging and encrypting to the remote user pubkey.
464. The remote signer responds to the client by `p`-tagging and encrypting to the local keypair pubkey.
30 47
31## Signer Protocol 48### Example flow for signing an event
32 49
33### Messages 50- Remote user pubkey (e.g. signing as) `fa984bd7dbb282f07e16e7ae87b26a2a7b9b90b7246a44771f0cf5ae58018f52`
51- Local pubkey is `eff37350d839ce3707332348af4549a96051bd695d3223af4aabce4993531d86`
34 52
35#### Request 53#### Signature request
36 54
37```json 55```json
38{ 56{
39 "id": <random_string>, 57 "kind": 24133,
40 "method": <one_of_the_methods>, 58 "pubkey": "eff37350d839ce3707332348af4549a96051bd695d3223af4aabce4993531d86",
41 "params": [<anything>, <else>] 59 "content": nip04({
60 "id": <random_string>,
61 "method": "sign_event",
62 "params": [json_stringified(<{
63 content: "Hello, I'm signing remotely",
64 kind: 1,
65 tags: [],
66 created_at: 1714078911
67 }>)]
68 }),
69 "tags": [["p", "fa984bd7dbb282f07e16e7ae87b26a2a7b9b90b7246a44771f0cf5ae58018f52"]], // p-tags the remote user pubkey
42} 70}
43``` 71```
44 72
45#### Response 73#### Response event
46 74
47```json 75```json
48{ 76{
49 "id": <request_id>, 77 "kind": 24133,
50 "result": <anything>, 78 "pubkey": "fa984bd7dbb282f07e16e7ae87b26a2a7b9b90b7246a44771f0cf5ae58018f52",
51 "error": <reason> 79 "content": nip04({
80 "id": <random_string>,
81 "result": json_stringified(<signed-event>)
82 }),
83 "tags": [["p", "eff37350d839ce3707332348af4549a96051bd695d3223af4aabce4993531d86"]], // p-tags the local keypair pubkey
52} 84}
53``` 85```
54 86
55### Methods 87#### Diagram
56
57 88
58#### Mandatory 89![signing-example](https://i.nostr.build/P3gW.png)
59 90
60These are mandatory methods the remote signer app MUST implement: 91## Request Events `kind: 24133`
61 92
62- **describe** 93```json
63 - params [] 94{
64 - result `["describe", "get_public_key", "sign_event", "connect", "disconnect", "delegate", ...]` 95 "id": <id>,
65- **get_public_key** 96 "kind": 24133,
66 - params [] 97 "pubkey": <local_keypair_pubkey>,
67 - result `pubkey` 98 "content": <nip04(<request>)>,
68- **sign_event** 99 "tags": [["p", <remote_user_pubkey>]], // NB: in the `create_account` event, the remote signer pubkey should be `p` tagged.
69 - params [`event`] 100 "created_at": <unix timestamp in seconds>
70 - result `event_with_signature` 101}
102```
71 103
72#### optional 104The `content` field is a JSON-RPC-like message that is [NIP-04](https://github.com/nostr-protocol/nips/blob/master/04.md) encrypted and has the following structure:
73 105
106```json
107{
108 "id": <random_string>,
109 "method": <method_name>,
110 "params": [array_of_strings]
111}
112```
74 113
75- **connect** 114- `id` is a random string that is a request ID. This same ID will be sent back in the response payload.
76 - params [`pubkey`] 115- `method` is the name of the method/command (detailed below).
77- **disconnect** 116- `params` is a positional array of string parameters.
78 - params []
79- **delegate**
80 - params [`delegatee`, `{ kind: number, since: number, until: number }`]
81 - result `{ from: string, to: string, cond: string, sig: string }`
82- **get_relays**
83 - params []
84 - result `{ [url: string]: {read: boolean, write: boolean} }`
85- **nip04_encrypt**
86 - params [`pubkey`, `plaintext`]
87 - result `nip4 ciphertext`
88- **nip04_decrypt**
89 - params [`pubkey`, `nip4 ciphertext`]
90 - result [`plaintext`]
91 117
118### Methods/Commands
92 119
93NOTICE: `pubkey` and `signature` are hex-encoded strings. 120Each of the following are methods that the client sends to the remote signer.
94 121
122| Command | Params | Result |
123| ------------------------ | ------------------------------------------------- | ---------------------------------------------------------------------- |
124| `connect` | `[<remote_user_pubkey>, <optional_secret>, <optional_requested_permissions>]` | "ack" |
125| `sign_event` | `[<{kind, content, tags, created_at}>]` | `json_stringified(<signed_event>)` |
126| `ping` | `[]` | "pong" |
127| `get_relays` | `[]` | `json_stringified({<relay_url>: {read: <boolean>, write: <boolean>}})` |
128| `get_public_key` | `[]` | `<hex-pubkey>` |
129| `nip04_encrypt` | `[<third_party_pubkey>, <plaintext_to_encrypt>]` | `<nip04_ciphertext>` |
130| `nip04_decrypt` | `[<third_party_pubkey>, <nip04_ciphertext_to_decrypt>]` | `<plaintext>` |
131| `nip44_encrypt` | `[<third_party_pubkey>, <plaintext_to_encrypt>]` | `<nip44_ciphertext>` |
132| `nip44_decrypt` | `[<third_party_pubkey>, <nip44_ciphertext_to_decrypt>]` | `<plaintext>` |
95 133
96### Nostr Connect URI 134### Requested permissions
97 135
98**Signer** discovers **App** by scanning a QR code, clicking on a deep link or copy-pasting an URI. 136The `connect` method may be provided with `optional_requested_permissions` for user convenience. The permissions are a comma-separated list of `method[:params]`, i.e. `nip04_encrypt,sign_event:4` meaning permissions to call `nip04_encrypt` and to call `sign_event` with `kind:4`. Optional parameter for `sign_event` is the kind number, parameters for other methods are to be defined later.
99 137
100The **App** generates a special URI with prefix `nostrconnect://` and base path the hex-encoded `pubkey` with the following querystring parameters **URL encoded** 138## Response Events `kind:24133`
101 139
102- `relay` URL of the relay of choice where the **App** is connected and the **Signer** must send and listen for messages. 140```json
103- `metadata` metadata JSON of the **App** 141{
104 - `name` human-readable name of the **App** 142 "id": <id>,
105 - `url` (optional) URL of the website requesting the connection 143 "kind": 24133,
106 - `description` (optional) description of the **App** 144 "pubkey": <remote_signer_pubkey>,
107 - `icons` (optional) array of URLs for icons of the **App**. 145 "content": <nip04(<response>)>,
146 "tags": [["p", <local_keypair_pubkey>]],
147 "created_at": <unix timestamp in seconds>
148}
149```
108 150
109#### JavaScript 151The `content` field is a JSON-RPC-like message that is [NIP-04](https://github.com/nostr-protocol/nips/blob/master/04.md) encrypted and has the following structure:
110 152
111```js 153```json
112const uri = `nostrconnect://<pubkey>?relay=${encodeURIComponent("wss://relay.damus.io")}&metadata=${encodeURIComponent(JSON.stringify({"name": "Example"}))}` 154{
155 "id": <request_id>,
156 "result": <results_string>,
157 "error": <optional_error_string>
158}
113``` 159```
114 160
115#### Example 161- `id` is the request ID that this response is for.
116```sh 162- `results` is a string of the result of the call (this can be either a string or a JSON stringified object)
117nostrconnect://b889ff5b1513b641e2a139f661a661364979c5beee91842f8f0ef42ab558e9d4?relay=wss%3A%2F%2Frelay.damus.io&metadata=%7B%22name%22%3A%22Example%22%7D 163- `error`, _optionally_, it is an error in string form, if any. Its presence indicates an error with the request.
164
165### Auth Challenges
166
167An Auth Challenge is a response that a remote signer can send back when it needs the user to authenticate via other means. This is currently used in the OAuth-like flow enabled by signers like [Nsecbunker](https://github.com/kind-0/nsecbunkerd/). The response `content` object will take the following form:
168
169```json
170{
171 "id": <request_id>,
172 "result": "auth_url",
173 "error": <URL_to_display_to_end_user>
174}
118``` 175```
119 176
177Clients should display (in a popup or new tab) the URL from the `error` field and then subscribe/listen for another response from the remote signer (reusing the same request ID). This event will be sent once the user authenticates in the other window (or will never arrive if the user doesn't authenticate). It's also possible to add a `redirect_uri` url parameter to the auth_url, which is helpful in situations when a client cannot open a new window or tab to display the auth challenge.
178
179#### Example event signing request with auth challenge
180
181![signing-example-with-auth-challenge](https://i.nostr.build/W3aj.png)
182
183## Remote Signer Commands
184
185Remote signers might support additional commands when communicating directly with it. These commands follow the same flow as noted above, the only difference is that when the client sends a request event, the `p`-tag is the pubkey of the remote signer itself and the `content` payload is encrypted to the same remote signer pubkey.
186
187### Methods/Commands
120 188
189Each of the following are methods that the client sends to the remote signer.
121 190
122## Flows 191| Command | Params | Result |
192| ---------------- | ------------------------------------------ | ------------------------------------ |
193| `create_account` | `[<username>, <domain>, <optional_email>, <optional_requested_permissions>]` | `<newly_created_remote_user_pubkey>` |
123 194
124The `content` field contains encrypted message as specified by [NIP04](https://github.com/nostr-protocol/nips/blob/master/04.md). The `kind` chosen is `24133`. 195## Appendix
125 196
126### Connect 197### NIP-05 Login Flow
127 198
1281. User clicks on **"Connect"** button on a website or scan it with a QR code 199Clients might choose to present a more familiar login flow, so users can type a NIP-05 address instead of a `bunker://` string.
1292. It will show an URI to open a "nostr connect" enabled **Signer**
1303. In the URI there is a pubkey of the **App** ie. `nostrconnect://<pubkey>&relay=<relay>&metadata=<metadata>`
1314. The **Signer** will send a message to ACK the `connect` request, along with his public key
132 200
133### Disconnect (from App) 201When the user types a NIP-05 the client:
134 202
1351. User clicks on **"Disconnect"** button on the **App** 203- Queries the `/.well-known/nostr.json` file from the domain for the NIP-05 address provided to get the user's pubkey (this is the **remote user pubkey**)
1362. The **App** will send a message to the **Signer** with a `disconnect` request 204- In the same `/.well-known/nostr.json` file, queries for the `nip46` key to get the relays that the remote signer will be listening on.
1373. The **Signer** will send a message to ACK the `disconnect` request 205- Now the client has enough information to send commands to the remote signer on behalf of the user.
138 206
139### Disconnect (from Signer) 207### OAuth-like Flow
140 208
1411. User clicks on **"Disconnect"** button on the **Signer** 209#### Remote signer discovery via NIP-89
1422. The **Signer** will send a message to the **App** with a `disconnect` request
143 210
211In this last case, most often used to facilitate an OAuth-like signin flow, the client first looks for remote signers that have announced themselves via NIP-89 application handler events.
144 212
145### Get Public Key 213First the client will query for `kind: 31990` events that have a `k` tag of `24133`.
146 214
1471. The **App** will send a message to the **Signer** with a `get_public_key` request 215These are generally shown to a user, and once the user selects which remote signer to use and provides the remote user pubkey they want to use (via npub, pubkey, or nip-05 value), the client can initiate a connection. Note that it's on the user to select the remote signer that is actually managing the remote key that they would like to use in this case. If the remote user pubkey is managed on another remote signer, the connection will fail.
1483. The **Signer** will send back a message with the public key as a response to the `get_public_key` request
149 216
150### Sign Event 217In addition, it's important that clients validate that the pubkey of the announced remote signer matches the pubkey of the `_` entry in the `/.well-known/nostr.json` file of the remote signer's announced domain.
151 218
1521. The **App** will send a message to the **Signer** with a `sign_event` request along with the **event** to be signed 219Clients that allow users to create new accounts should also consider validating the availability of a given username in the namespace of remote signer's domain by checking the `/.well-known/nostr.json` file for existing usernames. Clients can then show users feedback in the UI before sending a `create_account` event to the remote signer and receiving an error in return. Ideally, remote signers would also respond with understandable error messages if a client tries to create an account with an existing username.
1532. The **Signer** will show a popup to the user to inspect the event and sign it
1543. The **Signer** will send back a message with the event including the `id` and the schnorr `signature` as a response to the `sign_event` request
155 220
156### Delegate 221#### Example Oauth-like flow to create a new user account with Nsecbunker
157 222
1581. The **App** will send a message with metadata to the **Signer** with a `delegate` request along with the **conditions** query string and the **pubkey** of the **App** to be delegated. 223Coming soon...
1592. The **Signer** will show a popup to the user to delegate the **App** to sign on his behalf
1603. The **Signer** will send back a message with the signed [NIP-26 delegation token](https://github.com/nostr-protocol/nips/blob/master/26.md) or reject it
161 224
225## References
162 226
227- [NIP-04 - Encryption](https://github.com/nostr-protocol/nips/blob/master/04.md)