/* * NOSTR Core Library - NIP-042: Authentication of clients to relays * * Implements client authentication through signed ephemeral events */ #ifndef NIP042_H #define NIP042_H #include #include #include #include "../cjson/cJSON.h" #include "nostr_common.h" #ifdef __cplusplus extern "C" { #endif // ============================================================================= // NIP-42 CONSTANTS AND DEFINITIONS // ============================================================================= #define NOSTR_NIP42_AUTH_EVENT_KIND 22242 #define NOSTR_NIP42_DEFAULT_CHALLENGE_LENGTH 32 #define NOSTR_NIP42_DEFAULT_TIME_TOLERANCE 600 // 10 minutes in seconds #define NOSTR_NIP42_MAX_CHALLENGE_LENGTH 256 #define NOSTR_NIP42_MIN_CHALLENGE_LENGTH 16 // Authentication states for WebSocket client integration typedef enum { NOSTR_AUTH_STATE_NONE = 0, // No authentication attempted NOSTR_AUTH_STATE_CHALLENGE_RECEIVED = 1, // Challenge received from relay NOSTR_AUTH_STATE_AUTHENTICATING = 2, // AUTH event sent, waiting for OK NOSTR_AUTH_STATE_AUTHENTICATED = 3, // Successfully authenticated NOSTR_AUTH_STATE_REJECTED = 4 // Authentication rejected } nostr_auth_state_t; // Challenge storage structure typedef struct { char challenge[NOSTR_NIP42_MAX_CHALLENGE_LENGTH]; time_t received_at; int is_valid; } nostr_auth_challenge_t; // Authentication context for relay verification typedef struct { char* relay_url; char* challenge; time_t timestamp; int time_tolerance; char* pubkey_hex; } nostr_auth_context_t; // ============================================================================= // CLIENT-SIDE FUNCTIONS (for nostr clients) // ============================================================================= /** * Create NIP-42 authentication event (kind 22242) * @param challenge Challenge string received from relay * @param relay_url Relay URL (normalized) * @param private_key 32-byte private key for signing * @param timestamp Event timestamp (0 for current time) * @return cJSON event object or NULL on error */ cJSON* nostr_nip42_create_auth_event(const char* challenge, const char* relay_url, const unsigned char* private_key, time_t timestamp); /** * Create AUTH message JSON for relay communication * @param auth_event Authentication event (kind 22242) * @return JSON string for AUTH message or NULL on error (caller must free) */ char* nostr_nip42_create_auth_message(cJSON* auth_event); /** * Validate challenge string format and freshness * @param challenge Challenge string to validate * @param received_at Time when challenge was received (0 for no time check) * @param time_tolerance Maximum age in seconds (0 for default) * @return NOSTR_SUCCESS or error code */ int nostr_nip42_validate_challenge(const char* challenge, time_t received_at, int time_tolerance); /** * Parse AUTH challenge message from relay * @param message Raw message from relay * @param challenge_out Output buffer for challenge string * @param challenge_size Size of challenge buffer * @return NOSTR_SUCCESS or error code */ int nostr_nip42_parse_auth_challenge(const char* message, char* challenge_out, size_t challenge_size); // ============================================================================= // SERVER-SIDE FUNCTIONS (for relay implementations) // ============================================================================= /** * Generate cryptographically secure challenge string * @param challenge_out Output buffer for challenge (must be at least length*2+1) * @param length Desired challenge length in bytes (16-128) * @return NOSTR_SUCCESS or error code */ int nostr_nip42_generate_challenge(char* challenge_out, size_t length); /** * Verify NIP-42 authentication event * @param auth_event Authentication event to verify * @param expected_challenge Challenge that was sent to client * @param relay_url Expected relay URL * @param time_tolerance Maximum timestamp deviation in seconds * @return NOSTR_SUCCESS or error code */ int nostr_nip42_verify_auth_event(cJSON* auth_event, const char* expected_challenge, const char* relay_url, int time_tolerance); /** * Parse AUTH message from client * @param message Raw AUTH message from client * @param auth_event_out Output pointer to parsed event (caller must free) * @return NOSTR_SUCCESS or error code */ int nostr_nip42_parse_auth_message(const char* message, cJSON** auth_event_out); /** * Create "auth-required" error response * @param subscription_id Subscription ID (for CLOSED) or NULL (for OK) * @param event_id Event ID (for OK) or NULL (for CLOSED) * @param reason Human-readable reason * @return JSON string for response or NULL on error (caller must free) */ char* nostr_nip42_create_auth_required_message(const char* subscription_id, const char* event_id, const char* reason); /** * Create "restricted" error response * @param subscription_id Subscription ID (for CLOSED) or NULL (for OK) * @param event_id Event ID (for OK) or NULL (for CLOSED) * @param reason Human-readable reason * @return JSON string for response or NULL on error (caller must free) */ char* nostr_nip42_create_restricted_message(const char* subscription_id, const char* event_id, const char* reason); // ============================================================================= // URL NORMALIZATION FUNCTIONS // ============================================================================= /** * Normalize relay URL for comparison (removes trailing slashes, etc.) * @param url Original URL * @return Normalized URL string or NULL on error (caller must free) */ char* nostr_nip42_normalize_url(const char* url); /** * Check if two relay URLs match after normalization * @param url1 First URL * @param url2 Second URL * @return 1 if URLs match, 0 if they don't, -1 on error */ int nostr_nip42_urls_match(const char* url1, const char* url2); // ============================================================================= // UTILITY FUNCTIONS // ============================================================================= /** * Get string description of authentication state * @param state Authentication state * @return Human-readable string */ const char* nostr_nip42_auth_state_str(nostr_auth_state_t state); /** * Initialize authentication context structure * @param ctx Context to initialize * @param relay_url Relay URL * @param challenge Challenge string * @param time_tolerance Time tolerance in seconds * @return NOSTR_SUCCESS or error code */ int nostr_nip42_init_auth_context(nostr_auth_context_t* ctx, const char* relay_url, const char* challenge, int time_tolerance); /** * Free authentication context structure * @param ctx Context to free */ void nostr_nip42_free_auth_context(nostr_auth_context_t* ctx); /** * Validate authentication event structure (without signature verification) * @param auth_event Event to validate * @param relay_url Expected relay URL * @param challenge Expected challenge * @param time_tolerance Maximum timestamp deviation in seconds * @return NOSTR_SUCCESS or error code */ int nostr_nip42_validate_auth_event_structure(cJSON* auth_event, const char* relay_url, const char* challenge, int time_tolerance); // ============================================================================= // WEBSOCKET CLIENT INTEGRATION // ============================================================================= // Forward declaration for WebSocket client struct nostr_ws_client; /** * Authenticate WebSocket client with relay * @param client WebSocket client handle * @param private_key 32-byte private key for authentication * @param time_tolerance Maximum timestamp deviation in seconds (0 for default) * @return NOSTR_SUCCESS or error code */ int nostr_ws_authenticate(struct nostr_ws_client* client, const unsigned char* private_key, int time_tolerance); /** * Get current authentication state of WebSocket client * @param client WebSocket client handle * @return Current authentication state */ nostr_auth_state_t nostr_ws_get_auth_state(struct nostr_ws_client* client); /** * Check if WebSocket client has stored valid challenge * @param client WebSocket client handle * @return 1 if valid challenge exists, 0 otherwise */ int nostr_ws_has_valid_challenge(struct nostr_ws_client* client); /** * Get stored challenge from WebSocket client * @param client WebSocket client handle * @param challenge_out Output buffer for challenge * @param challenge_size Size of output buffer * @return NOSTR_SUCCESS or error code */ int nostr_ws_get_challenge(struct nostr_ws_client* client, char* challenge_out, size_t challenge_size); /** * Store challenge in WebSocket client (internal function) * @param client WebSocket client handle * @param challenge Challenge string to store * @return NOSTR_SUCCESS or error code */ int nostr_ws_store_challenge(struct nostr_ws_client* client, const char* challenge); /** * Clear authentication state in WebSocket client * @param client WebSocket client handle * @return NOSTR_SUCCESS or error code */ int nostr_ws_clear_auth_state(struct nostr_ws_client* client); #ifdef __cplusplus } #endif #endif // NIP042_H