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:
authorJeffG <202880+erskingardner@users.noreply.github.com>2024-02-20 21:56:17 +0100
committerGitHub <noreply@github.com>2024-02-20 17:56:17 -0300
commitc6cd655c1cba483fa9932b76e5ec62bafc56bc33 (patch)
treef3bd2744dfecec732d2df5ecfcd492aba2060ef7 /46.md
parentcbffa7a5de845bd6ed939c77de5540904d011d4b (diff)
New version of NIP-46 (#1047)
* New version of NIP-46 * Update pubkey references * Document what we have * Update terms and kind number in discovery * Update encypt/decrypt calls to handle arrays. Add redirect_uri param for auth_challenges * Move remote signer commands to own section, add appendix for oauth-like stuff. * Add diagrams
Diffstat (limited to '46.md')
-rw-r--r--46.md265
1 files changed, 195 insertions, 70 deletions
diff --git a/46.md b/46.md
index 94a052e..13a2fe4 100644
--- a/46.md
+++ b/46.md
@@ -1,98 +1,223 @@
1NIP-46 1# NIP-46 - Nostr Remote Signing
2======
3 2
4Nostr Connect 3## Rationale
5-------------
6 4
7`draft` `optional` 5Private keys should be exposed to as few systems - apps, operating systems, devices - as possible as each system adds to the attack surface.
8 6
9This NIP describes a method for 2-way communication between a **remote signer** and a normal 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. 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.
10 8
11## Signer Discovery 9## Terminology
12 10
13The client always starts by generating a random key which is used to communicate with the signer, then it one of the methods below is used to allow the client to know what is the signer public key for the session and which relays to use. 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 14
15### Started by the signer (nsecBunker) 15All pubkeys specified in this NIP are in hex format.
16 16
17The remote signer generates a connection token in the form 17## Initiating a connection
18
19To initiate a connection between a client and a remote signer there are a few different options.
20
21### Direct connection initiated by remote signer
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-pubkey>?relay=<wss://relay-to-connect-on>&relay=<wss://another-relay-to-connect-on>&secret=<optional-secret-value>
29```
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).
32
33### Direct connection initiated by the client
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).
18 36
19``` 37```
20bunker://<hex-pubkey>?relay=wss://...&relay=wss://...&secret=<optional-secret> 38nostrconnect://<local-keypair-pubkey>?relay=<wss://relay-to-connect-on>&metadata=<json metadata in the form: {"name":"...", "url": "...", "description": "..."}>
21``` 39```
22 40
23The user copies that token and pastes it in the client UI somehow. Then the client can send events of kind `24133` to the specified relays and wait for responses from the remote signer. 41## The flow
42
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.
47
48### Example flow for signing an event
24 49
25### Started by the client 50- Remote user pubkey (e.g. signing as) `fa984bd7dbb282f07e16e7ae87b26a2a7b9b90b7246a44771f0cf5ae58018f52`
51- Local pubkey is `eff37350d839ce3707332348af4549a96051bd695d3223af4aabce4993531d86`
26 52
27The client generates a QR code in the following form (URL-encoded): 53#### Signature request
28 54
55```json
56{
57 "kind": 24133,
58 "pubkey": "eff37350d839ce3707332348af4549a96051bd695d3223af4aabce4993531d86",
59 "content": nip04({
60 "id": <random_string>,
61 "method": "sign_event",
62 "params": [json_stringified(<{
63 content: "Hello, I'm signing remotely",
64 pubkey: "fa984bd7dbb282f07e16e7ae87b26a2a7b9b90b7246a44771f0cf5ae58018f52",
65 // ...the rest of the event data
66 }>)]
67 }),
68 "tags": [["p", "fa984bd7dbb282f07e16e7ae87b26a2a7b9b90b7246a44771f0cf5ae58018f52"]], // p-tags the remote user pubkey
69}
29``` 70```
30nostrconnect://<client-key-hex>?relay=wss://...&metadata={"name":"...", "url": "...", "description": "..."} 71
72#### Response event
73
74```json
75{
76 "kind": 24133,
77 "pubkey": "fa984bd7dbb282f07e16e7ae87b26a2a7b9b90b7246a44771f0cf5ae58018f52",
78 "content": nip04({
79 "id": <random_string>,
80 "result": json_stringified(<signed-event>)
81 }),
82 "tags": [["p", "eff37350d839ce3707332348af4549a96051bd695d3223af4aabce4993531d86"]], // p-tags the local keypair pubkey
83}
31``` 84```
32 85
33The signer scans the QR code and sends a `connect` message to the client in the specified relays. 86#### Diagram
34 87
35## Event payloads 88![signing-example](https://i.nostr.build/P3gW.png)
36 89
37Event payloads are [NIP-04](04.md)-encrypted JSON blobs that look like JSONRPC messages (their format is specified inside the `.content` of the event formats below). 90## Request Events `kind: 24133`
91
92```json
93{
94 "id": <id>,
95 "kind": 24133,
96 "pubkey": <local_keypair_pubkey>,
97 "content": <nip04(<request>)>,
98 "tags": [["p", <remote_user_pubkey>]], // NB: in the `create_account` event, the remote signer pubkey should be `p` tagged.
99 "created_at": <unix timestamp in seconds>,
100}
101```
38 102
39Events sent by the client to the remote signer have the following format: 103The `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:
40 104
41```js 105```json
42{ 106{
43 "pubkey": "<client-key-hex>" 107 "id": <random_string>,
44 "kind": 24133, 108 "method": <method_name>,
45 "tags": [ 109 "params": [array_of_strings]
46 ["p", "<signer-key-hex>"]
47 ],
48 "content": "nip04_encrypted_json({id: <random-string>, method: <see-below>, params: [array_of_strings]})",
49 ...
50} 110}
51``` 111```
52 112
53And the events the remote signer sends to the client have the following format: 113- `id` is a random string that is a request ID. This same ID will be sent back in the response payload.
114- `method` is the name of the method/command (detailed below).
115- `params` is a positional array of string parameters.
116
117### Methods/Commands
118
119Each of the following are methods that the client sends to the remote signer.
54 120
55```js 121| Command | Params | Result |
56 "pubkey": "<signer-key-hex>" 122| ------------------------ | ------------------------------------------------- | ---------------------------------------------------------------------- |
57 "kind": 24133, 123| `connect` | `[<remote_user_pubkey>, <optional_secret>]` | "ack" |
58 "tags": [ 124| `sign_event` | `[<json_stringified_event_to_sign>]` | `json_stringified(<signed_event>)` |
59 ["p", "<client-key-hex>"] 125| `ping` | `[]` | "pong" |
60 ], 126| `get_relays` | `[]` | `json_stringified({<relay_url>: {read: <boolean>, write: <boolean>}})` |
61 "content": "nip04_encrypted_json({id: <request-id>, result: <string>, error: <reason-string>})", 127| `get_public_key` | `[]` | `<hex-pubkey>` |
62 ... 128| `nip04_encrypt` | `[<third_party_pubkey>, <plaintext_to_encrypt>]` | `<nip04_ciphertext>` |
129| `nip04_decrypt` | `[<third_party_pubkey>, <ciphertext_to_decrypt>]` | `<plaintext>` |
130| `nip44_conversation_key` | Potential future addition | |
131| `nip44_encrypt` | Potential future addition | |
132| `nip44_decrypt` | Potential future addition | |
133
134## Response Events `kind:24133`
135
136```json
137{
138 "id": <id>,
139 "kind": 24133,
140 "pubkey": <remote_signer_pubkey>,
141 "content": <nip04(<response>)>,
142 "tags": [["p", <local_keypair_pubkey>]],
143 "created_at": <unix timestamp in seconds>,
144}
63``` 145```
64 146
65The signer key will always be the key of the user who controls the signer device. 147The `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:
66 148
67### Methods 149```json
68 150{
69- **connect** 151 "id": <request_id>,
70 - params: [`pubkey`, `secret`] 152 "result": <results_string>,
71 - result: `"ack"` 153 "error": <error_string>
72- **get_public_key** 154}
73 - params: [] 155```
74 - result: `pubkey-hex` 156
75- **sign_event** 157- `id` is the request ID that this response is for.
76 - params: [`event`] 158- `results` is a string of the result of the call (this can be either a string or a JSON stringified object)
77 - result: `json_string(event_with_pubkey_id_and_signature)` 159- `error` is an error in string form.
78- **get_relays** 160
79 - params: [] 161### Auth Challenges
80 - result: `json_string({[url: string]: {read: boolean, write: boolean}})` 162
81- **nip04_encrypt** 163An 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:
82 - params: [`third-party-pubkey`, `plaintext`] 164
83 - result: `nip04-ciphertext` 165```json
84- **nip04_decrypt** 166{
85 - params: [`third-party-pubkey`, `nip04-ciphertext`] 167 "id": <request_id>,
86 - result: `plaintext` 168 "result": "auth_url",
87- **nip44_get_key** 169 "error": <URL_to_display_to_end_user>
88 - params: [`third-party-pubkey`] 170}
89 - result: `nip44-conversation-key` 171```
90- **nip44_encrypt** 172
91 - params: [`third-party-pubkey`, `plaintext`] 173Clients 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.
92 - result: `nip44-ciphertext` 174
93- **nip44_decrypt** 175#### Example event signing request with auth challenge
94 - params: [`third-party-pubkey`, `nip44-ciphertext`] 176
95 - result: `plaintext` 177![signing-example-with-auth-challenge](https://i.nostr.build/W3aj.png)
96- **ping** 178
97 - params: [] 179## Remote Signer Commands
98 - result: `"pong"` 180
181Remote 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.
182
183### Methods/Commands
184
185Each of the following are methods that the client sends to the remote signer.
186
187| Command | Params | Result |
188| ---------------- | ------------------------------------------ | ------------------------------------ |
189| `create_account` | `[<username>, <domain>, <optional_email>]` | `<newly_created_remote_user_pubkey>` |
190
191## Appendix
192
193### NIP-05 Login Flow
194
195Clients might choose to present a more familiar login flow, so users can type a NIP-05 address instead of a `bunker://` string.
196
197When the user types a NIP-05 the client:
198
199- 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**)
200- 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.
201- Now the client has enough information to send commands to the remote signer on behalf of the user.
202
203### OAuth-like Flow
204
205#### Remote signer discovery via NIP-89
206
207In this last case, most often used to fascilitate an OAuth-like signin flow, the client first looks for remote signers that have announced themselves via NIP-89 application handler events.
208
209First the client will query for `kind: 31990` events that have a `k` tag of `24133`.
210
211These 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.
212
213In 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.
214
215Clients 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.
216
217#### Example Oauth-like flow to create a new user account with Nsecbunker
218
219Coming soon...
220
221## References
222
223- [NIP-04 - Encryption](https://github.com/nostr-protocol/nips/blob/master/04.md)