Updated synchronous relay queries to handle nip42.

This commit is contained in:
2025-09-30 12:16:23 -04:00
parent 9a63550863
commit 0d910ca181
8 changed files with 1086 additions and 98 deletions

View File

@@ -13,7 +13,7 @@
#define _GNU_SOURCE
#define _POSIX_C_SOURCE 200809L
#include "nostr_common.h"
#include "nostr_core.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -26,6 +26,9 @@
// cJSON for JSON handling
#include "../cjson/cJSON.h"
// NIP-42 Authentication
#include "nip042.h"
// =============================================================================
// TYPE DEFINITIONS FOR SYNCHRONOUS RELAY QUERIES
// =============================================================================
@@ -51,6 +54,12 @@ typedef struct {
cJSON** events; // Array of events from this relay
int events_capacity; // Allocated capacity
char subscription_id[32]; // Unique subscription ID
// NIP-42 Authentication fields
nostr_auth_state_t auth_state; // Current authentication state
char auth_challenge[NOSTR_NIP42_MAX_CHALLENGE_LENGTH]; // Stored challenge
time_t auth_challenge_time; // When challenge was received
int nip42_enabled; // Whether NIP-42 is enabled for this relay
} relay_connection_t;
@@ -65,7 +74,9 @@ cJSON** synchronous_query_relays_with_progress(
int* result_count,
int relay_timeout_seconds,
relay_progress_callback_t callback,
void* user_data) {
void* user_data,
int nip42_enabled,
const unsigned char* private_key) {
if (!relay_urls || relay_count <= 0 || !filter || !result_count) {
if (result_count) *result_count = 0;
@@ -95,11 +106,17 @@ cJSON** synchronous_query_relays_with_progress(
relays[i].last_activity = start_time;
relays[i].events_capacity = 10;
relays[i].events = malloc(relays[i].events_capacity * sizeof(cJSON*));
// Initialize NIP-42 authentication fields
relays[i].auth_state = NOSTR_AUTH_STATE_NONE;
memset(relays[i].auth_challenge, 0, sizeof(relays[i].auth_challenge));
relays[i].auth_challenge_time = 0;
relays[i].nip42_enabled = nip42_enabled;
// Generate unique subscription ID
snprintf(relays[i].subscription_id, sizeof(relays[i].subscription_id),
snprintf(relays[i].subscription_id, sizeof(relays[i].subscription_id),
"sync_%d_%ld", i, start_time);
if (callback) {
callback(relays[i].url, "connecting", NULL, 0, relay_count, 0, user_data);
}
@@ -191,19 +208,50 @@ cJSON** synchronous_query_relays_with_progress(
cJSON* parsed = NULL;
if (nostr_parse_relay_message(buffer, &msg_type, &parsed) == 0) {
if (msg_type && strcmp(msg_type, "EVENT") == 0) {
if (msg_type && strcmp(msg_type, "AUTH") == 0) {
// Handle AUTH challenge message: ["AUTH", <challenge-string>]
if (relay->nip42_enabled && private_key && cJSON_IsArray(parsed) && cJSON_GetArraySize(parsed) >= 2) {
cJSON* challenge_json = cJSON_GetArrayItem(parsed, 1);
if (cJSON_IsString(challenge_json)) {
const char* challenge = cJSON_GetStringValue(challenge_json);
// Store challenge and attempt authentication
strncpy(relay->auth_challenge, challenge, sizeof(relay->auth_challenge) - 1);
relay->auth_challenge[sizeof(relay->auth_challenge) - 1] = '\0';
relay->auth_challenge_time = time(NULL);
relay->auth_state = NOSTR_AUTH_STATE_CHALLENGE_RECEIVED;
// Create and send authentication event
cJSON* auth_event = nostr_nip42_create_auth_event(challenge, relay->url, private_key, 0);
if (auth_event) {
char* auth_message = nostr_nip42_create_auth_message(auth_event);
if (auth_message) {
if (nostr_ws_send_text(relay->client, auth_message) >= 0) {
relay->auth_state = NOSTR_AUTH_STATE_AUTHENTICATING;
if (callback) {
callback(relay->url, "authenticating", NULL, 0, relay_count, completed_relays, user_data);
}
}
free(auth_message);
}
cJSON_Delete(auth_event);
}
}
}
} else if (msg_type && strcmp(msg_type, "EVENT") == 0) {
// Handle EVENT message
if (cJSON_IsArray(parsed) && cJSON_GetArraySize(parsed) >= 3) {
cJSON* sub_id_json = cJSON_GetArrayItem(parsed, 1);
cJSON* event = cJSON_GetArrayItem(parsed, 2);
if (cJSON_IsString(sub_id_json) && event &&
strcmp(cJSON_GetStringValue(sub_id_json), relay->subscription_id) == 0) {
cJSON* event_id_json = cJSON_GetObjectItem(event, "id");
if (event_id_json && cJSON_IsString(event_id_json)) {
const char* event_id = cJSON_GetStringValue(event_id_json);
// Check for duplicate
int is_duplicate = 0;
for (int j = 0; j < seen_count; j++) {
@@ -212,31 +260,31 @@ cJSON** synchronous_query_relays_with_progress(
break;
}
}
if (!is_duplicate && seen_count < 1000) {
// New event - add to seen list
strncpy(seen_event_ids[seen_count], event_id, 64);
seen_event_ids[seen_count][64] = '\0';
seen_count++;
total_unique_events++;
// Store event in relay's array
if (relay->events_received >= relay->events_capacity) {
relay->events_capacity *= 2;
relay->events = realloc(relay->events,
relay->events = realloc(relay->events,
relay->events_capacity * sizeof(cJSON*));
}
relay->events[relay->events_received] = cJSON_Duplicate(event, 1);
relay->events_received++;
relay->state = RELAY_STATE_ACTIVE;
if (callback) {
callback(relay->url, "event_found", event_id,
relay->events_received, relay_count,
callback(relay->url, "event_found", event_id,
relay->events_received, relay_count,
completed_relays, user_data);
}
// For FIRST_RESULT mode, return immediately
if (mode == RELAY_QUERY_FIRST_RESULT) {
result_array = malloc(sizeof(cJSON*));
@@ -244,7 +292,7 @@ cJSON** synchronous_query_relays_with_progress(
result_array[0] = cJSON_Duplicate(event, 1);
*result_count = 1;
if (callback) {
callback(NULL, "first_result", event_id,
callback(NULL, "first_result", event_id,
1, relay_count, completed_relays, user_data);
}
}
@@ -254,7 +302,7 @@ cJSON** synchronous_query_relays_with_progress(
}
}
}
} else if (msg_type && strcmp(msg_type, "EOSE") == 0) {
// Handle End of Stored Events
cJSON* sub_id_json = cJSON_GetArrayItem(parsed, 1);
@@ -400,7 +448,9 @@ publish_result_t* synchronous_publish_event_with_progress(
int* success_count,
int relay_timeout_seconds,
publish_progress_callback_t callback,
void* user_data) {
void* user_data,
int nip42_enabled,
const unsigned char* private_key) {
if (!relay_urls || relay_count <= 0 || !event || !success_count) {
if (success_count) *success_count = 0;
@@ -443,7 +493,13 @@ publish_result_t* synchronous_publish_event_with_progress(
relays[i].state = RELAY_STATE_CONNECTING;
relays[i].last_activity = start_time;
results[i] = PUBLISH_ERROR; // Default to error
// Initialize NIP-42 authentication fields
relays[i].auth_state = NOSTR_AUTH_STATE_NONE;
memset(relays[i].auth_challenge, 0, sizeof(relays[i].auth_challenge));
relays[i].auth_challenge_time = 0;
relays[i].nip42_enabled = nip42_enabled;
if (callback) {
callback(relays[i].url, "connecting", NULL, 0, relay_count, 0, user_data);
}
@@ -535,34 +591,65 @@ publish_result_t* synchronous_publish_event_with_progress(
char* msg_type = NULL;
cJSON* parsed = NULL;
if (nostr_parse_relay_message(buffer, &msg_type, &parsed) == 0) {
if (msg_type && strcmp(msg_type, "OK") == 0) {
if (msg_type && strcmp(msg_type, "AUTH") == 0) {
// Handle AUTH challenge message: ["AUTH", <challenge-string>]
if (relay->nip42_enabled && private_key && cJSON_IsArray(parsed) && cJSON_GetArraySize(parsed) >= 2) {
cJSON* challenge_json = cJSON_GetArrayItem(parsed, 1);
if (cJSON_IsString(challenge_json)) {
const char* challenge = cJSON_GetStringValue(challenge_json);
// Store challenge and attempt authentication
strncpy(relay->auth_challenge, challenge, sizeof(relay->auth_challenge) - 1);
relay->auth_challenge[sizeof(relay->auth_challenge) - 1] = '\0';
relay->auth_challenge_time = time(NULL);
relay->auth_state = NOSTR_AUTH_STATE_CHALLENGE_RECEIVED;
// Create and send authentication event
cJSON* auth_event = nostr_nip42_create_auth_event(challenge, relay->url, private_key, 0);
if (auth_event) {
char* auth_message = nostr_nip42_create_auth_message(auth_event);
if (auth_message) {
if (nostr_ws_send_text(relay->client, auth_message) >= 0) {
relay->auth_state = NOSTR_AUTH_STATE_AUTHENTICATING;
if (callback) {
callback(relay->url, "authenticating", NULL, 0, relay_count, completed_relays, user_data);
}
}
free(auth_message);
}
cJSON_Delete(auth_event);
}
}
}
} else if (msg_type && strcmp(msg_type, "OK") == 0) {
// Handle OK message: ["OK", <event_id>, <true|false>, <message>]
if (cJSON_IsArray(parsed) && cJSON_GetArraySize(parsed) >= 3) {
cJSON* ok_event_id = cJSON_GetArrayItem(parsed, 1);
cJSON* accepted = cJSON_GetArrayItem(parsed, 2);
cJSON* message = cJSON_GetArrayItem(parsed, 3);
// Verify this OK is for our event
if (ok_event_id && cJSON_IsString(ok_event_id) && event_id &&
strcmp(cJSON_GetStringValue(ok_event_id), event_id) == 0) {
relay->state = RELAY_STATE_EOSE_RECEIVED; // Reuse for "completed"
active_relays--;
completed_relays++;
const char* ok_message = "";
if (message && cJSON_IsString(message)) {
ok_message = cJSON_GetStringValue(message);
}
if (accepted && cJSON_IsBool(accepted) && cJSON_IsTrue(accepted)) {
// Event accepted
results[i] = PUBLISH_SUCCESS;
(*success_count)++;
if (callback) {
callback(relay->url, "accepted", ok_message,
callback(relay->url, "accepted", ok_message,
*success_count, relay_count, completed_relays, user_data);
}
} else {
@@ -570,11 +657,11 @@ publish_result_t* synchronous_publish_event_with_progress(
results[i] = PUBLISH_REJECTED;
if (callback) {
callback(relay->url, "rejected", ok_message,
callback(relay->url, "rejected", ok_message,
*success_count, relay_count, completed_relays, user_data);
}
}
// Close connection
nostr_ws_close(relay->client);
relay->client = NULL;

View File

@@ -81,66 +81,11 @@
// Forward declaration for cJSON (to avoid requiring cJSON.h in header)
struct cJSON;
// Relay query modes
typedef enum {
RELAY_QUERY_FIRST_RESULT, // Return as soon as first event is received
RELAY_QUERY_MOST_RECENT, // Return the most recent event from all relays
RELAY_QUERY_ALL_RESULTS // Return all unique events from all relays
} relay_query_mode_t;
// Publish result types
typedef enum {
PUBLISH_SUCCESS, // Event was accepted by relay
PUBLISH_REJECTED, // Event was rejected by relay
PUBLISH_TIMEOUT, // No response within timeout
PUBLISH_ERROR // Connection or other error
} publish_result_t;
// Progress callback function types
typedef void (*relay_progress_callback_t)(
const char* relay_url,
const char* status,
const char* event_id,
int events_received,
int total_relays,
int completed_relays,
void* user_data);
typedef void (*publish_progress_callback_t)(
const char* relay_url,
const char* status,
const char* message,
int success_count,
int total_relays,
int completed_relays,
void* user_data);
// Function declarations
const char* nostr_strerror(int error_code);
// Library initialization functions
// Library initialization functions
int nostr_init(void);
void nostr_cleanup(void);
// Relay query functions
struct cJSON** synchronous_query_relays_with_progress(
const char** relay_urls,
int relay_count,
struct cJSON* filter,
relay_query_mode_t mode,
int* result_count,
int relay_timeout_seconds,
relay_progress_callback_t callback,
void* user_data);
// Relay publish functions
publish_result_t* synchronous_publish_event_with_progress(
const char** relay_urls,
int relay_count,
struct cJSON* event,
int* success_count,
int relay_timeout_seconds,
publish_progress_callback_t callback,
void* user_data);
#endif // NOSTR_COMMON_H

View File

@@ -82,6 +82,15 @@
* - nostr_auth_rule_add() -> Add authentication rule
* - nostr_auth_rule_remove() -> Remove authentication rule
*
* RELAY OPERATIONS:
* - synchronous_query_relays_with_progress() -> One-off query from multiple relays
* - synchronous_publish_event_with_progress() -> One-off publish to multiple relays
* *
* RELAY POOL OPERATIONS:
* - nostr_relay_pool_create() -> Create relay pool for persistent connections
* - nostr_relay_pool_subscribe() -> Subscribe to events with callbacks
* - nostr_relay_pool_run() -> Run event loop for receiving events
* SYSTEM FUNCTIONS:
* - nostr_crypto_init() -> Initialize crypto subsystem
* - nostr_crypto_cleanup() -> Cleanup crypto subsystem
@@ -142,6 +151,155 @@ extern "C" {
// Authentication and request validation system
#include "request_validator.h" // Request validation and authentication rules
// Relay pool types and functions
typedef enum {
NOSTR_POOL_RELAY_DISCONNECTED = 0,
NOSTR_POOL_RELAY_CONNECTING = 1,
NOSTR_POOL_RELAY_CONNECTED = 2,
NOSTR_POOL_RELAY_ERROR = -1
} nostr_pool_relay_status_t;
typedef struct {
int connection_attempts;
int connection_failures;
int events_received;
int events_published;
int events_published_ok;
int events_published_failed;
time_t last_event_time;
time_t connection_uptime_start;
double ping_latency_avg;
double ping_latency_min;
double ping_latency_max;
double ping_latency_current;
int ping_samples;
double query_latency_avg;
double query_latency_min;
double query_latency_max;
int query_samples;
double publish_latency_avg;
int publish_samples;
} nostr_relay_stats_t;
typedef struct nostr_relay_pool nostr_relay_pool_t;
typedef struct nostr_pool_subscription nostr_pool_subscription_t;
// Relay pool management functions
nostr_relay_pool_t* nostr_relay_pool_create(void);
int nostr_relay_pool_add_relay(nostr_relay_pool_t* pool, const char* relay_url);
int nostr_relay_pool_remove_relay(nostr_relay_pool_t* pool, const char* relay_url);
void nostr_relay_pool_destroy(nostr_relay_pool_t* pool);
// Subscription management
nostr_pool_subscription_t* nostr_relay_pool_subscribe(
nostr_relay_pool_t* pool,
const char** relay_urls,
int relay_count,
cJSON* filter,
void (*on_event)(cJSON* event, const char* relay_url, void* user_data),
void (*on_eose)(void* user_data),
void* user_data);
int nostr_pool_subscription_close(nostr_pool_subscription_t* subscription);
// Event loop functions
int nostr_relay_pool_run(nostr_relay_pool_t* pool, int timeout_ms);
int nostr_relay_pool_poll(nostr_relay_pool_t* pool, int timeout_ms);
// Synchronous query/publish functions
cJSON** nostr_relay_pool_query_sync(
nostr_relay_pool_t* pool,
const char** relay_urls,
int relay_count,
cJSON* filter,
int* event_count,
int timeout_ms);
cJSON* nostr_relay_pool_get_event(
nostr_relay_pool_t* pool,
const char** relay_urls,
int relay_count,
cJSON* filter,
int timeout_ms);
int nostr_relay_pool_publish(
nostr_relay_pool_t* pool,
const char** relay_urls,
int relay_count,
cJSON* event);
// Status and statistics functions
nostr_pool_relay_status_t nostr_relay_pool_get_relay_status(
nostr_relay_pool_t* pool,
const char* relay_url);
int nostr_relay_pool_list_relays(
nostr_relay_pool_t* pool,
char*** relay_urls,
nostr_pool_relay_status_t** statuses);
const nostr_relay_stats_t* nostr_relay_pool_get_relay_stats(
nostr_relay_pool_t* pool,
const char* relay_url);
int nostr_relay_pool_reset_relay_stats(
nostr_relay_pool_t* pool,
const char* relay_url);
double nostr_relay_pool_get_relay_query_latency(
nostr_relay_pool_t* pool,
const char* relay_url);
// Synchronous relay operations (one-off queries/publishes)
typedef enum {
RELAY_QUERY_FIRST_RESULT, // Return as soon as first event is received
RELAY_QUERY_MOST_RECENT, // Return the most recent event from all relays
RELAY_QUERY_ALL_RESULTS // Return all unique events from all relays
} relay_query_mode_t;
typedef enum {
PUBLISH_SUCCESS, // Event was accepted by relay
PUBLISH_REJECTED, // Event was rejected by relay
PUBLISH_TIMEOUT, // No response within timeout
PUBLISH_ERROR // Connection or other error
} publish_result_t;
typedef void (*relay_progress_callback_t)(
const char* relay_url,
const char* status,
const char* event_id,
int events_received,
int total_relays,
int completed_relays,
void* user_data);
typedef void (*publish_progress_callback_t)(
const char* relay_url,
const char* status,
const char* message,
int success_count,
int total_relays,
int completed_relays,
void* user_data);
// Synchronous relay query functions
struct cJSON** synchronous_query_relays_with_progress(
const char** relay_urls,
int relay_count,
struct cJSON* filter,
relay_query_mode_t mode,
int* result_count,
int relay_timeout_seconds,
relay_progress_callback_t callback,
void* user_data,
int nip42_enabled,
const unsigned char* private_key);
// Synchronous relay publish functions
publish_result_t* synchronous_publish_event_with_progress(
const char** relay_urls,
int relay_count,
struct cJSON* event,
int* success_count,
int relay_timeout_seconds,
publish_progress_callback_t callback,
void* user_data,
int nip42_enabled,
const unsigned char* private_key);
// Relay communication functions are defined in nostr_common.h
// WebSocket functions are defined in nostr_common.h

View File

@@ -13,6 +13,7 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <sqlite3.h>
#include <time.h>
@@ -241,8 +242,10 @@ void nostr_request_validator_cleanup(void) {
// CONVENIENCE FUNCTIONS
//=============================================================================
int nostr_auth_check_upload(const char* pubkey, const char* auth_header,
int nostr_auth_check_upload(const char* pubkey, const char* auth_header,
const char* hash, const char* mime_type, long file_size) {
(void)pubkey; // Parameter not used in this convenience function
nostr_request_t request = {
.operation = "upload",
.auth_header = auth_header,
@@ -265,6 +268,8 @@ int nostr_auth_check_upload(const char* pubkey, const char* auth_header,
}
int nostr_auth_check_delete(const char* pubkey, const char* auth_header, const char* hash) {
(void)pubkey; // Parameter not used in this convenience function
nostr_request_t request = {
.operation = "delete",
.auth_header = auth_header,
@@ -287,6 +292,8 @@ int nostr_auth_check_delete(const char* pubkey, const char* auth_header, const c
}
int nostr_auth_check_publish(const char* pubkey, struct cJSON* event) {
(void)pubkey; // Parameter not used in this convenience function
if (!event) {
return NOSTR_ERROR_INVALID_INPUT;
}
@@ -459,6 +466,8 @@ static int validate_nostr_event(struct cJSON* event, const char* expected_hash,
//=============================================================================
static int sqlite_auth_init(const char* db_path, const char* app_name) {
(void)app_name; // Parameter not used in this implementation
if (g_auth_db) {
return NOSTR_SUCCESS; // Already initialized
}
@@ -765,13 +774,15 @@ static int sqlite_auth_rule_update(const nostr_auth_rule_t* rule) {
}
static int sqlite_auth_rule_list(const char* operation, nostr_auth_rule_t** rules, int* count) {
(void)operation; // Parameter not used in this implementation
if (!g_auth_db || !rules || !count) {
return NOSTR_ERROR_INVALID_INPUT;
}
*rules = NULL;
*count = 0;
// For now, return empty list - would implement full rule listing
return NOSTR_SUCCESS;
}
@@ -1130,19 +1141,21 @@ static int evaluate_auth_rules(const char* pubkey, const char* operation, const
static void generate_auth_cache_key(const char* pubkey, const char* operation, const char* hash,
const char* mime_type, long file_size, char* cache_key, size_t key_size) {
char temp_buffer[1024];
snprintf(temp_buffer, sizeof(temp_buffer), "%s|%s|%s|%s|%ld",
pubkey ? pubkey : "", operation ? operation : "",
hash ? hash : "", mime_type ? mime_type : "", file_size);
int written = snprintf(temp_buffer, sizeof(temp_buffer), "%s|%s|%s|%s|%ld",
pubkey ? pubkey : "", operation ? operation : "",
hash ? hash : "", mime_type ? mime_type : "", file_size);
// Generate SHA-256 hash of the key components for consistent cache keys
unsigned char hash_bytes[32];
if (nostr_sha256((unsigned char*)temp_buffer, strlen(temp_buffer), hash_bytes) == NOSTR_SUCCESS) {
size_t hash_len = (written >= 0 && (size_t)written < sizeof(temp_buffer)) ? (size_t)written : sizeof(temp_buffer) - 1;
if (nostr_sha256((unsigned char*)temp_buffer, hash_len, hash_bytes) == NOSTR_SUCCESS) {
nostr_bytes_to_hex(hash_bytes, 32, cache_key);
cache_key[64] = '\0'; // Ensure null termination
} else {
// Fallback if hashing fails
strncpy(cache_key, temp_buffer, key_size - 1);
cache_key[key_size - 1] = '\0';
// Fallback if hashing fails - safely copy up to key_size - 1 characters
size_t copy_len = (written >= 0 && (size_t)written < key_size - 1) ? (size_t)written : key_size - 1;
memcpy(cache_key, temp_buffer, copy_len);
cache_key[copy_len] = '\0';
}
}
@@ -1179,6 +1192,8 @@ int nostr_auth_rule_list(const char* operation, nostr_auth_rule_t** rules, int*
}
void nostr_auth_rules_free(nostr_auth_rule_t* rules, int count) {
(void)count; // Parameter not used in this implementation
if (rules) {
free(rules);
}
@@ -1200,6 +1215,8 @@ int nostr_auth_cache_stats(int* hit_count, int* miss_count, int* entries) {
}
int nostr_auth_register_db_backend(const nostr_auth_db_interface_t* backend) {
(void)backend; // Parameter not used in this implementation
// For now, only SQLite backend is supported
return NOSTR_ERROR_AUTH_RULES_BACKEND_NOT_FOUND;
}