533 lines
17 KiB
C
533 lines
17 KiB
C
/*
|
|
* NOSTR Core Library - Streaming SHA-256 Tests
|
|
*
|
|
* Comprehensive tests for streaming SHA-256 implementation
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <assert.h>
|
|
#include <unistd.h>
|
|
#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;
|
|
}
|