v0.4.7 - Implement NIP-70 Protected Events - Add protected event support with authentication checks, comprehensive testing, and relay metadata protection

This commit is contained in:
Your Name
2025-10-03 06:44:27 -04:00
parent 88b4aaa301
commit 36c9c84047
9 changed files with 322 additions and 89 deletions

View File

@@ -160,6 +160,8 @@ int update_cache_value(const char* key, const char* value) {
g_unified_cache.nip42_challenge_timeout = atoi(value);
} else if (strcmp(key, "nip42_time_tolerance") == 0) {
g_unified_cache.nip42_time_tolerance = atoi(value);
} else if (strcmp(key, "nip70_protected_events_enabled") == 0) {
g_unified_cache.nip70_protected_events_enabled = (strcmp(value, "true") == 0) ? 1 : 0;
} else {
// For NIP-11 relay info fields, update the cache buffers
if (strcmp(key, "relay_name") == 0) {
@@ -259,6 +261,10 @@ static int refresh_unified_cache_from_table(void) {
const char* time_tolerance = get_config_value_from_table("nip42_time_tolerance");
g_unified_cache.nip42_time_tolerance = time_tolerance ? atoi(time_tolerance) : 300;
// Load NIP-70 protected events config
const char* nip70_enabled = get_config_value_from_table("nip70_protected_events_enabled");
g_unified_cache.nip70_protected_events_enabled = (nip70_enabled && strcmp(nip70_enabled, "true") == 0) ? 1 : 0;
// Set cache expiration
int cache_timeout = get_cache_timeout();
g_unified_cache.cache_expires = time(NULL) + cache_timeout;

View File

@@ -40,6 +40,7 @@ typedef struct {
int nip42_mode;
int nip42_challenge_timeout;
int nip42_time_tolerance;
int nip70_protected_events_enabled;
// Static buffer for config values (replaces static buffers in get_config_value functions)
char temp_buffer[CONFIG_VALUE_MAX_LENGTH];

View File

@@ -22,12 +22,15 @@ static const struct {
} DEFAULT_CONFIG_VALUES[] = {
// Authentication
{"auth_enabled", "false"},
// NIP-42 Authentication Settings
{"nip42_auth_required_events", "false"},
{"nip42_auth_required_subscriptions", "false"},
{"nip42_auth_required_kinds", "4,14"}, // Default: DM kinds require auth
{"nip42_challenge_expiration", "600"}, // 10 minutes
// NIP-70 Protected Events
{"nip70_protected_events_enabled", "false"},
// Server Core Settings
{"relay_port", "8888"},

View File

@@ -1,17 +1,15 @@
/*
* C-Relay Main Header - Version and Metadata Information
*
* This header contains version information and relay metadata that is
* automatically updated by the build system (build_and_push.sh).
*
* The build_and_push.sh script updates VERSION and related macros when
* creating new releases.
* This header contains version information and relay metadata.
* Version macros are auto-updated by the build system.
* Relay metadata should be manually maintained.
*/
#ifndef MAIN_H
#define MAIN_H
// Version information (auto-updated by build_and_push.sh)
// Version information (auto-updated by build system)
#define VERSION "v0.4.6"
#define VERSION_MAJOR 0
#define VERSION_MINOR 4
@@ -23,7 +21,7 @@
#define RELAY_CONTACT ""
#define RELAY_SOFTWARE "https://git.laantungir.net/laantungir/c-relay.git"
#define RELAY_VERSION VERSION // Use the same version as the build
#define SUPPORTED_NIPS "1,2,4,9,11,12,13,15,16,20,22,33,40,42"
#define SUPPORTED_NIPS "1,2,4,9,11,12,13,15,16,20,22,33,40,42,50,70"
#define LANGUAGE_TAGS ""
#define RELAY_COUNTRIES ""
#define POSTING_POLICY ""

View File

@@ -414,7 +414,55 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
// Cleanup event JSON string
free(event_json_str);
// Check for NIP-70 protected events
if (result == 0) {
// Check if event has protected tag ["-"]
int is_protected_event = 0;
cJSON* tags = cJSON_GetObjectItem(event, "tags");
if (tags && cJSON_IsArray(tags)) {
cJSON* tag = NULL;
cJSON_ArrayForEach(tag, tags) {
if (cJSON_IsArray(tag) && cJSON_GetArraySize(tag) >= 1) {
cJSON* tag_name = cJSON_GetArrayItem(tag, 0);
if (tag_name && cJSON_IsString(tag_name) &&
strcmp(cJSON_GetStringValue(tag_name), "-") == 0) {
is_protected_event = 1;
break;
}
}
}
}
if (is_protected_event) {
// Check if protected events are enabled using unified cache
int protected_events_enabled = g_unified_cache.nip70_protected_events_enabled;
if (!protected_events_enabled) {
// Protected events not supported
result = -1;
strncpy(error_message, "blocked: protected events not supported", sizeof(error_message) - 1);
error_message[sizeof(error_message) - 1] = '\0';
log_warning("Protected event rejected: protected events not enabled");
} else {
// Protected events enabled - check authentication
cJSON* pubkey_obj = cJSON_GetObjectItem(event, "pubkey");
const char* event_pubkey = pubkey_obj ? cJSON_GetStringValue(pubkey_obj) : NULL;
if (!pss || !pss->authenticated ||
!event_pubkey || strcmp(pss->authenticated_pubkey, event_pubkey) != 0) {
// Not authenticated or pubkey mismatch
result = -1;
strncpy(error_message, "auth-required: protected event requires authentication", sizeof(error_message) - 1);
error_message[sizeof(error_message) - 1] = '\0';
log_warning("Protected event rejected: authentication required");
} else {
log_info("Protected event accepted: authenticated publisher");
}
}
}
}
// Check for admin events (kind 23456) and intercept them
if (result == 0) {
cJSON* kind_obj = cJSON_GetObjectItem(event, "kind");