Nostr note validation added to nip01
This commit is contained in:
parent
1da4f6751e
commit
77d92dbcf9
|
@ -1,6 +1,6 @@
|
|||
This library is fully staticly linked. There should be no external dependencies.
|
||||
|
||||
When building, use build.sh, not make.
|
||||
When building, use build.sh, not make. When building for tests use build.sh -t
|
||||
|
||||
When making TUI menus, try to use the first leter of the command and the key to press to execute that command. For example, if the command is "Open file" try to use a keypress of "o" upper or lower case to signal to open the file. Use this instead of number keyed menus when possible. In the command, the letter should be underlined that signifies the command.
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -15,4 +15,9 @@
|
|||
// Function declarations
|
||||
cJSON* nostr_create_and_sign_event(int kind, const char* content, cJSON* tags, const unsigned char* private_key, time_t timestamp);
|
||||
|
||||
// Event validation functions
|
||||
int nostr_validate_event_structure(cJSON* event);
|
||||
int nostr_verify_event_signature(cJSON* event);
|
||||
int nostr_validate_event(cJSON* event);
|
||||
|
||||
#endif // NIP001_H
|
||||
|
|
|
@ -30,6 +30,14 @@ const char* nostr_strerror(int error_code) {
|
|||
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";
|
||||
case NOSTR_ERROR_EVENT_INVALID_STRUCTURE: return "Event has invalid structure";
|
||||
case NOSTR_ERROR_EVENT_INVALID_ID: return "Event has invalid ID";
|
||||
case NOSTR_ERROR_EVENT_INVALID_PUBKEY: return "Event has invalid public key";
|
||||
case NOSTR_ERROR_EVENT_INVALID_SIGNATURE: return "Event has invalid signature";
|
||||
case NOSTR_ERROR_EVENT_INVALID_CREATED_AT: return "Event has invalid timestamp";
|
||||
case NOSTR_ERROR_EVENT_INVALID_KIND: return "Event has invalid kind";
|
||||
case NOSTR_ERROR_EVENT_INVALID_TAGS: return "Event has invalid tags";
|
||||
case NOSTR_ERROR_EVENT_INVALID_CONTENT: return "Event has invalid content";
|
||||
default: return "Unknown error";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,6 +27,14 @@
|
|||
#define NOSTR_ERROR_NIP05_JSON_PARSE_FAILED -18
|
||||
#define NOSTR_ERROR_NIP05_NAME_NOT_FOUND -19
|
||||
#define NOSTR_ERROR_NIP05_PUBKEY_MISMATCH -20
|
||||
#define NOSTR_ERROR_EVENT_INVALID_STRUCTURE -30
|
||||
#define NOSTR_ERROR_EVENT_INVALID_ID -31
|
||||
#define NOSTR_ERROR_EVENT_INVALID_PUBKEY -32
|
||||
#define NOSTR_ERROR_EVENT_INVALID_SIGNATURE -33
|
||||
#define NOSTR_ERROR_EVENT_INVALID_CREATED_AT -34
|
||||
#define NOSTR_ERROR_EVENT_INVALID_KIND -35
|
||||
#define NOSTR_ERROR_EVENT_INVALID_TAGS -36
|
||||
#define NOSTR_ERROR_EVENT_INVALID_CONTENT -37
|
||||
|
||||
|
||||
// Constants
|
||||
|
|
BIN
tests/bip32_test
BIN
tests/bip32_test
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,494 @@
|
|||
/*
|
||||
* NIP-01 Event Validation Test Suite
|
||||
* Tests event structure validation and cryptographic verification
|
||||
* Following TESTS POLICY: Shows expected vs actual values, prints entire JSON events
|
||||
*/
|
||||
|
||||
#define _GNU_SOURCE // For strdup on Linux
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include "../nostr_core/nip001.h"
|
||||
#include "../nostr_core/nostr_common.h"
|
||||
#include "../nostr_core/utils.h"
|
||||
#include "../cjson/cJSON.h"
|
||||
|
||||
// Ensure strdup is declared
|
||||
#ifndef strdup
|
||||
extern char *strdup(const char *s);
|
||||
#endif
|
||||
|
||||
// Test counter for tracking progress
|
||||
static int test_count = 0;
|
||||
static int passed_tests = 0;
|
||||
|
||||
void print_test_header(const char* test_name) {
|
||||
test_count++;
|
||||
printf("\n=== TEST %d: %s ===\n", test_count, test_name);
|
||||
}
|
||||
|
||||
void print_test_result(int passed, const char* test_name) {
|
||||
if (passed) {
|
||||
passed_tests++;
|
||||
printf("✅ PASS: %s\n", test_name);
|
||||
} else {
|
||||
printf("❌ FAIL: %s\n", test_name);
|
||||
}
|
||||
}
|
||||
|
||||
void print_json_comparison(const char* label, cJSON* expected, cJSON* actual) {
|
||||
char* expected_str = cJSON_Print(expected);
|
||||
char* actual_str;
|
||||
|
||||
if (actual) {
|
||||
actual_str = cJSON_Print(actual);
|
||||
} else {
|
||||
actual_str = strdup("NULL");
|
||||
}
|
||||
|
||||
printf("%s Expected JSON:\n%s\n", label, expected_str ? expected_str : "NULL");
|
||||
printf("%s Actual JSON:\n%s\n", label, actual_str ? actual_str : "NULL");
|
||||
|
||||
if (expected_str) free(expected_str);
|
||||
if (actual_str) free(actual_str);
|
||||
}
|
||||
|
||||
// Test vector 1: Valid event from nak (should pass all validation)
|
||||
int test_valid_event_1(void) {
|
||||
print_test_header("Valid Event from nak - Basic");
|
||||
|
||||
// Generated with: nak event --sec nsec1j4c6269y9w0q2er2xjw8sv2ehyrtfxq3jwgdlxj6qfn8z4gjsq5qfvfk99 -c "Test event 1" -k 1
|
||||
const char* event_json = "{"
|
||||
"\"kind\":1,"
|
||||
"\"id\":\"f1e582c90f071c0110cc5bcac2dcc6d8c32250e3cc26fcbe93470d918f2ffaf0\","
|
||||
"\"pubkey\":\"aa4fc8665f5696e33db7e1a572e3b0f5b3d615837b0f362dcb1c8068b098c7b4\","
|
||||
"\"created_at\":1755599338,"
|
||||
"\"tags\":[],"
|
||||
"\"content\":\"Test event 1\","
|
||||
"\"sig\":\"b9c7148e5a3e4ae1985a4b83a6317df72e2ef78dc4389063b7b83d5642aeccb5fb42f7e820ccfaf38c8077f9f6fef1fa1fd7d05c27c7fbc797cf53ee249ea640\""
|
||||
"}";
|
||||
|
||||
printf("Input Event JSON:\n%s\n\n", event_json);
|
||||
|
||||
cJSON* event = cJSON_Parse(event_json);
|
||||
if (!event) {
|
||||
printf("❌ JSON Parse Error\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
printf("Testing Structure Validation...\n");
|
||||
int structure_result = nostr_validate_event_structure(event);
|
||||
printf("Expected: NOSTR_SUCCESS (0)\n");
|
||||
printf("Actual: %d (%s)\n", structure_result, nostr_strerror(structure_result));
|
||||
|
||||
if (structure_result != NOSTR_SUCCESS) {
|
||||
printf("❌ Structure validation failed\n");
|
||||
cJSON_Delete(event);
|
||||
return 0;
|
||||
}
|
||||
printf("✅ Structure validation passed\n\n");
|
||||
|
||||
printf("Testing Cryptographic Verification...\n");
|
||||
int crypto_result = nostr_verify_event_signature(event);
|
||||
printf("Expected: NOSTR_SUCCESS (0)\n");
|
||||
printf("Actual: %d (%s)\n", crypto_result, nostr_strerror(crypto_result));
|
||||
|
||||
if (crypto_result != NOSTR_SUCCESS) {
|
||||
printf("❌ Cryptographic verification failed\n");
|
||||
cJSON_Delete(event);
|
||||
return 0;
|
||||
}
|
||||
printf("✅ Cryptographic verification passed\n\n");
|
||||
|
||||
printf("Testing Complete Validation...\n");
|
||||
int complete_result = nostr_validate_event(event);
|
||||
printf("Expected: NOSTR_SUCCESS (0)\n");
|
||||
printf("Actual: %d (%s)\n", complete_result, nostr_strerror(complete_result));
|
||||
|
||||
cJSON_Delete(event);
|
||||
return (complete_result == NOSTR_SUCCESS);
|
||||
}
|
||||
|
||||
// Test vector 2: Valid event with tags (should pass all validation)
|
||||
int test_valid_event_with_tags(void) {
|
||||
print_test_header("Valid Event with Tags");
|
||||
|
||||
// Generated with: nak event --sec nsec1j4c6269y9w0q2er2xjw8sv2ehyrtfxq3jwgdlxj6qfn8z4gjsq5qfvfk99 -c "Test event with tags" -k 1 -t "hello=world" -t "test=value"
|
||||
const char* event_json = "{"
|
||||
"\"kind\":1,"
|
||||
"\"id\":\"781bf3185479315350c4039f719c3a8859a1ebb21a4b04e0e649addf0ae4665b\","
|
||||
"\"pubkey\":\"aa4fc8665f5696e33db7e1a572e3b0f5b3d615837b0f362dcb1c8068b098c7b4\","
|
||||
"\"created_at\":1755599345,"
|
||||
"\"tags\":[[\"hello\",\"world\"],[\"test\",\"value\"]],"
|
||||
"\"content\":\"Test event with tags\","
|
||||
"\"sig\":\"3a390ece9d746bd576cb8fbba6f9ed59730099781d0d414d1194f832ca072f23e7764935451a025399202e02f28a59fde2d4c25399e241f3de2ad0b3a4830d6a\""
|
||||
"}";
|
||||
|
||||
printf("Input Event JSON:\n%s\n\n", event_json);
|
||||
|
||||
cJSON* event = cJSON_Parse(event_json);
|
||||
if (!event) {
|
||||
printf("❌ JSON Parse Error\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
int result = nostr_validate_event(event);
|
||||
printf("Expected: NOSTR_SUCCESS (0)\n");
|
||||
printf("Actual: %d (%s)\n", result, nostr_strerror(result));
|
||||
|
||||
cJSON_Delete(event);
|
||||
return (result == NOSTR_SUCCESS);
|
||||
}
|
||||
|
||||
// Test vector 3: Valid metadata event (kind 0)
|
||||
int test_valid_metadata_event(void) {
|
||||
print_test_header("Valid Metadata Event (Kind 0)");
|
||||
|
||||
// Generated with: nak event --sec nsec1j4c6269y9w0q2er2xjw8sv2ehyrtfxq3jwgdlxj6qfn8z4gjsq5qfvfk99 -c "" -k 0 --ts 1640995200
|
||||
const char* event_json = "{"
|
||||
"\"kind\":0,"
|
||||
"\"id\":\"a2ed5175224f6597bc3a553e91d6fcea2e13f199e56afc90ccecca1c890b308a\","
|
||||
"\"pubkey\":\"aa4fc8665f5696e33db7e1a572e3b0f5b3d615837b0f362dcb1c8068b098c7b4\","
|
||||
"\"created_at\":1640995200,"
|
||||
"\"tags\":[],"
|
||||
"\"content\":\"\","
|
||||
"\"sig\":\"b9b2859ded73bbc010331145e2d297b30205d2b94d80ef0619077b687d53756b8d9eb3954ef1483b814059cd879c992153ca9d7b50dcf7053a3d530cd82fb884\""
|
||||
"}";
|
||||
|
||||
printf("Input Event JSON:\n%s\n\n", event_json);
|
||||
|
||||
cJSON* event = cJSON_Parse(event_json);
|
||||
if (!event) {
|
||||
printf("❌ JSON Parse Error\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
int result = nostr_validate_event(event);
|
||||
printf("Expected: NOSTR_SUCCESS (0)\n");
|
||||
printf("Actual: %d (%s)\n", result, nostr_strerror(result));
|
||||
|
||||
cJSON_Delete(event);
|
||||
return (result == NOSTR_SUCCESS);
|
||||
}
|
||||
|
||||
// Test 4: Missing required field (id)
|
||||
int test_missing_id_field(void) {
|
||||
print_test_header("Missing Required Field - ID");
|
||||
|
||||
const char* event_json = "{"
|
||||
"\"kind\":1,"
|
||||
"\"pubkey\":\"aa4fc8665f5696e33db7e1a572e3b0f5b3d615837b0f362dcb1c8068b098c7b4\","
|
||||
"\"created_at\":1755599338,"
|
||||
"\"tags\":[],"
|
||||
"\"content\":\"Test event 1\","
|
||||
"\"sig\":\"b9c7148e5a3e4ae1985a4b83a6317df72e2ef78dc4389063b7b83d5642aeccb5fb42f7e820ccfaf38c8077f9f6fef1fa1fd7d05c27c7fbc797cf53ee249ea640\""
|
||||
"}";
|
||||
|
||||
printf("Input Event JSON (missing 'id' field):\n%s\n\n", event_json);
|
||||
|
||||
cJSON* event = cJSON_Parse(event_json);
|
||||
if (!event) {
|
||||
printf("❌ JSON Parse Error\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
int result = nostr_validate_event_structure(event);
|
||||
printf("Expected: NOSTR_ERROR_EVENT_INVALID_STRUCTURE (-30)\n");
|
||||
printf("Actual: %d (%s)\n", result, nostr_strerror(result));
|
||||
|
||||
cJSON_Delete(event);
|
||||
return (result == NOSTR_ERROR_EVENT_INVALID_STRUCTURE);
|
||||
}
|
||||
|
||||
// Test 5: Invalid hex string length (pubkey too short)
|
||||
int test_invalid_pubkey_length(void) {
|
||||
print_test_header("Invalid Pubkey Length");
|
||||
|
||||
const char* event_json = "{"
|
||||
"\"kind\":1,"
|
||||
"\"id\":\"f1e582c90f071c0110cc5bcac2dcc6d8c32250e3cc26fcbe93470d918f2ffaf0\","
|
||||
"\"pubkey\":\"aa4fc8665f5696e33db7e1a572e3b0f5b3d615837b0f362dcb1c8068b098c7\"," // Too short (63 chars instead of 64)
|
||||
"\"created_at\":1755599338,"
|
||||
"\"tags\":[],"
|
||||
"\"content\":\"Test event 1\","
|
||||
"\"sig\":\"b9c7148e5a3e4ae1985a4b83a6317df72e2ef78dc4389063b7b83d5642aeccb5fb42f7e820ccfaf38c8077f9f6fef1fa1fd7d05c27c7fbc797cf53ee249ea640\""
|
||||
"}";
|
||||
|
||||
printf("Input Event JSON (pubkey too short - 63 chars instead of 64):\n%s\n\n", event_json);
|
||||
|
||||
cJSON* event = cJSON_Parse(event_json);
|
||||
if (!event) {
|
||||
printf("❌ JSON Parse Error\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
int result = nostr_validate_event_structure(event);
|
||||
printf("Expected: NOSTR_ERROR_EVENT_INVALID_PUBKEY (-32)\n");
|
||||
printf("Actual: %d (%s)\n", result, nostr_strerror(result));
|
||||
|
||||
cJSON_Delete(event);
|
||||
return (result == NOSTR_ERROR_EVENT_INVALID_PUBKEY);
|
||||
}
|
||||
|
||||
// Test 6: Invalid kind (negative)
|
||||
int test_invalid_kind_negative(void) {
|
||||
print_test_header("Invalid Kind - Negative Value");
|
||||
|
||||
const char* event_json = "{"
|
||||
"\"kind\":-1," // Invalid negative kind
|
||||
"\"id\":\"f1e582c90f071c0110cc5bcac2dcc6d8c32250e3cc26fcbe93470d918f2ffaf0\","
|
||||
"\"pubkey\":\"aa4fc8665f5696e33db7e1a572e3b0f5b3d615837b0f362dcb1c8068b098c7b4\","
|
||||
"\"created_at\":1755599338,"
|
||||
"\"tags\":[],"
|
||||
"\"content\":\"Test event 1\","
|
||||
"\"sig\":\"b9c7148e5a3e4ae1985a4b83a6317df72e2ef78dc4389063b7b83d5642aeccb5fb42f7e820ccfaf38c8077f9f6fef1fa1fd7d05c27c7fbc797cf53ee249ea640\""
|
||||
"}";
|
||||
|
||||
printf("Input Event JSON (kind = -1):\n%s\n\n", event_json);
|
||||
|
||||
cJSON* event = cJSON_Parse(event_json);
|
||||
if (!event) {
|
||||
printf("❌ JSON Parse Error\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
int result = nostr_validate_event_structure(event);
|
||||
printf("Expected: NOSTR_ERROR_EVENT_INVALID_KIND (-35)\n");
|
||||
printf("Actual: %d (%s)\n", result, nostr_strerror(result));
|
||||
|
||||
cJSON_Delete(event);
|
||||
return (result == NOSTR_ERROR_EVENT_INVALID_KIND);
|
||||
}
|
||||
|
||||
// Test 7: Invalid tags (not an array)
|
||||
int test_invalid_tags_not_array(void) {
|
||||
print_test_header("Invalid Tags - Not Array");
|
||||
|
||||
const char* event_json = "{"
|
||||
"\"kind\":1,"
|
||||
"\"id\":\"f1e582c90f071c0110cc5bcac2dcc6d8c32250e3cc26fcbe93470d918f2ffaf0\","
|
||||
"\"pubkey\":\"aa4fc8665f5696e33db7e1a572e3b0f5b3d615837b0f362dcb1c8068b098c7b4\","
|
||||
"\"created_at\":1755599338,"
|
||||
"\"tags\":\"not an array\"," // Should be array
|
||||
"\"content\":\"Test event 1\","
|
||||
"\"sig\":\"b9c7148e5a3e4ae1985a4b83a6317df72e2ef78dc4389063b7b83d5642aeccb5fb42f7e820ccfaf38c8077f9f6fef1fa1fd7d05c27c7fbc797cf53ee249ea640\""
|
||||
"}";
|
||||
|
||||
printf("Input Event JSON (tags is string instead of array):\n%s\n\n", event_json);
|
||||
|
||||
cJSON* event = cJSON_Parse(event_json);
|
||||
if (!event) {
|
||||
printf("❌ JSON Parse Error\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
int result = nostr_validate_event_structure(event);
|
||||
printf("Expected: NOSTR_ERROR_EVENT_INVALID_TAGS (-36)\n");
|
||||
printf("Actual: %d (%s)\n", result, nostr_strerror(result));
|
||||
|
||||
cJSON_Delete(event);
|
||||
return (result == NOSTR_ERROR_EVENT_INVALID_TAGS);
|
||||
}
|
||||
|
||||
// Test 8: Wrong event ID (cryptographic test)
|
||||
int test_wrong_event_id(void) {
|
||||
print_test_header("Wrong Event ID - Cryptographic Test");
|
||||
|
||||
// Take valid event but change the ID to something incorrect
|
||||
const char* event_json = "{"
|
||||
"\"kind\":1,"
|
||||
"\"id\":\"0000000000000000000000000000000000000000000000000000000000000000\"," // Wrong ID
|
||||
"\"pubkey\":\"aa4fc8665f5696e33db7e1a572e3b0f5b3d615837b0f362dcb1c8068b098c7b4\","
|
||||
"\"created_at\":1755599338,"
|
||||
"\"tags\":[],"
|
||||
"\"content\":\"Test event 1\","
|
||||
"\"sig\":\"b9c7148e5a3e4ae1985a4b83a6317df72e2ef78dc4389063b7b83d5642aeccb5fb42f7e820ccfaf38c8077f9f6fef1fa1fd7d05c27c7fbc797cf53ee249ea640\""
|
||||
"}";
|
||||
|
||||
printf("Input Event JSON (ID changed to all zeros):\n%s\n\n", event_json);
|
||||
|
||||
cJSON* event = cJSON_Parse(event_json);
|
||||
if (!event) {
|
||||
printf("❌ JSON Parse Error\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Structure should pass
|
||||
int structure_result = nostr_validate_event_structure(event);
|
||||
printf("Structure validation - Expected: NOSTR_SUCCESS (0)\n");
|
||||
printf("Structure validation - Actual: %d (%s)\n", structure_result, nostr_strerror(structure_result));
|
||||
|
||||
if (structure_result != NOSTR_SUCCESS) {
|
||||
printf("❌ Unexpected structure validation failure\n");
|
||||
cJSON_Delete(event);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Crypto should fail
|
||||
int crypto_result = nostr_verify_event_signature(event);
|
||||
printf("Crypto verification - Expected: NOSTR_ERROR_EVENT_INVALID_ID (-31)\n");
|
||||
printf("Crypto verification - Actual: %d (%s)\n", crypto_result, nostr_strerror(crypto_result));
|
||||
|
||||
cJSON_Delete(event);
|
||||
return (crypto_result == NOSTR_ERROR_EVENT_INVALID_ID);
|
||||
}
|
||||
|
||||
// Test 9: Invalid signature (cryptographic test)
|
||||
int test_invalid_signature(void) {
|
||||
print_test_header("Invalid Signature - Cryptographic Test");
|
||||
|
||||
// Take valid event but change the signature to a structurally valid but cryptographically wrong signature
|
||||
const char* event_json = "{"
|
||||
"\"kind\":1,"
|
||||
"\"id\":\"f1e582c90f071c0110cc5bcac2dcc6d8c32250e3cc26fcbe93470d918f2ffaf0\","
|
||||
"\"pubkey\":\"aa4fc8665f5696e33db7e1a572e3b0f5b3d615837b0f362dcb1c8068b098c7b4\","
|
||||
"\"created_at\":1755599338,"
|
||||
"\"tags\":[],"
|
||||
"\"content\":\"Test event 1\","
|
||||
"\"sig\":\"deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef\"" // Wrong but valid hex
|
||||
"}";
|
||||
|
||||
printf("Input Event JSON (signature changed to invalid hex):\n%s\n\n", event_json);
|
||||
|
||||
cJSON* event = cJSON_Parse(event_json);
|
||||
if (!event) {
|
||||
printf("❌ JSON Parse Error\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Structure should pass
|
||||
int structure_result = nostr_validate_event_structure(event);
|
||||
printf("Structure validation - Expected: NOSTR_SUCCESS (0)\n");
|
||||
printf("Structure validation - Actual: %d (%s)\n", structure_result, nostr_strerror(structure_result));
|
||||
|
||||
if (structure_result != NOSTR_SUCCESS) {
|
||||
printf("❌ Unexpected structure validation failure\n");
|
||||
cJSON_Delete(event);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Crypto should fail
|
||||
int crypto_result = nostr_verify_event_signature(event);
|
||||
printf("Crypto verification - Expected: NOSTR_ERROR_EVENT_INVALID_SIGNATURE (-33)\n");
|
||||
printf("Crypto verification - Actual: %d (%s)\n", crypto_result, nostr_strerror(crypto_result));
|
||||
|
||||
cJSON_Delete(event);
|
||||
return (crypto_result == NOSTR_ERROR_EVENT_INVALID_SIGNATURE);
|
||||
}
|
||||
|
||||
// Test 10: Integration test with our own event creation
|
||||
int test_integration_with_event_creation(void) {
|
||||
print_test_header("Integration Test - Validate Our Own Created Event");
|
||||
|
||||
// Create a test event using our existing function
|
||||
const char* private_key_hex = "91ba716fa9e7ea2fcbad360cf4f8e0d312f73984da63d90f524ad61a6a1e7dbe";
|
||||
unsigned char private_key[32];
|
||||
nostr_hex_to_bytes(private_key_hex, private_key, 32);
|
||||
|
||||
cJSON* tags = cJSON_CreateArray();
|
||||
cJSON* tag = cJSON_CreateArray();
|
||||
cJSON_AddItemToArray(tag, cJSON_CreateString("test"));
|
||||
cJSON_AddItemToArray(tag, cJSON_CreateString("integration"));
|
||||
cJSON_AddItemToArray(tags, tag);
|
||||
|
||||
printf("Creating event with our library...\n");
|
||||
cJSON* created_event = nostr_create_and_sign_event(1, "Integration test message", tags, private_key, time(NULL));
|
||||
|
||||
if (!created_event) {
|
||||
printf("❌ Event creation failed\n");
|
||||
cJSON_Delete(tags);
|
||||
return 0;
|
||||
}
|
||||
|
||||
char* event_str = cJSON_Print(created_event);
|
||||
printf("Created Event JSON:\n%s\n\n", event_str);
|
||||
free(event_str);
|
||||
|
||||
printf("Validating our created event...\n");
|
||||
int result = nostr_validate_event(created_event);
|
||||
printf("Expected: NOSTR_SUCCESS (0)\n");
|
||||
printf("Actual: %d (%s)\n", result, nostr_strerror(result));
|
||||
|
||||
if (result == NOSTR_SUCCESS) {
|
||||
printf("✅ Our library creates valid events that pass validation\n");
|
||||
} else {
|
||||
printf("❌ Our own created event failed validation\n");
|
||||
}
|
||||
|
||||
cJSON_Delete(created_event);
|
||||
return (result == NOSTR_SUCCESS);
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
printf("=== NIP-01 Event Validation Test Suite ===\n");
|
||||
printf("Following TESTS POLICY: Shows expected vs actual values, prints entire JSON events\n");
|
||||
|
||||
// Initialize crypto library
|
||||
if (nostr_init() != NOSTR_SUCCESS) {
|
||||
printf("❌ Failed to initialize nostr library\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
int all_passed = 1;
|
||||
int test_result;
|
||||
|
||||
// Valid event tests
|
||||
test_result = test_valid_event_1();
|
||||
print_test_result(test_result, "Valid Event from nak - Basic");
|
||||
if (!test_result) all_passed = 0;
|
||||
|
||||
test_result = test_valid_event_with_tags();
|
||||
print_test_result(test_result, "Valid Event with Tags");
|
||||
if (!test_result) all_passed = 0;
|
||||
|
||||
test_result = test_valid_metadata_event();
|
||||
print_test_result(test_result, "Valid Metadata Event (Kind 0)");
|
||||
if (!test_result) all_passed = 0;
|
||||
|
||||
// Invalid structure tests
|
||||
test_result = test_missing_id_field();
|
||||
print_test_result(test_result, "Missing Required Field - ID");
|
||||
if (!test_result) all_passed = 0;
|
||||
|
||||
test_result = test_invalid_pubkey_length();
|
||||
print_test_result(test_result, "Invalid Pubkey Length");
|
||||
if (!test_result) all_passed = 0;
|
||||
|
||||
test_result = test_invalid_kind_negative();
|
||||
print_test_result(test_result, "Invalid Kind - Negative Value");
|
||||
if (!test_result) all_passed = 0;
|
||||
|
||||
test_result = test_invalid_tags_not_array();
|
||||
print_test_result(test_result, "Invalid Tags - Not Array");
|
||||
if (!test_result) all_passed = 0;
|
||||
|
||||
// Invalid cryptography tests
|
||||
test_result = test_wrong_event_id();
|
||||
print_test_result(test_result, "Wrong Event ID - Cryptographic Test");
|
||||
if (!test_result) all_passed = 0;
|
||||
|
||||
test_result = test_invalid_signature();
|
||||
print_test_result(test_result, "Invalid Signature - Cryptographic Test");
|
||||
if (!test_result) all_passed = 0;
|
||||
|
||||
// Integration test
|
||||
test_result = test_integration_with_event_creation();
|
||||
print_test_result(test_result, "Integration Test - Validate Our Own Created Event");
|
||||
if (!test_result) all_passed = 0;
|
||||
|
||||
// Summary
|
||||
printf("\n=== TEST SUMMARY ===\n");
|
||||
printf("Total tests: %d\n", test_count);
|
||||
printf("Passed: %d\n", passed_tests);
|
||||
printf("Failed: %d\n", test_count - passed_tests);
|
||||
|
||||
if (all_passed) {
|
||||
printf("🎉 ALL TESTS PASSED! Event validation implementation is working correctly.\n");
|
||||
} else {
|
||||
printf("❌ SOME TESTS FAILED. Please review the output above.\n");
|
||||
}
|
||||
|
||||
nostr_cleanup();
|
||||
return all_passed ? 0 : 1;
|
||||
}
|
Binary file not shown.
BIN
tests/nip04_test
BIN
tests/nip04_test
Binary file not shown.
BIN
tests/nip05_test
BIN
tests/nip05_test
Binary file not shown.
BIN
tests/nip11_test
BIN
tests/nip11_test
Binary file not shown.
BIN
tests/nip44_test
BIN
tests/nip44_test
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
tests/wss_test
BIN
tests/wss_test
Binary file not shown.
129
todo.md
129
todo.md
|
@ -1,51 +1,110 @@
|
|||
### __Tier 1: Foundational & High-Priority NIPs (The Essentials)__
|
||||
# Nostr Event Validation Implementation Checklist
|
||||
|
||||
These are fundamental features that most Nostr applications rely on.
|
||||
## Implementation Plan: NIP-001 Event Validation
|
||||
|
||||
| NIP | Description | `nostr_core_lib` Status | `nak` / `nostr-tools` Status | Recommendation | | :-- | :--- | :--- | :--- | :--- | | __NIP-04__ | __Encrypted Direct Messages__ | ❌ __Missing__ | ✅ __Supported__ | __High Priority.__ Essential for private communication. The `encrypt` / `decrypt` functions are a must-have. | | __NIP-05__ | __Mapping to DNS-based Names__ | ❌ __Missing__ | ✅ __Supported__ | __High Priority.__ Needed for user-friendly identifiers (e.g., `user@domain.com`). We should add a function to verify these. | | __NIP-07__ | __`window.nostr` for Web Browsers__ | ❌ __Missing__ | ✅ __Supported__ | __Medium Priority (for C).__ This is browser-specific, but we should consider adding helper functions to format requests for browser extensions that implement NIP-07. | | __NIP-11__ | __Relay Information Document__ | ❌ __Missing__ | ✅ __Supported__ | __High Priority.__ Crucial for client-side relay discovery and capability checking. A function to fetch and parse this JSON is needed. | | __NIP-21__ | __`nostr:` URI Scheme__ | ❌ __Missing__ | ✅ __Supported__ | __Medium Priority.__ Useful for linking to profiles, events, etc. from outside Nostr clients. We should add parsing and generation functions. | | __NIP-57__| __Lightning Zaps__ | ❌ __Missing__ | ✅ __Supported__ | __High Priority.__ Zaps are a cornerstone of value-for-value on Nostr. We need functions to create and verify zap request events (Kind 9734) and zap receipt events (Kind 9735). |
|
||||
### 1. Create Test Suite `tests/nip01_validation_test.c` (FIRST - Test-Driven Development)
|
||||
- [x] Use `nak` command line tool to generate valid test events
|
||||
- [x] Create test vectors with known valid events
|
||||
- [x] Test valid event validation (should pass)
|
||||
- [x] Test invalid structure cases:
|
||||
- Missing required fields
|
||||
- Wrong field types
|
||||
- Invalid hex string lengths
|
||||
- Invalid timestamps
|
||||
- Invalid kind values
|
||||
- Invalid tag structures
|
||||
- [x] Test invalid cryptographic cases:
|
||||
- Wrong event ID
|
||||
- Invalid signature
|
||||
- Mismatched pubkey
|
||||
- [x] Test edge cases and boundary conditions
|
||||
- [x] Follow TESTS POLICY: Show expected vs actual values, print full JSON events
|
||||
|
||||
---
|
||||
### 2. Add Error Codes to `nostr_core/nostr_common.h`
|
||||
- [x] Add validation-specific error codes after existing NIP error codes (line ~21):
|
||||
```c
|
||||
#define NOSTR_ERROR_EVENT_INVALID_STRUCTURE -30
|
||||
#define NOSTR_ERROR_EVENT_INVALID_ID -31
|
||||
#define NOSTR_ERROR_EVENT_INVALID_PUBKEY -32
|
||||
#define NOSTR_ERROR_EVENT_INVALID_SIGNATURE -33
|
||||
#define NOSTR_ERROR_EVENT_INVALID_CREATED_AT -34
|
||||
#define NOSTR_ERROR_EVENT_INVALID_KIND -35
|
||||
#define NOSTR_ERROR_EVENT_INVALID_TAGS -36
|
||||
#define NOSTR_ERROR_EVENT_INVALID_CONTENT -37
|
||||
```
|
||||
|
||||
### __Tier 2: Advanced & Ecosystem NIPs (Expanding Capabilities)__
|
||||
### 3. Update Error String Function in `nostr_core/nostr_common.c`
|
||||
- [ ] Add cases for new error codes in `nostr_strerror()` function
|
||||
|
||||
These features enable more complex interactions and integrations.
|
||||
### 4. Add Function Declarations to `nostr_core/nip001.h`
|
||||
- [x] Add validation function declarations after existing function:
|
||||
```c
|
||||
// Event validation functions
|
||||
int nostr_validate_event_structure(cJSON* event);
|
||||
int nostr_verify_event_signature(cJSON* event);
|
||||
int nostr_validate_event(cJSON* event);
|
||||
```
|
||||
|
||||
| NIP | Description | `nostr_core_lib` Status | `nak` / `nostr-tools` Status | Recommendation | | :-- | :--- | :--- | :--- | :--- | | __NIP-25__ | __Reactions__ | ❌ __Missing__ | ✅ __Supported__ | __Medium Priority.__ Relatively simple to implement (Kind 7 events) and common in social clients. | | __NIP-26__ | __Delegated Event Signing__| ❌ __Missing__| ✅ __Supported__| __Medium Priority.__ Allows for one key to authorize another to sign events, useful for temporary or restricted keys. | | __NIP-44__ | __Versioned Encryption (v2)__ | ❌ __Missing__ | ✅ __Supported__ | __High Priority (along with NIP-04).__ Newer standard for encryption. We should aim to support both, but prioritize NIP-44 for new applications. | | __NIP-46__ | __Nostr Connect (Remote Signing)__| ❌ __Missing__ | ✅ __Supported__ | __Low Priority (for C library).__ More of an application-level concern, but we could provide structures and helpers to facilitate communication with signing "bunkers". | | __NIP-47__ | __Nostr Wallet Connect__ | ❌ __Missing__ | ✅ __Supported__| __Low Priority (for C library).__ Similar to NIP-46, this is primarily for app-level integration, but helpers would be valuable. | | __NIP-58__ | __Badges__ | ❌ __Missing__ | ✅ __Supported__ | __Medium Priority.__ Defines how to award and display badges (Kind 30008/30009 events). | | __NIP-94__ | __File Metadata__ | ❌ __Missing__ | ✅ __Supported__| __Medium Priority.__ For events that reference external files (images, videos), providing metadata like hashes and URLs. | | __NIP-98__ | __HTTP Auth__| ❌ __Missing__ | ✅ __Supported__| __High Priority.__ Allows services to authenticate users via a Nostr event, a powerful tool for integrating Nostr identity with web services. |
|
||||
### 5. Implement Functions in `nostr_core/nip001.c`
|
||||
- [ ] **`nostr_validate_event_structure()`** - Structure validation:
|
||||
- Check required fields exist: id, pubkey, created_at, kind, tags, content, sig
|
||||
- Validate field types (strings, numbers, arrays)
|
||||
- Validate hex string formats (id: 64 chars, pubkey: 64 chars, sig: 128 chars)
|
||||
- Validate created_at is valid timestamp
|
||||
- Validate kind is valid integer (0-65535)
|
||||
- Validate tags is array of string arrays
|
||||
- Validate content is string
|
||||
|
||||
---
|
||||
- [ ] **`nostr_verify_event_signature()`** - Cryptographic verification:
|
||||
- Generate serialized event string: `[0,<pubkey>,<created_at>,<kind>,<tags>,<content>]`
|
||||
- Calculate SHA-256 hash of serialized event
|
||||
- Convert hash to hex string and compare with event.id
|
||||
- Verify Schnorr signature using existing `nostr_schnorr_verify()` from utils.h
|
||||
- Use hex conversion functions from utils.h
|
||||
|
||||
### __Tier 3: Specialized & Less Common NIPs (Future Considerations)__
|
||||
- [ ] **`nostr_validate_event()`** - Complete validation:
|
||||
- Call `nostr_validate_event_structure()` first
|
||||
- If structure valid, call `nostr_verify_event_signature()`
|
||||
- Return appropriate error codes
|
||||
|
||||
| NIP | Description | `nostr_core_lib` Status | `nak` / `nostr-tools` Status | Recommendation | | :-- | :--- | :--- | :--- | :--- | | __NIP-18__| __Reposts__ | ❌ __Missing__ | ✅ __Supported__| __Low Priority.__ Simple to implement (Kind 6 events) but less critical than other features. | | __NIP-28__| __Public Chat Channels__| ❌ __Missing__ | ✅ __Supported__| __Low Priority.__ Defines event kinds for creating and managing public chat channels. | | __NIP-39__| __External Identities__ | ❌ __Missing__ | ✅ __Supported__| __Low Priority.__ For linking a Nostr profile to identities on other platforms like GitHub or Twitter. | | __NIP-42__ | __Relay Authentication__ | ❌ __Missing__| ✅ __Supported__| __Medium Priority.__ For paid relays or those requiring login. Essential for interacting with the full relay ecosystem. | | __NIP-43__| __Musig2 (Multisig)__ | ❌ __Missing__ | ✅ __Supported__| __Low Priority.__ A very powerful but niche feature. Complex to implement correctly. |
|
||||
### 6. Update Build System
|
||||
- [ ] Ensure new test compiles with existing build.sh
|
||||
- [ ] Test compilation of all new code
|
||||
|
||||
---
|
||||
### 7. Integration Testing
|
||||
- [ ] Test with real Nostr events from network
|
||||
- [ ] Test with events created by existing `nostr_create_and_sign_event()`
|
||||
- [ ] Verify compatibility with existing relay functions
|
||||
|
||||
### __Proposed Roadmap__
|
||||
## Technical Implementation Details
|
||||
|
||||
Based on this analysis, I recommend the following roadmap, prioritized by impact and foundational need:
|
||||
### Required Dependencies (Already Available):
|
||||
- `nostr_sha256()` from `nostr_core/utils.h`
|
||||
- `nostr_schnorr_verify()` from `nostr_core/utils.h`
|
||||
- `nostr_hex_to_bytes()` from `nostr_core/utils.h`
|
||||
- `nostr_bytes_to_hex()` from `nostr_core/utils.h`
|
||||
- cJSON library for JSON parsing
|
||||
|
||||
1. __Immediate Next Steps (High-Impact Basics):__
|
||||
### Validation Logic Based on NIP-01 and nostr-tools Reference:
|
||||
1. **Structure Validation**: Fast checks on JSON structure and basic format
|
||||
2. **Cryptographic Validation**: Expensive signature verification only after structure passes
|
||||
3. **Two-tier approach**: Allows early exit on malformed events
|
||||
|
||||
- __Implement NIP-04 & NIP-44:__ Encrypted messaging is a huge gap.
|
||||
- __Implement NIP-11:__ Fetching and parsing relay info documents.
|
||||
- __Implement NIP-05:__ DNS-based name verification.
|
||||
- __Implement NIP-57:__ Create and verify Zap events.
|
||||
- __Implement NIP-98:__ HTTP Auth for web services.
|
||||
### Error Handling Strategy:
|
||||
- Return specific error codes for different validation failures
|
||||
- Enable caller to understand exactly what failed
|
||||
- Maintain consistency with existing error code patterns
|
||||
|
||||
2. __Phase 2 (Ecosystem & Social Features):__
|
||||
## Files to Modify:
|
||||
- `nostr_core/nostr_common.h` (add error codes)
|
||||
- `nostr_core/nostr_common.c` (update error strings)
|
||||
- `nostr_core/nip001.h` (add function declarations)
|
||||
- `nostr_core/nip001.c` (implement functions)
|
||||
- `tests/nip01_validation_test.c` (create new file)
|
||||
|
||||
- __Implement NIP-25:__ Reactions.
|
||||
- __Implement NIP-26:__ Delegation.
|
||||
- __Implement NIP-42:__ Relay Authentication.
|
||||
- __Implement NIP-21:__ `nostr:` URI handling.
|
||||
- __Implement NIP-58:__ Badges.
|
||||
|
||||
3. __Phase 3 (Advanced & Specialized Features):__
|
||||
|
||||
- __Implement NIP-18:__ Reposts.
|
||||
- __Implement NIP-43:__ Musig2 (this is a big one).
|
||||
- Add helpers for NIP-07, NIP-46, and NIP-47.
|
||||
|
||||
What are your thoughts on this assessment and proposed roadmap? We can adjust the priorities based on your goals for the library. Once we have a plan you're happy with, I can start implementing the first features when you're ready to switch to
|
||||
|
||||
Act Mode (⌘⇧A).
|
||||
## Testing Priority:
|
||||
1. Structure validation with malformed events
|
||||
2. Cryptographic validation with tampered events
|
||||
3. Valid event validation end-to-end
|
||||
4. Integration with existing event creation functions
|
||||
5. Performance testing with large numbers of events
|
||||
|
|
Loading…
Reference in New Issue