nip13 validation added

This commit is contained in:
Laan Tungir 2025-09-05 13:42:03 -04:00
parent 445ab7a8f4
commit 55e2a9c68e
16 changed files with 885 additions and 1 deletions

View File

@ -29,3 +29,25 @@
[08:50:18.550] RECV nostr.mom:443: ["EVENT","sync_2_1755348617",{"content":"😂\nStay humble and stack zaps ⚡️ ","created_at":1755348616,"id":"974af84ac7c4041ccf44741adaeffb911aad9d5ed13543496a1ce2b519dc6fdf","kind":1,"pubkey":"3824552ea18ce24fae867d292514c40e0d4d1c39e18e752e852a51e4a0b2d7c8","sig":"04acf08ee2b2218604bc0e663f52ad6f0a52109674457e232c940e3bb91df971cc7baf33248ced999b1e294dd8de68c2e0ca5f586ed7bddecc0a88ddc58da781","tags":[["e","253a2c3eda166a82e8f42f0018c96845df080e71cf7c30ad7107e5936b6892dd","","root"],["p","7c765d407d3a9d5ea117cb8b8699628560787fc084a0c76afaa449bfbd121d84"]]}]
[08:50:18.560] RECV nostr.mom:443: ["EOSE","sync_2_1755348617"]
[08:50:18.560] SEND nostr.mom:443: ["CLOSE", "sync_2_1755348617"]
PoW mining: 0 attempts, best this round: 0, overall best: 0, goal: 8
PoW SUCCESS: Found difficulty 9 (target 8) at nonce 758 after 759 attempts
Final event: {
"pubkey": "b38ce15d3d9874ee710dfabb7ff9801b1e0e20aace6e9a1a05fa7482a04387d1",
"created_at": 1757093676,
"kind": 1,
"tags": [["nonce", "758", "8"]],
"content": "Testing PoW generation",
"id": "006e0b830d970626fb0d3c6eb395f29dcc729b7d671f8e6d85f48ccc8dfcccb1",
"sig": "ee3106e2b7dac582fd4ab4d851091ef566e9e4f4396c9a8976a6099cc34122e90378fe83d08d67a381e328d5e51ba6910fb451cb6c8234c85ab4ceac59830000"
}
PoW mining: 0 attempts, best this round: 0, overall best: 0, goal: 8
PoW SUCCESS: Found difficulty 10 (target 8) at nonce 25 after 26 attempts
Final event: {
"pubkey": "b38ce15d3d9874ee710dfabb7ff9801b1e0e20aace6e9a1a05fa7482a04387d1",
"created_at": 1757093771,
"kind": 1,
"tags": [["nonce", "25", "8"]],
"content": "Testing PoW generation",
"id": "0034dad20205876be164b768ba5efad8aaa23bdf16b273544785cae9f13a76f7",
"sig": "e1ef97bf916f9e1fd94e231e62833e6ff000be29a92e16fc3656142e51abbf4ee8795ab4100f448e6712c1a76bb128016c24306b165077188fa8e5b82c72f23f"
}

View File

@ -277,3 +277,332 @@ int nostr_add_proof_of_work(cJSON* event, const unsigned char* private_key,
// If we reach here, we've exceeded max attempts
return NOSTR_ERROR_CRYPTO_FAILED;
}
/**
* Calculate PoW difficulty (leading zero bits) for an event ID
*
* @param event_id_hex Hexadecimal event ID string (64 characters)
* @return Number of leading zero bits, or negative error code on failure
*/
int nostr_calculate_pow_difficulty(const char* event_id_hex) {
if (!event_id_hex) {
return NOSTR_ERROR_INVALID_INPUT;
}
// Validate hex string length (should be 64 characters for SHA-256)
size_t hex_len = strlen(event_id_hex);
if (hex_len != 64) {
return NOSTR_ERROR_NIP13_CALCULATION;
}
// Convert hex to bytes
unsigned char hash[32];
if (nostr_hex_to_bytes(event_id_hex, hash, 32) != NOSTR_SUCCESS) {
return NOSTR_ERROR_NIP13_CALCULATION;
}
// Use existing NIP-13 reference implementation
return count_leading_zero_bits(hash);
}
/**
* Extract nonce information from event tags
*
* @param event Complete event JSON object
* @param nonce_out Output pointer for nonce value (can be NULL)
* @param target_difficulty_out Output pointer for target difficulty (can be NULL)
* @return NOSTR_SUCCESS if nonce tag found, NOSTR_ERROR_NIP13_NO_NONCE_TAG if not found, other error codes on failure
*/
int nostr_extract_nonce_info(cJSON* event, uint64_t* nonce_out, int* target_difficulty_out) {
if (!event) {
return NOSTR_ERROR_INVALID_INPUT;
}
// Initialize output values
if (nonce_out) *nonce_out = 0;
if (target_difficulty_out) *target_difficulty_out = -1;
// Get tags array
cJSON* tags = cJSON_GetObjectItem(event, "tags");
if (!tags || !cJSON_IsArray(tags)) {
return NOSTR_ERROR_NIP13_NO_NONCE_TAG;
}
// Search for nonce tag
cJSON* tag = NULL;
cJSON_ArrayForEach(tag, tags) {
if (!cJSON_IsArray(tag) || cJSON_GetArraySize(tag) < 2) {
continue;
}
// Check if this is a nonce tag
cJSON* tag_type = cJSON_GetArrayItem(tag, 0);
if (!tag_type || !cJSON_IsString(tag_type)) {
continue;
}
const char* tag_name = cJSON_GetStringValue(tag_type);
if (!tag_name || strcmp(tag_name, "nonce") != 0) {
continue;
}
// Extract nonce value (second element)
cJSON* nonce_item = cJSON_GetArrayItem(tag, 1);
if (!nonce_item || !cJSON_IsString(nonce_item)) {
return NOSTR_ERROR_NIP13_INVALID_NONCE_TAG;
}
const char* nonce_str = cJSON_GetStringValue(nonce_item);
if (!nonce_str) {
return NOSTR_ERROR_NIP13_INVALID_NONCE_TAG;
}
// Parse nonce value
char* endptr;
uint64_t nonce_val = strtoull(nonce_str, &endptr, 10);
if (*endptr != '\0') {
return NOSTR_ERROR_NIP13_INVALID_NONCE_TAG;
}
if (nonce_out) *nonce_out = nonce_val;
// Extract target difficulty (third element, optional)
if (cJSON_GetArraySize(tag) >= 3) {
cJSON* target_item = cJSON_GetArrayItem(tag, 2);
if (target_item && cJSON_IsString(target_item)) {
const char* target_str = cJSON_GetStringValue(target_item);
if (target_str) {
char* endptr2;
long target_val = strtol(target_str, &endptr2, 10);
if (*endptr2 == '\0' && target_val >= 0) {
if (target_difficulty_out) *target_difficulty_out = (int)target_val;
}
}
}
}
return NOSTR_SUCCESS;
}
// No nonce tag found
return NOSTR_ERROR_NIP13_NO_NONCE_TAG;
}
/**
* Validate just the nonce tag format (without PoW calculation)
*
* @param nonce_tag_array JSON array representing a nonce tag
* @return NOSTR_SUCCESS if valid format, error code otherwise
*/
int nostr_validate_nonce_tag(cJSON* nonce_tag_array) {
if (!nonce_tag_array || !cJSON_IsArray(nonce_tag_array)) {
return NOSTR_ERROR_NIP13_INVALID_NONCE_TAG;
}
int array_size = cJSON_GetArraySize(nonce_tag_array);
if (array_size < 2) {
return NOSTR_ERROR_NIP13_INVALID_NONCE_TAG;
}
// First element should be "nonce"
cJSON* tag_type = cJSON_GetArrayItem(nonce_tag_array, 0);
if (!tag_type || !cJSON_IsString(tag_type)) {
return NOSTR_ERROR_NIP13_INVALID_NONCE_TAG;
}
const char* tag_name = cJSON_GetStringValue(tag_type);
if (!tag_name || strcmp(tag_name, "nonce") != 0) {
return NOSTR_ERROR_NIP13_INVALID_NONCE_TAG;
}
// Second element should be a valid nonce (numeric string)
cJSON* nonce_item = cJSON_GetArrayItem(nonce_tag_array, 1);
if (!nonce_item || !cJSON_IsString(nonce_item)) {
return NOSTR_ERROR_NIP13_INVALID_NONCE_TAG;
}
const char* nonce_str = cJSON_GetStringValue(nonce_item);
if (!nonce_str) {
return NOSTR_ERROR_NIP13_INVALID_NONCE_TAG;
}
// Validate nonce is a valid number
char* endptr;
strtoull(nonce_str, &endptr, 10);
if (*endptr != '\0') {
return NOSTR_ERROR_NIP13_INVALID_NONCE_TAG;
}
// Third element (target difficulty) is optional, but if present should be valid
if (array_size >= 3) {
cJSON* target_item = cJSON_GetArrayItem(nonce_tag_array, 2);
if (target_item && cJSON_IsString(target_item)) {
const char* target_str = cJSON_GetStringValue(target_item);
if (target_str) {
char* endptr2;
strtol(target_str, &endptr2, 10);
if (*endptr2 != '\0') {
return NOSTR_ERROR_NIP13_INVALID_NONCE_TAG;
}
}
}
}
return NOSTR_SUCCESS;
}
/**
* Validate Proof of Work for an event according to NIP-13
*
* @param event Complete event JSON object to validate
* @param min_difficulty Minimum difficulty required by the relay (0 = no requirement)
* @param validation_flags Bitflags for validation options
* @param result_info Optional output struct for detailed results (can be NULL)
* @return NOSTR_SUCCESS if PoW is valid and meets requirements, error code otherwise
*/
int nostr_validate_pow(cJSON* event, int min_difficulty, int validation_flags,
nostr_pow_result_t* result_info) {
if (!event) {
return NOSTR_ERROR_INVALID_INPUT;
}
// Initialize result info if provided
if (result_info) {
result_info->actual_difficulty = 0;
result_info->committed_target = -1;
result_info->nonce_value = 0;
result_info->has_nonce_tag = 0;
result_info->error_detail[0] = '\0';
}
// Get event ID for PoW calculation
cJSON* id_item = cJSON_GetObjectItem(event, "id");
if (!id_item || !cJSON_IsString(id_item)) {
if (result_info) {
snprintf(result_info->error_detail, sizeof(result_info->error_detail),
"Missing or invalid event ID");
}
return NOSTR_ERROR_EVENT_INVALID_ID;
}
const char* event_id = cJSON_GetStringValue(id_item);
if (!event_id) {
if (result_info) {
snprintf(result_info->error_detail, sizeof(result_info->error_detail),
"Event ID is not a string");
}
return NOSTR_ERROR_EVENT_INVALID_ID;
}
// Calculate actual PoW difficulty
int actual_difficulty = nostr_calculate_pow_difficulty(event_id);
if (actual_difficulty < 0) {
if (result_info) {
snprintf(result_info->error_detail, sizeof(result_info->error_detail),
"Failed to calculate PoW difficulty");
}
return actual_difficulty; // Return the specific error from calculation
}
if (result_info) {
result_info->actual_difficulty = actual_difficulty;
}
// Check if minimum difficulty requirement is met
if (min_difficulty > 0 && actual_difficulty < min_difficulty) {
if (result_info) {
snprintf(result_info->error_detail, sizeof(result_info->error_detail),
"Insufficient difficulty: %d < %d", actual_difficulty, min_difficulty);
}
return NOSTR_ERROR_NIP13_INSUFFICIENT;
}
// Extract nonce information if validation flags require it
uint64_t nonce_value = 0;
int committed_target = -1;
int nonce_extract_result = nostr_extract_nonce_info(event, &nonce_value, &committed_target);
if (result_info) {
result_info->nonce_value = nonce_value;
result_info->committed_target = committed_target;
result_info->has_nonce_tag = (nonce_extract_result == NOSTR_SUCCESS) ? 1 : 0;
}
// Validate nonce tag presence if required
if (validation_flags & NOSTR_POW_VALIDATE_NONCE_TAG) {
if (nonce_extract_result == NOSTR_ERROR_NIP13_NO_NONCE_TAG) {
if (result_info) {
snprintf(result_info->error_detail, sizeof(result_info->error_detail),
"Missing required nonce tag");
}
return NOSTR_ERROR_NIP13_NO_NONCE_TAG;
} else if (nonce_extract_result != NOSTR_SUCCESS) {
if (result_info) {
snprintf(result_info->error_detail, sizeof(result_info->error_detail),
"Invalid nonce tag format");
}
return nonce_extract_result;
}
// If strict format validation is enabled, validate the nonce tag structure
if (validation_flags & NOSTR_POW_STRICT_FORMAT) {
cJSON* tags = cJSON_GetObjectItem(event, "tags");
if (tags && cJSON_IsArray(tags)) {
cJSON* tag = NULL;
cJSON_ArrayForEach(tag, tags) {
if (cJSON_IsArray(tag) && cJSON_GetArraySize(tag) >= 2) {
cJSON* tag_type = cJSON_GetArrayItem(tag, 0);
if (tag_type && cJSON_IsString(tag_type)) {
const char* tag_name = cJSON_GetStringValue(tag_type);
if (tag_name && strcmp(tag_name, "nonce") == 0) {
int validation_result = nostr_validate_nonce_tag(tag);
if (validation_result != NOSTR_SUCCESS) {
if (result_info) {
snprintf(result_info->error_detail, sizeof(result_info->error_detail),
"Nonce tag failed strict format validation");
}
return validation_result;
}
break;
}
}
}
}
}
}
}
// Validate committed target difficulty if required
if (validation_flags & NOSTR_POW_VALIDATE_TARGET_COMMIT) {
if (nonce_extract_result == NOSTR_SUCCESS && committed_target == -1) {
if (result_info) {
snprintf(result_info->error_detail, sizeof(result_info->error_detail),
"Missing committed target difficulty in nonce tag");
}
return NOSTR_ERROR_NIP13_INVALID_NONCE_TAG;
}
// Check for target/difficulty mismatch if rejecting lower targets
if (validation_flags & NOSTR_POW_REJECT_LOWER_TARGET) {
// According to NIP-13: "if you require 40 bits to reply to your thread and see
// a committed target of 30, you can safely reject it even if the note has 40 bits difficulty"
// This means we reject if committed_target < min_difficulty, not actual_difficulty
if (committed_target != -1 && min_difficulty > 0 && committed_target < min_difficulty) {
if (result_info) {
snprintf(result_info->error_detail, sizeof(result_info->error_detail),
"Committed target (%d) is lower than required minimum (%d)",
committed_target, min_difficulty);
}
return NOSTR_ERROR_NIP13_TARGET_MISMATCH;
}
}
}
// All validations passed
if (result_info) {
snprintf(result_info->error_detail, sizeof(result_info->error_detail),
"PoW validation successful");
}
return NOSTR_SUCCESS;
}

View File

@ -8,6 +8,26 @@
#include "nip001.h"
#include <stdint.h>
// PoW validation flags
#define NOSTR_POW_VALIDATE_NONCE_TAG 0x01 // Require and validate nonce tag format
#define NOSTR_POW_VALIDATE_TARGET_COMMIT 0x02 // Validate committed target difficulty
#define NOSTR_POW_REJECT_LOWER_TARGET 0x04 // Reject if committed target < actual difficulty
#define NOSTR_POW_STRICT_FORMAT 0x08 // Strict nonce tag format validation
// Convenience combinations
#define NOSTR_POW_VALIDATE_BASIC (NOSTR_POW_VALIDATE_NONCE_TAG)
#define NOSTR_POW_VALIDATE_FULL (NOSTR_POW_VALIDATE_NONCE_TAG | NOSTR_POW_VALIDATE_TARGET_COMMIT)
#define NOSTR_POW_VALIDATE_ANTI_SPAM (NOSTR_POW_VALIDATE_FULL | NOSTR_POW_REJECT_LOWER_TARGET)
// Result information structure (optional output)
typedef struct {
int actual_difficulty; // Calculated difficulty (leading zero bits)
int committed_target; // Target difficulty from nonce tag (-1 if none)
uint64_t nonce_value; // Nonce value from tag (0 if none)
int has_nonce_tag; // 1 if nonce tag present, 0 otherwise
char error_detail[256]; // Detailed error description
} nostr_pow_result_t;
// Function declarations
int nostr_add_proof_of_work(cJSON* event, const unsigned char* private_key,
int target_difficulty, int max_attempts,
@ -15,4 +35,14 @@ int nostr_add_proof_of_work(cJSON* event, const unsigned char* private_key,
void (*progress_callback)(int current_difficulty, uint64_t nonce, void* user_data),
void* user_data);
// PoW validation functions
int nostr_validate_pow(cJSON* event, int min_difficulty, int validation_flags,
nostr_pow_result_t* result_info);
int nostr_calculate_pow_difficulty(const char* event_id_hex);
int nostr_extract_nonce_info(cJSON* event, uint64_t* nonce_out, int* target_difficulty_out);
int nostr_validate_nonce_tag(cJSON* nonce_tag_array);
#endif // NIP013_H

View File

@ -38,6 +38,11 @@ const char* nostr_strerror(int error_code) {
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";
case NOSTR_ERROR_NIP13_INSUFFICIENT: return "NIP-13: Insufficient PoW difficulty";
case NOSTR_ERROR_NIP13_NO_NONCE_TAG: return "NIP-13: Missing nonce tag";
case NOSTR_ERROR_NIP13_INVALID_NONCE_TAG: return "NIP-13: Invalid nonce tag format";
case NOSTR_ERROR_NIP13_TARGET_MISMATCH: return "NIP-13: Target difficulty mismatch";
case NOSTR_ERROR_NIP13_CALCULATION: return "NIP-13: PoW calculation error";
default: return "Unknown error";
}
}

View File

@ -36,6 +36,13 @@
#define NOSTR_ERROR_EVENT_INVALID_TAGS -36
#define NOSTR_ERROR_EVENT_INVALID_CONTENT -37
// NIP-13 PoW-specific error codes
#define NOSTR_ERROR_NIP13_INSUFFICIENT -100
#define NOSTR_ERROR_NIP13_NO_NONCE_TAG -101
#define NOSTR_ERROR_NIP13_INVALID_NONCE_TAG -102
#define NOSTR_ERROR_NIP13_TARGET_MISMATCH -103
#define NOSTR_ERROR_NIP13_CALCULATION -104
// Constants
#define NOSTR_PRIVATE_KEY_SIZE 32

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
tests/nip13_test Executable file

Binary file not shown.

491
tests/nip13_test.c Normal file
View File

@ -0,0 +1,491 @@
/*
* NIP-13 Proof of Work Test Suite
* Tests PoW generation, difficulty calculation, nonce extraction, and validation
* 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 <unistd.h>
#include "../nostr_core/nip013.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);
}
}
// Test 1: Difficulty calculation with NIP-13 example
int test_difficulty_calculation_reference(void) {
print_test_header("Difficulty Calculation - NIP-13 Reference");
// Event ID from NIP-13 spec: 000006d8c378af1779d2feebc7603a125d99eca0ccf1085959b307f64e5dd358
// This should have ~20 leading zero bits as stated in the spec
const char* event_id = "000006d8c378af1779d2feebc7603a125d99eca0ccf1085959b307f64e5dd358";
printf("Testing event ID: %s\n", event_id);
printf("Expected: ~20 leading zero bits\n");
int difficulty = nostr_calculate_pow_difficulty(event_id);
printf("Actual: %d leading zero bits\n", difficulty);
if (difficulty < 0) {
printf("❌ Error calculating difficulty: %d (%s)\n", difficulty, nostr_strerror(difficulty));
return 0;
}
// The NIP-13 spec example should have around 20 bits
if (difficulty >= 19 && difficulty <= 21) {
printf("✅ Difficulty calculation matches expected range (19-21 bits)\n");
return 1;
} else {
printf("❌ Difficulty %d is outside expected range (19-21 bits)\n", difficulty);
return 0;
}
}
// Test 2: Extract nonce info from NIP-13 reference event
int test_nonce_extraction_reference(void) {
print_test_header("Nonce Extraction - NIP-13 Reference Event");
// Complete event from NIP-13 spec
const char* event_json = "{"
"\"id\":\"000006d8c378af1779d2feebc7603a125d99eca0ccf1085959b307f64e5dd358\","
"\"pubkey\":\"a48380f4cfcc1ad5378294fcac36439770f9c878dd880ffa94bb74ea54a6f243\","
"\"created_at\":1651794653,"
"\"kind\":1,"
"\"tags\":[[\"nonce\",\"776797\",\"20\"]],"
"\"content\":\"It's just me mining my own business\","
"\"sig\":\"284622fc0a3f4f1303455d5175f7ba962a3300d136085b9566801bc2e0699de0c7e31e44c81fb40ad9049173742e904713c3594a1da0fc5d2382a25c11aba977\""
"}";
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;
}
uint64_t nonce_value = 0;
int target_difficulty = -1;
int result = nostr_extract_nonce_info(event, &nonce_value, &target_difficulty);
printf("Extraction result: %d (%s)\n", result, nostr_strerror(result));
printf("Expected nonce: 776797\n");
printf("Actual nonce: %llu\n", (unsigned long long)nonce_value);
printf("Expected target: 20\n");
printf("Actual target: %d\n", target_difficulty);
cJSON_Delete(event);
if (result != NOSTR_SUCCESS) {
printf("❌ Failed to extract nonce info\n");
return 0;
}
if (nonce_value == 776797 && target_difficulty == 20) {
printf("✅ Nonce extraction successful\n");
return 1;
} else {
printf("❌ Extracted values don't match expected\n");
return 0;
}
}
// Test 3: PoW validation with reference event
int test_pow_validation_reference(void) {
print_test_header("PoW Validation - NIP-13 Reference Event");
const char* event_json = "{"
"\"id\":\"000006d8c378af1779d2feebc7603a125d99eca0ccf1085959b307f64e5dd358\","
"\"pubkey\":\"a48380f4cfcc1ad5378294fcac36439770f9c878dd880ffa94bb74ea54a6f243\","
"\"created_at\":1651794653,"
"\"kind\":1,"
"\"tags\":[[\"nonce\",\"776797\",\"20\"]],"
"\"content\":\"It's just me mining my own business\","
"\"sig\":\"284622fc0a3f4f1303455d5175f7ba962a3300d136085b9566801bc2e0699de0c7e31e44c81fb40ad9049173742e904713c3594a1da0fc5d2382a25c11aba977\""
"}";
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;
}
// Test basic validation (no minimum difficulty)
nostr_pow_result_t result_info;
int validation_result = nostr_validate_pow(event, 0, NOSTR_POW_VALIDATE_BASIC, &result_info);
printf("Validation result: %d (%s)\n", validation_result, nostr_strerror(validation_result));
printf("Actual difficulty: %d\n", result_info.actual_difficulty);
printf("Committed target: %d\n", result_info.committed_target);
printf("Nonce value: %llu\n", (unsigned long long)result_info.nonce_value);
printf("Has nonce tag: %d\n", result_info.has_nonce_tag);
printf("Error detail: %s\n", result_info.error_detail);
cJSON_Delete(event);
return (validation_result == NOSTR_SUCCESS && result_info.has_nonce_tag == 1);
}
// Test 4: Generate PoW with our library and validate it
int test_pow_generation_and_validation(void) {
print_test_header("PoW Generation and Validation - Our Library");
// Create a test event for mining
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();
printf("Creating base event...\n");
cJSON* event = nostr_create_and_sign_event(1, "Testing PoW generation", tags, private_key, time(NULL));
if (!event) {
printf("❌ Event creation failed\n");
cJSON_Delete(tags);
return 0;
}
char* original_event_str = cJSON_Print(event);
printf("Original event (no PoW):\n%s\n\n", original_event_str);
free(original_event_str);
// Add PoW with difficulty 8 (reasonable for testing)
printf("Adding PoW with difficulty 8...\n");
int pow_result = nostr_add_proof_of_work(event, private_key, 8, 100000, 10000, 10000, NULL, NULL);
if (pow_result != NOSTR_SUCCESS) {
printf("❌ PoW generation failed: %d (%s)\n", pow_result, nostr_strerror(pow_result));
cJSON_Delete(event);
return 0;
}
char* pow_event_str = cJSON_Print(event);
printf("Event with PoW:\n%s\n\n", pow_event_str);
free(pow_event_str);
// Now validate the PoW
printf("Validating generated PoW...\n");
nostr_pow_result_t result_info;
int validation_result = nostr_validate_pow(event, 8, NOSTR_POW_VALIDATE_FULL, &result_info);
printf("Validation result: %d (%s)\n", validation_result, nostr_strerror(validation_result));
printf("Actual difficulty: %d\n", result_info.actual_difficulty);
printf("Committed target: %d\n", result_info.committed_target);
printf("Has nonce tag: %d\n", result_info.has_nonce_tag);
printf("Error detail: %s\n", result_info.error_detail);
cJSON_Delete(event);
if (validation_result == NOSTR_SUCCESS && result_info.actual_difficulty >= 8) {
printf("✅ PoW generation and validation successful\n");
return 1;
} else {
printf("❌ PoW validation failed\n");
return 0;
}
}
// Test 5: Test various validation flag combinations
int test_validation_flags(void) {
print_test_header("Validation Flags - Various Combinations");
// Use NIP-13 reference event
const char* event_json = "{"
"\"id\":\"000006d8c378af1779d2feebc7603a125d99eca0ccf1085959b307f64e5dd358\","
"\"pubkey\":\"a48380f4cfcc1ad5378294fcac36439770f9c878dd880ffa94bb74ea54a6f243\","
"\"created_at\":1651794653,"
"\"kind\":1,"
"\"tags\":[[\"nonce\",\"776797\",\"20\"]],"
"\"content\":\"It's just me mining my own business\","
"\"sig\":\"284622fc0a3f4f1303455d5175f7ba962a3300d136085b9566801bc2e0699de0c7e31e44c81fb40ad9049173742e904713c3594a1da0fc5d2382a25c11aba977\""
"}";
cJSON* event = cJSON_Parse(event_json);
if (!event) {
printf("❌ JSON Parse Error\n");
return 0;
}
int all_passed = 1;
nostr_pow_result_t result_info;
// Test 1: Basic validation
printf("\nSubtest 1: NOSTR_POW_VALIDATE_BASIC\n");
int result = nostr_validate_pow(event, 0, NOSTR_POW_VALIDATE_BASIC, &result_info);
printf("Result: %d (%s)\n", result, nostr_strerror(result));
if (result != NOSTR_SUCCESS) all_passed = 0;
// Test 2: Full validation
printf("\nSubtest 2: NOSTR_POW_VALIDATE_FULL\n");
result = nostr_validate_pow(event, 0, NOSTR_POW_VALIDATE_FULL, &result_info);
printf("Result: %d (%s)\n", result, nostr_strerror(result));
if (result != NOSTR_SUCCESS) all_passed = 0;
// Test 3: Anti-spam validation (should pass since committed target matches actual)
printf("\nSubtest 3: NOSTR_POW_VALIDATE_ANTI_SPAM\n");
result = nostr_validate_pow(event, 0, NOSTR_POW_VALIDATE_ANTI_SPAM, &result_info);
printf("Result: %d (%s)\n", result, nostr_strerror(result));
if (result != NOSTR_SUCCESS) all_passed = 0;
// Test 4: Minimum difficulty requirement (15 bits - should pass)
printf("\nSubtest 4: Minimum difficulty 15 bits\n");
result = nostr_validate_pow(event, 15, NOSTR_POW_VALIDATE_BASIC, &result_info);
printf("Result: %d (%s)\n", result, nostr_strerror(result));
if (result != NOSTR_SUCCESS) all_passed = 0;
// Test 5: Too high minimum difficulty (30 bits - should fail)
printf("\nSubtest 5: Minimum difficulty 30 bits (should fail)\n");
result = nostr_validate_pow(event, 30, NOSTR_POW_VALIDATE_BASIC, &result_info);
printf("Result: %d (%s)\n", result, nostr_strerror(result));
if (result == NOSTR_SUCCESS) {
printf("❌ Should have failed but didn't\n");
all_passed = 0;
} else if (result == NOSTR_ERROR_NIP13_INSUFFICIENT) {
printf("✅ Correctly failed with insufficient difficulty\n");
} else {
printf("❌ Failed with unexpected error\n");
all_passed = 0;
}
cJSON_Delete(event);
return all_passed;
}
// Test 6: Error conditions and edge cases
int test_error_conditions(void) {
print_test_header("Error Conditions and Edge Cases");
int all_passed = 1;
// Test 1: NULL event
printf("\nSubtest 1: NULL event\n");
int result = nostr_validate_pow(NULL, 0, NOSTR_POW_VALIDATE_BASIC, NULL);
printf("Expected: NOSTR_ERROR_INVALID_INPUT (-1)\n");
printf("Actual: %d (%s)\n", result, nostr_strerror(result));
if (result != NOSTR_ERROR_INVALID_INPUT) all_passed = 0;
// Test 2: Event without ID
printf("\nSubtest 2: Event without ID\n");
const char* no_id_json = "{\"kind\":1,\"content\":\"test\",\"tags\":[]}";
cJSON* no_id_event = cJSON_Parse(no_id_json);
result = nostr_validate_pow(no_id_event, 0, NOSTR_POW_VALIDATE_BASIC, NULL);
printf("Expected: NOSTR_ERROR_EVENT_INVALID_ID (-31)\n");
printf("Actual: %d (%s)\n", result, nostr_strerror(result));
if (result != NOSTR_ERROR_EVENT_INVALID_ID) all_passed = 0;
cJSON_Delete(no_id_event);
// Test 3: Event without nonce tag when required
printf("\nSubtest 3: Event without nonce tag when required\n");
const char* no_nonce_json = "{"
"\"id\":\"f1e582c90f071c0110cc5bcac2dcc6d8c32250e3cc26fcbe93470d918f2ffaf0\","
"\"kind\":1,\"content\":\"test\",\"tags\":[]"
"}";
cJSON* no_nonce_event = cJSON_Parse(no_nonce_json);
result = nostr_validate_pow(no_nonce_event, 0, NOSTR_POW_VALIDATE_NONCE_TAG, NULL);
printf("Expected: NOSTR_ERROR_NIP13_NO_NONCE_TAG (-101)\n");
printf("Actual: %d (%s)\n", result, nostr_strerror(result));
if (result != NOSTR_ERROR_NIP13_NO_NONCE_TAG) all_passed = 0;
cJSON_Delete(no_nonce_event);
// Test 4: Invalid hex ID
printf("\nSubtest 4: Invalid hex ID\n");
result = nostr_calculate_pow_difficulty("invalid_hex_string");
printf("Expected: NOSTR_ERROR_NIP13_CALCULATION (-104)\n");
printf("Actual: %d (%s)\n", result, nostr_strerror(result));
if (result != NOSTR_ERROR_NIP13_CALCULATION) all_passed = 0;
// Test 5: Wrong length hex ID
printf("\nSubtest 5: Wrong length hex ID\n");
result = nostr_calculate_pow_difficulty("f1e582c90f071c0110cc5bcac2dcc6d8c32250e3cc26fcbe93470d918f2ffa"); // 63 chars instead of 64
printf("Expected: NOSTR_ERROR_NIP13_CALCULATION (-104)\n");
printf("Actual: %d (%s)\n", result, nostr_strerror(result));
if (result != NOSTR_ERROR_NIP13_CALCULATION) all_passed = 0;
return all_passed;
}
// Test 7: Integration with nak-generated PoW events
int test_nak_integration(void) {
print_test_header("Integration with nak-generated PoW events");
// Generate a test event with nak and PoW difficulty 8
printf("Generating PoW event with nak (difficulty 8)...\n");
// Create a temporary key for this test
char temp_key[] = "/tmp/nip13_test_key_XXXXXX";
int fd = mkstemp(temp_key);
if (fd == -1) {
printf("❌ Failed to create temporary key file\n");
return 0;
}
// Write test key to file (same as used in other tests)
const char* test_key = "91ba716fa9e7ea2fcbad360cf4f8e0d312f73984da63d90f524ad61a6a1e7dbe";
ssize_t bytes_written = write(fd, test_key, strlen(test_key));
close(fd);
if (bytes_written != (ssize_t)strlen(test_key)) {
printf("❌ Failed to write test key to temporary file\n");
unlink(temp_key);
return 0;
}
// Generate event with PoW using nak
char cmd[1024];
snprintf(cmd, sizeof(cmd), "nak event --sec %s -c \"PoW test from nak\" --pow 8 --ts %ld",
test_key, (long)time(NULL));
printf("Executing: %s\n", cmd);
FILE* pipe = popen(cmd, "r");
if (!pipe) {
printf("❌ Failed to execute nak command\n");
unlink(temp_key);
return 0;
}
char event_json[4096] = {0};
if (!fgets(event_json, sizeof(event_json), pipe)) {
printf("❌ Failed to read nak output\n");
pclose(pipe);
unlink(temp_key);
return 0;
}
pclose(pipe);
unlink(temp_key);
// Remove trailing newline
event_json[strcspn(event_json, "\n")] = 0;
printf("nak-generated event:\n%s\n\n", event_json);
// Parse and validate the event
cJSON* event = cJSON_Parse(event_json);
if (!event) {
printf("❌ Failed to parse nak-generated JSON\n");
return 0;
}
// Validate the PoW
nostr_pow_result_t result_info;
int validation_result = nostr_validate_pow(event, 8, NOSTR_POW_VALIDATE_FULL, &result_info);
printf("Validation result: %d (%s)\n", validation_result, nostr_strerror(validation_result));
printf("Actual difficulty: %d\n", result_info.actual_difficulty);
printf("Committed target: %d\n", result_info.committed_target);
printf("Has nonce tag: %d\n", result_info.has_nonce_tag);
printf("Error detail: %s\n", result_info.error_detail);
cJSON_Delete(event);
if (validation_result == NOSTR_SUCCESS && result_info.actual_difficulty >= 8) {
printf("✅ nak-generated PoW event validates successfully\n");
return 1;
} else {
printf("❌ nak-generated PoW event validation failed\n");
return 0;
}
}
int main(void) {
printf("=== NIP-13 Proof of Work Test Suite ===\n");
printf("Following TESTS POLICY: Shows expected vs actual values, prints entire JSON events\n");
printf("Tests both PoW generation and validation functionality\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;
// Test 1: Basic difficulty calculation
test_result = test_difficulty_calculation_reference();
print_test_result(test_result, "Difficulty Calculation - NIP-13 Reference");
if (!test_result) all_passed = 0;
// Test 2: Nonce extraction
test_result = test_nonce_extraction_reference();
print_test_result(test_result, "Nonce Extraction - NIP-13 Reference Event");
if (!test_result) all_passed = 0;
// Test 3: Basic PoW validation
test_result = test_pow_validation_reference();
print_test_result(test_result, "PoW Validation - NIP-13 Reference Event");
if (!test_result) all_passed = 0;
// Test 4: PoW generation and validation
test_result = test_pow_generation_and_validation();
print_test_result(test_result, "PoW Generation and Validation - Our Library");
if (!test_result) all_passed = 0;
// Test 5: Validation flags
test_result = test_validation_flags();
print_test_result(test_result, "Validation Flags - Various Combinations");
if (!test_result) all_passed = 0;
// Test 6: Error conditions
test_result = test_error_conditions();
print_test_result(test_result, "Error Conditions and Edge Cases");
if (!test_result) all_passed = 0;
// Test 7: nak integration (optional - may fail if nak not available)
printf("\n=== Optional nak Integration Test ===\n");
test_result = test_nak_integration();
if (test_result) {
print_test_result(test_result, "Integration with nak-generated PoW events");
} else {
printf("⚠️ SKIP: Integration with nak-generated PoW events (nak may not be available)\n");
// Don't fail the entire test suite for this optional test
}
// 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! NIP-13 PoW implementation is working correctly.\n");
printf("✅ PoW generation works\n");
printf("✅ PoW difficulty calculation works\n");
printf("✅ Nonce extraction works\n");
printf("✅ PoW validation works\n");
printf("✅ Error handling works\n");
} else {
printf("❌ SOME TESTS FAILED. Please review the output above.\n");
}
nostr_cleanup();
return all_passed ? 0 : 1;
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.