From af06f23939d97b81f91603931844500b7a91d13a Mon Sep 17 00:00:00 2001 From: Laan Tungir Date: Sat, 11 Apr 2026 15:53:44 -0400 Subject: [PATCH] Fix NIP-59 dynamic buffer sizing for NIP-17 gift wraps --- VERSION | 2 +- nostr_core/nip059.c | 94 +++++++++++++++++++++++++++++++++++++---- nostr_core/nostr_core.h | 4 +- 3 files changed, 89 insertions(+), 11 deletions(-) diff --git a/VERSION b/VERSION index c5f3c9c4..3afb327e 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.5.15 +0.5.16 diff --git a/nostr_core/nip059.c b/nostr_core/nip059.c index 06f35344..9cd43787 100644 --- a/nostr_core/nip059.c +++ b/nostr_core/nip059.c @@ -41,6 +41,39 @@ static time_t random_past_timestamp(long max_delay_sec) { return now - random_offset; } +/** + * NIP-44 padding calculation (mirrors nip044.c for output sizing) + */ +static size_t calc_nip44_padded_len(size_t unpadded_len) { + if (unpadded_len <= 32) { + return 32; + } + + size_t next_power = 1; + while (next_power < unpadded_len) { + next_power <<= 1; + } + + size_t chunk = (next_power <= 256) ? 32 : (next_power / 8); + return chunk * ((unpadded_len - 1) / chunk + 1); +} + +/** + * Calculate safe output buffer size for NIP-44 encrypted base64 payload. + * Returns 0 if plaintext length exceeds NIP-44 max. + */ +static size_t calc_nip44_encrypted_b64_size(size_t plaintext_len) { + if (plaintext_len > NOSTR_NIP44_MAX_PLAINTEXT_SIZE) { + return 0; + } + + size_t padded_len = calc_nip44_padded_len(plaintext_len) + 2; // +2 for length prefix + size_t payload_len = 1 + 32 + padded_len + 32; // version + nonce + ciphertext + mac + size_t b64_len = ((payload_len + 2) / 3) * 4 + 1; // +1 for NUL terminator + + return b64_len; +} + /** * Generate a random private key for gift wrap */ @@ -160,18 +193,31 @@ cJSON* nostr_nip59_create_seal(cJSON* rumor, const unsigned char* sender_private } // Encrypt the rumor using NIP-44 - char encrypted_content[4096]; // Should be large enough for most events + size_t encrypted_size = calc_nip44_encrypted_b64_size(strlen(rumor_json)); + if (encrypted_size == 0) { + free(rumor_json); + return NULL; + } + + char* encrypted_content = malloc(encrypted_size); + if (!encrypted_content) { + free(rumor_json); + return NULL; + } + int encrypt_result = nostr_nip44_encrypt(sender_private_key, recipient_public_key, - rumor_json, encrypted_content, sizeof(encrypted_content)); + rumor_json, encrypted_content, encrypted_size); free(rumor_json); if (encrypt_result != NOSTR_SUCCESS) { + free(encrypted_content); return NULL; } // Get sender's public key unsigned char sender_public_key[32]; if (nostr_ec_public_key_from_private_key(sender_private_key, sender_public_key) != 0) { + free(encrypted_content); return NULL; } @@ -181,6 +227,7 @@ cJSON* nostr_nip59_create_seal(cJSON* rumor, const unsigned char* sender_private // Create seal event (kind 13) cJSON* seal = cJSON_CreateObject(); if (!seal) { + free(encrypted_content); return NULL; } @@ -191,6 +238,7 @@ cJSON* nostr_nip59_create_seal(cJSON* rumor, const unsigned char* sender_private cJSON_AddNumberToObject(seal, "kind", 13); cJSON_AddItemToObject(seal, "tags", cJSON_CreateArray()); // Empty tags array cJSON_AddStringToObject(seal, "content", encrypted_content); + free(encrypted_content); // Calculate event ID char event_id[65]; @@ -261,13 +309,27 @@ cJSON* nostr_nip59_create_gift_wrap(cJSON* seal, const char* recipient_public_ke } // Encrypt the seal using NIP-44 - char encrypted_content[8192]; // Larger buffer for nested encryption + size_t encrypted_size = calc_nip44_encrypted_b64_size(strlen(seal_json)); + if (encrypted_size == 0) { + memory_clear(random_private_key, 32); + free(seal_json); + return NULL; + } + + char* encrypted_content = malloc(encrypted_size); + if (!encrypted_content) { + memory_clear(random_private_key, 32); + free(seal_json); + return NULL; + } + int encrypt_result = nostr_nip44_encrypt(random_private_key, recipient_public_key, - seal_json, encrypted_content, sizeof(encrypted_content)); + seal_json, encrypted_content, encrypted_size); free(seal_json); if (encrypt_result != NOSTR_SUCCESS) { memory_clear(random_private_key, 32); + free(encrypted_content); return NULL; } @@ -275,6 +337,7 @@ cJSON* nostr_nip59_create_gift_wrap(cJSON* seal, const char* recipient_public_ke cJSON* gift_wrap = cJSON_CreateObject(); if (!gift_wrap) { memory_clear(random_private_key, 32); + free(encrypted_content); return NULL; } @@ -293,6 +356,7 @@ cJSON* nostr_nip59_create_gift_wrap(cJSON* seal, const char* recipient_public_ke cJSON_AddItemToObject(gift_wrap, "tags", tags); cJSON_AddStringToObject(gift_wrap, "content", encrypted_content); + free(encrypted_content); // Calculate event ID char event_id[65]; @@ -359,16 +423,23 @@ cJSON* nostr_nip59_unwrap_gift(cJSON* gift_wrap, const unsigned char* recipient_ } // Decrypt the content using NIP-44 - char decrypted_json[8192]; + size_t decrypted_size = strlen(encrypted_content) + 1; + char* decrypted_json = malloc(decrypted_size); + if (!decrypted_json) { + return NULL; + } + int decrypt_result = nostr_nip44_decrypt(recipient_private_key, sender_public_key, - encrypted_content, decrypted_json, sizeof(decrypted_json)); + encrypted_content, decrypted_json, decrypted_size); if (decrypt_result != NOSTR_SUCCESS) { + free(decrypted_json); return NULL; } // Parse the decrypted JSON as the seal event cJSON* seal = cJSON_Parse(decrypted_json); + free(decrypted_json); if (!seal) { return NULL; } @@ -394,16 +465,23 @@ cJSON* nostr_nip59_unseal_rumor(cJSON* seal, const unsigned char* sender_public_ const char* encrypted_content = cJSON_GetStringValue(content_item); // Decrypt the content using NIP-44 - char decrypted_json[4096]; + size_t decrypted_size = strlen(encrypted_content) + 1; + char* decrypted_json = malloc(decrypted_size); + if (!decrypted_json) { + return NULL; + } + int decrypt_result = nostr_nip44_decrypt(recipient_private_key, sender_public_key, - encrypted_content, decrypted_json, sizeof(decrypted_json)); + encrypted_content, decrypted_json, decrypted_size); if (decrypt_result != NOSTR_SUCCESS) { + free(decrypted_json); return NULL; } // Parse the decrypted JSON as the rumor event cJSON* rumor = cJSON_Parse(decrypted_json); + free(decrypted_json); if (!rumor) { return NULL; } diff --git a/nostr_core/nostr_core.h b/nostr_core/nostr_core.h index 9c15ffb0..19f3efdd 100644 --- a/nostr_core/nostr_core.h +++ b/nostr_core/nostr_core.h @@ -2,10 +2,10 @@ #define NOSTR_CORE_H // Version information (auto-updated by increment_and_push.sh) -#define VERSION "v0.5.15" +#define VERSION "v0.5.16" #define VERSION_MAJOR 0 #define VERSION_MINOR 5 -#define VERSION_PATCH 15 +#define VERSION_PATCH 16 /* * NOSTR Core Library - Complete API Reference