diff options
| author | fiatjaf <fiatjaf@gmail.com> | 2023-10-18 11:48:18 -0300 |
|---|---|---|
| committer | fiatjaf <fiatjaf@gmail.com> | 2023-11-21 15:49:40 -0300 |
| commit | cb37a9320e10fcc4d8c064571461311ca613a1d5 (patch) | |
| tree | c9456ccce0ae2283aa22710781bfa08b89995f55 /46.md | |
| parent | 5dcfe85306434f21ecb1e7a47edd92b2e3e64f9a (diff) | |
rewrite NIP-46.
Diffstat (limited to '46.md')
| -rw-r--r-- | 46.md | 176 |
1 files changed, 49 insertions, 127 deletions
| @@ -2,161 +2,83 @@ NIP-46 | |||
| 2 | ====== | 2 | ====== |
| 3 | 3 | ||
| 4 | Nostr Connect | 4 | Nostr Connect |
| 5 | ------------------------ | 5 | ------------- |
| 6 | 6 | ||
| 7 | `draft` `optional` | 7 | `draft` `optional` |
| 8 | 8 | ||
| 9 | ## Rationale | 9 | This 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. |
| 10 | 10 | ||
| 11 | Private keys should be exposed to as few systems - apps, operating systems, devices - as possible as each system adds to the attack surface. | 11 | ## Signer Discovery |
| 12 | 12 | ||
| 13 | Entering 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. | 13 | The client must somehow be able to contact the signer through a common relay. |
| 14 | 14 | ||
| 15 | ### Started by the signer | ||
| 15 | 16 | ||
| 16 | ## Terms | 17 | The remote signer generates a connection token in the form |
| 17 | |||
| 18 | * **App**: Nostr app on any platform that *requires* to act on behalf of a nostr account. | ||
| 19 | * **Signer**: Nostr app that holds the private key of a nostr account and *can sign* on its behalf. | ||
| 20 | 18 | ||
| 19 | ``` | ||
| 20 | <npub1...>#<secret>?relay=wss://...&relay=wss://... | ||
| 21 | ``` | ||
| 21 | 22 | ||
| 22 | ## `TL;DR` | 23 | The 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. |
| 23 | 24 | ||
| 25 | ### Started by the client | ||
| 24 | 26 | ||
| 25 | **App** and **Signer** sends ephemeral encrypted messages to each other using kind `24133`, using a relay of choice. | 27 | The client generates a QR code in the following form (URL-encoded): |
| 26 | 28 | ||
| 27 | App prompts the Signer to do things such as fetching the public key or signing events. | 29 | ``` |
| 30 | nostrconnect://<client-key-hex>?relay=wss://...&metadata={"name":"client-name"} | ||
| 31 | ``` | ||
| 28 | 32 | ||
| 29 | The `content` field must be an encrypted JSONRPC-ish **request** or **response**. | 33 | The signer scans the QR code and sends a `connect` message to the client in the specified relays. |
| 30 | 34 | ||
| 31 | ## Signer Protocol | 35 | ## Event payloads |
| 32 | 36 | ||
| 33 | ### Messages | 37 | Event payloads are [NIP-04](04.md)-encrypted JSON blobs that look like JSONRPC. |
| 34 | 38 | ||
| 35 | #### Request | 39 | Events sent by the client to the remote signer have the following format: |
| 36 | 40 | ||
| 37 | ```json | 41 | ```js |
| 38 | { | 42 | { |
| 39 | "id": <random_string>, | 43 | "pubkey": "<client-key-hex>" |
| 40 | "method": <one_of_the_methods>, | 44 | "kind": 24133, |
| 41 | "params": [<anything>, <else>] | 45 | "tags": [ |
| 46 | ["p", "<signer-key-hex>"] | ||
| 47 | ], | ||
| 48 | "content": "nip04_encrypted_json({id: <random-string>, method: <see-below>, params: []})", | ||
| 49 | ... | ||
| 42 | } | 50 | } |
| 43 | ``` | 51 | ``` |
| 44 | 52 | ||
| 45 | #### Response | 53 | And the events the remote signer sends to the client have the following format: |
| 46 | 54 | ||
| 47 | ```json | 55 | ```js |
| 48 | { | 56 | "pubkey": "<signer-key-hex>" |
| 49 | "id": <request_id>, | 57 | "kind": 24133, |
| 50 | "result": <anything>, | 58 | "tags": [ |
| 51 | "error": <reason> | 59 | ["p", "<client-key-hex>"] |
| 52 | } | 60 | ], |
| 61 | "content": "nip04_encrypted_json({id: <request-id>, result: <any>, error: <reason-string>})", | ||
| 62 | ... | ||
| 53 | ``` | 63 | ``` |
| 54 | 64 | ||
| 55 | ### Methods | 65 | ### Methods |
| 56 | 66 | ||
| 57 | 67 | - **connect** | |
| 58 | #### Mandatory | 68 | - params: [`pubkey`, `secret`] |
| 59 | 69 | - result: `null` | |
| 60 | These are mandatory methods the remote signer app MUST implement: | ||
| 61 | |||
| 62 | - **describe** | ||
| 63 | - params [] | ||
| 64 | - result `["describe", "get_public_key", "sign_event", "connect", "disconnect", "delegate", ...]` | ||
| 65 | - **get_public_key** | 70 | - **get_public_key** |
| 66 | - params [] | 71 | - params: [] |
| 67 | - result `pubkey` | 72 | - result: `pubkey` |
| 68 | - **sign_event** | 73 | - **sign_event** |
| 69 | - params [`event`] | 74 | - params: [`event`] |
| 70 | - result `event_with_signature` | 75 | - result: `event_with_pubkey_id_and_signature` |
| 71 | |||
| 72 | #### optional | ||
| 73 | |||
| 74 | |||
| 75 | - **connect** | ||
| 76 | - params [`pubkey`] | ||
| 77 | - **disconnect** | ||
| 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** | 76 | - **get_relays** |
| 83 | - params [] | 77 | - params: [] |
| 84 | - result `{ [url: string]: {read: boolean, write: boolean} }` | 78 | - result: `{ [url: string]: {read: boolean, write: boolean} }` |
| 85 | - **nip04_encrypt** | 79 | - **nip04_encrypt** |
| 86 | - params [`pubkey`, `plaintext`] | 80 | - params: [`pubkey`, `plaintext`] |
| 87 | - result `nip4 ciphertext` | 81 | - result: `nip4 ciphertext` |
| 88 | - **nip04_decrypt** | 82 | - **nip04_decrypt** |
| 89 | - params [`pubkey`, `nip4 ciphertext`] | 83 | - params: [`pubkey`, `nip4 ciphertext`] |
| 90 | - result [`plaintext`] | 84 | - result: [`plaintext`] |
| 91 | |||
| 92 | |||
| 93 | NOTICE: `pubkey` and `signature` are hex-encoded strings. | ||
| 94 | |||
| 95 | |||
| 96 | ### Nostr Connect URI | ||
| 97 | |||
| 98 | **Signer** discovers **App** by scanning a QR code, clicking on a deep link or copy-pasting an URI. | ||
| 99 | |||
| 100 | The **App** generates a special URI with prefix `nostrconnect://` and base path the hex-encoded `pubkey` with the following querystring parameters **URL encoded** | ||
| 101 | |||
| 102 | - `relay` URL of the relay of choice where the **App** is connected and the **Signer** must send and listen for messages. | ||
| 103 | - `metadata` metadata JSON of the **App** | ||
| 104 | - `name` human-readable name of the **App** | ||
| 105 | - `url` (optional) URL of the website requesting the connection | ||
| 106 | - `description` (optional) description of the **App** | ||
| 107 | - `icons` (optional) array of URLs for icons of the **App**. | ||
| 108 | |||
| 109 | #### JavaScript | ||
| 110 | |||
| 111 | ```js | ||
| 112 | const uri = `nostrconnect://<pubkey>?relay=${encodeURIComponent("wss://relay.damus.io")}&metadata=${encodeURIComponent(JSON.stringify({"name": "Example"}))}` | ||
| 113 | ``` | ||
| 114 | |||
| 115 | #### Example | ||
| 116 | ```sh | ||
| 117 | nostrconnect://b889ff5b1513b641e2a139f661a661364979c5beee91842f8f0ef42ab558e9d4?relay=wss%3A%2F%2Frelay.damus.io&metadata=%7B%22name%22%3A%22Example%22%7D | ||
| 118 | ``` | ||
| 119 | |||
| 120 | |||
| 121 | |||
| 122 | ## Flows | ||
| 123 | |||
| 124 | The `content` field contains encrypted message as specified by [NIP04](https://github.com/nostr-protocol/nips/blob/master/04.md). The `kind` chosen is `24133`. | ||
| 125 | |||
| 126 | ### Connect | ||
| 127 | |||
| 128 | 1. User clicks on **"Connect"** button on a website or scan it with a QR code | ||
| 129 | 2. It will show an URI to open a "nostr connect" enabled **Signer** | ||
| 130 | 3. In the URI there is a pubkey of the **App** ie. `nostrconnect://<pubkey>&relay=<relay>&metadata=<metadata>` | ||
| 131 | 4. The **Signer** will send a message to ACK the `connect` request, along with his public key | ||
| 132 | |||
| 133 | ### Disconnect (from App) | ||
| 134 | |||
| 135 | 1. User clicks on **"Disconnect"** button on the **App** | ||
| 136 | 2. The **App** will send a message to the **Signer** with a `disconnect` request | ||
| 137 | 3. The **Signer** will send a message to ACK the `disconnect` request | ||
| 138 | |||
| 139 | ### Disconnect (from Signer) | ||
| 140 | |||
| 141 | 1. User clicks on **"Disconnect"** button on the **Signer** | ||
| 142 | 2. The **Signer** will send a message to the **App** with a `disconnect` request | ||
| 143 | |||
| 144 | |||
| 145 | ### Get Public Key | ||
| 146 | |||
| 147 | 1. The **App** will send a message to the **Signer** with a `get_public_key` request | ||
| 148 | 3. The **Signer** will send back a message with the public key as a response to the `get_public_key` request | ||
| 149 | |||
| 150 | ### Sign Event | ||
| 151 | |||
| 152 | 1. The **App** will send a message to the **Signer** with a `sign_event` request along with the **event** to be signed | ||
| 153 | 2. The **Signer** will show a popup to the user to inspect the event and sign it | ||
| 154 | 3. 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 | |||
| 156 | ### Delegate | ||
| 157 | |||
| 158 | 1. 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. | ||
| 159 | 2. The **Signer** will show a popup to the user to delegate the **App** to sign on his behalf | ||
| 160 | 3. 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 | |||
| 162 | |||