v0.2.0 - Nip13 implemented
This commit is contained in:
210
src/main.c
210
src/main.c
@@ -14,6 +14,7 @@
|
||||
// Include nostr_core_lib for Nostr functionality
|
||||
#include "../nostr_core_lib/cjson/cJSON.h"
|
||||
#include "../nostr_core_lib/nostr_core/nostr_core.h"
|
||||
#include "../nostr_core_lib/nostr_core/nip013.h" // NIP-13: Proof of Work
|
||||
|
||||
// Server Configuration
|
||||
#define DEFAULT_PORT 8888
|
||||
@@ -74,6 +75,28 @@ struct relay_info {
|
||||
// Global relay information instance
|
||||
static struct relay_info g_relay_info = {0};
|
||||
|
||||
// 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
|
||||
};
|
||||
|
||||
// Global PoW configuration instance
|
||||
static struct pow_config g_pow_config = {
|
||||
.enabled = 1, // Enable PoW validation by default
|
||||
.min_pow_difficulty = 0, // No minimum difficulty by default
|
||||
.validation_flags = NOSTR_POW_VALIDATE_BASIC,
|
||||
.require_nonce_tag = 0, // Don't require nonce tags by default
|
||||
.reject_lower_targets = 0, // Allow lower committed targets by default
|
||||
.strict_format = 0, // Relaxed format validation by default
|
||||
.anti_spam_mode = 0 // Basic validation by default
|
||||
};
|
||||
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
@@ -190,6 +213,10 @@ void cleanup_relay_info();
|
||||
cJSON* generate_relay_info_json();
|
||||
int handle_nip11_http_request(struct lws* wsi, const char* accept_header);
|
||||
|
||||
// Forward declarations for NIP-13 PoW validation
|
||||
void init_pow_config();
|
||||
int validate_event_pow(cJSON* event, char* error_message, size_t error_size);
|
||||
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
@@ -1238,6 +1265,7 @@ void init_relay_info() {
|
||||
cJSON_AddItemToArray(g_relay_info.supported_nips, cJSON_CreateNumber(1)); // NIP-01: Basic protocol
|
||||
cJSON_AddItemToArray(g_relay_info.supported_nips, cJSON_CreateNumber(9)); // NIP-09: Event deletion
|
||||
cJSON_AddItemToArray(g_relay_info.supported_nips, cJSON_CreateNumber(11)); // NIP-11: Relay information
|
||||
cJSON_AddItemToArray(g_relay_info.supported_nips, cJSON_CreateNumber(13)); // NIP-13: Proof of Work
|
||||
cJSON_AddItemToArray(g_relay_info.supported_nips, cJSON_CreateNumber(15)); // NIP-15: EOSE
|
||||
cJSON_AddItemToArray(g_relay_info.supported_nips, cJSON_CreateNumber(20)); // NIP-20: Command results
|
||||
}
|
||||
@@ -1251,7 +1279,7 @@ void init_relay_info() {
|
||||
cJSON_AddNumberToObject(g_relay_info.limitation, "max_subid_length", SUBSCRIPTION_ID_MAX_LENGTH);
|
||||
cJSON_AddNumberToObject(g_relay_info.limitation, "max_event_tags", 100);
|
||||
cJSON_AddNumberToObject(g_relay_info.limitation, "max_content_length", 8196);
|
||||
cJSON_AddNumberToObject(g_relay_info.limitation, "min_pow_difficulty", 0);
|
||||
cJSON_AddNumberToObject(g_relay_info.limitation, "min_pow_difficulty", g_pow_config.min_pow_difficulty);
|
||||
cJSON_AddBoolToObject(g_relay_info.limitation, "auth_required", cJSON_False);
|
||||
cJSON_AddBoolToObject(g_relay_info.limitation, "payment_required", cJSON_False);
|
||||
cJSON_AddBoolToObject(g_relay_info.limitation, "restricted_writes", cJSON_False);
|
||||
@@ -1564,6 +1592,171 @@ int handle_nip11_http_request(struct lws* wsi, const char* accept_header) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
// NIP-13 PROOF OF WORK VALIDATION
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Initialize PoW configuration with environment variables and defaults
|
||||
void init_pow_config() {
|
||||
log_info("Initializing NIP-13 Proof of Work configuration");
|
||||
|
||||
// Initialize with defaults (already set in struct initialization)
|
||||
|
||||
// Check environment variables for configuration
|
||||
const char* pow_enabled_env = getenv("RELAY_POW_ENABLED");
|
||||
if (pow_enabled_env) {
|
||||
g_pow_config.enabled = (strcmp(pow_enabled_env, "1") == 0 ||
|
||||
strcmp(pow_enabled_env, "true") == 0 ||
|
||||
strcmp(pow_enabled_env, "yes") == 0);
|
||||
}
|
||||
|
||||
const char* min_diff_env = getenv("RELAY_MIN_POW_DIFFICULTY");
|
||||
if (min_diff_env) {
|
||||
int min_diff = atoi(min_diff_env);
|
||||
if (min_diff >= 0 && min_diff <= 64) { // Reasonable bounds
|
||||
g_pow_config.min_pow_difficulty = min_diff;
|
||||
}
|
||||
}
|
||||
|
||||
const char* pow_mode_env = getenv("RELAY_POW_MODE");
|
||||
if (pow_mode_env) {
|
||||
if (strcmp(pow_mode_env, "strict") == 0) {
|
||||
g_pow_config.validation_flags = NOSTR_POW_VALIDATE_ANTI_SPAM | NOSTR_POW_STRICT_FORMAT;
|
||||
g_pow_config.require_nonce_tag = 1;
|
||||
g_pow_config.reject_lower_targets = 1;
|
||||
g_pow_config.strict_format = 1;
|
||||
g_pow_config.anti_spam_mode = 1;
|
||||
log_info("PoW configured in strict anti-spam mode");
|
||||
} else if (strcmp(pow_mode_env, "full") == 0) {
|
||||
g_pow_config.validation_flags = NOSTR_POW_VALIDATE_FULL;
|
||||
g_pow_config.require_nonce_tag = 1;
|
||||
log_info("PoW configured in full validation mode");
|
||||
} else if (strcmp(pow_mode_env, "basic") == 0) {
|
||||
g_pow_config.validation_flags = NOSTR_POW_VALIDATE_BASIC;
|
||||
log_info("PoW configured in basic validation mode");
|
||||
} else if (strcmp(pow_mode_env, "disabled") == 0) {
|
||||
g_pow_config.enabled = 0;
|
||||
log_info("PoW validation disabled via RELAY_POW_MODE");
|
||||
}
|
||||
}
|
||||
|
||||
// Log final configuration
|
||||
char config_msg[512];
|
||||
snprintf(config_msg, sizeof(config_msg),
|
||||
"PoW Configuration: enabled=%s, min_difficulty=%d, validation_flags=0x%x, mode=%s",
|
||||
g_pow_config.enabled ? "true" : "false",
|
||||
g_pow_config.min_pow_difficulty,
|
||||
g_pow_config.validation_flags,
|
||||
g_pow_config.anti_spam_mode ? "anti-spam" :
|
||||
(g_pow_config.validation_flags & NOSTR_POW_VALIDATE_FULL) ? "full" : "basic");
|
||||
log_info(config_msg);
|
||||
}
|
||||
|
||||
// Validate event Proof of Work according to NIP-13
|
||||
int validate_event_pow(cJSON* event, char* error_message, size_t error_size) {
|
||||
if (!g_pow_config.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 (g_pow_config.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, g_pow_config.min_pow_difficulty,
|
||||
g_pow_config.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, g_pow_config.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 (g_pow_config.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, g_pow_config.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;
|
||||
}
|
||||
|
||||
// Log successful PoW validation (only if minimum difficulty is required)
|
||||
if (g_pow_config.min_pow_difficulty > 0 || pow_result.has_nonce_tag) {
|
||||
char debug_msg[256];
|
||||
snprintf(debug_msg, sizeof(debug_msg),
|
||||
"PoW validated: difficulty=%d, target=%d, nonce=%llu%s",
|
||||
pow_result.actual_difficulty,
|
||||
pow_result.committed_target,
|
||||
(unsigned long long)pow_result.nonce_value,
|
||||
pow_result.has_nonce_tag ? "" : " (no nonce tag)");
|
||||
log_info(debug_msg);
|
||||
}
|
||||
|
||||
return 0; // Success
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
// DATABASE FUNCTIONS
|
||||
@@ -2189,14 +2382,20 @@ int handle_event_message(cJSON* event, char* error_message, size_t error_size) {
|
||||
return signature_result;
|
||||
}
|
||||
|
||||
// Step 3: Complete event validation (combines structure + signature + additional checks)
|
||||
// Step 3: Validate Proof of Work (NIP-13) if enabled
|
||||
int pow_result = validate_event_pow(event, error_message, error_size);
|
||||
if (pow_result != 0) {
|
||||
return pow_result; // PoW validation failed, error message already set
|
||||
}
|
||||
|
||||
// Step 4: Complete event validation (combines structure + signature + additional checks)
|
||||
int validation_result = nostr_validate_event(event);
|
||||
if (validation_result != NOSTR_SUCCESS) {
|
||||
snprintf(error_message, error_size, "invalid: complete event validation failed");
|
||||
return validation_result;
|
||||
}
|
||||
|
||||
// Step 4: Check for special event types and handle accordingly
|
||||
// Step 5: Check for special event types and handle accordingly
|
||||
cJSON* kind_obj = cJSON_GetObjectItem(event, "kind");
|
||||
cJSON* pubkey_obj = cJSON_GetObjectItem(event, "pubkey");
|
||||
cJSON* created_at_obj = cJSON_GetObjectItem(event, "created_at");
|
||||
@@ -2236,7 +2435,7 @@ int handle_event_message(cJSON* event, char* error_message, size_t error_size) {
|
||||
}
|
||||
}
|
||||
|
||||
// Step 5: Store event in database
|
||||
// Step 6: Store event in database
|
||||
if (store_event(event) == 0) {
|
||||
error_message[0] = '\0'; // Success - empty error message
|
||||
log_success("Event validated and stored successfully");
|
||||
@@ -2603,6 +2802,9 @@ int main(int argc, char* argv[]) {
|
||||
// Initialize NIP-11 relay information
|
||||
init_relay_info();
|
||||
|
||||
// Initialize NIP-13 PoW configuration
|
||||
init_pow_config();
|
||||
|
||||
log_info("Starting relay server...");
|
||||
|
||||
// Start WebSocket Nostr relay server
|
||||
|
||||
Reference in New Issue
Block a user