You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
External HTTP .well-known/did/nostr resolver (configured URL, e.g. nostr.social) — cross-pod, but trusts the indexer
Fallback to did:nostr:<pubkey> as the agent identity
Step 3 is the only "cross-pod" path today, and it relies on a single configured HTTP indexer. That's a centralization point: every JSS deployment delegates global resolution to whoever runs that indexer.
The proposal
Insert a direct Nostr relay resolution step between today's #2 (local index) and #3 (HTTP indexer):
2.5 Relay-based resolution. Connect to a configured set of Nostr relays, query for the user's DID-metadata replaceable event (kind defined by the did:nostr spec), verify the event signature, parse content as the DID document, and extract alsoKnownAs[0] as the candidate WebID. Then run the same backlink check the other steps use (does the WebID profile declare this pubkey via CID-VM?).
This is what makes did:nostr genuinely decentralized. The pubkey-to-DID-doc binding is verifiable from the Schnorr signature on the relay event alone — no trusted indexer in the loop. The user controls publication via their own relays.
Why this matters
Without this step:
Every JSS deployment depends on nostr.social (or whoever is configured) staying online + honest
A user changing pods mid-session has to wait for the indexer to re-scrape
The alsoKnownAs claim could be silently rewritten by a compromised/malicious indexer
With it:
JSS reads the user's DID doc straight from relays the user chose
Indexers (nostr.social, nostr.rocks) become one path among many — useful as an HTTP cache layer, not the source of truth
A user who runs their own relay can be fully self-sovereign — no third party in the auth path
Implementation sketch
Relay client: NDK or @nostr/client already in the dependency tree from elsewhere — pick whichever is lightest. Subscribe pattern, not long-lived.
Default relay set: configurable via JSS_NOSTR_RELAYS env (comma-separated). Default to a small set of public relays (relay.damus.io, nos.lol).
Per-user relay hint: optional NIP-05 lookup on the WebID's domain to find user-preferred relays before falling back to defaults.
DID-metadata event kind: per the did:nostr spec (likely a replaceable kind in the 30000–39999 range — confirm against current spec draft).
Replaceable-event race: pick the event with the highest created_at across responding relays.
Caching: same TTL semantics as the current did-nostr.js cache (5m success, 1m transient failure), keyed on pubkey + relay-set-fingerprint.
Unit tests for resolveDidNostrViaRelays with a mocked relay client (success, no event, signature mismatch, replaceable-event ordering)
Integration test against relay.damus.io (or a local relay) with a published test DID-metadata event
Confirm the chain order: a known-local user still resolves via step 2 without ever hitting the relays
Confirm the HTTP indexer step still fires for users without published DID-metadata events on the configured relay set
Related deferred work
Real-time updates on the local index when the LDP write path mutates a profile (mentioned in auth: serve /.well-known/did/nostr/<pubkey>.json (#407) #408 description) — relay-based resolution makes the local index less load-bearing for cross-pod identities, but the same hook would benefit both
Refs #42 (did:nostr resolution spec), #4 (NIP-98 SSO), #386 (LWS 1.0 alignment). Builds on #408 (HTTP
.well-knownendpoint) and #411 (subdomain-mode local index).Where we are today
src/auth/nostr.js'sverifyNostrAuthresolves a Nostr pubkey to a WebID via a 4-step chain:<DATA_ROOT>/.idp/accounts/_webid_index.json(auth: serve /.well-known/did/nostr/<pubkey>.json so the pod is its own DID resolver #407 / well-known did:nostr index doesn't find subdomain-mode pods #411 — same-pod users).well-known/did/nostrresolver (configured URL, e.g.nostr.social) — cross-pod, but trusts the indexerdid:nostr:<pubkey>as the agent identityStep 3 is the only "cross-pod" path today, and it relies on a single configured HTTP indexer. That's a centralization point: every JSS deployment delegates global resolution to whoever runs that indexer.
The proposal
Insert a direct Nostr relay resolution step between today's #2 (local index) and #3 (HTTP indexer):
This is what makes did:nostr genuinely decentralized. The pubkey-to-DID-doc binding is verifiable from the Schnorr signature on the relay event alone — no trusted indexer in the loop. The user controls publication via their own relays.
Why this matters
Without this step:
nostr.social(or whoever is configured) staying online + honestalsoKnownAsclaim could be silently rewritten by a compromised/malicious indexerWith it:
nostr.social,nostr.rocks) become one path among many — useful as an HTTP cache layer, not the source of truthImplementation sketch
@nostr/clientalready in the dependency tree from elsewhere — pick whichever is lightest. Subscribe pattern, not long-lived.JSS_NOSTR_RELAYSenv (comma-separated). Default to a small set of public relays (relay.damus.io,nos.lol).30000–39999range — confirm against current spec draft).created_atacross responding relays.did-nostr.jscache (5m success, 1m transient failure), keyed onpubkey + relay-set-fingerprint.alsoKnownAsbacklink check (reuses existingcheckCidVmBacklinkfrom auth: serve /.well-known/did/nostr/<pubkey>.json (#407) #408).Insert into
verifyNostrAuth's chain insrc/auth/nostr.js:Test plan
resolveDidNostrViaRelayswith a mocked relay client (success, no event, signature mismatch, replaceable-event ordering)relay.damus.io(or a local relay) with a published test DID-metadata eventRelated deferred work
didResolverdiscovery (mentioned in auth: serve /.well-known/did/nostr/<pubkey>.json (#407) #408) — overlaps with the "user-preferred relays" hint aboveRefs
.well-knownand relay-based pathssrc/auth/nostr.js(verifyNostrAuth)src/auth/did-nostr.js(resolveDidNostrToWebId, with SSRF + redirect hardening from auth: serve /.well-known/did/nostr/<pubkey>.json (#407) #408)