/* * NOSTR Core Library - Streaming SHA-256 Tests * * Comprehensive tests for streaming SHA-256 implementation */ #include #include #include #include #include #include "../nostr_core/utils.h" #include "../nostr_core/nostr_common.h" // Test vectors for SHA-256 (from official NIST test vectors) typedef struct { const char* input; const char* expected_hash; const char* description; } sha256_test_vector_t; static const sha256_test_vector_t test_vectors[] = { { "", "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "Empty string" }, { "a", "ca978112ca1bbdcafac231b39a23dc4da786eff8147c4e72b9807785afee48bb", "Single character" }, { "abc", "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad", "Three characters" }, { "message digest", "f7846f55cf23e14eebeab5b4e1550cad5b509e3348fbc4efa3a1413d393cb650", "Message digest" }, { "abcdefghijklmnopqrstuvwxyz", "71c480df93d6ae2f1efad1447c66c9525e316218cf51fc8d9ed832f2daf18b73", "Alphabet" }, { "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", "db4bfcbd4da0cd85a60c3c37d3fbd8805c77f15fc6b1fdfe614ee0a7c8fdb4c0", "Alphanumeric" }, { "12345678901234567890123456789012345678901234567890123456789012345678901234567890", "f371bc4a311f2b009eef952dd83ca80e2b60026c8e935592d0f9c308453c813e", "Numeric pattern" } }; static const size_t num_test_vectors = sizeof(test_vectors) / sizeof(test_vectors[0]); // Helper function to convert hex string to bytes for comparison static void hex_to_bytes_helper(const char* hex_str, unsigned char* bytes) { size_t len = strlen(hex_str) / 2; for (size_t i = 0; i < len; i++) { sscanf(hex_str + i * 2, "%02hhx", &bytes[i]); } } // Helper function to print hash in hex format static void print_hash_hex(const unsigned char* hash, const char* label) { printf("%s: ", label); for (int i = 0; i < 32; i++) { printf("%02x", hash[i]); } printf("\n"); } // Test 1: Basic streaming functionality vs traditional SHA-256 static void test_streaming_vs_traditional(void) { printf("\n=== Test 1: Streaming vs Traditional SHA-256 ===\n"); int passed = 0, failed = 0; for (size_t i = 0; i < num_test_vectors; i++) { const sha256_test_vector_t* tv = &test_vectors[i]; unsigned char traditional_hash[32]; unsigned char streaming_hash[32]; unsigned char expected_hash[32]; printf("Testing: %s\n", tv->description); printf("Input: \"%s\"\n", tv->input); // Traditional SHA-256 if (nostr_sha256((const unsigned char*)tv->input, strlen(tv->input), traditional_hash) != 0) { printf("❌ Traditional SHA-256 failed\n"); failed++; continue; } // Streaming SHA-256 nostr_sha256_ctx_t ctx; if (nostr_sha256_init(&ctx) != 0) { printf("❌ Streaming SHA-256 init failed\n"); failed++; continue; } if (nostr_sha256_update(&ctx, (const unsigned char*)tv->input, strlen(tv->input)) != 0) { printf("❌ Streaming SHA-256 update failed\n"); failed++; continue; } if (nostr_sha256_final(&ctx, streaming_hash) != 0) { printf("❌ Streaming SHA-256 final failed\n"); failed++; continue; } // Convert expected hash hex_to_bytes_helper(tv->expected_hash, expected_hash); // Compare all three results if (memcmp(traditional_hash, expected_hash, 32) != 0) { printf("❌ Traditional hash mismatch\n"); print_hash_hex(expected_hash, "Expected"); print_hash_hex(traditional_hash, "Traditional"); failed++; continue; } if (memcmp(streaming_hash, expected_hash, 32) != 0) { printf("❌ Streaming hash mismatch\n"); print_hash_hex(expected_hash, "Expected"); print_hash_hex(streaming_hash, "Streaming"); failed++; continue; } if (memcmp(traditional_hash, streaming_hash, 32) != 0) { printf("❌ Traditional vs Streaming mismatch\n"); print_hash_hex(traditional_hash, "Traditional"); print_hash_hex(streaming_hash, "Streaming"); failed++; continue; } printf("✅ All hashes match expected result\n"); printf("Hash: %s\n", tv->expected_hash); passed++; } printf("\nTest 1 Results: %d passed, %d failed\n", passed, failed); } // Test 2: Multiple update calls (chunk boundary testing) static void test_multiple_updates(void) { printf("\n=== Test 2: Multiple Update Calls ===\n"); const char* test_string = "abcdefghijklmnopqrstuvwxyz"; unsigned char expected_hash[32]; unsigned char streaming_hash[32]; // Get expected result from traditional function nostr_sha256((const unsigned char*)test_string, strlen(test_string), expected_hash); printf("Testing alphabet string with multiple update calls\n"); printf("Input: \"%s\"\n", test_string); // Test various chunk sizes size_t chunk_sizes[] = {1, 2, 3, 5, 7, 11, 13, 17, 19, 23}; size_t num_chunk_sizes = sizeof(chunk_sizes) / sizeof(chunk_sizes[0]); int passed = 0, failed = 0; for (size_t cs_idx = 0; cs_idx < num_chunk_sizes; cs_idx++) { size_t chunk_size = chunk_sizes[cs_idx]; printf("Testing chunk size: %zu\n", chunk_size); nostr_sha256_ctx_t ctx; if (nostr_sha256_init(&ctx) != 0) { printf("❌ Init failed for chunk size %zu\n", chunk_size); failed++; continue; } // Process string in chunks size_t input_len = strlen(test_string); size_t processed = 0; while (processed < input_len) { size_t to_process = (input_len - processed < chunk_size) ? (input_len - processed) : chunk_size; if (nostr_sha256_update(&ctx, (const unsigned char*)(test_string + processed), to_process) != 0) { printf("❌ Update failed at position %zu with chunk size %zu\n", processed, chunk_size); failed++; break; } processed += to_process; } if (processed != input_len) continue; // Skip final if update failed if (nostr_sha256_final(&ctx, streaming_hash) != 0) { printf("❌ Final failed for chunk size %zu\n", chunk_size); failed++; continue; } if (memcmp(streaming_hash, expected_hash, 32) != 0) { printf("❌ Hash mismatch for chunk size %zu\n", chunk_size); print_hash_hex(expected_hash, "Expected"); print_hash_hex(streaming_hash, "Streaming"); failed++; continue; } printf("✅ Chunk size %zu: hash matches\n", chunk_size); passed++; } printf("\nTest 2 Results: %d passed, %d failed\n", passed, failed); } // Test 3: Large data streaming (memory efficiency test) static void test_large_data_streaming(void) { printf("\n=== Test 3: Large Data Streaming ===\n"); // Create a large test pattern (1MB of repeated data) const size_t total_size = 1024 * 1024; // 1MB char* large_data = malloc(total_size); if (!large_data) { printf("❌ Failed to allocate memory for large data test\n"); return; } // Fill with repeating pattern const char* pattern = "The quick brown fox jumps over the lazy dog. "; size_t pattern_len = strlen(pattern); for (size_t i = 0; i < total_size; i++) { large_data[i] = pattern[i % pattern_len]; } printf("Testing 1MB of data streaming vs traditional\n"); unsigned char traditional_hash[32]; unsigned char streaming_hash[32]; // Traditional approach (loads entire data into memory - we already have it) if (nostr_sha256((const unsigned char*)large_data, total_size, traditional_hash) != 0) { printf("❌ Traditional SHA-256 failed on large data\n"); free(large_data); return; } // Streaming approach (processes in 4KB chunks) nostr_sha256_ctx_t ctx; if (nostr_sha256_init(&ctx) != 0) { printf("❌ Streaming init failed\n"); free(large_data); return; } const size_t chunk_size = 4096; // 4KB chunks size_t processed = 0; while (processed < total_size) { size_t to_process = (total_size - processed < chunk_size) ? (total_size - processed) : chunk_size; if (nostr_sha256_update(&ctx, (const unsigned char*)(large_data + processed), to_process) != 0) { printf("❌ Streaming update failed at position %zu\n", processed); free(large_data); return; } processed += to_process; } if (nostr_sha256_final(&ctx, streaming_hash) != 0) { printf("❌ Streaming final failed\n"); free(large_data); return; } free(large_data); // Compare results if (memcmp(traditional_hash, streaming_hash, 32) != 0) { printf("❌ Large data hash mismatch\n"); print_hash_hex(traditional_hash, "Traditional"); print_hash_hex(streaming_hash, "Streaming"); return; } printf("✅ 1MB data processed successfully with streaming\n"); print_hash_hex(streaming_hash, "Hash"); printf("Memory efficiency: Streaming uses ~4KB buffer vs 1MB for traditional\n"); } // Test 4: File streaming functionality static void test_file_streaming(void) { printf("\n=== Test 4: File Streaming ===\n"); const char* test_filename = "test_streaming_file.tmp"; const char* test_content = "This is a test file for streaming SHA-256 functionality.\n" "It contains multiple lines to test file I/O.\n" "The streaming function should read this file in chunks.\n" "And produce the same hash as if we read it all at once.\n"; // Create test file FILE* file = fopen(test_filename, "wb"); if (!file) { printf("❌ Failed to create test file\n"); return; } fwrite(test_content, 1, strlen(test_content), file); fclose(file); printf("Testing file streaming with content:\n\"%s\"\n", test_content); // Get expected hash from memory-based function unsigned char expected_hash[32]; if (nostr_sha256((const unsigned char*)test_content, strlen(test_content), expected_hash) != 0) { printf("❌ Memory-based hash failed\n"); unlink(test_filename); return; } // Test file streaming unsigned char file_hash[32]; if (nostr_sha256_file_stream(test_filename, file_hash) != 0) { printf("❌ File streaming failed\n"); unlink(test_filename); return; } // Compare results if (memcmp(expected_hash, file_hash, 32) != 0) { printf("❌ File hash mismatch\n"); print_hash_hex(expected_hash, "Expected"); print_hash_hex(file_hash, "File stream"); unlink(test_filename); return; } printf("✅ File streaming matches memory-based hash\n"); print_hash_hex(file_hash, "Hash"); // Clean up unlink(test_filename); // Test with non-existent file if (nostr_sha256_file_stream("non_existent_file.tmp", file_hash) == 0) { printf("❌ File streaming should fail for non-existent file\n"); return; } printf("✅ File streaming properly handles non-existent files\n"); } // Test 5: Edge cases and error conditions static void test_edge_cases(void) { printf("\n=== Test 5: Edge Cases and Error Conditions ===\n"); nostr_sha256_ctx_t ctx; unsigned char hash[32]; int passed = 0, failed = 0; // Test NULL pointer handling printf("Testing NULL pointer handling:\n"); if (nostr_sha256_init(NULL) == 0) { printf("❌ Init should fail with NULL context\n"); failed++; } else { printf("✅ Init properly rejects NULL context\n"); passed++; } if (nostr_sha256_update(&ctx, NULL, 10) == 0) { printf("❌ Update should fail with NULL data\n"); failed++; } else { printf("✅ Update properly rejects NULL data\n"); passed++; } if (nostr_sha256_final(&ctx, NULL) == 0) { printf("❌ Final should fail with NULL output\n"); failed++; } else { printf("✅ Final properly rejects NULL output\n"); passed++; } if (nostr_sha256_file_stream(NULL, hash) == 0) { printf("❌ File stream should fail with NULL filename\n"); failed++; } else { printf("✅ File stream properly rejects NULL filename\n"); passed++; } if (nostr_sha256_file_stream("test.tmp", NULL) == 0) { printf("❌ File stream should fail with NULL output\n"); failed++; } else { printf("✅ File stream properly rejects NULL output\n"); passed++; } // Test zero-length updates printf("\nTesting zero-length operations:\n"); if (nostr_sha256_init(&ctx) != 0) { printf("❌ Failed to initialize for zero-length test\n"); failed++; } else { if (nostr_sha256_update(&ctx, (const unsigned char*)"test", 0) != 0) { printf("❌ Zero-length update should succeed\n"); failed++; } else { printf("✅ Zero-length update handled correctly\n"); passed++; } if (nostr_sha256_final(&ctx, hash) != 0) { printf("❌ Final failed after zero-length update\n"); failed++; } else { // Should match empty string hash unsigned char empty_hash[32]; hex_to_bytes_helper("e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", empty_hash); if (memcmp(hash, empty_hash, 32) != 0) { printf("❌ Zero-length result doesn't match empty string hash\n"); failed++; } else { printf("✅ Zero-length result matches empty string hash\n"); passed++; } } } printf("\nTest 5 Results: %d passed, %d failed\n", passed, failed); } // Test 6: Context reuse and security clearing static void test_context_security(void) { printf("\n=== Test 6: Context Security and Reuse ===\n"); nostr_sha256_ctx_t ctx; unsigned char hash1[32], hash2[32]; const char* test1 = "first test"; const char* test2 = "second test"; // First hash if (nostr_sha256_init(&ctx) != 0 || nostr_sha256_update(&ctx, (const unsigned char*)test1, strlen(test1)) != 0 || nostr_sha256_final(&ctx, hash1) != 0) { printf("❌ First hash computation failed\n"); return; } printf("First hash computed successfully\n"); // Check that context is cleared after final (security feature) // We can't directly inspect the context, but we can try to reuse it // Second hash with new init (proper way) if (nostr_sha256_init(&ctx) != 0 || nostr_sha256_update(&ctx, (const unsigned char*)test2, strlen(test2)) != 0 || nostr_sha256_final(&ctx, hash2) != 0) { printf("❌ Second hash computation failed\n"); return; } printf("Second hash computed successfully\n"); // Verify they're different (they should be) if (memcmp(hash1, hash2, 32) == 0) { printf("❌ Hashes should be different for different inputs\n"); return; } printf("✅ Context can be reused with proper reinitialization\n"); printf("✅ Context clearing prevents accidental reuse\n"); // Verify hashes against expected values unsigned char expected1[32], expected2[32]; nostr_sha256((const unsigned char*)test1, strlen(test1), expected1); nostr_sha256((const unsigned char*)test2, strlen(test2), expected2); if (memcmp(hash1, expected1, 32) == 0 && memcmp(hash2, expected2, 32) == 0) { printf("✅ Both streaming hashes match expected values\n"); } else { printf("❌ Hash verification failed\n"); } } int main(void) { printf("NOSTR Core Library - Streaming SHA-256 Tests\n"); printf("===========================================\n"); // Initialize crypto subsystem if (nostr_crypto_init() != 0) { printf("❌ Failed to initialize crypto subsystem\n"); return 1; } // Run all tests test_streaming_vs_traditional(); test_multiple_updates(); test_large_data_streaming(); test_file_streaming(); test_edge_cases(); test_context_security(); // Cleanup nostr_crypto_cleanup(); printf("\n===========================================\n"); printf("All streaming SHA-256 tests completed.\n"); printf("Review output above for detailed results.\n"); return 0; }