Files
crypto/mlkem/mlkem_comprehensive_test.go
Zach Kelling a9769373ca fix: resolve build errors and ensure crypto independence from geth
- Fix bn256/gnark imports to use crypto/bitutil instead of standalone bitutil
- Remove unused secp256k1 import from crypto.go
- Fix mlkem test files to use correct API (GenerateKeyPair returns pub,priv,err)
- Fix mlkem constant names (MLKEM512SharedKeySize not SharedSecretSize)
- Restore crypto/common types as standalone (not aliases to geth/common)
- Update crypto.go and keccak.go to use crypto/common instead of geth/common
- Fix secp256k1 fuzz tests to use correct DecompressPubkey/CompressPubkey API
- Remove stale test files and go.work files

crypto package is now fully independent of geth (geth -> crypto, not reverse)
2025-12-12 21:51:02 -08:00

459 lines
12 KiB
Go

package mlkem
import (
"bytes"
"crypto/rand"
"testing"
)
func TestMLKEMKeyGeneration(t *testing.T) {
modes := []struct {
name string
mode Mode
pubSize int
privSize int
ctSize int
ssSize int
}{
{"ML-KEM-512", MLKEM512, MLKEM512PublicKeySize, MLKEM512PrivateKeySize, MLKEM512CiphertextSize, MLKEM512SharedKeySize},
{"ML-KEM-768", MLKEM768, MLKEM768PublicKeySize, MLKEM768PrivateKeySize, MLKEM768CiphertextSize, MLKEM768SharedKeySize},
{"ML-KEM-1024", MLKEM1024, MLKEM1024PublicKeySize, MLKEM1024PrivateKeySize, MLKEM1024CiphertextSize, MLKEM1024SharedKeySize},
}
for _, tt := range modes {
t.Run(tt.name, func(t *testing.T) {
// Test key generation
pub, priv, err := GenerateKeyPair(rand.Reader, tt.mode)
if err != nil {
t.Fatalf("GenerateKeyPair failed: %v", err)
}
// Check key sizes
privBytes := priv.Bytes()
if len(privBytes) != tt.privSize {
t.Errorf("Private key size mismatch: got %d, want %d", len(privBytes), tt.privSize)
}
pubBytes := pub.Bytes()
if len(pubBytes) != tt.pubSize {
t.Errorf("Public key size mismatch: got %d, want %d", len(pubBytes), tt.pubSize)
}
// Test nil reader - should use crypto/rand internally
pub2, priv2, err := GenerateKeyPair(nil, tt.mode)
if err != nil {
t.Errorf("GenerateKeyPair with nil reader failed: %v", err)
}
if pub2 == nil || priv2 == nil {
t.Error("GenerateKeyPair with nil reader returned nil keys")
}
})
}
// Test invalid mode
_, _, err := GenerateKeyPair(rand.Reader, Mode(99))
if err == nil {
t.Error("GenerateKeyPair should fail with invalid mode")
}
}
func TestMLKEMEncapsulateDecapsulate(t *testing.T) {
modes := []Mode{MLKEM512, MLKEM768, MLKEM1024}
for _, mode := range modes {
t.Run(mode.String(), func(t *testing.T) {
// Generate key pair
pub, priv, err := GenerateKeyPair(rand.Reader, mode)
if err != nil {
t.Fatalf("GenerateKeyPair failed: %v", err)
}
// Encapsulate
ciphertext, sharedSecret, err := pub.Encapsulate(rand.Reader)
if err != nil {
t.Fatalf("Encapsulate failed: %v", err)
}
// Check ciphertext and shared secret sizes
var expectedCtSize, expectedSSSize int
switch mode {
case MLKEM512:
expectedCtSize = MLKEM512CiphertextSize
expectedSSSize = MLKEM512SharedKeySize
case MLKEM768:
expectedCtSize = MLKEM768CiphertextSize
expectedSSSize = MLKEM768SharedKeySize
case MLKEM1024:
expectedCtSize = MLKEM1024CiphertextSize
expectedSSSize = MLKEM1024SharedKeySize
}
if len(ciphertext) != expectedCtSize {
t.Errorf("Ciphertext size mismatch: got %d, want %d", len(ciphertext), expectedCtSize)
}
if len(sharedSecret) != expectedSSSize {
t.Errorf("Shared secret size mismatch: got %d, want %d", len(sharedSecret), expectedSSSize)
}
// Decapsulate
decapSecret, err := priv.Decapsulate(ciphertext)
if err != nil {
t.Fatalf("Decapsulate failed: %v", err)
}
// Verify shared secrets match
if !bytes.Equal(sharedSecret, decapSecret) {
t.Error("Shared secrets don't match")
}
// Test with wrong ciphertext size
wrongCiphertext := make([]byte, 100)
_, err = priv.Decapsulate(wrongCiphertext)
if err == nil {
t.Error("Decapsulate should fail with wrong ciphertext size")
}
// Test with tampered ciphertext
tamperedCiphertext := make([]byte, len(ciphertext))
copy(tamperedCiphertext, ciphertext)
tamperedCiphertext[0] ^= 0xFF
// Tampered ciphertext should produce different shared secret (implicit rejection)
tamperedSecret, err := priv.Decapsulate(tamperedCiphertext)
if err != nil {
t.Fatalf("Decapsulate failed with tampered ciphertext: %v", err)
}
if bytes.Equal(sharedSecret, tamperedSecret) {
t.Error("Tampered ciphertext produced same shared secret")
}
})
}
}
func TestMLKEMKeySerialization(t *testing.T) {
modes := []Mode{MLKEM512, MLKEM768, MLKEM1024}
for _, mode := range modes {
t.Run(mode.String(), func(t *testing.T) {
// Generate original key pair
origPub, origPriv, err := GenerateKeyPair(rand.Reader, mode)
if err != nil {
t.Fatalf("GenerateKeyPair failed: %v", err)
}
// Serialize and deserialize private key
privBytes := origPriv.Bytes()
newPrivKey, err := PrivateKeyFromBytes(privBytes, mode)
if err != nil {
t.Fatalf("PrivateKeyFromBytes failed: %v", err)
}
// Check keys are equal
if !bytes.Equal(origPriv.Bytes(), newPrivKey.Bytes()) {
t.Error("Private key serialization failed")
}
// Serialize and deserialize public key
pubBytes := origPub.Bytes()
newPubKey, err := PublicKeyFromBytes(pubBytes, mode)
if err != nil {
t.Fatalf("PublicKeyFromBytes failed: %v", err)
}
if !bytes.Equal(origPub.Bytes(), newPubKey.Bytes()) {
t.Error("Public key serialization failed")
}
// Test encapsulation with deserialized keys
ciphertext, sharedSecret, err := newPubKey.Encapsulate(rand.Reader)
if err != nil {
t.Fatalf("Encapsulate with deserialized key failed: %v", err)
}
decapSecret, err := newPrivKey.Decapsulate(ciphertext)
if err != nil {
t.Fatalf("Decapsulate with deserialized key failed: %v", err)
}
if !bytes.Equal(sharedSecret, decapSecret) {
t.Error("Shared secrets don't match with deserialized keys")
}
})
}
}
func TestMLKEMMultipleEncapsulations(t *testing.T) {
modes := []Mode{MLKEM512, MLKEM768, MLKEM1024}
for _, mode := range modes {
t.Run(mode.String(), func(t *testing.T) {
pub, priv, err := GenerateKeyPair(rand.Reader, mode)
if err != nil {
t.Fatalf("GenerateKeyPair failed: %v", err)
}
// Multiple encapsulations should produce different ciphertexts
// but all should decapsulate correctly
numEncaps := 10
ciphertexts := make([][]byte, numEncaps)
sharedSecrets := make([][]byte, numEncaps)
for i := 0; i < numEncaps; i++ {
ct, ss, err := pub.Encapsulate(rand.Reader)
if err != nil {
t.Fatalf("Encapsulate %d failed: %v", i, err)
}
ciphertexts[i] = ct
sharedSecrets[i] = ss
}
// Check that ciphertexts are different
for i := 0; i < numEncaps-1; i++ {
for j := i + 1; j < numEncaps; j++ {
if bytes.Equal(ciphertexts[i], ciphertexts[j]) {
t.Errorf("Ciphertexts %d and %d are identical", i, j)
}
}
}
// Check that all decapsulate correctly
for i := 0; i < numEncaps; i++ {
ss, err := priv.Decapsulate(ciphertexts[i])
if err != nil {
t.Fatalf("Decapsulate %d failed: %v", i, err)
}
if !bytes.Equal(ss, sharedSecrets[i]) {
t.Errorf("Shared secret %d doesn't match", i)
}
}
})
}
}
func TestMLKEMEdgeCases(t *testing.T) {
t.Run("InvalidMode", func(t *testing.T) {
_, _, err := GenerateKeyPair(rand.Reader, Mode(99))
if err == nil {
t.Error("GenerateKeyPair should fail with invalid mode")
}
})
t.Run("NilPublicKey", func(t *testing.T) {
var pubKey *PublicKey
_, _, err := pubKey.Encapsulate(rand.Reader)
if err == nil {
t.Error("Encapsulate should fail with nil public key")
}
})
t.Run("NilPrivateKey", func(t *testing.T) {
var privKey *PrivateKey
_, err := privKey.Decapsulate([]byte("test"))
if err == nil {
t.Error("Decapsulate should fail with nil private key")
}
})
t.Run("EmptyCiphertext", func(t *testing.T) {
_, priv, _ := GenerateKeyPair(rand.Reader, MLKEM512)
_, err := priv.Decapsulate([]byte{})
if err == nil {
t.Error("Decapsulate should fail with empty ciphertext")
}
})
t.Run("WrongSizeCiphertext", func(t *testing.T) {
_, priv, _ := GenerateKeyPair(rand.Reader, MLKEM512)
wrongCt := make([]byte, 100)
rand.Read(wrongCt)
_, err := priv.Decapsulate(wrongCt)
if err == nil {
t.Error("Decapsulate should fail with wrong size ciphertext")
}
})
t.Run("InvalidKeyBytes", func(t *testing.T) {
// Wrong size public key
_, err := PublicKeyFromBytes([]byte("short"), MLKEM512)
if err == nil {
t.Error("PublicKeyFromBytes should fail with wrong size")
}
// Wrong size private key
_, err = PrivateKeyFromBytes([]byte("short"), MLKEM512)
if err == nil {
t.Error("PrivateKeyFromBytes should fail with wrong size")
}
})
}
func TestMLKEMConcurrency(t *testing.T) {
pub, priv, err := GenerateKeyPair(rand.Reader, MLKEM768)
if err != nil {
t.Fatalf("GenerateKeyPair failed: %v", err)
}
// Test concurrent encapsulation
t.Run("ConcurrentEncapsulate", func(t *testing.T) {
const numGoroutines = 10
done := make(chan bool, numGoroutines)
for i := 0; i < numGoroutines; i++ {
go func(id int) {
ct, ss, err := pub.Encapsulate(rand.Reader)
if err != nil {
t.Errorf("Goroutine %d: Encapsulate failed: %v", id, err)
}
decapSS, err := priv.Decapsulate(ct)
if err != nil {
t.Errorf("Goroutine %d: Decapsulate failed: %v", id, err)
}
if !bytes.Equal(ss, decapSS) {
t.Errorf("Goroutine %d: Shared secrets don't match", id)
}
done <- true
}(i)
}
for i := 0; i < numGoroutines; i++ {
<-done
}
})
// Test concurrent decapsulation
t.Run("ConcurrentDecapsulate", func(t *testing.T) {
ciphertext, sharedSecret, _ := pub.Encapsulate(rand.Reader)
const numGoroutines = 10
done := make(chan bool, numGoroutines)
for i := 0; i < numGoroutines; i++ {
go func(id int) {
ss, err := priv.Decapsulate(ciphertext)
if err != nil {
t.Errorf("Goroutine %d: Decapsulate failed: %v", id, err)
}
if !bytes.Equal(ss, sharedSecret) {
t.Errorf("Goroutine %d: Shared secret mismatch", id)
}
done <- true
}(i)
}
for i := 0; i < numGoroutines; i++ {
<-done
}
})
}
func TestMLKEMPublicKeyMethod(t *testing.T) {
// Test that PrivateKey.PublicKey() returns the corresponding public key
pub, priv, err := GenerateKeyPair(rand.Reader, MLKEM768)
if err != nil {
t.Fatalf("GenerateKeyPair failed: %v", err)
}
derivedPub := priv.PublicKey()
if derivedPub == nil {
t.Fatal("PublicKey() returned nil")
}
// The derived public key should match the one from GenerateKeyPair
if !bytes.Equal(pub.Bytes(), derivedPub.Bytes()) {
t.Error("Derived public key doesn't match original")
}
// Should be able to encapsulate with derived public key
ct, ss, err := derivedPub.Encapsulate(rand.Reader)
if err != nil {
t.Fatalf("Encapsulate with derived public key failed: %v", err)
}
decapSS, err := priv.Decapsulate(ct)
if err != nil {
t.Fatalf("Decapsulate failed: %v", err)
}
if !bytes.Equal(ss, decapSS) {
t.Error("Shared secrets don't match")
}
}
func BenchmarkMLKEMKeyGen(b *testing.B) {
modes := []struct {
name string
mode Mode
}{
{"ML-KEM-512", MLKEM512},
{"ML-KEM-768", MLKEM768},
{"ML-KEM-1024", MLKEM1024},
}
for _, m := range modes {
b.Run(m.name, func(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, _, err := GenerateKeyPair(rand.Reader, m.mode)
if err != nil {
b.Fatal(err)
}
}
})
}
}
func BenchmarkMLKEMEncapsulate(b *testing.B) {
modes := []struct {
name string
mode Mode
}{
{"ML-KEM-512", MLKEM512},
{"ML-KEM-768", MLKEM768},
{"ML-KEM-1024", MLKEM1024},
}
for _, m := range modes {
pub, _, _ := GenerateKeyPair(rand.Reader, m.mode)
b.Run(m.name, func(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, _, err := pub.Encapsulate(rand.Reader)
if err != nil {
b.Fatal(err)
}
}
})
}
}
func BenchmarkMLKEMDecapsulate(b *testing.B) {
modes := []struct {
name string
mode Mode
}{
{"ML-KEM-512", MLKEM512},
{"ML-KEM-768", MLKEM768},
{"ML-KEM-1024", MLKEM1024},
}
for _, m := range modes {
pub, priv, _ := GenerateKeyPair(rand.Reader, m.mode)
ciphertext, _, _ := pub.Encapsulate(rand.Reader)
b.Run(m.name, func(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
ss, err := priv.Decapsulate(ciphertext)
if err != nil {
b.Fatal(err)
}
if len(ss) != 32 {
b.Fatal("invalid shared secret size")
}
}
})
}
}