173 lines
5.8 KiB
C
173 lines
5.8 KiB
C
/*
|
|
* 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 <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <time.h>
|
|
|
|
// 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;
|
|
}
|