mirror of
https://github.com/luxfi/crypto.git
synced 2026-07-27 01:54:50 +00:00
- 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)
459 lines
12 KiB
Go
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")
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|