-
Notifications
You must be signed in to change notification settings - Fork 706
Nostr relays as OHTTP targets #2088
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
# NIP-X -- Nostr Relays as OHTTP Targets | ||
|
||
`draft` `optional` | ||
|
||
This NIP defines how a Nostr relay can expose an **Oblivious HTTP (OHTTP) target** interface so that clients can send Nostr requests through an **OHTTP relay** without revealing their network identity to the target relay. Each request is encrypted with an ephemeral HPKE key pair and routed via an OHTTP relay as per RFC 9458. The target relay decrypts the payload, processes the encapsulated request, and returns a response; the OHTTP relay cannot read contents, and the target Nostr relay cannot see client metadata. | ||
|
||
Critical note: this design relies on two distinct roles (OHTTP relay and OHTTP target). If a single operator controls both and colludes, client unlinkability collapses (a known limitation of OHTTP). See RFC 9458 for the trust split and limitations. ([IETF](https://www.ietf.org/rfc/rfc9458.html), [RFC Editor](https://www.rfc-editor.org/info/rfc9458)) | ||
|
||
## Motivation | ||
|
||
Nostr today relies on persistent WebSocket connections that inherently expose a client’s IP address to the relay. This creates a metadata-leakage channel: even if messages are encrypted, relays can trivially correlate client activity over time and build behavioral profiles. While users can route connections through Tor or VPNs, these options add operational complexity and do not guarantee reliable performance. | ||
|
||
Oblivious HTTP (OHTTP) provides a standardized way to separate transport metadata from application payloads. By routing requests through an OHTTP relay, clients conceal their network identity from the target relay while preserving end-to-end confidentiality and integrity of their Nostr events. This introduces per-request unlinkability without requiring heavy overlay networks. | ||
|
||
The design keeps protocol churn minimal. Clients and relays continue to use NIP-01 semantics, but transport shifts from a connection-oriented WebSocket to stateless OHTTP messages. Deployment is incremental: relays can expose OHTTP endpoints alongside WebSocket interfaces, advertising support in NIP-11. Clients that do not adopt OHTTP remain fully interoperable. | ||
|
||
## Specification / Protocol Flow | ||
|
||
Note: This NIP uses "relay" where RFC 9540 uses "gateway." Relays MAY deploy the OHTTP gateway separately from the OHTTP resource if it suites their setup; however, this NIP and the reference code below assumes the OHTTP gateway is deployed on the same interface as the HTTP interface. | ||
|
||
### NIP-11 advertisement and OHTTP key configuration | ||
|
||
Relays MUST support fetching [RFC 9540 Key Configuration Fetching](https://www.rfc-editor.org/rfc/rfc9540.html#name-key-configuration-fetching) via GET request. RFC 9540 defines the gateway location as `/.well-known/ohttp-gateway`. RFC9540 also defines the key configuration encoding. | ||
|
||
Relays that implement this NIP MUST advertise the following new fields in their NIP-11 document: | ||
|
||
```json | ||
{ | ||
"ohttp": { | ||
"max_request_bytes": 65536, | ||
"max_response_bytes": 1048576, | ||
// Key configuration is optional, but if provided, it MUST be a hexified encoding of the key configuration. | ||
"keyconfig": "01001604d51a22bc641d1ff95729b815cd036f93d4eff9c76fa3c867000e4e05e1982e849b679050c981b9cea485adb2a2f1cfc905393345cf1364d8456e3aa3abc338da000400010003", | ||
} | ||
} | ||
``` | ||
|
||
Relays and clients MUST at minimum support the following configurations: | ||
|
||
* Key Encapsulation Mechanism (KEM): Secp256k1-HKDF-SHA256 | ||
* Key Derivation Function (KDF): HKDF-SHA256 | ||
* Authenticated Encryption with Associated Data (AEAD): ChaCha20Poly1305 | ||
|
||
The following configurations were chosen because most of the clients and relays are already using the same cryptographic primitives. | ||
|
||
Relays MAY advertise their key configuration in the NIP-11 document to reduce round trips for the clients. If relays choose to do so, the relay MUST provide a hexified encoding of the key configuration. | ||
|
||
### Retrieve key configuration | ||
|
||
Clients MUST retrieve the OHTTP [key configuration](https://www.ietf.org/rfc/rfc9458.html#section-3.1) from the relay using RFC 9540 at the `/.well-known/ohttp-gateway` endpoint via a GET request. Clients MUST ensure the key fetching request is secure and authenticated. If the relay advertises the configuration in its NIP-11 document, clients MAY use that as an alternative bootstrap source but doing so may reveal the clients network identity. | ||
|
||
The key configuration defines the HPKE parameters required to establish secure and authenticated communication with the target relay. When fetching the configuration, clients SHOULD route the request through an OHTTP relay to avoid exposing their network identity. | ||
|
||
Once the configuration is retrieved, the client encapsulates a NIP-1 message inside a Binary HTTP (BHTTP) request and then applies HPKE to form the OHTTP request per RFC 9458. | ||
|
||
* EVENT messages: OHTTP encapsulated `POST` request. The body MUST contain the event as a JSON string. | ||
* REQ messages: OHTTP encapsulated `GET` request. The query string MUST carry the NIP-1 filter, encoded either as a hexified JSON string or URL-encoded JSON (final encoding is TBD). // TODO decide on the encoding // TODO: decide on the query param. REQ messages MUST NOT include a subscription ID, as this leaks linkable metadata. | ||
|
||
In order to preserve per-request unlinkability, clients MUST use a fresh ephemeral HPKE key pair per request. Furthermore, clients MUST NOT re-send the same encapsulated request to the same relay. | ||
|
||
### Endpoints | ||
|
||
#### Target endpoints | ||
|
||
Clients that receive a `404 Not Found` response SHOULD interpret it as an indication that the relay does not support OHTTP. In this case, clients SHOULD fall back to a standard WebSocket connection if they wish to interact with the relay. | ||
|
||
* `POST /.well-known/ohttp-gateway` - Submit an OHTTP-encapsulated event. | ||
|
||
The HTTP body MUST be a binary payload as defined in RFC 9458. The request MUST include a `Content-Type` header with the value `message/ohttp-req`. Relays MUST decapsulate the payload using their advertised key configuration to obtain the inner BHTTP request. | ||
|
||
HTTP Responses MUST only include the encapsulated BHTTP response and they MUST return `200 OK` regardless of the inner BHTTP response. | ||
|
||
#### BHTTP inner requests | ||
|
||
After decapsulation, relays parse the inner BHTTP request. Two methods are supported. BHTTP responses MAY include status codes other than `200 OK`. | ||
Relays MUST enforce the request and response size limits advertised in NIP-11. All responses MUST be re-encapsulated and returned to the client using the client's reply key. | ||
Comment on lines
+73
to
+76
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. padding is implicitly allowed in BHTTP due to trailing (or truncated) 0 bytes being tolerated. random padding is technically also allowed after the final 0 bytes, but discouraged (in bip 77 we rely on that though for future proofing, as that would allow multi-hop OHTTP with consistent padding a la Sphinx) not sure if this NIP should reccomend padding (what size to reccomend if so? not clear) but it should probably require that padding be tolerated |
||
|
||
##### `POST` | ||
|
||
The request body MUST contain a single NIP-1 `EVENT` message. | ||
|
||
* If valid, the relay MUST return an empty `202 Accepted` response. | ||
* If malformed or invalid for any other reason, the relay MUST return `400 Bad Request`. | ||
* For server-side errors, the relay MUST return `500 Internal Server Error`. | ||
|
||
##### `GET` | ||
|
||
The request query parameter MUST contain a single NIP-1 `REQ` subscription filter message. | ||
|
||
* A filter MUST be included in the query string with the key `filter`. // TODO: decide on the encoding | ||
* The encoding format for this filter (hexified JSON vs. URL-encoded JSON) remains an open question. | ||
|
||
Relays MUST return a `200 OK` response with the inner BHTTP response if the request is valid. The response MUST be formatted as new line delimited JSON. All available events within the event scope (defined below) MUST be returned in the response. | ||
|
||
### Key configuration lifetime | ||
|
||
Relays SHOULD rotate their OHTTP key configurations periodically to limit replay attacks and reduce the impact of key compromise. Key rotation is signaled via the HTTP caching headers (Cache-Control, Expires, ETag) on the response to the key configuration fetch defined in RFC 9540. | ||
|
||
### Backwards Compatibility | ||
|
||
Relays continue to support NIP-01 over WebSockets. Clients that do not implement OHTTP interoperate unchanged. The OHTTP interface introduces new endpoints; no changes to NIP-01 message schemas. | ||
|
||
### Event Scope | ||
|
||
This NIP defines a client-server interaction model and is scoped to the following event types only: | ||
|
||
* [NIP-17](https://github.com/nostr-protocol/nips/blob/master/17.md) - Encrypted DMs | ||
* [NIP-57](https://github.com/nostr-protocol/nips/blob/master/57.md) - Zaps | ||
* [NIP-59](https://github.com/nostr-protocol/nips/blob/master/59.md) - Gift Wraps | ||
|
||
Support for additional NIPs may be defined in future revisions but is out of scope for this specification. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. these are the most pertinent event types to support metadata privacy for end to end encrypted communications, but it seems clear that in the future other types would be appropriate to support as well how should these types be introduced in a backwards compatible way? client opt in via filtering? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Perhaps supported event types should be listed in the nip-11 settings under the ohttp key. Clients would likely want to know what event types are supported before they send any request and/or fetch the keys. |
||
|
||
Relays that receive an event outside this scope MUST return a `400 Bad Request` status in the encapsulated BHTTP response. | ||
|
||
## Open Questions | ||
|
||
* Streaming: Whether to standardize a chunked/streaming profile once IETF work stabilizes. ([ietf-wg-ohai.github.io](https://ietf-wg-ohai.github.io/draft-ohai-chunked-ohttp/draft-ietf-ohai-chunked-ohttp.html)). Subscriptions: Avoid long-lived state through OHTTP. Use poll+cursor now; consider chunked OHTTP later when code and standards stabilize. | ||
|
||
* Denial of service protection: Standardize a NIP for token issuance/redemption to control abuse while preserving unlinkability. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. should this mention the types of tokens appropriate for this? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I am leaning on just removing this section. |
||
|
||
## Reference Implementation | ||
|
||
A client reference can be found [here](https://github.com/arminsabouri/nostr/blob/ohttp-example/crates/nostr-sdk/examples/ohttp.rs). | ||
A nostr relay reference can be found [here](https://github.com/arminsabouri/nostr-rs-relay/tree/ohttp-target). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this seems to imply the OHTTP gateway will be hosted on the same interface as where the websockets is, , maybe worth mentioning that the gateway can technically be separate from the OHTTP resource as in the nostr relay's HTTP interface but still make that implication explicit?