Revert NIP-44 to spec compliance (65535 bytes), keep NIP-04 at 1MB

This commit is contained in:
2025-10-27 12:51:50 -04:00
parent 23c2a58c2b
commit 9a3965243c
3 changed files with 78 additions and 195 deletions

View File

@@ -75,43 +75,42 @@ 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;
}
@@ -120,7 +119,7 @@ static char* unpad_plaintext(const unsigned char* padded, size_t padded_len) {
// 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';
@@ -132,11 +131,11 @@ 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;
}
@@ -316,10 +315,10 @@ 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) {
@@ -327,7 +326,7 @@ int nostr_nip44_encrypt(const unsigned char* sender_private_key,
}
return nostr_nip44_encrypt_with_nonce(sender_private_key, recipient_public_key,
plaintext, nonce, output, output_size);
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);
@@ -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) {

View File

@@ -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;

View File

@@ -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
}
};
@@ -122,114 +115,46 @@ static int test_nip44_round_trip(const nip44_test_vector_t* tv) {
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) {
@@ -345,8 +266,6 @@ 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;
}
char decrypted1[8192], decrypted2[8192], decrypted3[8192];
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);
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;
}