328 lines
11 KiB
C
328 lines
11 KiB
C
/*
|
|
* ChaCha20 Test Suite - RFC 8439 Reference Test Vectors
|
|
*
|
|
* This test suite validates our ChaCha20 implementation against the official
|
|
* test vectors from RFC 8439 "ChaCha20 and Poly1305 for IETF Protocols".
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <stdint.h>
|
|
#include "../nostr_core/nostr_chacha20.h"
|
|
|
|
// Helper function to convert hex string to bytes
|
|
static int hex_to_bytes(const char* hex, uint8_t* bytes, size_t len) {
|
|
for (size_t i = 0; i < len; i++) {
|
|
if (sscanf(hex + 2*i, "%2hhx", &bytes[i]) != 1) {
|
|
return -1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
// Helper function to convert bytes to hex string for display
|
|
static void bytes_to_hex(const uint8_t* bytes, size_t len, char* hex) {
|
|
for (size_t i = 0; i < len; i++) {
|
|
sprintf(hex + 2*i, "%02x", bytes[i]);
|
|
}
|
|
hex[2*len] = '\0';
|
|
}
|
|
|
|
// Helper function to compare byte arrays
|
|
static int bytes_equal(const uint8_t* a, const uint8_t* b, size_t len) {
|
|
return memcmp(a, b, len) == 0;
|
|
}
|
|
|
|
// Test 1: ChaCha Quarter Round (RFC 8439 Section 2.1.1)
|
|
static int test_quarter_round() {
|
|
printf("=== Test 1: ChaCha Quarter Round ===\n");
|
|
|
|
uint32_t state[16] = {0};
|
|
state[0] = 0x11111111;
|
|
state[1] = 0x01020304;
|
|
state[2] = 0x9b8d6f43;
|
|
state[3] = 0x01234567;
|
|
|
|
printf("Input: a=0x%08x, b=0x%08x, c=0x%08x, d=0x%08x\n",
|
|
state[0], state[1], state[2], state[3]);
|
|
|
|
chacha20_quarter_round(state, 0, 1, 2, 3);
|
|
|
|
printf("Output: a=0x%08x, b=0x%08x, c=0x%08x, d=0x%08x\n",
|
|
state[0], state[1], state[2], state[3]);
|
|
|
|
// Expected values from RFC 8439
|
|
uint32_t expected[4] = {0xea2a92f4, 0xcb1cf8ce, 0x4581472e, 0x5881c4bb};
|
|
|
|
if (state[0] == expected[0] && state[1] == expected[1] &&
|
|
state[2] == expected[2] && state[3] == expected[3]) {
|
|
printf("✅ Quarter round test PASSED\n\n");
|
|
return 1;
|
|
} else {
|
|
printf("❌ Quarter round test FAILED\n");
|
|
printf("Expected: a=0x%08x, b=0x%08x, c=0x%08x, d=0x%08x\n\n",
|
|
expected[0], expected[1], expected[2], expected[3]);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
// Test 2: ChaCha20 Block Function - RFC 8439 Appendix A.1 Test Vectors
|
|
static int test_chacha20_block_1() {
|
|
printf("=== Test 2: ChaCha20 Block Function - RFC 8439 Test Vectors ===\n");
|
|
|
|
uint8_t key[32] = {0};
|
|
uint8_t nonce[12] = {0};
|
|
uint8_t output0[64], output1[64];
|
|
|
|
printf("Key: all zeros\n");
|
|
printf("Nonce: all zeros\n");
|
|
|
|
// Test counter = 0 (RFC 8439 Appendix A.1 Test Vector #1)
|
|
printf("\nTesting counter = 0:\n");
|
|
chacha20_block(key, 0, nonce, output0);
|
|
char hex_output0[129];
|
|
bytes_to_hex(output0, 64, hex_output0);
|
|
printf("Counter=0 output: %s\n", hex_output0);
|
|
|
|
// Test counter = 1 (RFC 8439 Appendix A.1 Test Vector #2)
|
|
printf("\nTesting counter = 1:\n");
|
|
chacha20_block(key, 1, nonce, output1);
|
|
char hex_output1[129];
|
|
bytes_to_hex(output1, 64, hex_output1);
|
|
printf("Counter=1 output: %s\n", hex_output1);
|
|
|
|
// Expected for counter=0 from RFC 8439 Appendix A.1 Test Vector #1
|
|
const char* expected_counter0_hex =
|
|
"76b8e0ada0f13d90405d6ae55386bd28"
|
|
"bdd219b8a08ded1aa836efcc8b770dc7"
|
|
"da41597c5157488d7724e03fb8d84a37"
|
|
"6a43b8f41518a11cc387b669b2ee6586";
|
|
|
|
// Expected for counter=1 from RFC 8439 Appendix A.1 Test Vector #2
|
|
const char* expected_counter1_hex =
|
|
"9f07e7be5551387a98ba977c732d080d"
|
|
"cb0f29a048e3656912c6533e32ee7aed"
|
|
"29b721769ce64e43d57133b074d839d5"
|
|
"31ed1f28510afb45ace10a1f4b794d6f";
|
|
|
|
uint8_t expected0[64], expected1[64];
|
|
hex_to_bytes(expected_counter0_hex, expected0, 64);
|
|
hex_to_bytes(expected_counter1_hex, expected1, 64);
|
|
|
|
printf("\nExpected counter=0: %s\n", expected_counter0_hex);
|
|
printf("Expected counter=1: %s\n", expected_counter1_hex);
|
|
|
|
int test0_pass = bytes_equal(output0, expected0, 64);
|
|
int test1_pass = bytes_equal(output1, expected1, 64);
|
|
|
|
if (test0_pass) printf("✅ Counter=0 test PASSED\n");
|
|
else printf("❌ Counter=0 test FAILED\n");
|
|
|
|
if (test1_pass) printf("✅ Counter=1 test PASSED\n");
|
|
else printf("❌ Counter=1 test FAILED\n");
|
|
|
|
printf("\n");
|
|
return test0_pass && test1_pass; // Both tests must pass
|
|
}
|
|
|
|
// Test 3: ChaCha20 Block Function - Test Vector #2 (RFC 8439 Appendix A.1)
|
|
static int test_chacha20_block_2() {
|
|
printf("=== Test 3: ChaCha20 Block Function - Different Key ===\n");
|
|
|
|
// Key with last byte = 1, all-zero nonce, counter = 1
|
|
uint8_t key[32] = {0};
|
|
key[31] = 0x01; // Last byte = 1
|
|
uint8_t nonce[12] = {0};
|
|
uint32_t counter = 1;
|
|
uint8_t output[64];
|
|
|
|
printf("Key: all zeros except last byte = 0x01\n");
|
|
printf("Nonce: all zeros\n");
|
|
printf("Counter: %u\n", counter);
|
|
|
|
int result = chacha20_block(key, counter, nonce, output);
|
|
if (result != 0) {
|
|
printf("❌ ChaCha20 block function failed\n\n");
|
|
return 0;
|
|
}
|
|
|
|
// Expected output from RFC 8439 Appendix A.1 Test Vector #3
|
|
const char* expected_hex =
|
|
"3aeb5224ecf849929b9d828db1ced4dd"
|
|
"832025e8018b8160b82284f3c949aa5a"
|
|
"8eca00bbb4a73bdad192b5c42f73f2fd"
|
|
"4e273644c8b36125a64addeb006c13a0";
|
|
|
|
uint8_t expected[64];
|
|
hex_to_bytes(expected_hex, expected, 64);
|
|
|
|
char hex_output[129];
|
|
bytes_to_hex(output, 64, hex_output);
|
|
printf("Output: %s\n", hex_output);
|
|
|
|
if (bytes_equal(output, expected, 64)) {
|
|
printf("✅ ChaCha20 block test #2 PASSED\n\n");
|
|
return 1;
|
|
} else {
|
|
printf("❌ ChaCha20 block test #2 FAILED\n");
|
|
printf("Expected: %s\n\n", expected_hex);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
// Test 4: ChaCha20 Encryption - "Sunscreen" Test (RFC 8439 Section 2.4.2)
|
|
static int test_chacha20_encryption() {
|
|
printf("=== Test 4: ChaCha20 Encryption - Sunscreen Text ===\n");
|
|
|
|
// Key and nonce from RFC 8439 Section 2.4.2
|
|
const char* key_hex =
|
|
"000102030405060708090a0b0c0d0e0f"
|
|
"101112131415161718191a1b1c1d1e1f";
|
|
const char* nonce_hex = "000000000000004a00000000";
|
|
|
|
uint8_t key[32];
|
|
uint8_t nonce[12];
|
|
hex_to_bytes(key_hex, key, 32);
|
|
hex_to_bytes(nonce_hex, nonce, 12);
|
|
|
|
// Test plaintext (first part of "Sunscreen" text)
|
|
const char* plaintext =
|
|
"Ladies and Gentlemen of the class of '99: If I could offer you "
|
|
"only one tip for the future, sunscreen would be it.";
|
|
size_t plaintext_len = strlen(plaintext);
|
|
|
|
printf("Plaintext: \"%.50s...\"\n", plaintext);
|
|
printf("Length: %zu bytes\n", plaintext_len);
|
|
|
|
uint8_t ciphertext[256];
|
|
uint32_t counter = 1;
|
|
|
|
int result = chacha20_encrypt(key, counter, nonce,
|
|
(const uint8_t*)plaintext,
|
|
ciphertext, plaintext_len);
|
|
|
|
if (result != 0) {
|
|
printf("❌ ChaCha20 encryption failed\n\n");
|
|
return 0;
|
|
}
|
|
|
|
// Expected ciphertext (first 64 bytes) from RFC 8439
|
|
const char* expected_hex =
|
|
"6e2e359a2568f98041ba0728dd0d6981"
|
|
"e97e7aec1d4360c20a27afccfd9fae0b"
|
|
"f91b65c5524733ab8f593dabcd62b357"
|
|
"1639d624e65152ab8f530c359f0861d8";
|
|
|
|
uint8_t expected[64];
|
|
hex_to_bytes(expected_hex, expected, 64);
|
|
|
|
char hex_output[129];
|
|
bytes_to_hex(ciphertext, 64, hex_output);
|
|
printf("Ciphertext (first 64 bytes): %s\n", hex_output);
|
|
|
|
if (bytes_equal(ciphertext, expected, 64)) {
|
|
printf("✅ ChaCha20 encryption test PASSED\n");
|
|
|
|
// Test decryption (should get back original plaintext)
|
|
uint8_t decrypted[256];
|
|
result = chacha20_encrypt(key, counter, nonce, ciphertext, decrypted, plaintext_len);
|
|
|
|
if (result == 0 && memcmp(plaintext, decrypted, plaintext_len) == 0) {
|
|
printf("✅ ChaCha20 decryption test PASSED\n\n");
|
|
return 1;
|
|
} else {
|
|
printf("❌ ChaCha20 decryption test FAILED\n\n");
|
|
return 0;
|
|
}
|
|
} else {
|
|
printf("❌ ChaCha20 encryption test FAILED\n");
|
|
printf("Expected: %s\n\n", expected_hex);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
// Test 5: ChaCha20 Edge Cases and Additional Validation
|
|
static int test_chacha20_edge_cases() {
|
|
printf("=== Test 5: ChaCha20 Edge Cases and Additional Validation ===\n");
|
|
|
|
uint8_t key[32];
|
|
uint8_t nonce[12];
|
|
uint8_t input[64];
|
|
uint8_t output[64];
|
|
|
|
// Initialize test data
|
|
memset(key, 0, 32);
|
|
memset(nonce, 0, 12);
|
|
memset(input, 0xAA, 64); // Fill with test pattern
|
|
|
|
printf("Testing various scenarios...\n");
|
|
|
|
// Test 1: Counter = 0
|
|
int result1 = chacha20_encrypt(key, 0, nonce, input, output, 64);
|
|
printf("Counter=0 (64 bytes): %s\n", result1 == 0 ? "PASS" : "FAIL");
|
|
|
|
// Test 2: Counter = 1
|
|
int result2 = chacha20_encrypt(key, 1, nonce, input, output, 64);
|
|
printf("Counter=1 (64 bytes): %s\n", result2 == 0 ? "PASS" : "FAIL");
|
|
|
|
// Test 3: Empty data (0 bytes)
|
|
int result3 = chacha20_encrypt(key, 0, nonce, input, output, 0);
|
|
printf("Zero-length data: %s\n", result3 == 0 ? "PASS" : "FAIL");
|
|
|
|
// Test 4: Single byte
|
|
int result4 = chacha20_encrypt(key, 0, nonce, input, output, 1);
|
|
printf("Single byte: %s\n", result4 == 0 ? "PASS" : "FAIL");
|
|
|
|
// Test 5: Partial block (35 bytes)
|
|
int result5 = chacha20_encrypt(key, 0, nonce, input, output, 35);
|
|
printf("Partial block (35 bytes): %s\n", result5 == 0 ? "PASS" : "FAIL");
|
|
|
|
// Test 6: Multi-block (128 bytes)
|
|
uint8_t large_input[128];
|
|
uint8_t large_output[128];
|
|
memset(large_input, 0x55, 128);
|
|
int result6 = chacha20_encrypt(key, 0, nonce, large_input, large_output, 128);
|
|
printf("Multi-block (128 bytes): %s\n", result6 == 0 ? "PASS" : "FAIL");
|
|
|
|
if (result1 == 0 && result2 == 0 && result3 == 0 &&
|
|
result4 == 0 && result5 == 0 && result6 == 0) {
|
|
printf("✅ All edge case tests PASSED\n\n");
|
|
return 1;
|
|
} else {
|
|
printf("❌ Some edge case tests FAILED\n\n");
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
// Main test function
|
|
int main() {
|
|
printf("🧪 ChaCha20 Test Suite - RFC 8439 Reference Vectors\n");
|
|
printf("=====================================================\n\n");
|
|
|
|
int passed = 0;
|
|
int total = 5;
|
|
|
|
if (test_quarter_round()) passed++;
|
|
if (test_chacha20_block_1()) passed++;
|
|
if (test_chacha20_block_2()) passed++;
|
|
if (test_chacha20_encryption()) passed++;
|
|
if (test_chacha20_edge_cases()) passed++;
|
|
|
|
printf("=== Test Summary ===\n");
|
|
printf("Passed: %d/%d tests\n", passed, total);
|
|
|
|
if (passed == total) {
|
|
printf("🎉 ALL CHACHA20 TESTS PASSED! 🎉\n");
|
|
printf("\nOur ChaCha20 implementation is RFC 8439 compliant and ready for production use.\n");
|
|
printf("✅ Quarter round operations work correctly\n");
|
|
printf("✅ Block function matches reference vectors\n");
|
|
printf("✅ Encryption/decryption is bidirectional\n");
|
|
printf("✅ Edge cases handled properly\n");
|
|
return 0;
|
|
} else {
|
|
printf("❌ Some tests failed. ChaCha20 implementation needs fixes.\n");
|
|
return 1;
|
|
}
|
|
}
|