Using gift-wraps and sealing

This commit is contained in:
Oren 2024-06-03 02:12:46 +03:00
parent 6dc5be3cf2
commit 3cd22c0478
1 changed files with 130 additions and 17 deletions

147
x.md
View File

@ -1,17 +1,17 @@
NIP-X
=====
A method for transferring HTTP communication over Nostr Direct-Messages
-----------------------------------------------------------------------
# A method for transferring HTTP communication over Nostr
`draft` `optional`
Reasoning: The standard HTTP & DNS protocols are centralized because the server, in order to advertise itself to clients, needs to have a domain or a static ip address. TOR (onion) addresses solve the same issue, but the network is really slow and unreliable, especially when all you need is to transfer a small burst of information (i.e. a request to a json api). Nostr direct-messages, based on a list of relays, can solve this.
Reasoning: The standard HTTP & DNS protocols are centralized because the server, in order to advertise itself to clients, needs to have a domain or a static ip address. TOR (onion) addresses solve the same issue, but the network is really slow and unreliable, especially when all you need is to transfer a small burst of information (i.e. a request to a json api). A mechanism similar to Nostr direct-messages, using multiple relays relays, can solve this.
This NIP defines a standard method to transfer basic HTTP-like communication over directed-messages using [NIP-04](04.md).
We are aware that [NIP-04](04.md) is unrecommended in favor of [NIP-17](17.md), but since [NIP-17](17.md) is more complicated to implement and is not yet supported in many libraries and relays, we will focus on [NIP-04](04.md).
This NIP defines a standard method to transfer basic HTTP-like communication over using [NIP-44](44.md) encryption and [NIP-59](59.md) gift wraps, in a way that is almost identical to [NIP-17](17.md).
The main exception is that the content is not plain text, but a JSON string of an object that contains the information of the request/response.
This NIP does not define any new event. We simply define the format of the messages' content (before encryption).
## Nostr URL
A `Nostr URL` is an http-like URL where the "host" is a Nostr profile entity as defined in [NIP-19](19.md):
@ -30,9 +30,9 @@ A Nostr URL:
* Can have user-info followed by "@" before the "host" part. i.e. `http://myuser:mypassword@nprofile1qqsrhuxx8l9ex335q7he0f09aej04zpazpl0ne2cgukyawd24mayt8gpp4mhxue69uhhytnc9e3k7mgpz4mhxue69uhkg6nzv9ejuumpv34kytnrdaksjlyr9p.nostr/root/subroute?param=value`.
* Can have a query-param called "httpnostr" to specify future nostr-based NIPs that the server supports. For example, if a future NIP-XYZ will define a new method for transferring http communication (i.e. that uses [NIP-17](17.md) instead of [NIP-04](04.md)), the url query-string could look like `?param=value&httpnostr=nipxyz&x=y`. However, the server **MUST** also support the basic communication method desribed in this NIP.
Once the http client detects the ".nostr" TLD, it parses the `nprofile` entity to find the server's public-key and a list of relays that can be used to reach it. The client can then send the `Request` direct-message, whose content is a JSON string of an object with the following fields:
Once the http client detects the ".nostr" TLD, it parses the `nprofile` entity to find the server's public-key and a list of relays that can be used to reach it. The client can then send a `Request` message (see: [Sending Request and Response Messages](#sending-request-and-response-messages)) containing an object with the following fields:
* `id`: A non-empty cryptographically-secure random string of up to 64 characters. The randomness should prevent different users from detecting the communication of others.
* `id`: A non-empty cryptographically-secure random string of up to 64 characters. The randomness should prevent different users from detecting the communication of others, and from different requests having the same id.
* `url`: The route part + query-string of the URL, including the leading "/" char.
* `method`: The HTTP method, i.e. `GET`, `POST`, `PUT` etc.
* `headers`: A JSON object whose keys are the http header names, and the values are the header values (always as strings).
@ -40,7 +40,7 @@ Once the http client detects the ".nostr" TLD, it parses the `nprofile` entity t
* A `Host` header is not required.
* `bodyBase64`: The body of the request encoded in base64. Encoding the body in base64 will simplify debugging this communication protocol (A JSON object that contains strange characters might be hard to read). This NIP is not intended for large data transfers, where the data-increase in using base64 is significant (Such communications should not use Nostr anyways).
An example `Request` message:
An example `Request` object:
```json
{
@ -59,14 +59,14 @@ All the fields **MUST** exist even if they are empty (even `GET` messages who us
This NIP does not define how to handle long-polling and other HTTP communication features that keep the communication alive instead of sending the entire message body at once.
The server **MUST** reply to this direct-message with a `Response` direction-message, whose content is a JSON string of an object with the following fields:
The server **SHOULD** reply to a `Request` message with a `Response` message containing an object with the following fields:
* `id`: The same `id` as in the request.
* `status`: The response status-code (integer), i.e. 200, 404, etc.
* `headers`: A JSON object whose keys are the http header names, and the values are the header values (always as strings).
* `bodyBase64`: The body of the response encoded in base64.
An example `Response` message:
An example `Response` object:
```json
{
@ -82,16 +82,129 @@ An example `Response` message:
All the fields **MUST** exist even if they are empty.
Implementations
---------------
## Sending Request and Response Messages
https://github.com/oren-z0/http2nostr - An HTTP Proxy that receives HTTP requests, sends them as Nostr direct messages, waits for responding direct messages and sends them as HTTP responses.
Kind `80` (similar to the default HTTP port 80) is a request message sent from a `client` to a `server`. `p` tag identify the server. The client SHOULD create a new pair of private-public keys for each request, to enhance the obfuscation of the communication.
https://github.com/oren-z0/nostr2http - An HTTP Reverse Proxy that receives Nostr direct messages, sends them as HTTP requests, and sends the HTTP responses back as nostr direct messages.
```js
{
"id": "<usual hash>",
  "pubkey": "<client-one-time-pubkey>",
"created_at": now(),
  "kind": 80,
  "tags": [
    ["p", "<server-pubkey>", "<relay-url>"],
["relays", "<additional-relay1-url>", "<additional-relay2-url>", ...],
    ...
  ],
  "content": "<json-string-of-request-object>",
}
```
`.content` MUST be text. Fields `id` and `created_at` are required. The `server` MAY ignore the request if `created_at` is too far in the past or future.
An example request message:
```json
{
"id": "52027ef73c41df333621e810325b936246e2df30481ffbba5b08ac53d5a94bee",
"pubkey": "7e7e9c42a91bfef19fa929e5fda1b72e0ebc1a4c1141673e2794234d86addf4e",
"created_at": 1000000000,
"kind": 80,
"tags": [
["p", "3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d", "wss://r.x.com"],
["relays", "wss://djbas.sadkb.com"]
],
"content": "{\"id\":\"0da8a3dd-80e2-4fca-ba93-5c4261edcc9e\",\"url\":\"/api/test\",\"method\":\"GET\",\"headers\":{\"accept\":\"*/*\",\"user-agent\":\"curl/8.6.0\"},\"bodyBase64\":\"\"}"
}
```
Similarly, Kind `81` is a request message sent from a `server` to a `client`. `p` tag identify the client's one-time pubkey.
```js
{
"id": "<usual hash>",
  "pubkey": "<server-pubkey>",
"created_at": now(),
  "kind": 81,
  "tags": [
    ["p", "<client-one-time-pubkey>", "<relay-url>"],
["relays", "<additional-relay1-url>", "<additional-relay2-url>", ...],
    ...
  ],
  "content": "<json-string-of-response-object>",
}
```
`.content` MUST be text. Fields `id` and `created_at` are required.
An example response message:
```json
{
"id": "48a83f500a5ed1ea90eddba3675bc4e2dbf5d72cffd3837cf161da398d0c7e77",
"pubkey": "3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d",
"created_at": 1000000001,
"kind": 81,
"tags": [
["p", "7e7e9c42a91bfef19fa929e5fda1b72e0ebc1a4c1141673e2794234d86addf4e", "wss://r.x.com"],
["relays", "wss://djbas.sadkb.com"]
],
"content": "{\"id\":\"0da8a3dd-80e2-4fca-ba93-5c4261edcc9e\",\"status\":200,\"headers\":{\"date\":\"Fri, 31 May 2024 00:00:00 GMT\",\"content-type\":\"application/json; charset=utf-8\"},\"bodyBase64\":\"eyJoZWxsbyI6IndvcmxkIn0=\"}"
}
```
Kinds `80`s and `81`s MUST never be signed. If it is signed, the message might leak to relays and become **fully public**. They must be encrypted first.
## Encrypting
Following [NIP-59](59.md), the **unsigned** `kind:80` and `kind:81` messages must be sealed (`kind:13`) and then gift-wrapped **with a new kind** `21059`, to the receiver.
`kind:21059` will be used in the same way of `kind:1059`, but will be ephemeral as described in [NIP-01](01.md).
```js
{
"id": "<usual hash>",
  "pubkey": randomPublicKey,
  "created_at": randomTimeUpTo2DaysInThePast(),
"kind": 21059, // ephemeral gift wrap
  "tags": [
    ["p", receiverPublicKey, "<relay-url>"], // receiver
["relays", "<additional-relay1-url>", "<additional-relay2-url>", ...],
  ],
  "content": nip44Encrypt(
    {
"id": "<usual hash>",
      "pubkey": senderPublicKey,
      "created_at": randomTimeUpTo2DaysInThePast(),
      "kind": 13, // seal
      "tags": [], // no tags
      "content": nip44Encrypt(unsignedKind80or81, senderPrivateKey, receiverPublicKey),
      "sig": "<signed by senderPrivateKey>"
    },
    randomPrivateKey, receiverPublicKey
  ),
  "sig": "<signed by randomPrivateKey>"
}
```
The encryption algorithm MUST use the latest version of [NIP-44](44.md).
Receiver MUST verify if pubkey of the `kind:13` is the same pubkey on the `kind:80` or `kind:81`, otherwise any sender can impersonate others by simply changing the pubkey on `kind:80`/`kind:81`.
Sender SHOULD randomize `created_at` in up to two days in the past in both the seal and the gift wrap to make sure grouping by `created_at` doesn't reveal any metadata.
The gift wrap's `p`-tag can be the server's main pubkey (for the request message) or the client's one-time pubkey (for the response message).
## Implementations
https://github.com/oren-z0/http2nostr - An HTTP Proxy that receives HTTP requests, sends them as Nostr direct messages, waits for responding direct messages and sends them as HTTP responses. In the future it will use the sealing and gift-wrapping mentioned above.
https://github.com/oren-z0/nostr2http - An HTTP Reverse Proxy that receives Nostr direct messages, sends them as HTTP requests, and sends the HTTP responses back as nostr direct messages. In the future it will use the sealing and gift-wrapping mentioned above.
https://vimeo.com/950881613 - A demonstration of a static LNURL-pay identifier that encodes a ".nostr" address (instead of a ".onion" address), and a custom client that pays a custom server using this identifier.
Related work
------------
## Related work
https://dnstr.org - Domain Name Mapping for Nostr Public Keys
https://github.com/lnurl/luds/pull/203 - A suggestion for LNURL over Nostr
https://github.com/shocknet/Lightning.Pub - a Nostr-native account system designed to make running Lightning infrastructure for your friends/family/customers easier.