feat(relay): NIP-OA agent authentication for NIP-43 membership (WS + REST + git)#471
Merged
tlongwell-block merged 3 commits intomainfrom May 4, 2026
Merged
feat(relay): NIP-OA agent authentication for NIP-43 membership (WS + REST + git)#471tlongwell-block merged 3 commits intomainfrom
tlongwell-block merged 3 commits intomainfrom
Conversation
Allow agents bearing a valid NIP-OA auth tag to authenticate to NIP-43-enforcing relays. The relay verifies the agent's NIP-42 AUTH first, then verifies the embedded NIP-OA tag, and checks the *owner's* pubkey for relay membership instead of the agent's. Key design: - Gated by SPROUT_ALLOW_NIP_OA_AUTH=true (default: false) - Agent gets session-scoped access (no persistent member record) - AuthContext carries owner_pubkey for downstream attribution - Rejects events with multiple auth tags (spec: exactly 0 or 1) - Fail-closed on verification errors - WebSocket path only (REST/audio/git are future work) Files changed: - sprout-auth: AuthMethod::Nip42OwnerAttestation + owner_pubkey field - sprout-relay/config: allow_nip_oa_auth config flag - sprout-relay/handlers/auth: NIP-OA detection, verification, membership routing - sprout-relay/Cargo.toml: sprout-sdk dependency
Thread the auth_tag through the NIP-42 AUTH response path in both sprout-mcp and sprout-acp. Previously, the auth tag was only injected into regular events (kind:9, etc.) via sign_event(), but the relay-side NIP-OA verification looks for it on the kind:22242 AUTH event itself. Without this fix, agents with NIP-OA credentials would pass NIP-42 verification but the relay would never find the auth tag to verify owner attestation against. Changes: - sprout-mcp: thread auth_tag through build_auth_event → do_connect → send_auth_response → handle_ws_message → run_background_task - sprout-acp: parse SPROUT_AUTH_TAG at connect time, thread through send_auth_response and all reconnection paths
f592387 to
9f1abb7
Compare
The original NIP-OA implementation only covered the WebSocket NIP-42 auth path. Agents could authenticate on the WS but HTTP API calls (channel discovery, presence, profiles, git) would 403 because the REST membership gate had no NIP-OA awareness. This commit extends NIP-OA to all HTTP endpoints via the X-Auth-Tag header. Agents send their NIP-OA tag with every HTTP request; the relay verifies the tag cryptographically and checks the owner's membership — same logic as the WS path, just triggered by a header instead of a WS event tag. Design: - Stateless per-request verification (no session coupling) - X-Auth-Tag header carries the full JSON auth tag - enforce_relay_membership gains NIP-OA fallback: if agent isn't a direct member AND allow_nip_oa_auth is enabled AND header is present → verify tag → check owner membership - Duplicate X-Auth-Tag headers rejected (400) — mirrors WS path's "exactly 0 or 1 auth tags" rule - Gated behind existing SPROUT_ALLOW_NIP_OA_AUTH=true flag - Fail-closed on verification errors Changes: - sprout-relay/api/relay_members.rs: NIP-OA fallback in enforce_relay_membership - sprout-relay/api/mod.rs: extract_single_auth_tag helper - sprout-relay/api/git/transport.rs: X-Auth-Tag for git NIP-98 path - sprout-relay/api/media.rs: X-Auth-Tag for media upload path - sprout-relay/api/tokens.rs: X-Auth-Tag for token minting path - sprout-relay/audio/handler.rs: pass None (audio is WS-only) - sprout-acp/relay.rs: apply_auth_with_tag, RestClient.auth_tag field - sprout-mcp/relay_client.rs: apply_auth sends X-Auth-Tag - sprout-sdk/examples/compute_auth_tag.rs: utility for generating auth tags
9f1abb7 to
75e6954
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Allow NIP-OA bearing agents to authenticate to relays that enforce NIP-43 membership — across all transport paths: WebSocket, REST API, and git.
The relay verifies the agent's NIP-42 AUTH (WS) or
X-Auth-Tagheader (HTTP), cryptographically verifies the owner attestation, and checks the owner's pubkey for relay membership — granting the agent access without a persistent member record.How it works
WebSocket (NIP-42)
REST / Git (X-Auth-Tag header)
Changes
sprout-auth/src/lib.rsAuthMethod::Nip42OwnerAttestationvariant +owner_pubkeyfieldsprout-relay/src/config.rsallow_nip_oa_authconfig flag (SPROUT_ALLOW_NIP_OA_AUTH=true)sprout-relay/src/handlers/auth.rssprout-relay/src/api/relay_members.rsenforce_relay_membershipsprout-relay/src/api/mod.rsextract_single_auth_taghelper (rejects duplicates)sprout-relay/src/api/git/transport.rssprout-relay/src/api/media.rssprout-relay/src/api/tokens.rssprout-relay/src/audio/handler.rsNone(WS-only, no HTTP headers)sprout-acp/src/relay.rsapply_auth_with_tag,RestClient.auth_tagfieldsprout-mcp/src/relay_client.rsapply_authsends X-Auth-Tagsprout-sdk/examples/compute_auth_tag.rssprout-relay/Cargo.tomlsprout-sdkdependencyDesign decisions
SPROUT_ALLOW_NIP_OA_AUTH=true.X-Auth-Tagheaders (400).AuthContext.owner_pubkeycarries the owner for downstream audit/rate-limiting.E2E Testing
Tested with agents that are NOT relay members, authenticated purely via NIP-OA:
Future work