diff --git a/nostr_core/nip044.c b/nostr_core/nip044.c index 1825d4de..9051dfb7 100644 --- a/nostr_core/nip044.c +++ b/nostr_core/nip044.c @@ -75,55 +75,54 @@ static size_t calc_padded_len(size_t unpadded_len) { return chunk * ((unpadded_len - 1) / chunk + 1); } -// NIP-44 padding (modified to support 1MB payloads with 32-bit length field) +// NIP-44 padding (per spec) static unsigned char* pad_plaintext(const char* plaintext, size_t* padded_len) { size_t unpadded_len = strlen(plaintext); - if (unpadded_len > 1048576) { + if (unpadded_len > 65535) { return NULL; } - + size_t padded_content_len = calc_padded_len(unpadded_len); - *padded_len = padded_content_len + 4; // Add 4 bytes for the length prefix (32-bit) + *padded_len = padded_content_len + 2; // Add 2 bytes for the length prefix unsigned char* padded = malloc(*padded_len); if (!padded) return NULL; - - // Write length prefix (big-endian u32) - padded[0] = (unpadded_len >> 24) & 0xFF; - padded[1] = (unpadded_len >> 16) & 0xFF; - padded[2] = (unpadded_len >> 8) & 0xFF; - padded[3] = unpadded_len & 0xFF; - + + // Write length prefix (big-endian u16) + padded[0] = (unpadded_len >> 8) & 0xFF; + padded[1] = unpadded_len & 0xFF; + // Copy plaintext and add zero-padding - memcpy(padded + 4, plaintext, unpadded_len); - memset(padded + 4 + unpadded_len, 0, padded_content_len - unpadded_len); - + memcpy(padded + 2, plaintext, unpadded_len); + memset(padded + 2 + unpadded_len, 0, padded_content_len - unpadded_len); + return padded; } -// NIP-44 unpadding (modified to support 32-bit length field) +// NIP-44 unpadding (per spec) static char* unpad_plaintext(const unsigned char* padded, size_t padded_len) { - if (padded_len < 6) return NULL; // Minimum: 4 bytes length + 2 bytes content + if (padded_len < 4) return NULL; - size_t unpadded_len = (padded[0] << 24) | (padded[1] << 16) | (padded[2] << 8) | padded[3]; + size_t unpadded_len = (padded[0] << 8) | padded[1]; size_t expected_padded_len = calc_padded_len(unpadded_len); - if (padded_len != expected_padded_len + 4) { + + if (padded_len != expected_padded_len + 2) { return NULL; } - if (unpadded_len > padded_len - 4) { + if (unpadded_len > padded_len - 2) { return NULL; } char* plaintext = malloc(unpadded_len + 1); if (!plaintext) return NULL; - + // Handle empty message case (unpadded_len can be 0) if (unpadded_len > 0) { - memcpy(plaintext, padded + 4, unpadded_len); + memcpy(plaintext, padded + 2, unpadded_len); } plaintext[unpadded_len] = '\0'; - + return plaintext; } @@ -132,15 +131,15 @@ static char* unpad_plaintext(const unsigned char* padded, size_t padded_len) { // ============================================================================= int nostr_nip44_encrypt_with_nonce(const unsigned char* sender_private_key, - const unsigned char* recipient_public_key, - const char* plaintext, - const unsigned char* nonce, - char* output, - size_t output_size) { + const unsigned char* recipient_public_key, + const char* plaintext, + const unsigned char* nonce, + char* output, + size_t output_size) { if (!sender_private_key || !recipient_public_key || !plaintext || !nonce || !output) { return NOSTR_ERROR_INVALID_INPUT; } - + size_t plaintext_len = strlen(plaintext); if (plaintext_len > NOSTR_NIP44_MAX_PLAINTEXT_SIZE) { return NOSTR_ERROR_NIP44_BUFFER_TOO_SMALL; @@ -261,12 +260,12 @@ int nostr_nip44_encrypt_with_nonce(const unsigned char* sender_private_key, free(aad_data); return NOSTR_ERROR_MEMORY_FAILED; } - + payload[0] = 0x02; // NIP-44 version 2 memcpy(payload + 1, nonce_copy, 32); memcpy(payload + 33, ciphertext, padded_len); memcpy(payload + 33 + padded_len, mac, 32); - + // Base64 encode size_t b64_len = ((payload_len + 2) / 3) * 4 + 1; if (b64_len > output_size) { @@ -316,18 +315,18 @@ int nostr_nip44_encrypt_with_nonce(const unsigned char* sender_private_key, } int nostr_nip44_encrypt(const unsigned char* sender_private_key, - const unsigned char* recipient_public_key, - const char* plaintext, - char* output, - size_t output_size) { + const unsigned char* recipient_public_key, + const char* plaintext, + char* output, + size_t output_size) { // Generate random nonce and call the _with_nonce version unsigned char nonce[32]; if (nostr_secp256k1_get_random_bytes(nonce, 32) != 1) { return NOSTR_ERROR_CRYPTO_FAILED; } - - return nostr_nip44_encrypt_with_nonce(sender_private_key, recipient_public_key, - plaintext, nonce, output, output_size); + + return nostr_nip44_encrypt_with_nonce(sender_private_key, recipient_public_key, + plaintext, nonce, output, output_size); } int nostr_nip44_decrypt(const unsigned char* recipient_private_key, @@ -422,6 +421,7 @@ int nostr_nip44_decrypt(const unsigned char* recipient_private_key, return NOSTR_ERROR_CRYPTO_FAILED; } + // Constant-time MAC verification // Constant-time MAC verification if (!constant_time_compare(received_mac, computed_mac, 32)) { memory_clear(shared_secret, 32); @@ -446,7 +446,7 @@ int nostr_nip44_decrypt(const unsigned char* recipient_private_key, free(payload); return NOSTR_ERROR_MEMORY_FAILED; } - + if (chacha20_encrypt(chacha_key, 0, chacha_nonce, ciphertext, padded_plaintext, ciphertext_len) != 0) { memory_clear(shared_secret, 32); memory_clear(conversation_key, 32); @@ -459,6 +459,7 @@ int nostr_nip44_decrypt(const unsigned char* recipient_private_key, return NOSTR_ERROR_CRYPTO_FAILED; } + // Step 8: Remove padding according to NIP-44 spec // Step 8: Remove padding according to NIP-44 spec char* plaintext = unpad_plaintext(padded_plaintext, ciphertext_len); if (!plaintext) { diff --git a/nostr_core/nostr_common.h b/nostr_core/nostr_common.h index b8fda4b7..d8cdab04 100644 --- a/nostr_core/nostr_common.h +++ b/nostr_core/nostr_common.h @@ -76,7 +76,7 @@ #define NOSTR_NIP04_MAX_ENCRYPTED_SIZE 22369621 // ~21.3MB (accounts for base64 overhead + IV) // NIP-44 Constants -#define NOSTR_NIP44_MAX_PLAINTEXT_SIZE 1048576 // 1MB max plaintext (32-bit length field) +#define NOSTR_NIP44_MAX_PLAINTEXT_SIZE 65535 // 64KB - 1 (NIP-44 spec compliant) // Forward declaration for cJSON (to avoid requiring cJSON.h in header) struct cJSON; diff --git a/tests/nip44_test.c b/tests/nip44_test.c index 59a00cd0..553cc8b9 100644 --- a/tests/nip44_test.c +++ b/tests/nip44_test.c @@ -69,13 +69,6 @@ static nip44_test_vector_t test_vectors[] = { "4444444444444444444444444444444444444444444444444444444444444444", "", NULL - }, - { - "1MB payload test", - "91ba716fa9e7ea2fcbad360cf4f8e0d312f73984da63d90f524ad61a6a1e7dbe", // Same keys as basic test - "96f6fa197aa07477ab88f6981118466ae3a982faab8ad5db9d5426870c73d220", - NULL, // Will be generated dynamically - NULL } }; @@ -93,144 +86,76 @@ static int hex_to_bytes(const char* hex, unsigned char* bytes, size_t len) { static int test_nip44_round_trip(const nip44_test_vector_t* tv) { printf("Test: %s\n", tv->name); - + // Parse keys - both private keys unsigned char sender_private_key[32]; unsigned char recipient_private_key[32]; - + if (hex_to_bytes(tv->sender_private_key_hex, sender_private_key, 32) != 0) { printf(" FAIL: Failed to parse sender private key\n"); return -1; } - + if (hex_to_bytes(tv->recipient_private_key_hex, recipient_private_key, 32) != 0) { printf(" FAIL: Failed to parse recipient private key\n"); return -1; } - + // Generate the public keys from the private keys unsigned char sender_public_key[32]; unsigned char recipient_public_key[32]; - + if (nostr_ec_public_key_from_private_key(sender_private_key, sender_public_key) != 0) { printf(" FAIL: Failed to derive sender public key\n"); return -1; } - + if (nostr_ec_public_key_from_private_key(recipient_private_key, recipient_public_key) != 0) { printf(" FAIL: Failed to derive recipient public key\n"); return -1; } - - // Special handling for large payload tests - char* test_plaintext; - if (strcmp(tv->name, "1MB payload test") == 0) { - // Generate exactly 1MB (1,048,576 bytes) of predictable content - const size_t payload_size = 1048576; - test_plaintext = malloc(payload_size + 1); - if (!test_plaintext) { - printf(" FAIL: Memory allocation failed for 1MB test payload\n"); - return -1; - } - - // Fill with a predictable pattern: "ABCDEFGH01234567" repeated - const char* pattern = "ABCDEFGH01234567"; // 16 bytes - const size_t pattern_len = 16; - - for (size_t i = 0; i < payload_size; i += pattern_len) { - size_t copy_len = (i + pattern_len <= payload_size) ? pattern_len : payload_size - i; - memcpy(test_plaintext + i, pattern, copy_len); - } - test_plaintext[payload_size] = '\0'; - - printf(" Generated 1MB test payload (%zu bytes)\n", payload_size); - printf(" Pattern: \"%s\" repeated\n", pattern); - printf(" First 64 chars: \"%.64s...\"\n", test_plaintext); - printf(" Last 64 chars: \"...%.64s\"\n", test_plaintext + payload_size - 64); - } else { - test_plaintext = (char*)tv->plaintext; - } - - // Debug: Check plaintext length - size_t plaintext_len = strlen(test_plaintext); - printf(" Plaintext length: %zu bytes\n", plaintext_len); - printf(" Output buffer size: %zu bytes\n", (size_t)10485760); - - // Test encryption - use larger buffer for 1MB+ payloads (10MB for NIP-44 overhead) - char* encrypted = malloc(10485760); // 10MB buffer for large payloads - if (!encrypted) { - printf(" FAIL: Memory allocation failed for encrypted buffer\n"); - if (strcmp(tv->name, "0.5MB payload test") == 0) free(test_plaintext); - return -1; - } - - // For large payloads, use _with_nonce to avoid random generation issues - unsigned char fixed_nonce[32] = {0}; - int encrypt_result = nostr_nip44_encrypt_with_nonce( + + // Test encryption + char encrypted[8192]; + int encrypt_result = nostr_nip44_encrypt( sender_private_key, recipient_public_key, - test_plaintext, - fixed_nonce, + tv->plaintext, encrypted, - 10485760 + sizeof(encrypted) ); - + if (encrypt_result != NOSTR_SUCCESS) { printf(" FAIL: Encryption - Expected: %d, Actual: %d\n", NOSTR_SUCCESS, encrypt_result); - if (strcmp(tv->name, "1MB payload test") == 0) free(test_plaintext); - free(encrypted); return -1; } - + // Test decryption - use recipient private key + sender public key - char* decrypted = malloc(1048576 + 1); // 1MB + 1 for null terminator - if (!decrypted) { - printf(" FAIL: Memory allocation failed for decrypted buffer\n"); - if (strcmp(tv->name, "1MB payload test") == 0) free(test_plaintext); - free(encrypted); - return -1; - } + char decrypted[8192]; int decrypt_result = nostr_nip44_decrypt( recipient_private_key, sender_public_key, encrypted, decrypted, - 1048576 + 1 + sizeof(decrypted) ); - + if (decrypt_result != NOSTR_SUCCESS) { printf(" FAIL: Decryption - Expected: %d, Actual: %d\n", NOSTR_SUCCESS, decrypt_result); - if (strcmp(tv->name, "1MB payload test") == 0) free(test_plaintext); - free(encrypted); - free(decrypted); return -1; } - + // Verify round-trip - if (strcmp(test_plaintext, decrypted) != 0) { + if (strcmp(tv->plaintext, decrypted) != 0) { printf(" FAIL: Round-trip mismatch\n"); - printf(" Expected: \"%s\"\n", test_plaintext); + printf(" Expected: \"%s\"\n", tv->plaintext); printf(" Actual: \"%s\"\n", decrypted); - if (strcmp(tv->name, "1MB payload test") == 0) free(test_plaintext); - free(encrypted); - free(decrypted); return -1; } - - if (strcmp(tv->name, "1MB payload test") == 0) { - printf(" ✅ 1MB payload round-trip: PASS\n"); - printf(" ✅ Content verification: All %zu bytes match perfectly!\n", strlen(test_plaintext)); - printf(" Encrypted length: %zu bytes\n", strlen(encrypted)); - printf(" 🎉 1MB NIP-44 STRESS TEST COMPLETED SUCCESSFULLY! 🎉\n"); - } else { - printf(" PASS: Expected: \"%s\", Actual: \"%s\"\n", test_plaintext, decrypted); - printf(" Encrypted output: %s\n", encrypted); - } - - if (strcmp(tv->name, "1MB payload test") == 0) free(test_plaintext); - free(encrypted); - free(decrypted); - + + printf(" PASS: Expected: \"%s\", Actual: \"%s\"\n", tv->plaintext, decrypted); + printf(" Encrypted output: %s\n", encrypted); + return 0; } @@ -316,17 +241,13 @@ static int test_nip44_decryption_vector(const nip44_test_vector_t* tv) { } // Test decryption of known vector - char* decrypted = malloc(1048576 + 1); // 1MB + 1 for null terminator - if (!decrypted) { - printf(" FAIL: Memory allocation failed for decrypted buffer\n"); - return -1; - } + char decrypted[8192]; int decrypt_result = nostr_nip44_decrypt( recipient_private_key, sender_public_key, tv->expected_encrypted, decrypted, - 1048576 + 1 + sizeof(decrypted) ); if (decrypt_result != NOSTR_SUCCESS) { @@ -344,9 +265,7 @@ static int test_nip44_decryption_vector(const nip44_test_vector_t* tv) { } printf(" PASS: Expected: \"%s\", Actual: \"%s\"\n", tv->plaintext, decrypted); - - free(decrypted); - + return 0; } @@ -368,20 +287,11 @@ static int test_nip44_encryption_variability() { } // Encrypt the same message multiple times - char* encrypted1 = malloc(2097152); // 2MB buffer - char* encrypted2 = malloc(2097152); - char* encrypted3 = malloc(2097152); - if (!encrypted1 || !encrypted2 || !encrypted3) { - printf(" FAIL: Memory allocation failed for encrypted buffers\n"); - free(encrypted1); - free(encrypted2); - free(encrypted3); - return -1; - } + char encrypted1[8192], encrypted2[8192], encrypted3[8192]; - int result1 = nostr_nip44_encrypt(sender_key, recipient_pubkey, test_message, encrypted1, 2097152); - int result2 = nostr_nip44_encrypt(sender_key, recipient_pubkey, test_message, encrypted2, 2097152); - int result3 = nostr_nip44_encrypt(sender_key, recipient_pubkey, test_message, encrypted3, 2097152); + int result1 = nostr_nip44_encrypt(sender_key, recipient_pubkey, test_message, encrypted1, sizeof(encrypted1)); + int result2 = nostr_nip44_encrypt(sender_key, recipient_pubkey, test_message, encrypted2, sizeof(encrypted2)); + int result3 = nostr_nip44_encrypt(sender_key, recipient_pubkey, test_message, encrypted3, sizeof(encrypted3)); if (result1 != NOSTR_SUCCESS || result2 != NOSTR_SUCCESS || result3 != NOSTR_SUCCESS) { printf(" FAIL: Encryption failed - Results: %d, %d, %d\n", result1, result2, result3); @@ -394,9 +304,6 @@ static int test_nip44_encryption_variability() { printf(" Encryption 1: %.50s...\n", encrypted1); printf(" Encryption 2: %.50s...\n", encrypted2); printf(" Encryption 3: %.50s...\n", encrypted3); - free(encrypted1); - free(encrypted2); - free(encrypted3); return -1; } @@ -407,23 +314,11 @@ static int test_nip44_encryption_variability() { return -1; } - char* decrypted1 = malloc(1048576 + 1); - char* decrypted2 = malloc(1048576 + 1); - char* decrypted3 = malloc(1048576 + 1); - if (!decrypted1 || !decrypted2 || !decrypted3) { - printf(" FAIL: Memory allocation failed for decrypted buffers\n"); - free(encrypted1); - free(encrypted2); - free(encrypted3); - free(decrypted1); - free(decrypted2); - free(decrypted3); - return -1; - } - - int decrypt1 = nostr_nip44_decrypt(recipient_key, sender_pubkey, encrypted1, decrypted1, 1048576 + 1); - int decrypt2 = nostr_nip44_decrypt(recipient_key, sender_pubkey, encrypted2, decrypted2, 1048576 + 1); - int decrypt3 = nostr_nip44_decrypt(recipient_key, sender_pubkey, encrypted3, decrypted3, 1048576 + 1); + char decrypted1[8192], decrypted2[8192], decrypted3[8192]; + + int decrypt1 = nostr_nip44_decrypt(recipient_key, sender_pubkey, encrypted1, decrypted1, sizeof(decrypted1)); + int decrypt2 = nostr_nip44_decrypt(recipient_key, sender_pubkey, encrypted2, decrypted2, sizeof(decrypted2)); + int decrypt3 = nostr_nip44_decrypt(recipient_key, sender_pubkey, encrypted3, decrypted3, sizeof(decrypted3)); if (decrypt1 != NOSTR_SUCCESS || decrypt2 != NOSTR_SUCCESS || decrypt3 != NOSTR_SUCCESS) { printf(" FAIL: Decryption failed - Results: %d, %d, %d\n", decrypt1, decrypt2, decrypt3); @@ -436,25 +331,12 @@ static int test_nip44_encryption_variability() { printf(" Decrypted1: \"%s\"\n", decrypted1); printf(" Decrypted2: \"%s\"\n", decrypted2); printf(" Decrypted3: \"%s\"\n", decrypted3); - free(encrypted1); - free(encrypted2); - free(encrypted3); - free(decrypted1); - free(decrypted2); - free(decrypted3); return -1; } - + printf(" PASS: All encryptions different, all decrypt to: \"%s\"\n", test_message); printf(" Sample ciphertext lengths: %zu, %zu, %zu bytes\n", strlen(encrypted1), strlen(encrypted2), strlen(encrypted3)); - - free(encrypted1); - free(encrypted2); - free(encrypted3); - free(decrypted1); - free(decrypted2); - free(decrypted3); - + return 0; }