From ad201adaded8c22f9c76fdf1b44b6f048c2c5b1e Mon Sep 17 00:00:00 2001 From: Atridad Lahiji Date: Thu, 11 Dec 2025 15:12:21 -0700 Subject: [PATCH] Tests are finally also ready --- Makefile | 13 + README.md | 14 + pkg/engine/engine_test.go | 892 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 919 insertions(+) create mode 100644 Makefile create mode 100644 pkg/engine/engine_test.go diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..d1f5e28 --- /dev/null +++ b/Makefile @@ -0,0 +1,13 @@ +.PHONY: build test bench clean + +build: + go build -o himbocrypt cmd/himbocrypt/main.go + +test: + go test ./pkg/engine/... -v + +bench: + go test ./pkg/engine/... -bench=. -benchmem + +clean: + rm -f himbocrypt diff --git a/README.md b/README.md index a6320c0..6a3ad09 100644 --- a/README.md +++ b/README.md @@ -58,3 +58,17 @@ import "himbocrypt/pkg/engine" e := engine.NewEngine() ``` + +## Testing + +Run the full test suite: + +```bash +go test ./pkg/engine/... -v +``` + +Run benchmarks: + +```bash +go test ./pkg/engine/... -bench=. -benchmem +``` diff --git a/pkg/engine/engine_test.go b/pkg/engine/engine_test.go new file mode 100644 index 0000000..cd94818 --- /dev/null +++ b/pkg/engine/engine_test.go @@ -0,0 +1,892 @@ +package engine + +import ( + "bytes" + "crypto/ecdh" + "crypto/rand" + "encoding/hex" + "strings" + "testing" +) + +func TestNewEngine(t *testing.T) { + e := NewEngine() + if e == nil { + t.Fatal("NewEngine() returned nil") + } +} + +func TestGenerateKeyPair(t *testing.T) { + e := NewEngine() + kp, err := e.GenerateKeyPair() + if err != nil { + t.Fatalf("GenerateKeyPair() error = %v", err) + } + + if kp.PrivateKey == nil { + t.Error("PrivateKey is nil") + } + if kp.PublicKey == nil { + t.Error("PublicKey is nil") + } + + // Verify key lengths (X25519 keys are 32 bytes) + if len(kp.PrivateKey.Bytes()) != 32 { + t.Errorf("PrivateKey length = %d, want 32", len(kp.PrivateKey.Bytes())) + } + if len(kp.PublicKey.Bytes()) != 32 { + t.Errorf("PublicKey length = %d, want 32", len(kp.PublicKey.Bytes())) + } +} + +func TestGenerateKeyPair_Uniqueness(t *testing.T) { + e := NewEngine() + kp1, err := e.GenerateKeyPair() + if err != nil { + t.Fatalf("GenerateKeyPair() error = %v", err) + } + + kp2, err := e.GenerateKeyPair() + if err != nil { + t.Fatalf("GenerateKeyPair() error = %v", err) + } + + if bytes.Equal(kp1.PrivateKey.Bytes(), kp2.PrivateKey.Bytes()) { + t.Error("Two generated key pairs have identical private keys") + } + if bytes.Equal(kp1.PublicKey.Bytes(), kp2.PublicKey.Bytes()) { + t.Error("Two generated key pairs have identical public keys") + } +} + +func TestEncodePublicKey(t *testing.T) { + e := NewEngine() + kp, err := e.GenerateKeyPair() + if err != nil { + t.Fatalf("GenerateKeyPair() error = %v", err) + } + + encoded := kp.EncodePublicKey() + + // Should be 64 hex characters (32 bytes * 2) + if len(encoded) != 64 { + t.Errorf("EncodePublicKey() length = %d, want 64", len(encoded)) + } + + // Should be valid hex + _, err = hex.DecodeString(encoded) + if err != nil { + t.Errorf("EncodePublicKey() produced invalid hex: %v", err) + } +} + +func TestEncodePrivateKey(t *testing.T) { + e := NewEngine() + kp, err := e.GenerateKeyPair() + if err != nil { + t.Fatalf("GenerateKeyPair() error = %v", err) + } + + encoded := kp.EncodePrivateKey() + + // Should be 64 hex characters (32 bytes * 2) + if len(encoded) != 64 { + t.Errorf("EncodePrivateKey() length = %d, want 64", len(encoded)) + } + + // Should be valid hex + _, err = hex.DecodeString(encoded) + if err != nil { + t.Errorf("EncodePrivateKey() produced invalid hex: %v", err) + } +} + +func TestDecodePrivateKey(t *testing.T) { + e := NewEngine() + kp, err := e.GenerateKeyPair() + if err != nil { + t.Fatalf("GenerateKeyPair() error = %v", err) + } + + encoded := kp.EncodePrivateKey() + decoded, err := e.DecodePrivateKey(encoded) + if err != nil { + t.Fatalf("DecodePrivateKey() error = %v", err) + } + + if !bytes.Equal(decoded.Bytes(), kp.PrivateKey.Bytes()) { + t.Error("Decoded private key does not match original") + } +} + +func TestDecodePublicKey(t *testing.T) { + e := NewEngine() + kp, err := e.GenerateKeyPair() + if err != nil { + t.Fatalf("GenerateKeyPair() error = %v", err) + } + + encoded := kp.EncodePublicKey() + decoded, err := e.DecodePublicKey(encoded) + if err != nil { + t.Fatalf("DecodePublicKey() error = %v", err) + } + + if !bytes.Equal(decoded.Bytes(), kp.PublicKey.Bytes()) { + t.Error("Decoded public key does not match original") + } +} + +func TestDecodePrivateKey_InvalidHex(t *testing.T) { + e := NewEngine() + _, err := e.DecodePrivateKey("not-valid-hex!") + if err == nil { + t.Error("DecodePrivateKey() should error on invalid hex") + } +} + +func TestDecodePublicKey_InvalidHex(t *testing.T) { + e := NewEngine() + _, err := e.DecodePublicKey("not-valid-hex!") + if err == nil { + t.Error("DecodePublicKey() should error on invalid hex") + } +} + +func TestDecodePrivateKey_WrongLength(t *testing.T) { + e := NewEngine() + // 16 bytes instead of 32 + shortKey := strings.Repeat("00", 16) + _, err := e.DecodePrivateKey(shortKey) + if err == nil { + t.Error("DecodePrivateKey() should error on wrong length key") + } +} + +func TestDecodePublicKey_WrongLength(t *testing.T) { + e := NewEngine() + // 16 bytes instead of 32 + shortKey := strings.Repeat("00", 16) + _, err := e.DecodePublicKey(shortKey) + if err == nil { + t.Error("DecodePublicKey() should error on wrong length key") + } +} + +func TestEncryptDecrypt_RoundTrip(t *testing.T) { + e := NewEngine() + + // Generate sender and recipient key pairs + sender, err := e.GenerateKeyPair() + if err != nil { + t.Fatalf("GenerateKeyPair() for sender error = %v", err) + } + + recipient, err := e.GenerateKeyPair() + if err != nil { + t.Fatalf("GenerateKeyPair() for recipient error = %v", err) + } + + plaintext := []byte("Hello, World! This is a secret message.") + + // Encrypt + ciphertext, err := e.Encrypt(sender.PrivateKey, recipient.PublicKey, plaintext) + if err != nil { + t.Fatalf("Encrypt() error = %v", err) + } + + // Decrypt + decrypted, err := e.Decrypt(recipient.PrivateKey, sender.PublicKey, ciphertext) + if err != nil { + t.Fatalf("Decrypt() error = %v", err) + } + + if !bytes.Equal(decrypted, plaintext) { + t.Errorf("Decrypted message = %q, want %q", decrypted, plaintext) + } +} + +func TestEncryptDecrypt_EmptyMessage(t *testing.T) { + e := NewEngine() + + sender, err := e.GenerateKeyPair() + if err != nil { + t.Fatalf("GenerateKeyPair() for sender error = %v", err) + } + + recipient, err := e.GenerateKeyPair() + if err != nil { + t.Fatalf("GenerateKeyPair() for recipient error = %v", err) + } + + plaintext := []byte("") + + ciphertext, err := e.Encrypt(sender.PrivateKey, recipient.PublicKey, plaintext) + if err != nil { + t.Fatalf("Encrypt() error = %v", err) + } + + decrypted, err := e.Decrypt(recipient.PrivateKey, sender.PublicKey, ciphertext) + if err != nil { + t.Fatalf("Decrypt() error = %v", err) + } + + if !bytes.Equal(decrypted, plaintext) { + t.Errorf("Decrypted message = %q, want empty string", decrypted) + } +} + +func TestEncryptDecrypt_LargeMessage(t *testing.T) { + e := NewEngine() + + sender, err := e.GenerateKeyPair() + if err != nil { + t.Fatalf("GenerateKeyPair() for sender error = %v", err) + } + + recipient, err := e.GenerateKeyPair() + if err != nil { + t.Fatalf("GenerateKeyPair() for recipient error = %v", err) + } + + // Create a 1MB message + plaintext := make([]byte, 1024*1024) + if _, err := rand.Read(plaintext); err != nil { + t.Fatalf("Failed to generate random plaintext: %v", err) + } + + ciphertext, err := e.Encrypt(sender.PrivateKey, recipient.PublicKey, plaintext) + if err != nil { + t.Fatalf("Encrypt() error = %v", err) + } + + decrypted, err := e.Decrypt(recipient.PrivateKey, sender.PublicKey, ciphertext) + if err != nil { + t.Fatalf("Decrypt() error = %v", err) + } + + if !bytes.Equal(decrypted, plaintext) { + t.Error("Decrypted large message does not match original") + } +} + +func TestEncryptDecrypt_BinaryData(t *testing.T) { + e := NewEngine() + + sender, err := e.GenerateKeyPair() + if err != nil { + t.Fatalf("GenerateKeyPair() for sender error = %v", err) + } + + recipient, err := e.GenerateKeyPair() + if err != nil { + t.Fatalf("GenerateKeyPair() for recipient error = %v", err) + } + + // Binary data with null bytes and special characters + plaintext := []byte{0x00, 0x01, 0x02, 0xff, 0xfe, 0x00, 0x00, 0x10, 0x20} + + ciphertext, err := e.Encrypt(sender.PrivateKey, recipient.PublicKey, plaintext) + if err != nil { + t.Fatalf("Encrypt() error = %v", err) + } + + decrypted, err := e.Decrypt(recipient.PrivateKey, sender.PublicKey, ciphertext) + if err != nil { + t.Fatalf("Decrypt() error = %v", err) + } + + if !bytes.Equal(decrypted, plaintext) { + t.Errorf("Decrypted binary data = %v, want %v", decrypted, plaintext) + } +} + +func TestDecrypt_WrongRecipientKey(t *testing.T) { + e := NewEngine() + + sender, _ := e.GenerateKeyPair() + recipient, _ := e.GenerateKeyPair() + wrongRecipient, _ := e.GenerateKeyPair() + + plaintext := []byte("Secret message") + ciphertext, err := e.Encrypt(sender.PrivateKey, recipient.PublicKey, plaintext) + if err != nil { + t.Fatalf("Encrypt() error = %v", err) + } + + // Try to decrypt with wrong recipient's private key + _, err = e.Decrypt(wrongRecipient.PrivateKey, sender.PublicKey, ciphertext) + if err == nil { + t.Error("Decrypt() should fail with wrong recipient key") + } +} + +func TestDecrypt_WrongSenderPublicKey(t *testing.T) { + e := NewEngine() + + sender, _ := e.GenerateKeyPair() + recipient, _ := e.GenerateKeyPair() + wrongSender, _ := e.GenerateKeyPair() + + plaintext := []byte("Secret message") + ciphertext, err := e.Encrypt(sender.PrivateKey, recipient.PublicKey, plaintext) + if err != nil { + t.Fatalf("Encrypt() error = %v", err) + } + + // Try to decrypt with wrong sender's public key + _, err = e.Decrypt(recipient.PrivateKey, wrongSender.PublicKey, ciphertext) + if err == nil { + t.Error("Decrypt() should fail with wrong sender public key") + } +} + +func TestDecrypt_TamperedCiphertext(t *testing.T) { + e := NewEngine() + + sender, _ := e.GenerateKeyPair() + recipient, _ := e.GenerateKeyPair() + + plaintext := []byte("Secret message") + ciphertext, err := e.Encrypt(sender.PrivateKey, recipient.PublicKey, plaintext) + if err != nil { + t.Fatalf("Encrypt() error = %v", err) + } + + // Tamper with the ciphertext (flip a bit in the actual ciphertext portion) + if len(ciphertext) > 60 { + ciphertext[60] ^= 0x01 + } + + _, err = e.Decrypt(recipient.PrivateKey, sender.PublicKey, ciphertext) + if err == nil { + t.Error("Decrypt() should fail with tampered ciphertext") + } +} + +func TestDecrypt_TruncatedMessage(t *testing.T) { + e := NewEngine() + + sender, _ := e.GenerateKeyPair() + recipient, _ := e.GenerateKeyPair() + + plaintext := []byte("Secret message") + ciphertext, err := e.Encrypt(sender.PrivateKey, recipient.PublicKey, plaintext) + if err != nil { + t.Fatalf("Encrypt() error = %v", err) + } + + // Truncate the message + truncated := ciphertext[:len(ciphertext)-10] + + _, err = e.Decrypt(recipient.PrivateKey, sender.PublicKey, truncated) + if err == nil { + t.Error("Decrypt() should fail with truncated ciphertext") + } +} + +func TestDecrypt_MessageTooShort(t *testing.T) { + e := NewEngine() + + sender, _ := e.GenerateKeyPair() + recipient, _ := e.GenerateKeyPair() + + // Message shorter than pubKeySize + nonceSize (32 + 24 = 56 bytes) + shortMessage := make([]byte, 50) + + _, err := e.Decrypt(recipient.PrivateKey, sender.PublicKey, shortMessage) + if err == nil { + t.Error("Decrypt() should fail with message too short") + } + if err.Error() != "message too short" { + t.Errorf("Decrypt() error = %v, want 'message too short'", err) + } +} + +func TestDecrypt_InvalidEphemeralPublicKey(t *testing.T) { + e := NewEngine() + + sender, _ := e.GenerateKeyPair() + recipient, _ := e.GenerateKeyPair() + + // Create a message with invalid ephemeral public key (all zeros) + invalidMessage := make([]byte, 100) + + _, err := e.Decrypt(recipient.PrivateKey, sender.PublicKey, invalidMessage) + // This should fail during decryption due to authentication failure + if err == nil { + t.Error("Decrypt() should fail with invalid ephemeral public key") + } +} + +func TestEncrypt_DifferentCiphertextEachTime(t *testing.T) { + e := NewEngine() + + sender, _ := e.GenerateKeyPair() + recipient, _ := e.GenerateKeyPair() + + plaintext := []byte("Same message") + + ciphertext1, err := e.Encrypt(sender.PrivateKey, recipient.PublicKey, plaintext) + if err != nil { + t.Fatalf("First Encrypt() error = %v", err) + } + + ciphertext2, err := e.Encrypt(sender.PrivateKey, recipient.PublicKey, plaintext) + if err != nil { + t.Fatalf("Second Encrypt() error = %v", err) + } + + // Due to ephemeral keys and random nonce, ciphertexts should be different + if bytes.Equal(ciphertext1, ciphertext2) { + t.Error("Encrypting same message twice should produce different ciphertexts (due to ephemeral keys)") + } + + // But both should decrypt to the same plaintext + decrypted1, err := e.Decrypt(recipient.PrivateKey, sender.PublicKey, ciphertext1) + if err != nil { + t.Fatalf("Decrypt first error = %v", err) + } + decrypted2, err := e.Decrypt(recipient.PrivateKey, sender.PublicKey, ciphertext2) + if err != nil { + t.Fatalf("Decrypt second error = %v", err) + } + + if !bytes.Equal(decrypted1, plaintext) || !bytes.Equal(decrypted2, plaintext) { + t.Error("Both ciphertexts should decrypt to the same plaintext") + } +} + +func TestCiphertextFormat(t *testing.T) { + e := NewEngine() + + sender, _ := e.GenerateKeyPair() + recipient, _ := e.GenerateKeyPair() + + plaintext := []byte("Test message") + + ciphertext, err := e.Encrypt(sender.PrivateKey, recipient.PublicKey, plaintext) + if err != nil { + t.Fatalf("Encrypt() error = %v", err) + } + + // Ciphertext format: [EphemeralPub (32)] [Nonce (24)] [Ciphertext + Tag (len(plaintext) + 16)] + expectedMinLength := 32 + 24 + len(plaintext) + 16 // 16 is Poly1305 tag size + if len(ciphertext) != expectedMinLength { + t.Errorf("Ciphertext length = %d, want %d", len(ciphertext), expectedMinLength) + } +} + +func TestKeyPairConsistency(t *testing.T) { + e := NewEngine() + kp, err := e.GenerateKeyPair() + if err != nil { + t.Fatalf("GenerateKeyPair() error = %v", err) + } + + // Public key should be derivable from private key + derivedPub := kp.PrivateKey.PublicKey() + if !bytes.Equal(derivedPub.Bytes(), kp.PublicKey.Bytes()) { + t.Error("Public key should be consistent with private key") + } +} + +func TestEncodeDecodeRoundTrip(t *testing.T) { + e := NewEngine() + kp, err := e.GenerateKeyPair() + if err != nil { + t.Fatalf("GenerateKeyPair() error = %v", err) + } + + // Test private key round trip + privEncoded := kp.EncodePrivateKey() + privDecoded, err := e.DecodePrivateKey(privEncoded) + if err != nil { + t.Fatalf("DecodePrivateKey() error = %v", err) + } + + // Test public key round trip + pubEncoded := kp.EncodePublicKey() + pubDecoded, err := e.DecodePublicKey(pubEncoded) + if err != nil { + t.Fatalf("DecodePublicKey() error = %v", err) + } + + // Use decoded keys for encryption/decryption + recipient, _ := e.GenerateKeyPair() + plaintext := []byte("Round trip test") + + ciphertext, err := e.Encrypt(privDecoded, recipient.PublicKey, plaintext) + if err != nil { + t.Fatalf("Encrypt with decoded key error = %v", err) + } + + decrypted, err := e.Decrypt(recipient.PrivateKey, pubDecoded, ciphertext) + if err != nil { + t.Fatalf("Decrypt with decoded key error = %v", err) + } + + if !bytes.Equal(decrypted, plaintext) { + t.Error("Decoded keys should work correctly for encryption/decryption") + } +} + +func TestMultipleRecipients(t *testing.T) { + e := NewEngine() + + sender, _ := e.GenerateKeyPair() + recipient1, _ := e.GenerateKeyPair() + recipient2, _ := e.GenerateKeyPair() + + plaintext := []byte("Message for multiple recipients") + + // Encrypt for recipient1 + ciphertext1, err := e.Encrypt(sender.PrivateKey, recipient1.PublicKey, plaintext) + if err != nil { + t.Fatalf("Encrypt for recipient1 error = %v", err) + } + + // Encrypt for recipient2 + ciphertext2, err := e.Encrypt(sender.PrivateKey, recipient2.PublicKey, plaintext) + if err != nil { + t.Fatalf("Encrypt for recipient2 error = %v", err) + } + + // Both should be able to decrypt their own messages + decrypted1, err := e.Decrypt(recipient1.PrivateKey, sender.PublicKey, ciphertext1) + if err != nil { + t.Fatalf("Decrypt for recipient1 error = %v", err) + } + if !bytes.Equal(decrypted1, plaintext) { + t.Error("Recipient1 should be able to decrypt their message") + } + + decrypted2, err := e.Decrypt(recipient2.PrivateKey, sender.PublicKey, ciphertext2) + if err != nil { + t.Fatalf("Decrypt for recipient2 error = %v", err) + } + if !bytes.Equal(decrypted2, plaintext) { + t.Error("Recipient2 should be able to decrypt their message") + } + + // Cross decryption should fail + _, err = e.Decrypt(recipient1.PrivateKey, sender.PublicKey, ciphertext2) + if err == nil { + t.Error("Recipient1 should NOT be able to decrypt recipient2's message") + } + + _, err = e.Decrypt(recipient2.PrivateKey, sender.PublicKey, ciphertext1) + if err == nil { + t.Error("Recipient2 should NOT be able to decrypt recipient1's message") + } +} + +func TestUnicodeMessage(t *testing.T) { + e := NewEngine() + + sender, _ := e.GenerateKeyPair() + recipient, _ := e.GenerateKeyPair() + + // Unicode message with various scripts + plaintext := []byte("Hello 世界! Привет мир! 🔐🔑") + + ciphertext, err := e.Encrypt(sender.PrivateKey, recipient.PublicKey, plaintext) + if err != nil { + t.Fatalf("Encrypt() error = %v", err) + } + + decrypted, err := e.Decrypt(recipient.PrivateKey, sender.PublicKey, ciphertext) + if err != nil { + t.Fatalf("Decrypt() error = %v", err) + } + + if !bytes.Equal(decrypted, plaintext) { + t.Errorf("Decrypted unicode message = %q, want %q", decrypted, plaintext) + } +} + +// Benchmark tests +func BenchmarkGenerateKeyPair(b *testing.B) { + e := NewEngine() + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, err := e.GenerateKeyPair() + if err != nil { + b.Fatal(err) + } + } +} + +func BenchmarkEncrypt(b *testing.B) { + e := NewEngine() + sender, _ := e.GenerateKeyPair() + recipient, _ := e.GenerateKeyPair() + plaintext := []byte("Benchmark encryption test message") + + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, err := e.Encrypt(sender.PrivateKey, recipient.PublicKey, plaintext) + if err != nil { + b.Fatal(err) + } + } +} + +func BenchmarkDecrypt(b *testing.B) { + e := NewEngine() + sender, _ := e.GenerateKeyPair() + recipient, _ := e.GenerateKeyPair() + plaintext := []byte("Benchmark decryption test message") + ciphertext, _ := e.Encrypt(sender.PrivateKey, recipient.PublicKey, plaintext) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, err := e.Decrypt(recipient.PrivateKey, sender.PublicKey, ciphertext) + if err != nil { + b.Fatal(err) + } + } +} + +func BenchmarkEncryptLargeMessage(b *testing.B) { + e := NewEngine() + sender, _ := e.GenerateKeyPair() + recipient, _ := e.GenerateKeyPair() + plaintext := make([]byte, 1024*1024) // 1MB + rand.Read(plaintext) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, err := e.Encrypt(sender.PrivateKey, recipient.PublicKey, plaintext) + if err != nil { + b.Fatal(err) + } + } +} + +// Test that verifies the authentication property - message must come from correct sender +func TestSenderAuthentication(t *testing.T) { + e := NewEngine() + + sender, _ := e.GenerateKeyPair() + recipient, _ := e.GenerateKeyPair() + imposter, _ := e.GenerateKeyPair() + + plaintext := []byte("Authenticated message") + + // Sender encrypts + ciphertext, err := e.Encrypt(sender.PrivateKey, recipient.PublicKey, plaintext) + if err != nil { + t.Fatalf("Encrypt() error = %v", err) + } + + // Recipient tries to decrypt thinking it's from imposter - should fail + _, err = e.Decrypt(recipient.PrivateKey, imposter.PublicKey, ciphertext) + if err == nil { + t.Error("Decryption should fail when wrong sender public key is provided") + } + + // Recipient decrypts with correct sender public key - should succeed + decrypted, err := e.Decrypt(recipient.PrivateKey, sender.PublicKey, ciphertext) + if err != nil { + t.Fatalf("Decrypt() with correct sender should succeed: %v", err) + } + if !bytes.Equal(decrypted, plaintext) { + t.Error("Decrypted message should match plaintext") + } +} + +// Test forward secrecy by verifying ephemeral keys are different each encryption +func TestForwardSecrecy(t *testing.T) { + e := NewEngine() + + sender, _ := e.GenerateKeyPair() + recipient, _ := e.GenerateKeyPair() + + plaintext := []byte("Forward secrecy test") + + ciphertext1, _ := e.Encrypt(sender.PrivateKey, recipient.PublicKey, plaintext) + ciphertext2, _ := e.Encrypt(sender.PrivateKey, recipient.PublicKey, plaintext) + + // Extract ephemeral public keys (first 32 bytes) + ephemeralPub1 := ciphertext1[:32] + ephemeralPub2 := ciphertext2[:32] + + // Ephemeral keys should be different for each encryption + if bytes.Equal(ephemeralPub1, ephemeralPub2) { + t.Error("Ephemeral public keys should be different for each encryption (forward secrecy)") + } +} + +// Test with known test vectors (useful for cross-implementation testing) +func TestDecodeKnownKeys(t *testing.T) { + e := NewEngine() + + // Generate a key pair and verify it can be encoded and decoded + kp, err := e.GenerateKeyPair() + if err != nil { + t.Fatalf("GenerateKeyPair() error = %v", err) + } + + privHex := kp.EncodePrivateKey() + pubHex := kp.EncodePublicKey() + + // Verify the hex strings are lowercase + if privHex != strings.ToLower(privHex) { + t.Error("Private key hex should be lowercase") + } + if pubHex != strings.ToLower(pubHex) { + t.Error("Public key hex should be lowercase") + } + + // Verify decoding + priv, err := e.DecodePrivateKey(privHex) + if err != nil { + t.Fatalf("DecodePrivateKey() error = %v", err) + } + + pub, err := e.DecodePublicKey(pubHex) + if err != nil { + t.Fatalf("DecodePublicKey() error = %v", err) + } + + // Verify the decoded public key matches derived public key + if !bytes.Equal(priv.PublicKey().Bytes(), pub.Bytes()) { + t.Error("Decoded public key should match public key derived from decoded private key") + } +} + +// Test edge case: decode with uppercase hex (should work since hex.DecodeString handles both) +func TestDecodeUppercaseHex(t *testing.T) { + e := NewEngine() + + kp, _ := e.GenerateKeyPair() + + privHex := strings.ToUpper(kp.EncodePrivateKey()) + pubHex := strings.ToUpper(kp.EncodePublicKey()) + + priv, err := e.DecodePrivateKey(privHex) + if err != nil { + t.Fatalf("DecodePrivateKey() should handle uppercase hex: %v", err) + } + + pub, err := e.DecodePublicKey(pubHex) + if err != nil { + t.Fatalf("DecodePublicKey() should handle uppercase hex: %v", err) + } + + if !bytes.Equal(priv.Bytes(), kp.PrivateKey.Bytes()) { + t.Error("Decoded private key should match original") + } + if !bytes.Equal(pub.Bytes(), kp.PublicKey.Bytes()) { + t.Error("Decoded public key should match original") + } +} + +// Fuzz-like test with random message sizes +func TestRandomMessageSizes(t *testing.T) { + e := NewEngine() + sender, _ := e.GenerateKeyPair() + recipient, _ := e.GenerateKeyPair() + + sizes := []int{0, 1, 15, 16, 17, 31, 32, 33, 63, 64, 65, 127, 128, 256, 512, 1000, 4096} + + for _, size := range sizes { + t.Run(string(rune(size)), func(t *testing.T) { + plaintext := make([]byte, size) + if size > 0 { + rand.Read(plaintext) + } + + ciphertext, err := e.Encrypt(sender.PrivateKey, recipient.PublicKey, plaintext) + if err != nil { + t.Fatalf("Encrypt() with size %d error = %v", size, err) + } + + decrypted, err := e.Decrypt(recipient.PrivateKey, sender.PublicKey, ciphertext) + if err != nil { + t.Fatalf("Decrypt() with size %d error = %v", size, err) + } + + if !bytes.Equal(decrypted, plaintext) { + t.Errorf("Round trip failed for size %d", size) + } + }) + } +} + +// Test concurrent encryption/decryption +func TestConcurrentOperations(t *testing.T) { + e := NewEngine() + sender, _ := e.GenerateKeyPair() + recipient, _ := e.GenerateKeyPair() + + const numGoroutines = 100 + done := make(chan bool, numGoroutines) + + for i := 0; i < numGoroutines; i++ { + go func(id int) { + plaintext := []byte("Concurrent message " + string(rune('A'+id%26))) + + ciphertext, err := e.Encrypt(sender.PrivateKey, recipient.PublicKey, plaintext) + if err != nil { + t.Errorf("Goroutine %d: Encrypt() error = %v", id, err) + done <- false + return + } + + decrypted, err := e.Decrypt(recipient.PrivateKey, sender.PublicKey, ciphertext) + if err != nil { + t.Errorf("Goroutine %d: Decrypt() error = %v", id, err) + done <- false + return + } + + if !bytes.Equal(decrypted, plaintext) { + t.Errorf("Goroutine %d: decrypted != plaintext", id) + done <- false + return + } + + done <- true + }(i) + } + + for i := 0; i < numGoroutines; i++ { + <-done + } +} + +// Test self-encryption (sender encrypts to themselves) +func TestSelfEncryption(t *testing.T) { + e := NewEngine() + user, _ := e.GenerateKeyPair() + + plaintext := []byte("Message to myself") + + ciphertext, err := e.Encrypt(user.PrivateKey, user.PublicKey, plaintext) + if err != nil { + t.Fatalf("Encrypt() error = %v", err) + } + + decrypted, err := e.Decrypt(user.PrivateKey, user.PublicKey, ciphertext) + if err != nil { + t.Fatalf("Decrypt() error = %v", err) + } + + if !bytes.Equal(decrypted, plaintext) { + t.Error("Self-encryption should work correctly") + } +} + +// Ensure interface compatibility (Engine implements expected methods) +func TestEngineInterface(t *testing.T) { + e := NewEngine() + + // Test that all public methods exist and have correct signatures + var _ func() (*KeyPair, error) = e.GenerateKeyPair + var _ func(string) (*ecdh.PrivateKey, error) = e.DecodePrivateKey + var _ func(string) (*ecdh.PublicKey, error) = e.DecodePublicKey + var _ func(*ecdh.PrivateKey, *ecdh.PublicKey, []byte) ([]byte, error) = e.Encrypt + var _ func(*ecdh.PrivateKey, *ecdh.PublicKey, []byte) ([]byte, error) = e.Decrypt +}