// NIP-13 Proof of Work validation module #include #include #include #include #include "../nostr_core_lib/cjson/cJSON.h" #include "../nostr_core_lib/nostr_core/nostr_core.h" #include "../nostr_core_lib/nostr_core/nip013.h" #include "config.h" // Forward declarations for logging functions void log_info(const char* message); void log_success(const char* message); void log_error(const char* message); void log_warning(const char* message); // NIP-13 PoW configuration structure struct pow_config { int enabled; // 0 = disabled, 1 = enabled int min_pow_difficulty; // Minimum required difficulty (0 = no requirement) int validation_flags; // Bitflags for validation options int require_nonce_tag; // 1 = require nonce tag presence int reject_lower_targets; // 1 = reject if committed < actual difficulty int strict_format; // 1 = enforce strict nonce tag format int anti_spam_mode; // 1 = full anti-spam validation }; // Initialize PoW configuration using configuration system void init_pow_config() { // Get all config values first (without holding mutex to avoid deadlock) int pow_enabled = get_config_bool("pow_enabled", 1); int pow_min_difficulty = get_config_int("pow_min_difficulty", 0); const char* pow_mode = get_config_value("pow_mode"); pthread_mutex_lock(&g_unified_cache.cache_lock); // Load PoW settings from configuration system g_unified_cache.pow_config.enabled = pow_enabled; g_unified_cache.pow_config.min_pow_difficulty = pow_min_difficulty; // Configure PoW mode if (pow_mode) { if (strcmp(pow_mode, "strict") == 0) { g_unified_cache.pow_config.validation_flags = NOSTR_POW_VALIDATE_ANTI_SPAM | NOSTR_POW_STRICT_FORMAT; g_unified_cache.pow_config.require_nonce_tag = 1; g_unified_cache.pow_config.reject_lower_targets = 1; g_unified_cache.pow_config.strict_format = 1; g_unified_cache.pow_config.anti_spam_mode = 1; } else if (strcmp(pow_mode, "full") == 0) { g_unified_cache.pow_config.validation_flags = NOSTR_POW_VALIDATE_FULL; g_unified_cache.pow_config.require_nonce_tag = 1; } else if (strcmp(pow_mode, "basic") == 0) { g_unified_cache.pow_config.validation_flags = NOSTR_POW_VALIDATE_BASIC; } else if (strcmp(pow_mode, "disabled") == 0) { g_unified_cache.pow_config.enabled = 0; } free((char*)pow_mode); // Free dynamically allocated string } else { // Default to basic mode g_unified_cache.pow_config.validation_flags = NOSTR_POW_VALIDATE_BASIC; } pthread_mutex_unlock(&g_unified_cache.cache_lock); } // Validate event Proof of Work according to NIP-13 int validate_event_pow(cJSON* event, char* error_message, size_t error_size) { pthread_mutex_lock(&g_unified_cache.cache_lock); int enabled = g_unified_cache.pow_config.enabled; int min_pow_difficulty = g_unified_cache.pow_config.min_pow_difficulty; int validation_flags = g_unified_cache.pow_config.validation_flags; pthread_mutex_unlock(&g_unified_cache.cache_lock); if (!enabled) { return 0; // PoW validation disabled } if (!event) { snprintf(error_message, error_size, "pow: null event"); return NOSTR_ERROR_INVALID_INPUT; } // If min_pow_difficulty is 0, only validate events that have nonce tags // This allows events without PoW when difficulty requirement is 0 if (min_pow_difficulty == 0) { cJSON* tags = cJSON_GetObjectItem(event, "tags"); int has_nonce_tag = 0; if (tags && cJSON_IsArray(tags)) { cJSON* tag = NULL; cJSON_ArrayForEach(tag, tags) { if (cJSON_IsArray(tag) && cJSON_GetArraySize(tag) >= 2) { cJSON* tag_name = cJSON_GetArrayItem(tag, 0); if (cJSON_IsString(tag_name)) { const char* name = cJSON_GetStringValue(tag_name); if (name && strcmp(name, "nonce") == 0) { has_nonce_tag = 1; break; } } } } } // If no minimum difficulty required and no nonce tag, skip PoW validation if (!has_nonce_tag) { return 0; // Accept event without PoW when min_difficulty=0 } } // Perform PoW validation using nostr_core_lib nostr_pow_result_t pow_result; int validation_result = nostr_validate_pow(event, min_pow_difficulty, validation_flags, &pow_result); if (validation_result != NOSTR_SUCCESS) { // Handle specific error cases with appropriate messages switch (validation_result) { case NOSTR_ERROR_NIP13_INSUFFICIENT: snprintf(error_message, error_size, "pow: insufficient difficulty: %d < %d", pow_result.actual_difficulty, min_pow_difficulty); log_warning("Event rejected: insufficient PoW difficulty"); break; case NOSTR_ERROR_NIP13_NO_NONCE_TAG: // This should not happen with min_difficulty=0 after our check above if (min_pow_difficulty > 0) { snprintf(error_message, error_size, "pow: missing required nonce tag"); log_warning("Event rejected: missing nonce tag"); } else { return 0; // Allow when min_difficulty=0 } break; case NOSTR_ERROR_NIP13_INVALID_NONCE_TAG: snprintf(error_message, error_size, "pow: invalid nonce tag format"); log_warning("Event rejected: invalid nonce tag format"); break; case NOSTR_ERROR_NIP13_TARGET_MISMATCH: snprintf(error_message, error_size, "pow: committed target (%d) lower than minimum (%d)", pow_result.committed_target, min_pow_difficulty); log_warning("Event rejected: committed target too low (anti-spam protection)"); break; case NOSTR_ERROR_NIP13_CALCULATION: snprintf(error_message, error_size, "pow: difficulty calculation failed"); log_error("PoW difficulty calculation error"); break; case NOSTR_ERROR_EVENT_INVALID_ID: snprintf(error_message, error_size, "pow: invalid event ID format"); log_warning("Event rejected: invalid event ID for PoW calculation"); break; default: snprintf(error_message, error_size, "pow: validation failed - %s", strlen(pow_result.error_detail) > 0 ? pow_result.error_detail : "unknown error"); log_warning("Event rejected: PoW validation failed"); } return validation_result; } return 0; // Success }