Compare commits

...
3 Commits
Author SHA1 Message Date
laantungir 2c128a6295 Upgrade nip4 and nip44 to be able to handle 1MB payloads. 2025-10-24 18:56:30 -04:00
laantungir 24903b8c90 Adding async publish to relay pool 2025-10-09 09:19:11 -04:00
laantungir c6896eebb4 added nip 17 2025-10-04 18:32:28 -04:00
17 changed files with 923 additions and 1725 deletions
+5 -5
View File
@@ -16,7 +16,7 @@ This document describes the public API for the Nostr Relay Pool implementation i
| [`nostr_relay_pool_poll()`](nostr_core/core_relay_pool.c:1232) | Single iteration poll and dispatch |
| [`nostr_relay_pool_query_sync()`](nostr_core/core_relay_pool.c:695) | Synchronous query returning event array |
| [`nostr_relay_pool_get_event()`](nostr_core/core_relay_pool.c:825) | Get single most recent event |
| [`nostr_relay_pool_publish()`](nostr_core/core_relay_pool.c:866) | Publish event and wait for OK responses |
| [`nostr_relay_pool_publish_async()`](nostr_core/core_relay_pool.c:866) | Publish event with async callbacks |
| [`nostr_relay_pool_get_relay_status()`](nostr_core/core_relay_pool.c:944) | Get connection status for a relay |
| [`nostr_relay_pool_list_relays()`](nostr_core/core_relay_pool.c:960) | List all relays and their statuses |
| [`nostr_relay_pool_get_relay_stats()`](nostr_core/core_relay_pool.c:992) | Get detailed statistics for a relay |
@@ -438,9 +438,9 @@ int get_latest_note_from_pubkey(nostr_relay_pool_t* pool, const char* pubkey_hex
```
### Publish Event
**Function:** [`nostr_relay_pool_publish()`](nostr_core/core_relay_pool.c:866)
**Function:** [`nostr_relay_pool_publish_async()`](nostr_core/core_relay_pool.c:866)
```c
int nostr_relay_pool_publish(
int nostr_relay_pool_publish_async(
nostr_relay_pool_t* pool,
const char** relay_urls,
int relay_count,
@@ -471,8 +471,8 @@ int publish_text_note(nostr_relay_pool_t* pool, const char* content) {
printf("Publishing note: %s\n", content);
int success_count = nostr_relay_pool_publish(
pool, relay_urls, 3, event);
int success_count = nostr_relay_pool_publish_async(
pool, relay_urls, 3, event, my_callback, user_data);
cJSON_Delete(event);
+12 -4
View File
@@ -531,16 +531,24 @@ The library uses automatic semantic versioning based on Git tags. Each build inc
**Build fails with secp256k1 errors:**
```bash
# Install secp256k1 with Schnorr support
sudo apt install libsecp256k1-dev # Ubuntu/Debian
# or
sudo yum install libsecp256k1-devel # CentOS/RHEL
# or
brew install secp256k1 # macOS
# If still failing, build from source with Schnorr support:
git clone https://github.com/bitcoin-core/secp256k1.git
cd secp256k1
./autogen.sh
./configure --enable-module-schnorrsig --enable-module-ecdh
make
cd ..
./build.sh lib
sudo make install
```
**Library too large:**
The x64 library is intentionally large (~15MB) because it includes all secp256k1 cryptographic functions and OpenSSL for complete self-containment. The ARM64 library is smaller (~2.4MB) as it links against system OpenSSL.
**Library size:**
The library is small (~500KB) as it links against system libraries (secp256k1, OpenSSL, curl) rather than including them statically. This keeps the binary size manageable while maintaining full functionality.
**Linking errors:**
Make sure to include the math library:
+1 -1
View File
@@ -1 +1 @@
0.4.5
0.4.7
+1 -1
View File
@@ -176,7 +176,7 @@ if [ "$CURRENT_DIR" != "nostr_core_lib" ]; then
echo " cd nostr_core_lib"
echo " ./build.sh"
echo " cd .."
echo " gcc your_app.c nostr_core_lib/libnostr_core_x64.a -lz -ldl -lpthread -lm -lssl -lcrypto -lcurl -o your_app"
echo " gcc your_app.c nostr_core_lib/libnostr_core_x64.a -lz -ldl -lpthread -lm -lssl -lcrypto -lcurl -lsecp256k1 -o your_app"
echo ""
exit 1
fi
+204 -7
View File
@@ -8,6 +8,7 @@
*/
#define _POSIX_C_SOURCE 200809L
#define _DEFAULT_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -109,7 +110,7 @@ void* poll_thread_func(void* arg) {
// Print menu
void print_menu() {
printf("\n=== NOSTR Relay Pool Test Menu ===\n");
printf("1. Start Pool (wss://relay.laantungir.net)\n");
printf("1. Start Pool (ws://localhost:7555)\n");
printf("2. Stop Pool\n");
printf("3. Add relay to pool\n");
printf("4. Remove relay from pool\n");
@@ -117,7 +118,8 @@ void print_menu() {
printf("6. Remove subscription\n");
printf("7. Show pool status\n");
printf("8. Test reconnection (simulate disconnect)\n");
printf("9. Exit\n");
printf("9. Publish Event\n");
printf("0. Exit\n");
printf("Choice: ");
}
@@ -405,11 +407,24 @@ void show_pool_status() {
printf("├── %s: %s\n", relay_urls[i], status_str);
// Show connection and publish error details
const char* conn_error = nostr_relay_pool_get_relay_last_connection_error(pool, relay_urls[i]);
const char* pub_error = nostr_relay_pool_get_relay_last_publish_error(pool, relay_urls[i]);
if (conn_error) {
printf("│ ├── Connection error: %s\n", conn_error);
}
if (pub_error) {
printf("│ ├── Last publish error: %s\n", pub_error);
}
const nostr_relay_stats_t* stats = nostr_relay_pool_get_relay_stats(pool, relay_urls[i]);
if (stats) {
printf("│ ├── Events received: %d\n", stats->events_received);
printf("│ ├── Connection attempts: %d\n", stats->connection_attempts);
printf("│ ├── Connection failures: %d\n", stats->connection_failures);
printf("│ ├── Events published: %d (OK: %d, Failed: %d)\n",
stats->events_published, stats->events_published_ok, stats->events_published_failed);
printf("│ ├── Ping latency: %.2f ms\n", stats->ping_latency_current);
printf("│ └── Query latency: %.2f ms\n", stats->query_latency_avg);
}
@@ -422,6 +437,148 @@ void show_pool_status() {
printf("\n");
}
// Async publish callback context
typedef struct {
int total_relays;
int responses_received;
int success_count;
time_t start_time;
} async_publish_context_t;
// Async publish callback - called for each relay response
void async_publish_callback(const char* relay_url, const char* event_id,
int success, const char* message, void* user_data) {
async_publish_context_t* ctx = (async_publish_context_t*)user_data;
ctx->responses_received++;
if (success) {
ctx->success_count++;
}
// Calculate elapsed time
time_t now = time(NULL);
double elapsed = difftime(now, ctx->start_time);
// Log to file with real-time feedback
char timestamp[26];
ctime_r(&now, timestamp);
timestamp[24] = '\0';
if (success) {
printf("✅ %s: Published successfully (%.1fs)\n", relay_url, elapsed);
dprintf(log_fd, "[%s] ✅ ASYNC: %s published successfully (%.1fs)\n",
timestamp, relay_url, elapsed);
} else {
printf("❌ %s: Failed - %s (%.1fs)\n", relay_url, message ? message : "unknown error", elapsed);
dprintf(log_fd, "[%s] ❌ ASYNC: %s failed - %s (%.1fs)\n",
timestamp, relay_url, message ? message : "unknown error", elapsed);
}
// Show progress
printf(" Progress: %d/%d responses received\n", ctx->responses_received, ctx->total_relays);
if (ctx->responses_received >= ctx->total_relays) {
printf("\n🎉 All relays responded! Final result: %d/%d successful\n",
ctx->success_count, ctx->total_relays);
dprintf(log_fd, "[%s] 🎉 ASYNC: All relays responded - %d/%d successful\n\n",
timestamp, ctx->success_count, ctx->total_relays);
}
}
// Publish test event with async callbacks
void publish_event() {
if (!pool) {
printf("❌ Pool not started\n");
return;
}
printf("\n--- Publish Test Event ---\n");
// Generate random keypair
unsigned char private_key[32], public_key[32];
if (nostr_generate_keypair(private_key, public_key) != NOSTR_SUCCESS) {
printf("❌ Failed to generate keypair\n");
return;
}
// Get current timestamp
time_t now = time(NULL);
// Format content with date/time
char content[256];
struct tm* tm_info = localtime(&now);
strftime(content, sizeof(content), "Test post at %Y-%m-%d %H:%M:%S", tm_info);
// Create kind 1 event
cJSON* event = nostr_create_and_sign_event(1, content, NULL, private_key, now);
if (!event) {
printf("❌ Failed to create event\n");
return;
}
// Get relay URLs from pool
char** relay_urls = NULL;
nostr_pool_relay_status_t* statuses = NULL;
int relay_count = nostr_relay_pool_list_relays(pool, &relay_urls, &statuses);
if (relay_count <= 0) {
printf("❌ No relays in pool\n");
cJSON_Delete(event);
return;
}
printf("📤 Publishing event to %d relay(s)...\n", relay_count);
printf("Watch for real-time responses below:\n\n");
// Setup callback context
async_publish_context_t ctx = {0};
ctx.total_relays = relay_count;
ctx.start_time = time(NULL);
// Log the event
char* event_json = cJSON_Print(event);
char timestamp[26];
ctime_r(&now, timestamp);
timestamp[24] = '\0';
dprintf(log_fd, "[%s] 📤 Publishing test event\n", timestamp);
dprintf(log_fd, "Event: %s\n\n", event_json);
free(event_json);
// Publish using async function
int sent_count = nostr_relay_pool_publish_async(pool, (const char**)relay_urls,
relay_count, event,
async_publish_callback, &ctx);
if (sent_count > 0) {
printf("📡 Event sent to %d/%d relays, waiting for responses...\n\n",
sent_count, relay_count);
// Wait for all responses or timeout (10 seconds)
time_t wait_start = time(NULL);
while (ctx.responses_received < ctx.total_relays &&
(time(NULL) - wait_start) < 10) {
// Let the polling thread process messages
usleep(100000); // 100ms
}
if (ctx.responses_received < ctx.total_relays) {
printf("\n⏰ Timeout reached - %d/%d relays responded\n",
ctx.responses_received, ctx.total_relays);
}
} else {
printf("❌ Failed to send event to any relays\n");
}
// Cleanup
for (int i = 0; i < relay_count; i++) {
free(relay_urls[i]);
}
free(relay_urls);
free(statuses);
cJSON_Delete(event);
}
int main() {
// Setup logging to file
log_fd = open("pool.log", O_WRONLY | O_CREAT | O_TRUNC, 0644);
@@ -489,14 +646,14 @@ int main() {
break;
}
if (nostr_relay_pool_add_relay(pool, "wss://relay.laantungir.net") != NOSTR_SUCCESS) {
if (nostr_relay_pool_add_relay(pool, "ws://localhost:7555") != NOSTR_SUCCESS) {
printf("❌ Failed to add default relay\n");
nostr_relay_pool_destroy(pool);
pool = NULL;
break;
}
printf("✅ Pool started with wss://relay.laantungir.net\n");
printf("✅ Pool started with ws://localhost:7555\n");
now = time(NULL);
ctime_r(&now, timestamp);
@@ -544,13 +701,49 @@ int main() {
if (url && strlen(url) > 0) {
if (nostr_relay_pool_add_relay(pool, url) == NOSTR_SUCCESS) {
printf("✅ Relay added: %s\n", url);
printf("⏳ Attempting to connect...\n");
// Give it a moment to attempt connection
sleep(2);
// Check connection status and show any errors
nostr_pool_relay_status_t status = nostr_relay_pool_get_relay_status(pool, url);
const char* error_msg = nostr_relay_pool_get_relay_last_connection_error(pool, url);
switch (status) {
case NOSTR_POOL_RELAY_CONNECTED:
printf("🟢 Successfully connected to %s\n", url);
break;
case NOSTR_POOL_RELAY_CONNECTING:
printf("🟡 Still connecting to %s...\n", url);
break;
case NOSTR_POOL_RELAY_DISCONNECTED:
printf("⚪ Disconnected from %s\n", url);
if (error_msg) {
printf(" Last error: %s\n", error_msg);
}
break;
case NOSTR_POOL_RELAY_ERROR:
printf("🔴 Connection error for %s\n", url);
if (error_msg) {
printf(" Error details: %s\n", error_msg);
}
break;
default:
printf("❓ Unknown status for %s\n", url);
break;
}
now = time(NULL);
ctime_r(&now, timestamp);
timestamp[24] = '\0';
dprintf(log_fd, "[%s] Relay added: %s\n\n", timestamp, url);
dprintf(log_fd, "[%s] Relay added: %s (status: %d)\n", timestamp, url, status);
if (error_msg) {
dprintf(log_fd, " Connection error: %s\n", error_msg);
}
dprintf(log_fd, "\n");
} else {
printf("❌ Failed to add relay\n");
printf("❌ Failed to add relay to pool\n");
}
}
free(url);
@@ -655,7 +848,11 @@ int main() {
break;
}
case '9': // Exit
case '9': // Publish Event
publish_event();
break;
case '0': // Exit
running = 0;
break;
+227 -59
View File
@@ -41,6 +41,7 @@
#define NOSTR_POOL_SUBSCRIPTION_ID_SIZE 32
#define NOSTR_POOL_PING_INTERVAL 59 // 59 seconds - keeps connections alive
#define NOSTR_POOL_MAX_PENDING_SUBSCRIPTIONS 8 // Max concurrent subscription timings per relay
#define NOSTR_POOL_MAX_PENDING_PUBLISHES 32 // Max concurrent publish operations
// High-resolution timing helper
static double get_current_time_ms(void) {
@@ -60,6 +61,17 @@ typedef struct subscription_timing {
int active;
} subscription_timing_t;
// Publish operation tracking for async callbacks
typedef struct publish_operation {
char event_id[65]; // Event ID being published
publish_response_callback_t callback; // User callback function
void* user_data; // User data for callback
time_t publish_time; // When publish was initiated
int pending_relay_count; // Number of relays still pending response
char** pending_relay_urls; // URLs of relays still pending
int total_relay_count; // Total number of relays for this publish
} publish_operation_t;
// Internal structures
typedef struct relay_connection {
char* url;
@@ -147,6 +159,10 @@ struct nostr_relay_pool {
nostr_pool_subscription_t* subscriptions[NOSTR_POOL_MAX_SUBSCRIPTIONS];
int subscription_count;
// Active publish operations for async callbacks
publish_operation_t* publish_operations[NOSTR_POOL_MAX_PENDING_PUBLISHES];
int publish_operation_count;
// Pool-wide settings
int default_timeout_ms;
};
@@ -217,6 +233,122 @@ static double remove_subscription_timing(relay_connection_t* relay, const char*
return -1.0; // Not found
}
// Helper functions for managing publish operations
static int add_publish_operation(nostr_relay_pool_t* pool, const char* event_id,
const char** relay_urls, int relay_count,
publish_response_callback_t callback, void* user_data) {
if (!pool || !event_id || !relay_urls || relay_count <= 0) return -1;
// Check if we have space for another operation
if (pool->publish_operation_count >= NOSTR_POOL_MAX_PENDING_PUBLISHES) {
return -1; // No space available
}
// Create new publish operation
publish_operation_t* op = calloc(1, sizeof(publish_operation_t));
if (!op) return -1;
// Copy event ID
strncpy(op->event_id, event_id, sizeof(op->event_id) - 1);
op->event_id[sizeof(op->event_id) - 1] = '\0';
// Set callback and user data
op->callback = callback;
op->user_data = user_data;
op->publish_time = time(NULL);
op->total_relay_count = relay_count;
op->pending_relay_count = relay_count;
// Copy relay URLs
op->pending_relay_urls = malloc(relay_count * sizeof(char*));
if (!op->pending_relay_urls) {
free(op);
return -1;
}
for (int i = 0; i < relay_count; i++) {
op->pending_relay_urls[i] = strdup(relay_urls[i]);
if (!op->pending_relay_urls[i]) {
// Cleanup on failure
for (int j = 0; j < i; j++) {
free(op->pending_relay_urls[j]);
}
free(op->pending_relay_urls);
free(op);
return -1;
}
}
// Add to pool
pool->publish_operations[pool->publish_operation_count++] = op;
return 0;
}
static publish_operation_t* find_publish_operation(nostr_relay_pool_t* pool, const char* event_id) {
if (!pool || !event_id) return NULL;
for (int i = 0; i < pool->publish_operation_count; i++) {
if (pool->publish_operations[i] &&
strcmp(pool->publish_operations[i]->event_id, event_id) == 0) {
return pool->publish_operations[i];
}
}
return NULL;
}
static void remove_publish_operation(nostr_relay_pool_t* pool, const char* event_id) {
if (!pool || !event_id) return;
for (int i = 0; i < pool->publish_operation_count; i++) {
if (pool->publish_operations[i] &&
strcmp(pool->publish_operations[i]->event_id, event_id) == 0) {
publish_operation_t* op = pool->publish_operations[i];
// Free relay URLs (only non-NULL ones)
if (op->pending_relay_urls) {
for (int j = 0; j < op->total_relay_count; j++) {
if (op->pending_relay_urls[j]) {
free(op->pending_relay_urls[j]);
op->pending_relay_urls[j] = NULL;
}
}
free(op->pending_relay_urls);
op->pending_relay_urls = NULL;
}
free(op);
// Shift remaining operations
for (int j = i; j < pool->publish_operation_count - 1; j++) {
pool->publish_operations[j] = pool->publish_operations[j + 1];
}
pool->publish_operations[--pool->publish_operation_count] = NULL;
break;
}
}
}
static int remove_relay_from_publish_operation(publish_operation_t* op, const char* relay_url) {
if (!op || !relay_url) return -1;
for (int i = 0; i < op->pending_relay_count; i++) {
if (op->pending_relay_urls[i] && strcmp(op->pending_relay_urls[i], relay_url) == 0) {
// Free this relay URL
free(op->pending_relay_urls[i]);
op->pending_relay_urls[i] = NULL; // Mark as freed
// Shift remaining URLs
for (int j = i; j < op->pending_relay_count - 1; j++) {
op->pending_relay_urls[j] = op->pending_relay_urls[j + 1];
}
op->pending_relay_urls[op->pending_relay_count - 1] = NULL; // Clear the last slot
op->pending_relay_count--;
return op->pending_relay_count; // Return remaining count
}
}
return -1; // Relay not found
}
// Helper function to ensure relay connection
static int ensure_relay_connection(relay_connection_t* relay) {
if (!relay) {
@@ -559,6 +691,20 @@ void nostr_relay_pool_destroy(nostr_relay_pool_t* pool) {
}
}
// Clean up all pending publish operations
for (int i = 0; i < pool->publish_operation_count; i++) {
if (pool->publish_operations[i]) {
publish_operation_t* op = pool->publish_operations[i];
// Free relay URLs
for (int j = 0; j < op->total_relay_count; j++) {
free(op->pending_relay_urls[j]);
}
free(op->pending_relay_urls);
free(op);
}
}
// Close all relay connections
for (int i = 0; i < pool->relay_count; i++) {
if (pool->relays[i]) {
@@ -902,25 +1048,48 @@ static void process_relay_message(nostr_relay_pool_t* pool, relay_connection_t*
} else if (strcmp(msg_type, "OK") == 0) {
// Handle OK response: ["OK", event_id, true/false, message]
if (cJSON_IsArray(parsed) && cJSON_GetArraySize(parsed) >= 3) {
cJSON* event_id_json = cJSON_GetArrayItem(parsed, 1);
cJSON* success_flag = cJSON_GetArrayItem(parsed, 2);
if (cJSON_IsBool(success_flag)) {
if (cJSON_IsTrue(success_flag)) {
if (cJSON_IsString(event_id_json) && cJSON_IsBool(success_flag)) {
const char* event_id = cJSON_GetStringValue(event_id_json);
int success = cJSON_IsTrue(success_flag);
const char* error_message = NULL;
// Extract error message if available
if (!success && cJSON_GetArraySize(parsed) >= 4) {
cJSON* error_msg = cJSON_GetArrayItem(parsed, 3);
if (cJSON_IsString(error_msg)) {
error_message = cJSON_GetStringValue(error_msg);
}
}
// Update relay statistics
if (success) {
relay->stats.events_published_ok++;
} else {
relay->stats.events_published_failed++;
// Store error message if available
if (cJSON_GetArraySize(parsed) >= 4) {
cJSON* error_msg = cJSON_GetArrayItem(parsed, 3);
if (cJSON_IsString(error_msg)) {
const char* msg = cJSON_GetStringValue(error_msg);
if (msg) {
strncpy(relay->last_publish_error, msg,
sizeof(relay->last_publish_error) - 1);
relay->last_publish_error[sizeof(relay->last_publish_error) - 1] = '\0';
relay->last_publish_error_time = time(NULL);
}
}
// Store error message for legacy API
if (error_message) {
strncpy(relay->last_publish_error, error_message,
sizeof(relay->last_publish_error) - 1);
relay->last_publish_error[sizeof(relay->last_publish_error) - 1] = '\0';
relay->last_publish_error_time = time(NULL);
}
}
// Check for async publish operation callback
publish_operation_t* op = find_publish_operation(pool, event_id);
if (op && op->callback) {
// Call the user's callback
op->callback(relay->url, event_id, success, error_message, op->user_data);
// Remove this relay from the pending list
int remaining = remove_relay_from_publish_operation(op, relay->url);
// If no more relays pending, remove the operation
if (remaining == 0) {
remove_publish_operation(pool, event_id);
}
}
}
@@ -1113,16 +1282,35 @@ cJSON* nostr_relay_pool_get_event(
return result;
}
int nostr_relay_pool_publish(
int nostr_relay_pool_publish_async(
nostr_relay_pool_t* pool,
const char** relay_urls,
int relay_count,
cJSON* event) {
cJSON* event,
publish_response_callback_t callback,
void* user_data) {
if (!pool || !relay_urls || relay_count <= 0 || !event) {
return -1;
}
// Extract event ID for tracking
cJSON* event_id_json = cJSON_GetObjectItem(event, "id");
if (!event_id_json || !cJSON_IsString(event_id_json)) {
return -1; // Event must have an ID
}
const char* event_id = cJSON_GetStringValue(event_id_json);
// Add publish operation for tracking (only if callback provided)
publish_operation_t* op = NULL;
if (callback) {
if (add_publish_operation(pool, event_id, relay_urls, relay_count, callback, user_data) != 0) {
return -1; // Failed to add operation
}
op = find_publish_operation(pool, event_id);
}
int success_count = 0;
for (int i = 0; i < relay_count; i++) {
@@ -1135,55 +1323,35 @@ int nostr_relay_pool_publish(
}
if (relay && ensure_relay_connection(relay) == 0) {
double start_time_ms = get_current_time_ms();
// Send EVENT message
if (nostr_relay_send_event(relay->ws_client, event) >= 0) {
relay->stats.events_published++;
// Wait for OK response
char buffer[1024];
time_t wait_start = time(NULL);
int got_response = 0;
while (time(NULL) - wait_start < 5 && !got_response) { // 5 second timeout
int len = nostr_ws_receive(relay->ws_client, buffer, sizeof(buffer) - 1, 1000);
if (len > 0) {
buffer[len] = '\0';
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) {
// Handle OK response
if (cJSON_IsArray(parsed) && cJSON_GetArraySize(parsed) >= 3) {
cJSON* success_flag = cJSON_GetArrayItem(parsed, 2);
if (cJSON_IsBool(success_flag) && cJSON_IsTrue(success_flag)) {
success_count++;
relay->stats.events_published_ok++;
// Update publish latency statistics
double latency_ms = get_current_time_ms() - start_time_ms;
if (relay->stats.publish_samples == 0) {
relay->stats.publish_latency_avg = latency_ms;
} else {
relay->stats.publish_latency_avg =
(relay->stats.publish_latency_avg * relay->stats.publish_samples + latency_ms) /
(relay->stats.publish_samples + 1);
}
relay->stats.publish_samples++;
} else {
relay->stats.events_published_failed++;
}
}
got_response = 1;
}
if (msg_type) free(msg_type);
if (parsed) cJSON_Delete(parsed);
}
success_count++;
} else {
// If send failed and we have a callback, notify immediately
if (callback && op) {
callback(relay_urls[i], event_id, 0, "Failed to send event to relay", user_data);
// Remove this relay from the pending operation
int remaining = remove_relay_from_publish_operation(op, relay_urls[i]);
if (remaining == 0) {
remove_publish_operation(pool, event_id);
op = NULL; // Mark as removed to prevent double-free
}
}
}
} else {
// Connection failed - notify callback immediately if provided
if (callback && op) {
callback(relay_urls[i], event_id, 0, "Failed to connect to relay", user_data);
// Remove this relay from the pending operation
int remaining = remove_relay_from_publish_operation(op, relay_urls[i]);
if (remaining == 0) {
remove_publish_operation(pool, event_id);
op = NULL; // Mark as removed to prevent double-free
}
}
}
}
+1 -1
View File
@@ -13,7 +13,7 @@ extern "C" {
#endif
// NIP-04 constants
// #define NOSTR_NIP04_MAX_PLAINTEXT_SIZE 65535
// #define NOSTR_NIP04_MAX_PLAINTEXT_SIZE 1048576 // 1MB
// NIP-04 Constants
// #define NOSTR_NIP04_MAX_PLAINTEXT_SIZE 16777216 // 16MB
// #define NOSTR_NIP04_MAX_ENCRYPTED_SIZE 22369621 // ~21.3MB (accounts for base64 overhead + IV)
+38 -39
View File
@@ -75,54 +75,55 @@ static size_t calc_padded_len(size_t unpadded_len) {
return chunk * ((unpadded_len - 1) / chunk + 1);
}
// NIP-44 padding (per spec)
// NIP-44 padding (modified to support 1MB payloads with 32-bit length field)
static unsigned char* pad_plaintext(const char* plaintext, size_t* padded_len) {
size_t unpadded_len = strlen(plaintext);
if (unpadded_len > 65535) {
if (unpadded_len > 1048576) {
return NULL;
}
size_t padded_content_len = calc_padded_len(unpadded_len);
*padded_len = padded_content_len + 2; // Add 2 bytes for the length prefix
*padded_len = padded_content_len + 4; // Add 4 bytes for the length prefix (32-bit)
unsigned char* padded = malloc(*padded_len);
if (!padded) return NULL;
// Write length prefix (big-endian u16)
padded[0] = (unpadded_len >> 8) & 0xFF;
padded[1] = unpadded_len & 0xFF;
// Write length prefix (big-endian u32)
padded[0] = (unpadded_len >> 24) & 0xFF;
padded[1] = (unpadded_len >> 16) & 0xFF;
padded[2] = (unpadded_len >> 8) & 0xFF;
padded[3] = unpadded_len & 0xFF;
// Copy plaintext and add zero-padding
memcpy(padded + 2, plaintext, unpadded_len);
memset(padded + 2 + unpadded_len, 0, padded_content_len - unpadded_len);
memcpy(padded + 4, plaintext, unpadded_len);
memset(padded + 4 + unpadded_len, 0, padded_content_len - unpadded_len);
return padded;
}
// NIP-44 unpadding (per spec)
// NIP-44 unpadding (modified to support 32-bit length field)
static char* unpad_plaintext(const unsigned char* padded, size_t padded_len) {
if (padded_len < 4) return NULL;
if (padded_len < 6) return NULL; // Minimum: 4 bytes length + 2 bytes content
size_t unpadded_len = (padded[0] << 8) | padded[1];
size_t unpadded_len = (padded[0] << 24) | (padded[1] << 16) | (padded[2] << 8) | padded[3];
size_t expected_padded_len = calc_padded_len(unpadded_len);
if (padded_len != expected_padded_len + 2) {
if (padded_len != expected_padded_len + 4) {
return NULL;
}
if (unpadded_len > padded_len - 2) {
if (unpadded_len > padded_len - 4) {
return NULL;
}
char* plaintext = malloc(unpadded_len + 1);
if (!plaintext) return NULL;
// Handle empty message case (unpadded_len can be 0)
if (unpadded_len > 0) {
memcpy(plaintext, padded + 2, unpadded_len);
memcpy(plaintext, padded + 4, unpadded_len);
}
plaintext[unpadded_len] = '\0';
return plaintext;
}
@@ -131,15 +132,15 @@ static char* unpad_plaintext(const unsigned char* padded, size_t padded_len) {
// =============================================================================
int nostr_nip44_encrypt_with_nonce(const unsigned char* sender_private_key,
const unsigned char* recipient_public_key,
const char* plaintext,
const unsigned char* nonce,
char* output,
size_t output_size) {
const unsigned char* recipient_public_key,
const char* plaintext,
const unsigned char* nonce,
char* output,
size_t output_size) {
if (!sender_private_key || !recipient_public_key || !plaintext || !nonce || !output) {
return NOSTR_ERROR_INVALID_INPUT;
}
size_t plaintext_len = strlen(plaintext);
if (plaintext_len > NOSTR_NIP44_MAX_PLAINTEXT_SIZE) {
return NOSTR_ERROR_NIP44_BUFFER_TOO_SMALL;
@@ -260,12 +261,12 @@ int nostr_nip44_encrypt_with_nonce(const unsigned char* sender_private_key,
free(aad_data);
return NOSTR_ERROR_MEMORY_FAILED;
}
payload[0] = 0x02; // NIP-44 version 2
memcpy(payload + 1, nonce_copy, 32);
memcpy(payload + 33, ciphertext, padded_len);
memcpy(payload + 33 + padded_len, mac, 32);
// Base64 encode
size_t b64_len = ((payload_len + 2) / 3) * 4 + 1;
if (b64_len > output_size) {
@@ -315,18 +316,18 @@ int nostr_nip44_encrypt_with_nonce(const unsigned char* sender_private_key,
}
int nostr_nip44_encrypt(const unsigned char* sender_private_key,
const unsigned char* recipient_public_key,
const char* plaintext,
char* output,
size_t output_size) {
const unsigned char* recipient_public_key,
const char* plaintext,
char* output,
size_t output_size) {
// Generate random nonce and call the _with_nonce version
unsigned char nonce[32];
if (nostr_secp256k1_get_random_bytes(nonce, 32) != 1) {
return NOSTR_ERROR_CRYPTO_FAILED;
}
return nostr_nip44_encrypt_with_nonce(sender_private_key, recipient_public_key,
plaintext, nonce, output, output_size);
return nostr_nip44_encrypt_with_nonce(sender_private_key, recipient_public_key,
plaintext, nonce, output, output_size);
}
int nostr_nip44_decrypt(const unsigned char* recipient_private_key,
@@ -421,7 +422,6 @@ int nostr_nip44_decrypt(const unsigned char* recipient_private_key,
return NOSTR_ERROR_CRYPTO_FAILED;
}
// Constant-time MAC verification
// Constant-time MAC verification
if (!constant_time_compare(received_mac, computed_mac, 32)) {
memory_clear(shared_secret, 32);
@@ -446,7 +446,7 @@ int nostr_nip44_decrypt(const unsigned char* recipient_private_key,
free(payload);
return NOSTR_ERROR_MEMORY_FAILED;
}
if (chacha20_encrypt(chacha_key, 0, chacha_nonce, ciphertext, padded_plaintext, ciphertext_len) != 0) {
memory_clear(shared_secret, 32);
memory_clear(conversation_key, 32);
@@ -459,7 +459,6 @@ int nostr_nip44_decrypt(const unsigned char* recipient_private_key,
return NOSTR_ERROR_CRYPTO_FAILED;
}
// Step 8: Remove padding according to NIP-44 spec
// Step 8: Remove padding according to NIP-44 spec
char* plaintext = unpad_plaintext(padded_plaintext, ciphertext_len);
if (!plaintext) {
+1 -1
View File
@@ -13,7 +13,7 @@ extern "C" {
#endif
// NIP-44 constants
// #define NOSTR_NIP44_MAX_PLAINTEXT_SIZE 65535
// #define NOSTR_NIP44_MAX_PLAINTEXT_SIZE 1048576
/**
* NIP-44: Encrypt a message using ECDH + ChaCha20 + HMAC
+3 -3
View File
@@ -72,11 +72,11 @@
#define NIP05_DEFAULT_TIMEOUT 10
// NIP-04 Constants
#define NOSTR_NIP04_MAX_PLAINTEXT_SIZE 16777216 // 16MB
#define NOSTR_NIP04_MAX_PLAINTEXT_SIZE 1048576 // 1MB
#define NOSTR_NIP04_MAX_ENCRYPTED_SIZE 22369621 // ~21.3MB (accounts for base64 overhead + IV)
// NIP-44 Constants
#define NOSTR_NIP44_MAX_PLAINTEXT_SIZE 65536 // 64KB max plaintext (matches crypto header)
// NIP-44 Constants
#define NOSTR_NIP44_MAX_PLAINTEXT_SIZE 1048576 // 1MB max plaintext (32-bit length field)
// Forward declaration for cJSON (to avoid requiring cJSON.h in header)
struct cJSON;
+16 -4
View File
@@ -2,10 +2,10 @@
#define NOSTR_CORE_H
// Version information (auto-updated by increment_and_push.sh)
#define VERSION "v0.4.5"
#define VERSION "v0.4.7"
#define VERSION_MAJOR 0
#define VERSION_MINOR 4
#define VERSION_PATCH 5
#define VERSION_PATCH 7
/*
* NOSTR Core Library - Complete API Reference
@@ -288,11 +288,23 @@ cJSON* nostr_relay_pool_get_event(
int relay_count,
cJSON* filter,
int timeout_ms);
int nostr_relay_pool_publish(
// Async publish callback typedef
typedef void (*publish_response_callback_t)(
const char* relay_url,
const char* event_id,
int success, // 1 for OK, 0 for rejection
const char* message, // Error message if rejected, NULL if success
void* user_data
);
// Async publish function (only async version available)
int nostr_relay_pool_publish_async(
nostr_relay_pool_t* pool,
const char** relay_urls,
int relay_count,
cJSON* event);
cJSON* event,
publish_response_callback_t callback,
void* user_data);
// Status and statistics functions
nostr_pool_relay_status_t nostr_relay_pool_get_relay_status(
+34 -1553
View File
File diff suppressed because it is too large Load Diff
+93
View File
@@ -0,0 +1,93 @@
#define _DEFAULT_SOURCE
#include "../nostr_core/nostr_core.h"
#include "../cjson/cJSON.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
// Test callback function
static int callback_count = 0;
static int success_count = 0;
void test_callback(const char* relay_url, const char* event_id,
int success, const char* message, void* user_data) {
callback_count++;
if (success) {
success_count++;
}
printf("📡 Callback %d: Relay %s, Event %s, Success: %s\n",
callback_count, relay_url, event_id, success ? "YES" : "NO");
if (message) {
printf(" Message: %s\n", message);
}
// Mark test as complete when we get the expected number of callbacks
int* expected_callbacks = (int*)user_data;
if (callback_count >= *expected_callbacks) {
printf("✅ All callbacks received!\n");
}
}
int main() {
printf("🧪 Testing Async Publish Functionality\n");
printf("=====================================\n");
// Create pool
nostr_relay_pool_t* pool = nostr_relay_pool_create(NULL);
if (!pool) {
printf("❌ Failed to create pool\n");
return 1;
}
// Create a test event
cJSON* event = cJSON_CreateObject();
cJSON_AddStringToObject(event, "id", "test_event_12345");
cJSON_AddNumberToObject(event, "kind", 1);
cJSON_AddStringToObject(event, "content", "Test async publish");
cJSON_AddNumberToObject(event, "created_at", time(NULL));
cJSON_AddStringToObject(event, "pubkey", "test_pubkey");
cJSON_AddStringToObject(event, "sig", "test_signature");
// Test with non-existent relays (should trigger connection failure callbacks)
const char* test_relays[] = {
"ws://nonexistent1.example.com",
"ws://nonexistent2.example.com"
};
int expected_callbacks = 2;
printf("🚀 Testing async publish with connection failure callbacks...\n");
// Call async publish
int sent_count = nostr_relay_pool_publish_async(
pool, test_relays, 2, event, test_callback, &expected_callbacks);
printf("📊 Sent to %d relays\n", sent_count);
// Wait a bit for callbacks (connection failures should be immediate)
printf("⏳ Waiting for callbacks...\n");
for (int i = 0; i < 10 && callback_count < expected_callbacks; i++) {
nostr_relay_pool_poll(pool, 100);
usleep(100000); // 100ms
}
printf("\n📈 Results:\n");
printf(" Callbacks received: %d/%d\n", callback_count, expected_callbacks);
printf(" Successful publishes: %d\n", success_count);
// Test backward compatibility with synchronous version
printf("\n🔄 Testing backward compatibility (sync version)...\n");
int sync_result = nostr_relay_pool_publish_async(pool, test_relays, 2, event, NULL, NULL);
printf(" Sync publish result: %d successful publishes\n", sync_result);
// Cleanup
cJSON_Delete(event);
nostr_relay_pool_destroy(pool);
printf("\n✅ Async publish test completed!\n");
printf(" - Async callbacks: %s\n", callback_count >= expected_callbacks ? "PASS" : "FAIL");
printf(" - Backward compatibility: %s\n", sync_result >= 0 ? "PASS" : "FAIL");
return (callback_count >= expected_callbacks && sync_result >= 0) ? 0 : 1;
}
+49
View File
@@ -0,0 +1,49 @@
#define _DEFAULT_SOURCE
#include "../nostr_core/nostr_core.h"
#include "../cjson/cJSON.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int main() {
printf("🧪 Backward Compatibility Test\n");
printf("===============================\n");
// Create pool
nostr_relay_pool_t* pool = nostr_relay_pool_create(NULL);
if (!pool) {
printf("❌ Failed to create pool\n");
return 1;
}
// Create a test event
cJSON* event = cJSON_CreateObject();
cJSON_AddStringToObject(event, "id", "test_event_sync");
cJSON_AddNumberToObject(event, "kind", 1);
cJSON_AddStringToObject(event, "content", "Test sync publish");
cJSON_AddNumberToObject(event, "created_at", time(NULL));
cJSON_AddStringToObject(event, "pubkey", "test_pubkey");
cJSON_AddStringToObject(event, "sig", "test_signature");
// Test with non-existent relay (should return 0 successful publishes)
const char* test_relays[] = {"ws://nonexistent.example.com"};
printf("🚀 Testing synchronous publish (backward compatibility)...\n");
// Call synchronous publish (old API)
int result = nostr_relay_pool_publish_async(pool, test_relays, 1, event, NULL, NULL);
printf("📊 Synchronous publish result: %d successful publishes\n", result);
// Cleanup
cJSON_Delete(event);
nostr_relay_pool_destroy(pool);
printf("\n✅ Backward compatibility test completed!\n");
printf(" Expected: 0 successful publishes (connection failure)\n");
printf(" Actual: %d successful publishes\n", result);
printf(" Result: %s\n", result == 0 ? "PASS" : "FAIL");
return result == 0 ? 0 : 1;
}
+3 -3
View File
@@ -671,8 +671,8 @@ int test_vector_7_10kb_payload(void) {
printf("Last 80 chars: \"...%.80s\"\n", encrypted + encrypted_len - 80);
printf("\n");
// Test decryption with our ciphertext
char* decrypted = malloc(NOSTR_NIP04_MAX_PLAINTEXT_SIZE);
// Test decryption with our ciphertext - allocate larger buffer for safety
char* decrypted = malloc(NOSTR_NIP04_MAX_PLAINTEXT_SIZE + 1024); // 1MB + 1KB extra
if (!decrypted) {
printf("❌ MEMORY ALLOCATION FAILED for decrypted buffer\n");
free(large_plaintext);
@@ -680,7 +680,7 @@ int test_vector_7_10kb_payload(void) {
return 0;
}
printf("Testing decryption of 1MB ciphertext (Bob decrypts from Alice)...\n");
result = nostr_nip04_decrypt(sk2, pk1, encrypted, decrypted, NOSTR_NIP04_MAX_PLAINTEXT_SIZE);
result = nostr_nip04_decrypt(sk2, pk1, encrypted, decrypted, NOSTR_NIP04_MAX_PLAINTEXT_SIZE + 1024);
if (result != NOSTR_SUCCESS) {
printf("❌ 1MB DECRYPTION FAILED: %s\n", nostr_strerror(result));
+162 -44
View File
@@ -69,6 +69,13 @@ static nip44_test_vector_t test_vectors[] = {
"4444444444444444444444444444444444444444444444444444444444444444",
"",
NULL
},
{
"1MB payload test",
"91ba716fa9e7ea2fcbad360cf4f8e0d312f73984da63d90f524ad61a6a1e7dbe", // Same keys as basic test
"96f6fa197aa07477ab88f6981118466ae3a982faab8ad5db9d5426870c73d220",
NULL, // Will be generated dynamically
NULL
}
};
@@ -86,76 +93,144 @@ static int hex_to_bytes(const char* hex, unsigned char* bytes, size_t len) {
static int test_nip44_round_trip(const nip44_test_vector_t* tv) {
printf("Test: %s\n", tv->name);
// Parse keys - both private keys
unsigned char sender_private_key[32];
unsigned char recipient_private_key[32];
if (hex_to_bytes(tv->sender_private_key_hex, sender_private_key, 32) != 0) {
printf(" FAIL: Failed to parse sender private key\n");
return -1;
}
if (hex_to_bytes(tv->recipient_private_key_hex, recipient_private_key, 32) != 0) {
printf(" FAIL: Failed to parse recipient private key\n");
return -1;
}
// Generate the public keys from the private keys
unsigned char sender_public_key[32];
unsigned char recipient_public_key[32];
if (nostr_ec_public_key_from_private_key(sender_private_key, sender_public_key) != 0) {
printf(" FAIL: Failed to derive sender public key\n");
return -1;
}
if (nostr_ec_public_key_from_private_key(recipient_private_key, recipient_public_key) != 0) {
printf(" FAIL: Failed to derive recipient public key\n");
return -1;
}
// Test encryption
char encrypted[8192];
int encrypt_result = nostr_nip44_encrypt(
sender_private_key,
recipient_public_key,
tv->plaintext,
encrypted,
sizeof(encrypted)
);
if (encrypt_result != NOSTR_SUCCESS) {
printf(" FAIL: Encryption - Expected: %d, Actual: %d\n", NOSTR_SUCCESS, encrypt_result);
// Special handling for large payload tests
char* test_plaintext;
if (strcmp(tv->name, "1MB payload test") == 0) {
// Generate exactly 1MB (1,048,576 bytes) of predictable content
const size_t payload_size = 1048576;
test_plaintext = malloc(payload_size + 1);
if (!test_plaintext) {
printf(" FAIL: Memory allocation failed for 1MB test payload\n");
return -1;
}
// Fill with a predictable pattern: "ABCDEFGH01234567" repeated
const char* pattern = "ABCDEFGH01234567"; // 16 bytes
const size_t pattern_len = 16;
for (size_t i = 0; i < payload_size; i += pattern_len) {
size_t copy_len = (i + pattern_len <= payload_size) ? pattern_len : payload_size - i;
memcpy(test_plaintext + i, pattern, copy_len);
}
test_plaintext[payload_size] = '\0';
printf(" Generated 1MB test payload (%zu bytes)\n", payload_size);
printf(" Pattern: \"%s\" repeated\n", pattern);
printf(" First 64 chars: \"%.64s...\"\n", test_plaintext);
printf(" Last 64 chars: \"...%.64s\"\n", test_plaintext + payload_size - 64);
} else {
test_plaintext = (char*)tv->plaintext;
}
// Debug: Check plaintext length
size_t plaintext_len = strlen(test_plaintext);
printf(" Plaintext length: %zu bytes\n", plaintext_len);
printf(" Output buffer size: %zu bytes\n", (size_t)10485760);
// Test encryption - use larger buffer for 1MB+ payloads (10MB for NIP-44 overhead)
char* encrypted = malloc(10485760); // 10MB buffer for large payloads
if (!encrypted) {
printf(" FAIL: Memory allocation failed for encrypted buffer\n");
if (strcmp(tv->name, "0.5MB payload test") == 0) free(test_plaintext);
return -1;
}
// For large payloads, use _with_nonce to avoid random generation issues
unsigned char fixed_nonce[32] = {0};
int encrypt_result = nostr_nip44_encrypt_with_nonce(
sender_private_key,
recipient_public_key,
test_plaintext,
fixed_nonce,
encrypted,
10485760
);
if (encrypt_result != NOSTR_SUCCESS) {
printf(" FAIL: Encryption - Expected: %d, Actual: %d\n", NOSTR_SUCCESS, encrypt_result);
if (strcmp(tv->name, "1MB payload test") == 0) free(test_plaintext);
free(encrypted);
return -1;
}
// Test decryption - use recipient private key + sender public key
char decrypted[8192];
char* decrypted = malloc(1048576 + 1); // 1MB + 1 for null terminator
if (!decrypted) {
printf(" FAIL: Memory allocation failed for decrypted buffer\n");
if (strcmp(tv->name, "1MB payload test") == 0) free(test_plaintext);
free(encrypted);
return -1;
}
int decrypt_result = nostr_nip44_decrypt(
recipient_private_key,
sender_public_key,
encrypted,
decrypted,
sizeof(decrypted)
1048576 + 1
);
if (decrypt_result != NOSTR_SUCCESS) {
printf(" FAIL: Decryption - Expected: %d, Actual: %d\n", NOSTR_SUCCESS, decrypt_result);
if (strcmp(tv->name, "1MB payload test") == 0) free(test_plaintext);
free(encrypted);
free(decrypted);
return -1;
}
// Verify round-trip
if (strcmp(tv->plaintext, decrypted) != 0) {
if (strcmp(test_plaintext, decrypted) != 0) {
printf(" FAIL: Round-trip mismatch\n");
printf(" Expected: \"%s\"\n", tv->plaintext);
printf(" Expected: \"%s\"\n", test_plaintext);
printf(" Actual: \"%s\"\n", decrypted);
if (strcmp(tv->name, "1MB payload test") == 0) free(test_plaintext);
free(encrypted);
free(decrypted);
return -1;
}
printf(" PASS: Expected: \"%s\", Actual: \"%s\"\n", tv->plaintext, decrypted);
printf(" Encrypted output: %s\n", encrypted);
if (strcmp(tv->name, "1MB payload test") == 0) {
printf(" ✅ 1MB payload round-trip: PASS\n");
printf(" ✅ Content verification: All %zu bytes match perfectly!\n", strlen(test_plaintext));
printf(" Encrypted length: %zu bytes\n", strlen(encrypted));
printf(" 🎉 1MB NIP-44 STRESS TEST COMPLETED SUCCESSFULLY! 🎉\n");
} else {
printf(" PASS: Expected: \"%s\", Actual: \"%s\"\n", test_plaintext, decrypted);
printf(" Encrypted output: %s\n", encrypted);
}
if (strcmp(tv->name, "1MB payload test") == 0) free(test_plaintext);
free(encrypted);
free(decrypted);
return 0;
}
@@ -241,13 +316,17 @@ static int test_nip44_decryption_vector(const nip44_test_vector_t* tv) {
}
// Test decryption of known vector
char decrypted[8192];
char* decrypted = malloc(1048576 + 1); // 1MB + 1 for null terminator
if (!decrypted) {
printf(" FAIL: Memory allocation failed for decrypted buffer\n");
return -1;
}
int decrypt_result = nostr_nip44_decrypt(
recipient_private_key,
sender_public_key,
tv->expected_encrypted,
decrypted,
sizeof(decrypted)
1048576 + 1
);
if (decrypt_result != NOSTR_SUCCESS) {
@@ -265,7 +344,9 @@ static int test_nip44_decryption_vector(const nip44_test_vector_t* tv) {
}
printf(" PASS: Expected: \"%s\", Actual: \"%s\"\n", tv->plaintext, decrypted);
free(decrypted);
return 0;
}
@@ -287,11 +368,20 @@ static int test_nip44_encryption_variability() {
}
// Encrypt the same message multiple times
char encrypted1[8192], encrypted2[8192], encrypted3[8192];
char* encrypted1 = malloc(2097152); // 2MB buffer
char* encrypted2 = malloc(2097152);
char* encrypted3 = malloc(2097152);
if (!encrypted1 || !encrypted2 || !encrypted3) {
printf(" FAIL: Memory allocation failed for encrypted buffers\n");
free(encrypted1);
free(encrypted2);
free(encrypted3);
return -1;
}
int result1 = nostr_nip44_encrypt(sender_key, recipient_pubkey, test_message, encrypted1, sizeof(encrypted1));
int result2 = nostr_nip44_encrypt(sender_key, recipient_pubkey, test_message, encrypted2, sizeof(encrypted2));
int result3 = nostr_nip44_encrypt(sender_key, recipient_pubkey, test_message, encrypted3, sizeof(encrypted3));
int result1 = nostr_nip44_encrypt(sender_key, recipient_pubkey, test_message, encrypted1, 2097152);
int result2 = nostr_nip44_encrypt(sender_key, recipient_pubkey, test_message, encrypted2, 2097152);
int result3 = nostr_nip44_encrypt(sender_key, recipient_pubkey, test_message, encrypted3, 2097152);
if (result1 != NOSTR_SUCCESS || result2 != NOSTR_SUCCESS || result3 != NOSTR_SUCCESS) {
printf(" FAIL: Encryption failed - Results: %d, %d, %d\n", result1, result2, result3);
@@ -304,6 +394,9 @@ static int test_nip44_encryption_variability() {
printf(" Encryption 1: %.50s...\n", encrypted1);
printf(" Encryption 2: %.50s...\n", encrypted2);
printf(" Encryption 3: %.50s...\n", encrypted3);
free(encrypted1);
free(encrypted2);
free(encrypted3);
return -1;
}
@@ -314,11 +407,23 @@ static int test_nip44_encryption_variability() {
return -1;
}
char decrypted1[8192], decrypted2[8192], decrypted3[8192];
int decrypt1 = nostr_nip44_decrypt(recipient_key, sender_pubkey, encrypted1, decrypted1, sizeof(decrypted1));
int decrypt2 = nostr_nip44_decrypt(recipient_key, sender_pubkey, encrypted2, decrypted2, sizeof(decrypted2));
int decrypt3 = nostr_nip44_decrypt(recipient_key, sender_pubkey, encrypted3, decrypted3, sizeof(decrypted3));
char* decrypted1 = malloc(1048576 + 1);
char* decrypted2 = malloc(1048576 + 1);
char* decrypted3 = malloc(1048576 + 1);
if (!decrypted1 || !decrypted2 || !decrypted3) {
printf(" FAIL: Memory allocation failed for decrypted buffers\n");
free(encrypted1);
free(encrypted2);
free(encrypted3);
free(decrypted1);
free(decrypted2);
free(decrypted3);
return -1;
}
int decrypt1 = nostr_nip44_decrypt(recipient_key, sender_pubkey, encrypted1, decrypted1, 1048576 + 1);
int decrypt2 = nostr_nip44_decrypt(recipient_key, sender_pubkey, encrypted2, decrypted2, 1048576 + 1);
int decrypt3 = nostr_nip44_decrypt(recipient_key, sender_pubkey, encrypted3, decrypted3, 1048576 + 1);
if (decrypt1 != NOSTR_SUCCESS || decrypt2 != NOSTR_SUCCESS || decrypt3 != NOSTR_SUCCESS) {
printf(" FAIL: Decryption failed - Results: %d, %d, %d\n", decrypt1, decrypt2, decrypt3);
@@ -331,12 +436,25 @@ static int test_nip44_encryption_variability() {
printf(" Decrypted1: \"%s\"\n", decrypted1);
printf(" Decrypted2: \"%s\"\n", decrypted2);
printf(" Decrypted3: \"%s\"\n", decrypted3);
free(encrypted1);
free(encrypted2);
free(encrypted3);
free(decrypted1);
free(decrypted2);
free(decrypted3);
return -1;
}
printf(" PASS: All encryptions different, all decrypt to: \"%s\"\n", test_message);
printf(" Sample ciphertext lengths: %zu, %zu, %zu bytes\n", strlen(encrypted1), strlen(encrypted2), strlen(encrypted3));
free(encrypted1);
free(encrypted2);
free(encrypted3);
free(decrypted1);
free(decrypted2);
free(decrypted3);
return 0;
}
+73
View File
@@ -0,0 +1,73 @@
#define _DEFAULT_SOURCE
#include "../nostr_core/nostr_core.h"
#include "../cjson/cJSON.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
// Test callback function
static int callback_count = 0;
void test_callback(const char* relay_url, const char* event_id,
int success, const char* message, void* user_data) {
(void)event_id; // Suppress unused parameter warning
(void)user_data; // Suppress unused parameter warning
callback_count++;
printf("📡 Callback %d: Relay %s, Success: %s\n",
callback_count, relay_url, success ? "YES" : "NO");
if (message) {
printf(" Message: %s\n", message);
}
}
int main() {
printf("🧪 Simple Async Publish Test\n");
printf("============================\n");
// Create pool
nostr_relay_pool_t* pool = nostr_relay_pool_create(NULL);
if (!pool) {
printf("❌ Failed to create pool\n");
return 1;
}
// Create a test event
cJSON* event = cJSON_CreateObject();
cJSON_AddStringToObject(event, "id", "test_event_simple");
cJSON_AddNumberToObject(event, "kind", 1);
cJSON_AddStringToObject(event, "content", "Test async publish");
cJSON_AddNumberToObject(event, "created_at", time(NULL));
cJSON_AddStringToObject(event, "pubkey", "test_pubkey");
cJSON_AddStringToObject(event, "sig", "test_signature");
// Test with non-existent relay (should trigger connection failure callback)
const char* test_relays[] = {"ws://nonexistent.example.com"};
printf("🚀 Testing async publish...\n");
// Call async publish
int sent_count = nostr_relay_pool_publish_async(
pool, test_relays, 1, event, test_callback, NULL);
printf("📊 Sent to %d relays\n", sent_count);
// Wait a bit for callback
printf("⏳ Waiting for callback...\n");
for (int i = 0; i < 5 && callback_count == 0; i++) {
nostr_relay_pool_poll(pool, 100);
usleep(100000); // 100ms
}
printf("\n📈 Results:\n");
printf(" Callbacks received: %d\n", callback_count);
// Cleanup
cJSON_Delete(event);
nostr_relay_pool_destroy(pool);
printf("\n✅ Simple async test completed!\n");
return callback_count > 0 ? 0 : 1;
}