6.6 KiB
HTTP Messages
A specification for sending/receiving HTTP messages (request/response) via a remote server. Header / Body etc are encrypted, either in the content
(small messages) or via a blossom server (larger requests).
Overview
Enables a local client to make and receive http requests (PUT, POST, GET, PATCH etc) from a remote computer. Requires:
- A trusted machine to process the messages (can be a home PC or Raspberry Pi)
- A relay (can be untrusted)
- A blossom server (can be untrusted)
Architecture
Sequence Diagram
The remote server should periodically scan for expired RESPONSE events (and associated blossom blobs) and delete them.
Server Advertisement Event (Kind 31120)
To facilitate discovery of HTTP-over-Nostr servers, a dedicated event kind is used to advertise server availability.
{
"kind": 31120,
"pubkey": "<pubkey of server operator>",
"content": "HTTP-over-Nostr server", // Optional markdown description of the http server(s)
"tags": [
["d", "<hex pubkey of server>"], // Server pubkey that will be listening for requests
["relay", "wss://relay.one"], // Relay where server is listening (can have multiple)
["relay", "wss://relay.two"],
["expiry", "<unix timestamp>"], // How long this server will be online
],
// other fields...
}
Explanations:
kind:31120
- BIP39 word #1120 (message) plus 30000 to make it addressable."content"
- Optional description of the server in markdown"d"
- The hex pubkey of the HTTP server that will be processing requests"relay"
- Relays where this server is listening for kind 21120 events (can have multiple)"expiry"
- Timestamp after which this server advertisement should be considered expired. Can update this to 0 to indicate an expired event.
Clients looking to use HTTP over Nostr can query for these kind 31120 events to discover available servers and may communicate with the server operator to get permission to use them.
HTTP Requests - Kind 21120
Example request with a small payload. Payload is in content
and P
tag is the npub of the remote HTTP server.
{
"kind": 21120,
"pubkey": "<pubkey>",
"content": "$encryptedPayload",
"tags": [
["p", "<pubkey of remote server>"], // P tag entry, this is a REQUEST
["key","nip44Encrypt($decryptkey)"],
["r", "https://relay.one"],
["expiration",<unix timestamp>]
],
// other fields...
}
Explanations:
kind:21120
- BIP39 word #1120 (message), plus 20,000 to be treated as ephemeral (not stored by relays)."content"
- encrypted JSON with location of blob OR the content itself (if under a threshold). NIP-44 is NOT used as the payload may be large, affecting bunker signing stability."p"
- the pubkey of the remote HTTP server."key"
- the decryption key for thecontent
field, also the key for the blossom blob (if used)."expiration"
- remote servers should not process requests after this time. Relays SHOULD delete events after this time."r"
- (optional) relay on which the response should be sent.
HTTP Response - Kind 21121
Example response with a large payload. Valid JSON is in content
and E
tag is populated. For privacy, the requestor npub is NOT shown - the requestor instead should be fetching the response using the E
tag.
{
"kind": 21121,
"pubkey": "<pubkey>",
"content": "encrypt({'url':'blossom.one','hash':'xx'},$decryptkey)",
"tags": [
["key","nip44Encrypt($decryptkey)"],
["E", "<request event id>"], // E tag entry, this is a RESPONSE
["expiration",<unix timestamp>]
],
// other fields...
}
Explanations:
kind:21121
- A different kind is used for responses, to help with filtering and other use cases."content"
- encrypted JSON with location of blob OR the content itself (if under a threshold). NIP-44 is NOT used as the payload may be large, affecting bunker signing stability."key"
- the decryption key for thecontent
field, also the key for the blossom blob (if used)."E"
- ID of the request event. Enables a response to be identified, and fetched."expiration"
- remote servers should not process requests after this time. Relays SHOULD delete events after this time.
There is no "p" tag as the "E" tag already identifies the request.
Considerations
This approach only makes sense in cases where privacy and anonymity are important, or if censorship is a concern.
There are a number of drawbacks to the approach:
- Complexity. Many more moving parts than a direct request.
- Speed. Each request/response now requires:
- Encrypting the payload
- Loading to blossom (if large)
- Signing event
- broadcasting event
- remote server fetching event
- remote server decrypting event
- remote server fetching blossom blob (if large)
- remote server decrypting blossom blob (if used)
- making the actual request
And the same flow in reverse.
So why would you use it? Several reasons:
- enables a plethora of open source apps to be made available from inside private networks (localhost) but over nostr
- maximum server privacy (no domain needed, or port forwarding)
- Make regular API calls over Nostr