feat(nests): adopt deployed nostrnests schema (streaming/auth/live + paired 10112)
The deployed nostrnests reference (NestsUI v2 + moq-auth + moq-rs)
emits a different on-the-wire schema than the previous EGG-01 / EGG-09
drafts and Quartz writers. Verified by reading the production
NestsUI bundle. The deployed schema is now canonical:
kind:30312 (room event)
- relay URL → ["streaming", url] (was ["endpoint", url])
- auth URL → ["auth", url] (was ["service", url])
- live status → ["status", "live"] (was "open")
- ended status → ["status", "ended"] (was "closed")
- room name → ["title", name] (was ["room", name])
kind:10112 (user MoQ-server list)
- 3-element entries: ["server", relay, auth]
- first-element name "relay" accepted as a synonym (legacy)
- 2-element entries: derive auth host by replacing leading "moq."
with "moq-auth.", or prepending "moq-auth." otherwise
Implementation
- ServiceUrlTag.TAG_NAME = "auth", LEGACY_TAG_NAME = "service"
- EndpointUrlTag.TAG_NAME = "streaming", LEGACY_TAG_NAME = "endpoint"
- StatusTag enum: PLANNED/LIVE/PRIVATE/ENDED with code "live"/"ended";
legacy "open"/"closed" still parsed on read
- NestsServersEvent: emit/read 3-element [server, relay, auth] tags
with the legacy-shape tolerances above; expose a NestsServer pair
- Account.nestsServers.flow now produces List<NestsServer> pairs
- NestsServersScreen: rewritten edit-field asks for both URLs,
recommended row shows the nostrnests pair, list rows show both URLs
- NestsScreen first-time setup writes the nostrnests pair, not a
single URL; gate the create FAB on both URLs being parseable
- CreateNestViewModel: drop the resolveServerPair hardcoded mapping —
the saved pair is now authoritative; defaults stay correct for an
empty kind-10112 list
Specs
- EGG-01 rewritten to canonical streaming/auth/live/ended with a
legacy-spelling table; example uses the real nostrnests pair
- EGG-02 references "auth" tag throughout; error taxonomy says "ended"
- EGG-09 rewritten to 3-element server tag with derivation fallback
- New nestsClient/specs/nip-53-proposed.md — a single self-contained
proposed update to upstream NIP-53 covering kind 30312 + kind 10112
with the deployed schema and the JWT-mint flow
All existing unit tests adjusted; quartz:jvmTest, amethyst:testPlayDebugUnitTest,
and nestsClient:jvmTest pass.
This commit is contained in:
+16
-12
@@ -11,15 +11,17 @@ auth sidecar (moq-auth) using a NIP-98 HTTP signature, receives a short-lived
|
||||
JWT, then opens a WebTransport session against the moq-relay carrying the JWT
|
||||
in the URL.
|
||||
|
||||
Two separate URLs are involved: the `service` tag from EGG-01 is the auth
|
||||
sidecar; the `endpoint` tag is the relay.
|
||||
Two separate URLs are involved: the `auth` tag from EGG-01 is the auth
|
||||
sidecar (HTTP/1.1 or HTTP/2 over TCP); the `streaming` tag is the moq-relay
|
||||
(WebTransport over QUIC). The deployed nostrnests reference puts these on
|
||||
different hosts — see EGG-01 §4 for why they cannot be collapsed.
|
||||
|
||||
## Wire format
|
||||
|
||||
### Step 1 — token request
|
||||
|
||||
```
|
||||
POST <service>/auth
|
||||
POST <auth>/auth
|
||||
Authorization: Nostr <base64(NIP-98 kind:27235 event JSON)>
|
||||
Content-Type: application/json; charset=utf-8
|
||||
|
||||
@@ -29,8 +31,10 @@ Content-Type: application/json; charset=utf-8
|
||||
|
||||
`<kind>` is `30312` for nests audio rooms (EGG-01).
|
||||
|
||||
The `<service>` URL is the EGG-01 `service` tag value with any trailing `/`
|
||||
stripped, then `/auth` appended literally.
|
||||
The `<auth>` URL is the EGG-01 `auth` tag value with any trailing `/`
|
||||
stripped, then `/auth` appended literally. (The same path component is
|
||||
used regardless of how the host is named — `<auth>` here is just the
|
||||
sidecar base URL, so the full request line is `POST <auth-base>/auth`.)
|
||||
|
||||
The body is a single-line UTF-8 JSON object — the server hashes the **exact
|
||||
bytes sent** to compare against NIP-98's `payload` tag, so producers MUST
|
||||
@@ -50,7 +54,7 @@ The signed kind 27235 event MUST carry exactly these tags (NIP-98 §1):
|
||||
"pubkey": "<requester pubkey hex>",
|
||||
"created_at": <unix seconds>,
|
||||
"tags": [
|
||||
["u", "<service>/auth"], // exact request URL, scheme included
|
||||
["u", "<auth>/auth"], // exact request URL, scheme included
|
||||
["method", "POST"],
|
||||
["payload", "<sha256 of request body, lowercase hex>"]
|
||||
],
|
||||
@@ -91,7 +95,7 @@ RFC 7518 §3.4). The auth sidecar MUST publish its public verification keys
|
||||
as a JWKS at:
|
||||
|
||||
```
|
||||
GET <service>/.well-known/jwks.json
|
||||
GET <auth>/.well-known/jwks.json
|
||||
```
|
||||
|
||||
The response is an `application/json` body shaped per RFC 7517:
|
||||
@@ -108,7 +112,7 @@ The response is an `application/json` body shaped per RFC 7517:
|
||||
}
|
||||
```
|
||||
|
||||
The relay (EGG-03 endpoint) MUST verify inbound JWTs against this JWKS.
|
||||
The relay (EGG-03 streaming endpoint) MUST verify inbound JWTs against this JWKS.
|
||||
Relays SHOULD cache the JWKS for at most 5 minutes so a key rotation
|
||||
propagates without requiring a relay restart. A relay that cannot reach
|
||||
the JWKS endpoint MUST refuse new sessions rather than fall through to
|
||||
@@ -120,7 +124,7 @@ the JWKS endpoint MUST refuse new sessions rather than fall through to
|
||||
:method = CONNECT
|
||||
:protocol = webtransport
|
||||
:scheme = https
|
||||
:authority = <host:port from `endpoint`>
|
||||
:authority = <host:port from `streaming`>
|
||||
:path = /<namespace>?jwt=<token>
|
||||
```
|
||||
|
||||
@@ -167,7 +171,7 @@ MAY ignore `reason` but MUST surface `error` to user-facing error toasts.
|
||||
| 401 | `wrong_method` | NIP-98 `method` tag is not `POST` |
|
||||
| 401 | `wrong_payload` | NIP-98 `payload` tag does not match sha256 of the body bytes |
|
||||
| 401 | `stale` | NIP-98 `created_at` outside the ±60 s tolerance |
|
||||
| 403 | `room_closed` | room status is `closed` (EGG-01) or `planned` (EGG-08) |
|
||||
| 403 | `room_closed` | room status is `ended` (EGG-01) or `planned` (EGG-08) |
|
||||
| 403 | `not_invited` | room status is `private` and requester is not on the allowlist |
|
||||
| 403 | `publish_forbidden` | `publish: true` requested but caller is not a speaker per EGG-07 |
|
||||
| 410 | `unknown_room` | no `kind:30312` known to the sidecar for `(host, d)` |
|
||||
@@ -177,7 +181,7 @@ MAY ignore `reason` but MUST surface `error` to user-facing error toasts.
|
||||
Receivers MUST treat unknown 4xx slugs as "fatal, do not retry" and
|
||||
unknown 5xx slugs as "transient, retry with exponential backoff".
|
||||
|
||||
The relay (EGG-03 endpoint) signals authorization failures through
|
||||
The relay (EGG-03 streaming endpoint) signals authorization failures through
|
||||
WebTransport CONNECT response codes, NOT through the auth-sidecar table:
|
||||
|
||||
| WT status | meaning |
|
||||
@@ -190,7 +194,7 @@ WebTransport CONNECT response codes, NOT through the auth-sidecar table:
|
||||
## Example
|
||||
|
||||
```
|
||||
> POST https://moq.nostrnests.com/auth
|
||||
> POST https://moq-auth.nostrnests.com/auth
|
||||
> Authorization: Nostr eyJ...kind27235...
|
||||
> Content-Type: application/json
|
||||
>
|
||||
|
||||
Reference in New Issue
Block a user