# NIP-42 Authentication Implementation ## Overview This relay implements NIP-42 (Authentication of clients to relays) providing granular authentication controls for event submission and subscription operations. The implementation supports both challenge-response authentication and per-connection state management. ## Architecture ### Core Components 1. **Per-Session Authentication State** (`struct per_session_data`) - `authenticated`: Boolean flag indicating authentication status - `authenticated_pubkey[65]`: Hex-encoded public key of authenticated user - `active_challenge[65]`: Current authentication challenge - `challenge_created`: Timestamp when challenge was generated - `challenge_expires`: Challenge expiration timestamp - `nip42_auth_required_events`: Whether auth is required for EVENT submission - `nip42_auth_required_subscriptions`: Whether auth is required for REQ operations - `auth_challenge_sent`: Flag indicating if challenge has been sent 2. **Challenge Management** (via `request_validator.c`) - `nostr_nip42_generate_challenge()`: Generates cryptographically secure challenges - `nostr_nip42_verify_auth_event()`: Validates signed authentication events - Challenge storage and cleanup with expiration handling 3. **WebSocket Protocol Integration** - AUTH message handling in `nostr_relay_callback()` - Challenge generation and transmission - Authentication verification and session state updates ## Configuration Options ### Event-Based Configuration NIP-42 authentication is configured using kind 33334 configuration events with the following tags: | Tag | Description | Default | Values | |-----|-------------|---------|--------| | `nip42_auth_required_events` | Require auth for EVENT submission | `false` | `true`/`false` | | `nip42_auth_required_subscriptions` | Require auth for REQ operations | `false` | `true`/`false` | ### Example Configuration Event ```json { "kind": 33334, "content": "C Nostr Relay Configuration", "tags": [ ["d", ""], ["nip42_auth_required_events", "true"], ["nip42_auth_required_subscriptions", "false"], ["relay_description", "Authenticated Nostr Relay"] ], "created_at": 1640995200, "pubkey": "", "id": "", "sig": "" } ``` ## Authentication Flow ### 1. Challenge Generation When authentication is required and client is not authenticated: ``` Client -> Relay: ["EVENT", ] (unauthenticated) Relay -> Client: ["AUTH", ] ``` The challenge is a 64-character hex string generated using cryptographically secure random numbers. ### 2. Authentication Response Client creates and signs an authentication event (kind 22242): ```json { "kind": 22242, "content": "", "tags": [ ["relay", "ws://relay.example.com"], ["challenge", ""] ], "created_at": , "pubkey": "", "id": "", "sig": "" } ``` Client sends this event back to relay: ``` Client -> Relay: ["AUTH", ] ``` ### 3. Verification and Session Update The relay: 1. Validates the authentication event signature 2. Verifies the challenge matches the one sent 3. Checks challenge expiration (default: 10 minutes) 4. Updates session state with authenticated public key 5. Sends confirmation notice ``` Relay -> Client: ["NOTICE", "NIP-42 authentication successful"] ``` ## Granular Authentication Controls ### Separate Controls for Events vs Subscriptions The implementation provides separate authentication requirements: - **Event Submission**: Control whether clients must authenticate to publish events - **Subscription Access**: Control whether clients must authenticate to create subscriptions This allows flexible relay policies: - **Public Read, Authenticated Write**: `events=true, subscriptions=false` - **Fully Authenticated**: `events=true, subscriptions=true` - **Public Access**: `events=false, subscriptions=false` (default) - **Authenticated Read Only**: `events=false, subscriptions=true` ### Per-Connection State Each WebSocket connection maintains its own authentication state: - Authentication persists for the lifetime of the connection - Challenges expire after 10 minutes - Session cleanup on connection close ## Security Features ### Challenge Security - 64-character hexadecimal challenges (256 bits of entropy) - Cryptographically secure random generation - Challenge expiration to prevent replay attacks - One-time use challenges ### Event Validation - Complete signature verification using secp256k1 - Event ID validation - Challenge-response binding verification - Timestamp validation with configurable tolerance ### Session Management - Thread-safe per-session state management - Automatic cleanup on disconnection - Challenge expiration handling ## Client Integration ### Using nak Client ```bash # Generate keypair PRIVKEY=$(nak key --gen) PUBKEY=$(nak key --pub $PRIVKEY) # Connect and authenticate automatically nak event -k 1 --content "Authenticated message" --sec $PRIVKEY --relay ws://localhost:8888 # nak handles NIP-42 authentication automatically when required ``` ### Manual WebSocket Integration ```javascript const ws = new WebSocket('ws://localhost:8888'); ws.onmessage = (event) => { const message = JSON.parse(event.data); if (message[0] === 'AUTH') { const challenge = message[1]; // Create auth event (kind 22242) const authEvent = { kind: 22242, content: "", tags: [ ["relay", "ws://localhost:8888"], ["challenge", challenge] ], created_at: Math.floor(Date.now() / 1000), pubkey: clientPubkey, // ... calculate id and signature }; // Send auth response ws.send(JSON.stringify(["AUTH", authEvent])); } }; // Send event (may trigger AUTH challenge) ws.send(JSON.stringify(["EVENT", myEvent])); ``` ## Administration ### Enabling Authentication 1. **Get Admin Private Key**: Extract from relay startup logs (shown once) 2. **Create Configuration Event**: Use nak or custom tooling 3. **Publish Configuration**: Send to relay with admin signature ```bash # Enable auth for events only nak event -k 33334 \ --content "C Nostr Relay Configuration" \ --tag "d=$RELAY_PUBKEY" \ --tag "nip42_auth_required_events=true" \ --tag "nip42_auth_required_subscriptions=false" \ --sec $ADMIN_PRIVKEY \ --relay ws://localhost:8888 ``` ### Monitoring Authentication - Check relay logs for authentication events - Monitor `NOTICE` messages for auth status - Use `get_settings.sh` script to view current configuration ```bash ./get_settings.sh ``` ## Troubleshooting ### Common Issues 1. **Challenge Expiration** - Default: 10 minutes - Client must respond within expiration window - Generate new challenge for expired attempts 2. **Signature Verification Failures** - Verify event structure matches NIP-42 specification - Check challenge value matches exactly - Ensure proper secp256k1 signature generation 3. **Configuration Not Applied** - Verify admin private key is correct - Check configuration event signature - Ensure relay pubkey in 'd' tag matches relay ### Debug Commands ```bash # Check supported NIPs curl -H "Accept: application/nostr+json" http://localhost:8888 | jq .supported_nips # View current configuration nak req -k 33334 ws://localhost:8888 | jq . # Test authentication flow ./tests/42_nip_test.sh ``` ## Performance Considerations - Challenge generation: ~1ms overhead per unauthenticated connection - Authentication verification: ~2-5ms per auth event - Memory overhead: ~200 bytes per connection for auth state - Database impact: Configuration events cached, minimal query overhead ## Integration with Other NIPs ### NIP-01 (Basic Protocol) - AUTH messages integrated into standard WebSocket flow - Compatible with existing EVENT/REQ/CLOSE message handling ### NIP-11 (Relay Information) - NIP-42 advertised in `supported_nips` array - Authentication requirements reflected in relay metadata ### NIP-20 (Command Results) - OK responses include authentication-related error messages - NOTICE messages provide authentication status updates ## Future Extensions ### Potential Enhancements - Role-based authentication (admin, user, read-only) - Time-based access controls - Rate limiting based on authentication status - Integration with external authentication providers ### Configuration Extensions - Per-kind authentication requirements - Whitelist/blacklist integration - Custom challenge expiration times - Authentication logging and metrics