|
|
|
|
@@ -6,6 +6,7 @@
|
|
|
|
|
|
|
|
|
|
#include "nip001.h"
|
|
|
|
|
#include "utils.h"
|
|
|
|
|
#include "crypto/nostr_secp256k1.h"
|
|
|
|
|
#include "../cjson/cJSON.h"
|
|
|
|
|
#include <stdio.h>
|
|
|
|
|
#include <stdlib.h>
|
|
|
|
|
@@ -131,3 +132,203 @@ cJSON* nostr_create_and_sign_event(int kind, const char* content, cJSON* tags, c
|
|
|
|
|
|
|
|
|
|
return event;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Validate the structure of a NOSTR event
|
|
|
|
|
* Checks required fields, types, and basic format validation
|
|
|
|
|
*/
|
|
|
|
|
int nostr_validate_event_structure(cJSON* event) {
|
|
|
|
|
if (!event || !cJSON_IsObject(event)) {
|
|
|
|
|
return NOSTR_ERROR_EVENT_INVALID_STRUCTURE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check required fields exist
|
|
|
|
|
cJSON* id_item = cJSON_GetObjectItem(event, "id");
|
|
|
|
|
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");
|
|
|
|
|
cJSON* sig_item = cJSON_GetObjectItem(event, "sig");
|
|
|
|
|
|
|
|
|
|
if (!id_item || !pubkey_item || !created_at_item || !kind_item ||
|
|
|
|
|
!tags_item || !content_item || !sig_item) {
|
|
|
|
|
return NOSTR_ERROR_EVENT_INVALID_STRUCTURE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Validate field types
|
|
|
|
|
if (!cJSON_IsString(id_item)) return NOSTR_ERROR_EVENT_INVALID_ID;
|
|
|
|
|
if (!cJSON_IsString(pubkey_item)) return NOSTR_ERROR_EVENT_INVALID_PUBKEY;
|
|
|
|
|
if (!cJSON_IsNumber(created_at_item)) return NOSTR_ERROR_EVENT_INVALID_CREATED_AT;
|
|
|
|
|
if (!cJSON_IsNumber(kind_item)) return NOSTR_ERROR_EVENT_INVALID_KIND;
|
|
|
|
|
if (!cJSON_IsArray(tags_item)) return NOSTR_ERROR_EVENT_INVALID_TAGS;
|
|
|
|
|
if (!cJSON_IsString(content_item)) return NOSTR_ERROR_EVENT_INVALID_CONTENT;
|
|
|
|
|
if (!cJSON_IsString(sig_item)) return NOSTR_ERROR_EVENT_INVALID_SIGNATURE;
|
|
|
|
|
|
|
|
|
|
// Validate hex string lengths
|
|
|
|
|
const char* id_str = cJSON_GetStringValue(id_item);
|
|
|
|
|
const char* pubkey_str = cJSON_GetStringValue(pubkey_item);
|
|
|
|
|
const char* sig_str = cJSON_GetStringValue(sig_item);
|
|
|
|
|
|
|
|
|
|
if (!id_str || strlen(id_str) != 64) return NOSTR_ERROR_EVENT_INVALID_ID;
|
|
|
|
|
if (!pubkey_str || strlen(pubkey_str) != 64) return NOSTR_ERROR_EVENT_INVALID_PUBKEY;
|
|
|
|
|
if (!sig_str || strlen(sig_str) != 128) return NOSTR_ERROR_EVENT_INVALID_SIGNATURE;
|
|
|
|
|
|
|
|
|
|
// Validate hex characters (lowercase)
|
|
|
|
|
for (int i = 0; i < 64; i++) {
|
|
|
|
|
char c = id_str[i];
|
|
|
|
|
if (!((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f'))) {
|
|
|
|
|
return NOSTR_ERROR_EVENT_INVALID_ID;
|
|
|
|
|
}
|
|
|
|
|
c = pubkey_str[i];
|
|
|
|
|
if (!((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f'))) {
|
|
|
|
|
return NOSTR_ERROR_EVENT_INVALID_PUBKEY;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Validate signature hex characters (lowercase) - 128 characters
|
|
|
|
|
for (int i = 0; i < 128; i++) {
|
|
|
|
|
char c = sig_str[i];
|
|
|
|
|
if (!((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f'))) {
|
|
|
|
|
return NOSTR_ERROR_EVENT_INVALID_SIGNATURE;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Validate created_at is a valid timestamp (positive number)
|
|
|
|
|
double created_at = cJSON_GetNumberValue(created_at_item);
|
|
|
|
|
if (created_at < 0) return NOSTR_ERROR_EVENT_INVALID_CREATED_AT;
|
|
|
|
|
|
|
|
|
|
// Validate kind is valid (0-65535)
|
|
|
|
|
double kind = cJSON_GetNumberValue(kind_item);
|
|
|
|
|
if (kind < 0 || kind > 65535 || kind != (int)kind) {
|
|
|
|
|
return NOSTR_ERROR_EVENT_INVALID_KIND;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Validate tags array structure (array of arrays of strings)
|
|
|
|
|
cJSON* tag_item;
|
|
|
|
|
cJSON_ArrayForEach(tag_item, tags_item) {
|
|
|
|
|
if (!cJSON_IsArray(tag_item)) {
|
|
|
|
|
return NOSTR_ERROR_EVENT_INVALID_TAGS;
|
|
|
|
|
}
|
|
|
|
|
cJSON* tag_element;
|
|
|
|
|
cJSON_ArrayForEach(tag_element, tag_item) {
|
|
|
|
|
if (!cJSON_IsString(tag_element)) {
|
|
|
|
|
return NOSTR_ERROR_EVENT_INVALID_TAGS;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return NOSTR_SUCCESS;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Verify the cryptographic signature of a NOSTR event
|
|
|
|
|
* Validates event ID and signature according to NIP-01
|
|
|
|
|
*/
|
|
|
|
|
int nostr_verify_event_signature(cJSON* event) {
|
|
|
|
|
if (!event) {
|
|
|
|
|
return NOSTR_ERROR_INVALID_INPUT;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Get event fields
|
|
|
|
|
cJSON* id_item = cJSON_GetObjectItem(event, "id");
|
|
|
|
|
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");
|
|
|
|
|
cJSON* sig_item = cJSON_GetObjectItem(event, "sig");
|
|
|
|
|
|
|
|
|
|
if (!id_item || !pubkey_item || !created_at_item || !kind_item ||
|
|
|
|
|
!tags_item || !content_item || !sig_item) {
|
|
|
|
|
return NOSTR_ERROR_EVENT_INVALID_STRUCTURE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Create serialization array: [0, pubkey, created_at, kind, tags, content]
|
|
|
|
|
cJSON* serialize_array = cJSON_CreateArray();
|
|
|
|
|
if (!serialize_array) {
|
|
|
|
|
return NOSTR_ERROR_MEMORY_FAILED;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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) {
|
|
|
|
|
return NOSTR_ERROR_MEMORY_FAILED;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 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);
|
|
|
|
|
return NOSTR_ERROR_CRYPTO_FAILED;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Convert hash to hex for event ID verification
|
|
|
|
|
char calculated_id[65];
|
|
|
|
|
nostr_bytes_to_hex(event_hash, 32, calculated_id);
|
|
|
|
|
|
|
|
|
|
// Compare with provided event ID
|
|
|
|
|
const char* provided_id = cJSON_GetStringValue(id_item);
|
|
|
|
|
if (!provided_id || strcmp(calculated_id, provided_id) != 0) {
|
|
|
|
|
free(serialize_string);
|
|
|
|
|
return NOSTR_ERROR_EVENT_INVALID_ID;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Verify signature
|
|
|
|
|
const char* pubkey_str = cJSON_GetStringValue(pubkey_item);
|
|
|
|
|
const char* sig_str = cJSON_GetStringValue(sig_item);
|
|
|
|
|
|
|
|
|
|
if (!pubkey_str || !sig_str) {
|
|
|
|
|
free(serialize_string);
|
|
|
|
|
return NOSTR_ERROR_EVENT_INVALID_STRUCTURE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Convert hex strings to bytes
|
|
|
|
|
unsigned char pubkey_bytes[32];
|
|
|
|
|
unsigned char sig_bytes[64];
|
|
|
|
|
|
|
|
|
|
if (nostr_hex_to_bytes(pubkey_str, pubkey_bytes, 32) != 0 ||
|
|
|
|
|
nostr_hex_to_bytes(sig_str, sig_bytes, 64) != 0) {
|
|
|
|
|
free(serialize_string);
|
|
|
|
|
return NOSTR_ERROR_CRYPTO_FAILED;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Parse the public key into secp256k1 format
|
|
|
|
|
nostr_secp256k1_xonly_pubkey xonly_pubkey;
|
|
|
|
|
if (!nostr_secp256k1_xonly_pubkey_parse(&xonly_pubkey, pubkey_bytes)) {
|
|
|
|
|
free(serialize_string);
|
|
|
|
|
return NOSTR_ERROR_EVENT_INVALID_PUBKEY;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Verify Schnorr signature
|
|
|
|
|
if (!nostr_secp256k1_schnorrsig_verify(sig_bytes, event_hash, &xonly_pubkey)) {
|
|
|
|
|
free(serialize_string);
|
|
|
|
|
return NOSTR_ERROR_EVENT_INVALID_SIGNATURE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
free(serialize_string);
|
|
|
|
|
return NOSTR_SUCCESS;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Complete validation of a NOSTR event
|
|
|
|
|
* Performs both structure and cryptographic validation
|
|
|
|
|
*/
|
|
|
|
|
int nostr_validate_event(cJSON* event) {
|
|
|
|
|
// First validate structure (fast check)
|
|
|
|
|
int structure_result = nostr_validate_event_structure(event);
|
|
|
|
|
if (structure_result != NOSTR_SUCCESS) {
|
|
|
|
|
return structure_result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Then verify signature (expensive check)
|
|
|
|
|
return nostr_verify_event_signature(event);
|
|
|
|
|
}
|
|
|
|
|
|