Skip to content

Conversation

arminsabouri
Copy link

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 encapsulated with an ephemeral HPKE key pair and routed via an OHTTP relay as per RFC 9458. The target relay processes the (decapsulated) request, decrypts the payload, and returns a response; the OHTTP relay cannot read contents, and the target relay cannot see client metadata.

Drafting this for now: couple more details to iron out in the spec.

@arminsabouri arminsabouri marked this pull request as draft October 15, 2025 20:37
@arminsabouri arminsabouri changed the title Add Ohttp nostr nip Nostr relays as OHTTP targets Oct 15, 2025

### 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.

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?

Comment on lines +71 to +76
#### 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.

Choose a reason for hiding this comment

The 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

ohttp-nostr.md Outdated
* 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.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ndjson implies all available events matching the filter will be returned, but this is not clear

* [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.

Choose a reason for hiding this comment

The 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?

Copy link
Author

Choose a reason for hiding this comment

The 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.


* 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.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should this mention the types of tokens appropriate for this?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am leaning on just removing this section.

@fiatjaf
Copy link
Member

fiatjaf commented Oct 16, 2025

This is cool but looks like it's completely unrelated to Nostr.

@vitorpamplona
Copy link
Collaborator

This looks really cool.

I am just not sure what the cryptography is doing that a regular gift wrap to the relays nostr pubkeys wouldn't do. Do we really need new crypto primitives for this? We try to avoid having clients and relays implement a bunch of different crypto primitives to simplify people's lives.

Amethyst sends all events via Tor to achieve a similar goal. But it would be nice if we had our own protocol for this, since Tor is so bloated. Ideally, the client could choose a list of these types of proxy servers and scatter events throughout them. They would push to the right locations without sharing the user's IP.

However:

  1. Many relays require AUTH with the user's own keys (like a web of trust relay). We might need to find a way to auth in a ring signature of sorts such that the relay knows the user is one of the authorized accounts, but doen't know which one is it.
  2. It should work in the opposing direction too. When the client needs to download events from random relays, the flow can go through a proxy to avoid sharing the user's IP/npub.

@arminsabouri
Copy link
Author

Thanks for the feedback @vitorpamplona

I am just not sure what the cryptography is doing that a regular gift wrap to the relays nostr pubkeys wouldn't do.

Hpke encrypts the payload s.t the OHTTP relay cannot read the contents of the clients requests. i.e the OHTTP relay wouldn't know if you are sending a giftwrap or a zap -- they only see a fixed length encrypted blob.

Do we really need new crypto primitives for this? We try to avoid having clients and relays implement a bunch of different crypto primitives to simplify people's lives.

We chose crypto primitives already used by clients for NIP-44 DMs. The only exception is the MAC in the AEAD. The current document recommends Poly1305 because BIP-77 uses it, but since that introduces a new dependency for existing clients, we should replace it with HMAC-SHA256.

@vitorpamplona
Copy link
Collaborator

Hpke encrypts the payload s.t the OHTTP relay cannot read the contents of the clients requests. i.e the OHTTP relay wouldn't know if you are sending a giftwrap or a zap -- they only see a fixed length encrypted blob.

Sure, but GiftWrap is already an encryption that blocks the viewer of the wrap from seeing event content. I am not sure what this is adding on top of it.

We chose crypto primitives already used by clients for NIP-44 DMs.

Why not just using NIP-44 directly?

@nothingmuch
Copy link

nothingmuch commented Oct 17, 2025

Why not just using NIP-44 directly?

OHTTP is an IETF standard, and that uses HPKE for encryption, which is very similar to but not compatible with NIP 44 even with the same cipher suite. The main differences is OHTTP supports multiple keys and cipher suite specification is done using this mechanism, as opposed to NIP 44's versions, and more importantly HPKE supports multiple messages from the same KEM shared secret. This is used in OHTTP for encrypting the response without another KEM from the server to the client for the response.

Sure, but GiftWrap is already an encryption that blocks the viewer of the wrap from seeing event content. I am not sure what this is adding on top of it.

Gift wrapping addresses data encryption. It does not encrypt event metadata that is on the external event, and does not protect IP level metadata privacy, making it possible for the nostr relay to link these pieces of information to each other. In order to allow privacy sensitive protocols (such as payjoin) to use nostr to communicate without needing to trust the nostr relays, something like this is needed.

OHTTP at the transport layer and NIP 44 for encrypted payloads is not unlike having a 2 layer onion encryption, with the nostr relay as another hop without a layer of onion encryption associated with it.

Using Tor of course achieves a similar goal, the Tor relays can't see the nostr metadata (or cleartext payloads) that the nostr relay can. The reason for OHTTP complements Tor is that Tor is connection based, and has a much larger time to first byte if not already connected. In exchange Tor offers stronger privacy assurances than OHTTP due to 3 hops, but still does not address global passive adversary doing traffic analysis.

To connect to a clearnet address via tor, ignoring tor's bootstrap, a client must first connect to a guard node over TCP and TLS, which is 3 single hop round trip times (assuming all hops have the same latency), then it needs to build a circuit starting with to the second relay, that's 1 RTT to tell the first hop to create a stream, and then an end to end round trip for the handshaking, with each e2e round trip taking 2 round trips, so 3 more round trip times, and this is assuming the guard and 2nd relay already have a connection (otherwise there's 3 more round trip times for that). Then to the 3rd hop, which which requires an end to end round trip to the 2nd relay, 2 RTTs, an end to end round trip to handshake with the 3rd relay for 3 (or 5) more. Finally a 3 hop round trip to tell the 3rd relay to establish a clearnet connection to the target, 1 RTT for SYN/ACK between them, and then two 4 hop round trips for TLS, and one more 4 hop round trip for the request/response. This comes to 3 + (1+2) + (2+3) + (3+1+4+4) = 23 [edit: oops, 27 not 23, last hop should be (3+1+8+4)) RTTs minimum, if all relays are already connected to each other. This gets worse if accounting for bootstrap, or connecting to a hidden service (which requires two circuits and has end to end latency of 6 hops once established). After a circuit is built the marginal cost of an additional stream (TCP connection from exit node or virtual stream to hidden service) is minimal, so this is primarily an issue when opening connections. This is a lot to ask of mobile clients on a limited time budget, unless an app keeps the screen on. Tor daemon also does not handle failures during this process particularly well, due to implementation reasons and the limitation of the SOCKS interface. Incidentally, from stuff I've heard about arti for bitchat it seems that it handles such failures much more gracefully, but the 23+ round trips for first request are inherent in its design.

In contrast OHTTP, similarly assuming the OHTTP relay is already connected to the OHTTP gateway (which should be more likely than a random 2nd/3rd hop Tor relay) this requires requires 3 RTTs for the client to connect to the relay with TLS if using HTTP 1.1 or HTTP 2, or 2 or even 1 if QUIC is used. It's then a 2 hop end to end round trip to send the request and receive the response, so only 3-5 IP level RTTs for 1 end to end request/response round trip (assuming the OHTTP gateway and the OHTTP target are on the same host).

Being request/response oriented instead of connection oriented also makes it possible to limit nostr relays ability to profile clients based on the nostr level metadata available to them. This is still a concern with nostr over Tor. For example filters of the same client are only temporally correlated by default if requested over OHTTP, whereas over via NIP-01 connections they are be unequivocally linked unless two isolated connections are used. Enabling these more privacy preserving access patterns and hiding the IP level metadata would also make it harder for nostr relays to perform statistical disclosure attacks or other metadata based attacks on privacy that attempt to expose the communication patterns of different users. OHTTP makes it possible to use such access patterns while maintaining most of the benefits in responsiveness of a connection based approach, since the client to OHTTP relay and OHTTP relay to nostr relay connections can be long lived, the latter just multiplexes individually encrypted requests & responses from/to multiple clients.

@nothingmuch
Copy link

  1. Many relays require AUTH with the user's own keys (like a web of trust relay). We might need to find a way to auth in a ring signature of sorts such that the relay knows the user is one of the authorized accounts, but doen't know which one is it.

This is better addressed by anonymous credentials supporting unlinkable multi show. This would also be suitable for anonymous rate limiting as briefly mentioned in the doc. However, that would require a separate NIP IMO

  1. It should work in the opposing direction too. When the client needs to download events from random relays, the flow can go through a proxy to avoid sharing the user's IP/npub.

This proposal is designed to address that, in addition to posting events there is also something that could be described as one shot or ephemeral subscriptions servicing a single REQ

@vitorpamplona
Copy link
Collaborator

OHTTP is an IETF standard

That's probably more of a negative than a positive. :) We don't need to be compatible with it.

Gift wrapping addresses data encryption. It does not encrypt event metadata that is on the external event,

It doesn't need to. There is nothing there, just the address (npub) of the relay the message is going to.

and does not protect IP level metadata privacy,

That is true. But giftwrapping is not a transport-level protocol. What I wonder is if we could use an OHTTP-like server with a simpler GiftWrap message, as we already do. There is def nothing to gain when encrypting a giftwrap again. The keyconfig param then could just be the relay's own npub.

This proposal is designed to address that, in addition to posting events there is also something that could be described as one shot or ephemeral subscriptions servicing a single REQ

Does that mean that the protocol allows me to hold a subscription live for minutes or hours, waiting for events from a REQ?

The weird part is having to map all WebSocket commands into HTTP calls and accept multiple REQs that can switch filters in under a second while also keeping listening forever.

@nothingmuch
Copy link

nothingmuch commented Oct 17, 2025

OHTTP is an IETF standard

That's probably more of a negative than a positive. :) We don't need to be compatible with it.

There's good reasons to support OHTTP, it's becoming more widely adopted by large companies for DoH etc (albeit with different cipher suites), BIP 77 uses it and recently CDK mints and an esplora have seen proofs of concept to. This means a single set of OHTTP relays can provide a shared infrastructure for a larger and more diverse userbase, which is important for privacy.

It doesn't need to. There is nothing there, just the address (npub) of the relay the message is going to.
...
That is true. But giftwrapping is not a transport-level protocol. What I wonder is if we could use an OHTTP-like server with a simpler GiftWrap message, as we already do. There is def nothing to gain when encrypting a giftwrap again. The keyconfig param then could just be the relay's own npub.

If only addressing posting events, then yes giftwrapping achieves this unlinking.

Does that mean that the protocol allows me to hold a subscription live for minutes or hours, waiting for events from a REQ?

Not as implemented/specced, but that's what OHTTP streaming support would enable.

The weird part is having to map all WebSocket commands into HTTP calls and accept multiple REQs that can switch filters in under a second while also keeping listening forever.

One of the design goals was to move away from multiple REQs for subscriptions tied to the same connection. That inherently requires making an efficiency tradeoff that linking REQs can avoid, but that's already supported with NIP 01. Protecting transport level metadata for such connections is relatively straightforward with a connection oriented proxy (tcp or websockets based) if that's not a concern.

A non-OHTTP based approach might be to define a way of doing REQs etc encapsulated within nostr events, and use giftwrapping to support that kind of indirection for privacy reasons. This would still require shoehorning the connection oriented approach to a message based one (edit: with the additional complexity of buffering such events). It would also more naturally support async usage, making it possible to build a true mixnet.

@vitorpamplona
Copy link
Collaborator

vitorpamplona commented Oct 17, 2025

Not as implemented/specced, but that's what OHTTP streaming support would enable.

This PR will need to define them all.

One of the design goals was to move away from multiple REQs for subscriptions tied to the same connection

Well, there will always be multiple REQs from the client to the OHTTP server, and each REQ ID can then be updated multiple times with new filters. So whatever was processing the past REQ in the relay needs to be stopped and restarted with the new filters. The OHTTP will need to manage multiple GETs with rotating encrypted payloads, a few times per second.

The current version of Amethyst, for instance, will require a single OHTTP server to connect to 400-1000 different relays for each phone and manage between 5-25 live subscriptions (listening forever / until the app goes to the background) for each of those relays.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants