/* * NOSTR Core Library - NIP-001: Basic Protocol Flow * * Event creation, signing, serialization and core protocol functions */ #include "nip001.h" #include "nostr_crypto.h" #include "../cjson/cJSON.h" #include #include #include #include // Declare utility functions void nostr_bytes_to_hex(const unsigned char* bytes, size_t len, char* hex); int nostr_hex_to_bytes(const char* hex, unsigned char* bytes, size_t len); /** * Initialize the NOSTR library */ int nostr_init(void) { if (nostr_crypto_init() != 0) { return NOSTR_ERROR_CRYPTO_FAILED; } return NOSTR_SUCCESS; } /** * Cleanup the NOSTR library */ void nostr_cleanup(void) { nostr_crypto_cleanup(); } /** * Convert error code to human-readable string */ const char* nostr_strerror(int error_code) { switch (error_code) { case NOSTR_SUCCESS: return "Success"; case NOSTR_ERROR_INVALID_INPUT: return "Invalid input"; case NOSTR_ERROR_CRYPTO_FAILED: return "Cryptographic operation failed"; case NOSTR_ERROR_MEMORY_FAILED: return "Memory allocation failed"; case NOSTR_ERROR_IO_FAILED: return "I/O operation failed"; case NOSTR_ERROR_NETWORK_FAILED: return "Network operation failed"; case NOSTR_ERROR_NIP04_INVALID_FORMAT: return "NIP-04 invalid format"; case NOSTR_ERROR_NIP04_DECRYPT_FAILED: return "NIP-04 decryption failed"; case NOSTR_ERROR_NIP04_BUFFER_TOO_SMALL: return "NIP-04 buffer too small"; case NOSTR_ERROR_NIP05_INVALID_IDENTIFIER: return "NIP-05: Invalid identifier format"; case NOSTR_ERROR_NIP05_HTTP_FAILED: return "NIP-05: HTTP request failed"; case NOSTR_ERROR_NIP05_JSON_PARSE_FAILED: return "NIP-05: JSON parsing failed"; case NOSTR_ERROR_NIP05_NAME_NOT_FOUND: return "NIP-05: Name not found in .well-known"; case NOSTR_ERROR_NIP05_PUBKEY_MISMATCH: return "NIP-05: Public key mismatch"; default: return "Unknown error"; } } /** * Create and sign a NOSTR event */ cJSON* nostr_create_and_sign_event(int kind, const char* content, cJSON* tags, const unsigned char* private_key, time_t timestamp) { if (!private_key) { return NULL; } if (!content) { content = ""; // Default to empty content } // Convert private key to public key unsigned char public_key[32]; if (nostr_ec_public_key_from_private_key(private_key, public_key) != 0) { return NULL; } // Convert public key to hex char pubkey_hex[65]; nostr_bytes_to_hex(public_key, 32, pubkey_hex); // Create event structure cJSON* event = cJSON_CreateObject(); if (!event) { return NULL; } // Use provided timestamp or current time if timestamp is 0 time_t event_time = (timestamp == 0) ? time(NULL) : timestamp; cJSON_AddStringToObject(event, "pubkey", pubkey_hex); cJSON_AddNumberToObject(event, "created_at", (double)event_time); cJSON_AddNumberToObject(event, "kind", kind); // Add tags (copy provided tags or create empty array) if (tags) { cJSON_AddItemToObject(event, "tags", cJSON_Duplicate(tags, 1)); } else { cJSON_AddItemToObject(event, "tags", cJSON_CreateArray()); } cJSON_AddStringToObject(event, "content", content); // ============================================================================ // INLINE SERIALIZATION AND SIGNING LOGIC // ============================================================================ // Get event fields for serialization cJSON* pubkey_item = cJSON_GetObjectItem(event, "pubkey"); cJSON* created_at_item = cJSON_GetObjectItem(event, "created_at"); cJSON* kind_item = cJSON_GetObjectItem(event, "kind"); cJSON* tags_item = cJSON_GetObjectItem(event, "tags"); cJSON* content_item = cJSON_GetObjectItem(event, "content"); if (!pubkey_item || !created_at_item || !kind_item || !tags_item || !content_item) { cJSON_Delete(event); return NULL; } // Create serialization array: [0, pubkey, created_at, kind, tags, content] cJSON* serialize_array = cJSON_CreateArray(); if (!serialize_array) { cJSON_Delete(event); return NULL; } cJSON_AddItemToArray(serialize_array, cJSON_CreateNumber(0)); cJSON_AddItemToArray(serialize_array, cJSON_Duplicate(pubkey_item, 1)); cJSON_AddItemToArray(serialize_array, cJSON_Duplicate(created_at_item, 1)); cJSON_AddItemToArray(serialize_array, cJSON_Duplicate(kind_item, 1)); cJSON_AddItemToArray(serialize_array, cJSON_Duplicate(tags_item, 1)); cJSON_AddItemToArray(serialize_array, cJSON_Duplicate(content_item, 1)); char* serialize_string = cJSON_PrintUnformatted(serialize_array); cJSON_Delete(serialize_array); if (!serialize_string) { cJSON_Delete(event); return NULL; } // Hash the serialized event unsigned char event_hash[32]; if (nostr_sha256((const unsigned char*)serialize_string, strlen(serialize_string), event_hash) != 0) { free(serialize_string); cJSON_Delete(event); return NULL; } // Convert hash to hex for event ID char event_id[65]; nostr_bytes_to_hex(event_hash, 32, event_id); // Sign the hash using ECDSA unsigned char signature[64]; if (nostr_ec_sign(private_key, event_hash, signature) != 0) { free(serialize_string); cJSON_Delete(event); return NULL; } // Convert signature to hex char sig_hex[129]; nostr_bytes_to_hex(signature, 64, sig_hex); // Add ID and signature to the event cJSON_AddStringToObject(event, "id", event_id); cJSON_AddStringToObject(event, "sig", sig_hex); free(serialize_string); return event; }