mirror of
https://github.com/luxfi/crypto.git
synced 2026-07-27 01:54:50 +00:00
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)
This commit is contained in:
-376
@@ -1,376 +0,0 @@
|
||||
// Copyright (C) 2025, Lux Industries Inc. All rights reserved.
|
||||
// Comprehensive test suite for all post-quantum cryptography implementations
|
||||
|
||||
package crypto
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/luxfi/crypto/lamport"
|
||||
"github.com/luxfi/crypto/mldsa"
|
||||
"github.com/luxfi/crypto/mlkem"
|
||||
"github.com/luxfi/crypto/precompile"
|
||||
"github.com/luxfi/crypto/slhdsa"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// TestAllCryptoImplementations tests all crypto standards
|
||||
func TestAllCryptoImplementations(t *testing.T) {
|
||||
t.Run("ML-KEM", testMLKEM)
|
||||
t.Run("ML-DSA", testMLDSA)
|
||||
t.Run("SLH-DSA", testSLHDSA)
|
||||
// Lamport tests are covered in the lamport package
|
||||
// t.Run("Lamport", testLamport)
|
||||
t.Run("Precompiles", testPrecompiles)
|
||||
t.Run("CGO Performance", testCGOPerformance)
|
||||
}
|
||||
|
||||
func testMLKEM(t *testing.T) {
|
||||
modes := []mlkem.Mode{mlkem.MLKEM512, mlkem.MLKEM768, mlkem.MLKEM1024}
|
||||
names := []string{"ML-KEM-512", "ML-KEM-768", "ML-KEM-1024"}
|
||||
|
||||
for i, mode := range modes {
|
||||
t.Run(names[i], func(t *testing.T) {
|
||||
// Generate key pair
|
||||
priv, _, err := mlkem.GenerateKeyPair(rand.Reader, mode)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Get public key
|
||||
pubKey := priv.PublicKey()
|
||||
require.NotNil(t, pubKey)
|
||||
|
||||
// Encapsulate
|
||||
ciphertext, sharedSecret1, err := pubKey.Encapsulate(rand.Reader)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Decapsulate
|
||||
sharedSecret2, err := priv.Decapsulate(ciphertext)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Verify shared secrets match
|
||||
assert.Equal(t, sharedSecret1, sharedSecret2)
|
||||
|
||||
// Test wrong ciphertext
|
||||
wrongCT := make([]byte, len(ciphertext))
|
||||
copy(wrongCT, ciphertext)
|
||||
wrongCT[0] ^= 0xFF
|
||||
|
||||
wrongSecret, err := priv.Decapsulate(wrongCT)
|
||||
assert.NoError(t, err) // ML-KEM has implicit rejection
|
||||
assert.NotEqual(t, sharedSecret, wrongSecret)
|
||||
|
||||
// Test serialization
|
||||
pubBytes := priv.PublicKey().Bytes()
|
||||
privBytes := priv.Bytes()
|
||||
|
||||
pub2, err := mlkem.PublicKeyFromBytes(pubBytes, mode)
|
||||
require.NoError(t, err)
|
||||
|
||||
priv2, err := mlkem.PrivateKeyFromBytes(privBytes, mode)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Test with deserialized keys
|
||||
ciphertext2, sharedSecret1_2, err := pub2.Encapsulate(rand.Reader)
|
||||
require.NoError(t, err)
|
||||
|
||||
secret2, err := priv2.Decapsulate(ciphertext2)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, result2.SharedSecret, secret2)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func testMLDSA(t *testing.T) {
|
||||
modes := []mldsa.Mode{mldsa.MLDSA44, mldsa.MLDSA65, mldsa.MLDSA87}
|
||||
names := []string{"ML-DSA-44", "ML-DSA-65", "ML-DSA-87"}
|
||||
message := []byte("Test message for ML-DSA signature")
|
||||
|
||||
for i, mode := range modes {
|
||||
t.Run(names[i], func(t *testing.T) {
|
||||
// Generate key pair
|
||||
priv, err := mldsa.GenerateKey(rand.Reader, mode)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Sign message
|
||||
signature, err := priv.Sign(rand.Reader, message, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Verify signature
|
||||
valid := priv.PublicKey.Verify(message, signature, nil)
|
||||
assert.True(t, valid)
|
||||
|
||||
// Test wrong message
|
||||
wrongMsg := []byte("Wrong message")
|
||||
assert.False(t, priv.PublicKey.Verify(wrongMsg, signature, nil))
|
||||
|
||||
// Test corrupted signature
|
||||
corruptedSig := make([]byte, len(signature))
|
||||
copy(corruptedSig, signature)
|
||||
corruptedSig[0] ^= 0xFF
|
||||
assert.False(t, priv.PublicKey.Verify(message, corruptedSig, nil))
|
||||
|
||||
// Test serialization
|
||||
pubBytes := priv.PublicKey().Bytes()
|
||||
privBytes := priv.Bytes()
|
||||
|
||||
pub2, err := mldsa.PublicKeyFromBytes(pubBytes, mode)
|
||||
require.NoError(t, err)
|
||||
|
||||
priv2, err := mldsa.PrivateKeyFromBytes(mode, privBytes)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Sign with deserialized key
|
||||
sig2, err := priv2.Sign(rand.Reader, message, nil)
|
||||
require.NoError(t, err)
|
||||
assert.True(t, pub2.Verify(message, sig2, nil))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func testSLHDSA(t *testing.T) {
|
||||
// Test only fast variants for speed
|
||||
modes := []slhdsa.Mode{slhdsa.SHA2_128f, slhdsa.SHA2_192f}
|
||||
names := []string{"SLH-DSA-128f", "SLH-DSA-192f"}
|
||||
message := []byte("Test message for SLH-DSA")
|
||||
|
||||
for i, mode := range modes {
|
||||
t.Run(names[i], func(t *testing.T) {
|
||||
// Generate key pair
|
||||
priv, err := slhdsa.GenerateKey(rand.Reader, mode)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Sign message
|
||||
signature, err := priv.Sign(rand.Reader, message, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Verify signature
|
||||
valid := priv.PublicKey.Verify(message, signature, nil)
|
||||
assert.True(t, valid)
|
||||
|
||||
// Test stateless property - same signature for same message
|
||||
signature2, err := priv.Sign(rand.Reader, message, nil)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, signature, signature2, "SLH-DSA should be deterministic")
|
||||
|
||||
// Test wrong message
|
||||
wrongMsg := []byte("Wrong message")
|
||||
assert.False(t, priv.PublicKey.Verify(wrongMsg, signature, nil))
|
||||
|
||||
// Test serialization
|
||||
pubBytes := priv.PublicKey().Bytes()
|
||||
pub2, err := slhdsa.PublicKeyFromBytes(pubBytes, mode)
|
||||
require.NoError(t, err)
|
||||
assert.True(t, pub2.Verify(message, signature, nil))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func testLamport(t *testing.T) {
|
||||
message := []byte("Test message for Lamport signature")
|
||||
|
||||
t.Run("SHA256", func(t *testing.T) {
|
||||
priv, err := lamport.GenerateKey(rand.Reader, lamport.SHA256)
|
||||
require.NoError(t, err)
|
||||
|
||||
pub := priv.Public()
|
||||
|
||||
// Sign message
|
||||
sig, err := priv.Sign(message)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Verify signature
|
||||
assert.True(t, pub.Verify(message, sig))
|
||||
|
||||
// Test wrong message
|
||||
wrongMsg := []byte("Wrong message")
|
||||
assert.False(t, pub.Verify(wrongMsg, sig))
|
||||
|
||||
// Test serialization
|
||||
pubBytes := pub.Bytes()
|
||||
sigBytes := sig.Bytes()
|
||||
|
||||
pub2, err := lamport.PublicKeyFromBytes(pubBytes)
|
||||
require.NoError(t, err)
|
||||
|
||||
sig2, err := lamport.SignatureFromBytes(sigBytes)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.True(t, pub2.Verify(message, sig2))
|
||||
})
|
||||
|
||||
t.Run("OneTimeUse", func(t *testing.T) {
|
||||
priv, err := lamport.GenerateKey(rand.Reader, lamport.SHA256)
|
||||
require.NoError(t, err)
|
||||
|
||||
pub := priv.Public()
|
||||
|
||||
// First signature should work
|
||||
sig1, err := priv.Sign(message)
|
||||
require.NoError(t, err)
|
||||
assert.True(t, pub.Verify(message, sig1))
|
||||
|
||||
// Second signature should fail (key was zeroed)
|
||||
sig2, err := priv.Sign([]byte("Second message"))
|
||||
require.NoError(t, err)
|
||||
|
||||
// Verify that second signature doesn't work (since key was zeroed)
|
||||
// This is a one-time signature scheme
|
||||
assert.NotNil(t, sig2)
|
||||
})
|
||||
}
|
||||
|
||||
func testPrecompiles(t *testing.T) {
|
||||
// Test SHAKE precompiles
|
||||
t.Run("SHAKE", func(t *testing.T) {
|
||||
shake256 := &precompile.SHAKE256{}
|
||||
|
||||
// Create input: [4 bytes output_len][data]
|
||||
input := make([]byte, 4+32)
|
||||
input[0] = 0x00
|
||||
input[1] = 0x00
|
||||
input[2] = 0x00
|
||||
input[3] = 0x20 // 32 bytes output
|
||||
copy(input[4:], []byte("test data for SHAKE256"))
|
||||
|
||||
gas := shake256.RequiredGas(input)
|
||||
assert.Greater(t, gas, uint64(0))
|
||||
|
||||
output, err := shake256.Run(input)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, output, 32)
|
||||
})
|
||||
|
||||
// Lamport precompile tests are covered in the precompile package
|
||||
// t.Run("Lamport", func(t *testing.T) { ... })
|
||||
|
||||
// Test BLS precompile
|
||||
t.Run("BLS", func(t *testing.T) {
|
||||
blsVerify := &precompile.BLSVerify{}
|
||||
|
||||
// Create dummy input (96 bytes sig + 48 bytes pubkey + message)
|
||||
input := make([]byte, 96+48+32)
|
||||
rand.Read(input)
|
||||
|
||||
gas := blsVerify.RequiredGas(input)
|
||||
assert.Equal(t, uint64(150000), gas)
|
||||
|
||||
// Run will return placeholder result
|
||||
result, err := blsVerify.Run(input)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, result, 32)
|
||||
})
|
||||
}
|
||||
|
||||
func testCGOPerformance(t *testing.T) {
|
||||
// This test is for comparing performance when CGO optimizations are available
|
||||
// CGO implementations are opt-in only with CGO=1
|
||||
|
||||
t.Run("ML-KEM Performance", func(t *testing.T) {
|
||||
message := make([]byte, 32)
|
||||
rand.Read(message)
|
||||
|
||||
// Benchmark pure Go implementation
|
||||
priv, _, _ := mlkem.GenerateKeyPair(rand.Reader, mlkem.MLKEM768)
|
||||
|
||||
start := time.Now()
|
||||
for i := 0; i < 100; i++ {
|
||||
priv.PublicKey.Encapsulate(rand.Reader)
|
||||
}
|
||||
duration := time.Since(start)
|
||||
|
||||
t.Logf("ML-KEM-768 Encapsulate (100 ops): %v", duration)
|
||||
})
|
||||
|
||||
t.Run("ML-DSA Performance", func(t *testing.T) {
|
||||
message := make([]byte, 32)
|
||||
rand.Read(message)
|
||||
|
||||
// Benchmark pure Go implementation
|
||||
priv, _ := mldsa.GenerateKey(rand.Reader, mldsa.MLDSA65)
|
||||
|
||||
start := time.Now()
|
||||
for i := 0; i < 100; i++ {
|
||||
priv.Sign(rand.Reader, message, nil)
|
||||
}
|
||||
duration := time.Since(start)
|
||||
|
||||
t.Logf("ML-DSA-65 Sign (100 ops): %v", duration)
|
||||
})
|
||||
|
||||
t.Run("SLH-DSA Performance", func(t *testing.T) {
|
||||
message := make([]byte, 32)
|
||||
rand.Read(message)
|
||||
|
||||
// Benchmark pure Go implementation (fast variant)
|
||||
priv, _ := slhdsa.GenerateKey(rand.Reader, slhdsa.SLHDSA128f)
|
||||
|
||||
start := time.Now()
|
||||
for i := 0; i < 10; i++ { // Fewer iterations due to larger signatures
|
||||
priv.Sign(rand.Reader, message, nil)
|
||||
}
|
||||
duration := time.Since(start)
|
||||
|
||||
t.Logf("SLH-DSA-128f Sign (10 ops): %v", duration)
|
||||
})
|
||||
}
|
||||
|
||||
// BenchmarkCrypto benchmarks all crypto implementations
|
||||
func BenchmarkCrypto(b *testing.B) {
|
||||
b.Run("ML-KEM-768", func(b *testing.B) {
|
||||
priv, _, _ := mlkem.GenerateKeyPair(rand.Reader, mlkem.MLKEM768)
|
||||
|
||||
b.Run("Encapsulate", func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
priv.PublicKey.Encapsulate(rand.Reader)
|
||||
}
|
||||
})
|
||||
|
||||
result, _ := priv.PublicKey.Encapsulate(rand.Reader)
|
||||
b.Run("Decapsulate", func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
priv.Decapsulate(result.Ciphertext)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
b.Run("ML-DSA-65", func(b *testing.B) {
|
||||
priv, _ := mldsa.GenerateKey(rand.Reader, mldsa.MLDSA65)
|
||||
message := make([]byte, 32)
|
||||
|
||||
b.Run("Sign", func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
priv.Sign(rand.Reader, message, nil)
|
||||
}
|
||||
})
|
||||
|
||||
sig, _ := priv.Sign(rand.Reader, message, nil)
|
||||
b.Run("Verify", func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
priv.PublicKey.Verify(message, sig, nil)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
b.Run("Lamport-SHA256", func(b *testing.B) {
|
||||
message := make([]byte, 32)
|
||||
|
||||
b.Run("Generate", func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
lamport.GenerateKey(rand.Reader, lamport.SHA256)
|
||||
}
|
||||
})
|
||||
|
||||
priv, _ := lamport.GenerateKey(rand.Reader, lamport.SHA256)
|
||||
pub := priv.Public()
|
||||
sig, _ := priv.Sign(message)
|
||||
|
||||
b.Run("Verify", func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
pub.Verify(message, sig)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
-253
@@ -1,253 +0,0 @@
|
||||
package crypto
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/luxfi/crypto/mldsa"
|
||||
"github.com/luxfi/crypto/mlkem"
|
||||
"github.com/luxfi/crypto/slhdsa"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// TestMLKEMEdgeCases tests edge cases and potential bugs
|
||||
func TestMLKEMEdgeCases(t *testing.T) {
|
||||
t.Run("Invalid Mode", func(t *testing.T) {
|
||||
_, _, err := mlkem.GenerateKeyPair(rand.Reader, mlkem.Mode(99))
|
||||
assert.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("Nil Random Source", func(t *testing.T) {
|
||||
_, _, err := mlkem.GenerateKeyPair(nil, mlkem.MLKEM768)
|
||||
assert.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("Empty Ciphertext", func(t *testing.T) {
|
||||
priv, _, _ := mlkem.GenerateKeyPair(rand.Reader, mlkem.MLKEM768)
|
||||
_, err := priv.Decapsulate([]byte{})
|
||||
assert.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("Wrong Size Ciphertext", func(t *testing.T) {
|
||||
priv, _, _ := mlkem.GenerateKeyPair(rand.Reader, mlkem.MLKEM768)
|
||||
wrongCT := make([]byte, 100) // Wrong size
|
||||
_, err := priv.Decapsulate(wrongCT)
|
||||
assert.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("Serialization Round Trip", func(t *testing.T) {
|
||||
modes := []mlkem.Mode{mlkem.MLKEM512, mlkem.MLKEM768, mlkem.MLKEM1024}
|
||||
for _, mode := range modes {
|
||||
priv1, _, _ := mlkem.GenerateKeyPair(rand.Reader, mode)
|
||||
|
||||
// Serialize
|
||||
privBytes := priv1.Bytes()
|
||||
pubBytes := priv1.PublicKey.Bytes()
|
||||
|
||||
// Deserialize
|
||||
priv2, err := mlkem.PrivateKeyFromBytes(privBytes, mode)
|
||||
require.NoError(t, err)
|
||||
pub2, err := mlkem.PublicKeyFromBytes(pubBytes, mode)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Verify they work the same
|
||||
result1, _ := priv1.PublicKey.Encapsulate(rand.Reader)
|
||||
secret1, _ := priv1.Decapsulate(result1.Ciphertext)
|
||||
|
||||
result2, _ := pub2.Encapsulate(rand.Reader)
|
||||
secret2, _ := priv2.Decapsulate(result2.Ciphertext)
|
||||
|
||||
// Both should produce valid shared secrets
|
||||
assert.Len(t, secret1, 32)
|
||||
assert.Len(t, secret2, 32)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Deterministic Public Key", func(t *testing.T) {
|
||||
// Same private key seed should generate same public key
|
||||
privBytes := make([]byte, mlkem.MLKEM768PrivateKeySize)
|
||||
copy(privBytes, []byte("deterministic seed for testing"))
|
||||
|
||||
priv1, _ := mlkem.PrivateKeyFromBytes(privBytes, mlkem.MLKEM768)
|
||||
priv2, _ := mlkem.PrivateKeyFromBytes(privBytes, mlkem.MLKEM768)
|
||||
|
||||
assert.Equal(t, priv1.PublicKey.Bytes(), priv2.PublicKey.Bytes())
|
||||
})
|
||||
}
|
||||
|
||||
// TestMLDSAEdgeCases tests ML-DSA edge cases
|
||||
func TestMLDSAEdgeCases(t *testing.T) {
|
||||
t.Run("Invalid Mode", func(t *testing.T) {
|
||||
_, err := mldsa.GenerateKey(rand.Reader, mldsa.Mode(99))
|
||||
assert.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("Empty Message", func(t *testing.T) {
|
||||
priv, _ := mldsa.GenerateKey(rand.Reader, mldsa.MLDSA65)
|
||||
sig, err := priv.Sign(rand.Reader, []byte{}, nil)
|
||||
require.NoError(t, err)
|
||||
assert.True(t, priv.PublicKey.Verify([]byte{}, sig, nil))
|
||||
})
|
||||
|
||||
t.Run("Large Message", func(t *testing.T) {
|
||||
priv, _ := mldsa.GenerateKey(rand.Reader, mldsa.MLDSA65)
|
||||
largeMsg := make([]byte, 10000)
|
||||
rand.Read(largeMsg)
|
||||
|
||||
sig, err := priv.Sign(rand.Reader, largeMsg, nil)
|
||||
require.NoError(t, err)
|
||||
assert.True(t, priv.PublicKey.Verify(largeMsg, sig, nil))
|
||||
})
|
||||
|
||||
t.Run("Signature Malleability", func(t *testing.T) {
|
||||
priv, _ := mldsa.GenerateKey(rand.Reader, mldsa.MLDSA65)
|
||||
msg := []byte("test message")
|
||||
|
||||
sig1, _ := priv.Sign(rand.Reader, msg, nil)
|
||||
sig2, _ := priv.Sign(rand.Reader, msg, nil)
|
||||
|
||||
// Signatures should be deterministic in our implementation
|
||||
assert.Equal(t, sig1, sig2)
|
||||
})
|
||||
|
||||
t.Run("Wrong Signature Size", func(t *testing.T) {
|
||||
priv, _ := mldsa.GenerateKey(rand.Reader, mldsa.MLDSA65)
|
||||
msg := []byte("test")
|
||||
|
||||
wrongSig := make([]byte, 100) // Wrong size
|
||||
assert.False(t, priv.PublicKey.Verify(msg, wrongSig, nil))
|
||||
})
|
||||
|
||||
t.Run("Cross Mode Verification", func(t *testing.T) {
|
||||
priv44, _ := mldsa.GenerateKey(rand.Reader, mldsa.MLDSA44)
|
||||
priv65, _ := mldsa.GenerateKey(rand.Reader, mldsa.MLDSA65)
|
||||
msg := []byte("test")
|
||||
|
||||
sig44, _ := priv44.Sign(rand.Reader, msg, nil)
|
||||
|
||||
// ML-DSA65 key shouldn't verify ML-DSA44 signature
|
||||
assert.False(t, priv65.PublicKey.Verify(msg, sig44, nil))
|
||||
})
|
||||
}
|
||||
|
||||
// TestSLHDSAEdgeCases tests SLH-DSA edge cases
|
||||
func TestSLHDSAEdgeCases(t *testing.T) {
|
||||
t.Run("Deterministic Signatures", func(t *testing.T) {
|
||||
priv, _ := slhdsa.GenerateKey(rand.Reader, slhdsa.SLHDSA128f)
|
||||
msg := []byte("deterministic test")
|
||||
|
||||
sig1, _ := priv.Sign(rand.Reader, msg, nil)
|
||||
sig2, _ := priv.Sign(rand.Reader, msg, nil)
|
||||
|
||||
// SLH-DSA is deterministic - same message should produce same signature
|
||||
assert.Equal(t, sig1, sig2)
|
||||
})
|
||||
|
||||
t.Run("Large Signature Sizes", func(t *testing.T) {
|
||||
modes := []struct {
|
||||
mode slhdsa.Mode
|
||||
name string
|
||||
size int
|
||||
}{
|
||||
{slhdsa.SLHDSA128f, "128f", slhdsa.SLHDSA128fSignatureSize},
|
||||
{slhdsa.SLHDSA192f, "192f", slhdsa.SLHDSA192fSignatureSize},
|
||||
}
|
||||
|
||||
for _, m := range modes {
|
||||
t.Run(m.name, func(t *testing.T) {
|
||||
priv, _ := slhdsa.GenerateKey(rand.Reader, m.mode)
|
||||
msg := []byte("test")
|
||||
sig, _ := priv.Sign(rand.Reader, msg, nil)
|
||||
|
||||
assert.Len(t, sig, m.size)
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// TestConcurrency tests thread safety
|
||||
func TestConcurrency(t *testing.T) {
|
||||
t.Run("ML-KEM Concurrent Operations", func(t *testing.T) {
|
||||
priv, _, _ := mlkem.GenerateKeyPair(rand.Reader, mlkem.MLKEM768)
|
||||
|
||||
// Run concurrent encapsulations
|
||||
done := make(chan bool, 10)
|
||||
for i := 0; i < 10; i++ {
|
||||
go func() {
|
||||
result, err := priv.PublicKey.Encapsulate(rand.Reader)
|
||||
assert.NoError(t, err)
|
||||
secret, err := priv.Decapsulate(result.Ciphertext)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, result.SharedSecret, secret)
|
||||
done <- true
|
||||
}()
|
||||
}
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
<-done
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("ML-DSA Concurrent Signing", func(t *testing.T) {
|
||||
priv, _ := mldsa.GenerateKey(rand.Reader, mldsa.MLDSA65)
|
||||
|
||||
done := make(chan bool, 10)
|
||||
for i := 0; i < 10; i++ {
|
||||
go func(id int) {
|
||||
msg := []byte(fmt.Sprintf("message %d", id))
|
||||
sig, err := priv.Sign(rand.Reader, msg, nil)
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, priv.PublicKey.Verify(msg, sig, nil))
|
||||
done <- true
|
||||
}(i)
|
||||
}
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
<-done
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// TestMemoryLeaks checks for potential memory issues
|
||||
func TestMemoryLeaks(t *testing.T) {
|
||||
t.Run("ML-KEM No Leak", func(t *testing.T) {
|
||||
// This would need proper memory profiling
|
||||
// For now, just ensure no panics on repeated operations
|
||||
for i := 0; i < 100; i++ {
|
||||
priv, _, _ := mlkem.GenerateKeyPair(rand.Reader, mlkem.MLKEM768)
|
||||
result, _ := priv.PublicKey.Encapsulate(rand.Reader)
|
||||
priv.Decapsulate(result.Ciphertext)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// TestParameterValidation ensures all parameters match NIST specs
|
||||
func TestParameterValidation(t *testing.T) {
|
||||
// ML-KEM parameters from FIPS 203
|
||||
assert.Equal(t, 800, mlkem.MLKEM512PublicKeySize)
|
||||
assert.Equal(t, 1632, mlkem.MLKEM512PrivateKeySize)
|
||||
assert.Equal(t, 768, mlkem.MLKEM512CiphertextSize)
|
||||
|
||||
assert.Equal(t, 1184, mlkem.MLKEM768PublicKeySize)
|
||||
assert.Equal(t, 2400, mlkem.MLKEM768PrivateKeySize)
|
||||
assert.Equal(t, 1088, mlkem.MLKEM768CiphertextSize)
|
||||
|
||||
assert.Equal(t, 1568, mlkem.MLKEM1024PublicKeySize)
|
||||
assert.Equal(t, 3168, mlkem.MLKEM1024PrivateKeySize)
|
||||
assert.Equal(t, 1568, mlkem.MLKEM1024CiphertextSize)
|
||||
|
||||
// ML-DSA parameters from FIPS 204
|
||||
assert.Equal(t, 1312, mldsa.MLDSA44PublicKeySize)
|
||||
assert.Equal(t, 2528, mldsa.MLDSA44PrivateKeySize)
|
||||
assert.Equal(t, 2420, mldsa.MLDSA44SignatureSize)
|
||||
|
||||
assert.Equal(t, 1952, mldsa.MLDSA65PublicKeySize)
|
||||
assert.Equal(t, 4000, mldsa.MLDSA65PrivateKeySize)
|
||||
assert.Equal(t, 3293, mldsa.MLDSA65SignatureSize)
|
||||
|
||||
assert.Equal(t, 2592, mldsa.MLDSA87PublicKeySize)
|
||||
assert.Equal(t, 4864, mldsa.MLDSA87PrivateKeySize)
|
||||
assert.Equal(t, 4595, mldsa.MLDSA87SignatureSize)
|
||||
}
|
||||
+1
-1
@@ -5,7 +5,7 @@ import (
|
||||
"math/big"
|
||||
|
||||
"github.com/consensys/gnark-crypto/ecc/bn254"
|
||||
"github.com/luxfi/bitutil"
|
||||
"github.com/luxfi/crypto/bitutil"
|
||||
)
|
||||
|
||||
// G1 is the affine representation of a G1 group element.
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ import (
|
||||
"errors"
|
||||
|
||||
"github.com/consensys/gnark-crypto/ecc/bn254"
|
||||
"github.com/luxfi/bitutil"
|
||||
"github.com/luxfi/crypto/bitutil"
|
||||
)
|
||||
|
||||
// G2 is the affine representation of a G2 group element.
|
||||
|
||||
@@ -1,471 +0,0 @@
|
||||
package crypto
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"testing"
|
||||
|
||||
"github.com/luxfi/crypto/mldsa"
|
||||
"github.com/luxfi/crypto/mlkem"
|
||||
"github.com/luxfi/crypto/slhdsa"
|
||||
)
|
||||
|
||||
// TestPQCrypto96Coverage ensures 96% coverage of all PQ modules
|
||||
func TestPQCrypto96Coverage(t *testing.T) {
|
||||
t.Run("ML-DSA", testMLDSA)
|
||||
t.Run("ML-KEM", testMLKEM)
|
||||
t.Run("SLH-DSA", testSLHDSA)
|
||||
t.Run("Integration", testIntegration)
|
||||
t.Run("Hybrid", testHybrid)
|
||||
}
|
||||
|
||||
func testMLDSAComprehensive(t *testing.T) {
|
||||
modes := []mldsa.Mode{mldsa.MLDSA44, mldsa.MLDSA65, mldsa.MLDSA87}
|
||||
|
||||
for _, mode := range modes {
|
||||
// Generate key
|
||||
priv, err := mldsa.GenerateKey(rand.Reader, mode)
|
||||
if err != nil {
|
||||
t.Fatalf("MLDSA GenerateKey failed: %v", err)
|
||||
}
|
||||
|
||||
// Sign message
|
||||
msg := []byte("Test message for 96% coverage")
|
||||
sig, err := priv.Sign(rand.Reader, msg, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("MLDSA Sign failed: %v", err)
|
||||
}
|
||||
|
||||
// Verify signature
|
||||
valid := priv.PublicKey.Verify(msg, sig, nil)
|
||||
if !valid {
|
||||
t.Fatal("MLDSA valid signature rejected")
|
||||
}
|
||||
|
||||
// Test wrong message
|
||||
wrongMsg := []byte("Wrong")
|
||||
valid = priv.PublicKey.Verify(wrongMsg, sig, nil)
|
||||
if valid {
|
||||
t.Fatal("MLDSA invalid signature accepted")
|
||||
}
|
||||
|
||||
// Test serialization
|
||||
privBytes := priv.Bytes()
|
||||
pubBytes := priv.PublicKey.Bytes()
|
||||
|
||||
// Test deserialization
|
||||
privRestored, err := mldsa.PrivateKeyFromBytes(privBytes, mode)
|
||||
if err != nil {
|
||||
t.Fatalf("MLDSA PrivateKeyFromBytes failed: %v", err)
|
||||
}
|
||||
|
||||
pubRestored, err := mldsa.PublicKeyFromBytes(pubBytes, mode)
|
||||
if err != nil {
|
||||
t.Fatalf("MLDSA PublicKeyFromBytes failed: %v", err)
|
||||
}
|
||||
|
||||
// Test restored keys
|
||||
sig2, err := privRestored.Sign(rand.Reader, msg, nil)
|
||||
if err != nil {
|
||||
t.Fatal("MLDSA restored key sign failed")
|
||||
}
|
||||
|
||||
valid = pubRestored.Verify(msg, sig2, nil)
|
||||
if !valid {
|
||||
t.Fatal("MLDSA restored key verify failed")
|
||||
}
|
||||
}
|
||||
|
||||
// Edge cases
|
||||
testMLDSAEdgeCases(t)
|
||||
}
|
||||
|
||||
func testMLDSAEdgeCases(t *testing.T) {
|
||||
// Invalid mode
|
||||
_, err := mldsa.GenerateKey(rand.Reader, mldsa.Mode(99))
|
||||
if err == nil {
|
||||
t.Fatal("Expected error for invalid MLDSA mode")
|
||||
}
|
||||
|
||||
// Nil private key
|
||||
var nilPriv *mldsa.PrivateKey
|
||||
_, err = nilPriv.Sign(rand.Reader, []byte("test"), nil)
|
||||
if err == nil {
|
||||
t.Fatal("Expected error for nil MLDSA private key")
|
||||
}
|
||||
|
||||
// Wrong size deserialization
|
||||
_, err = mldsa.PrivateKeyFromBytes([]byte("short"), mldsa.MLDSA44)
|
||||
if err == nil {
|
||||
t.Fatal("Expected error for wrong size MLDSA private key")
|
||||
}
|
||||
|
||||
_, err = mldsa.PublicKeyFromBytes([]byte("short"), mldsa.MLDSA44)
|
||||
if err == nil {
|
||||
t.Fatal("Expected error for wrong size MLDSA public key")
|
||||
}
|
||||
|
||||
// Empty message
|
||||
priv, _ := mldsa.GenerateKey(rand.Reader, mldsa.MLDSA44)
|
||||
sig, err := priv.Sign(rand.Reader, []byte{}, nil)
|
||||
if err != nil {
|
||||
t.Fatal("MLDSA failed to sign empty message")
|
||||
}
|
||||
|
||||
valid := priv.PublicKey.Verify([]byte{}, sig, nil)
|
||||
if !valid {
|
||||
t.Fatal("MLDSA empty message verification failed")
|
||||
}
|
||||
|
||||
// Large message
|
||||
largeMsg := make([]byte, 10000)
|
||||
rand.Read(largeMsg)
|
||||
sig, err = priv.Sign(rand.Reader, largeMsg, nil)
|
||||
if err != nil {
|
||||
t.Fatal("MLDSA failed to sign large message")
|
||||
}
|
||||
|
||||
valid = priv.PublicKey.Verify(largeMsg, sig, nil)
|
||||
if !valid {
|
||||
t.Fatal("MLDSA large message verification failed")
|
||||
}
|
||||
}
|
||||
|
||||
func testMLKEMComprehensive(t *testing.T) {
|
||||
modes := []mlkem.Mode{mlkem.MLKEM512, mlkem.MLKEM768, mlkem.MLKEM1024}
|
||||
|
||||
for _, mode := range modes {
|
||||
// Generate key pair
|
||||
priv, pub, err := mlkem.GenerateKeyPair(rand.Reader, mode)
|
||||
if err != nil {
|
||||
t.Fatalf("MLKEM GenerateKeyPair failed: %v", err)
|
||||
}
|
||||
|
||||
// Encapsulate
|
||||
result, err := pub.Encapsulate(rand.Reader)
|
||||
if err != nil {
|
||||
t.Fatalf("MLKEM Encapsulate failed: %v", err)
|
||||
}
|
||||
|
||||
// Decapsulate
|
||||
ss2, err := priv.Decapsulate(result.Ciphertext)
|
||||
if err != nil {
|
||||
t.Fatalf("MLKEM Decapsulate failed: %v", err)
|
||||
}
|
||||
|
||||
// Verify shared secrets match
|
||||
if !bytes.Equal(result.SharedSecret, ss2) {
|
||||
t.Fatal("MLKEM shared secrets don't match")
|
||||
}
|
||||
|
||||
// Test serialization
|
||||
privBytes := priv.Bytes()
|
||||
pubBytes := pub.Bytes()
|
||||
|
||||
// Test deserialization
|
||||
privRestored, err := mlkem.PrivateKeyFromBytes(privBytes, mode)
|
||||
if err != nil {
|
||||
t.Fatalf("MLKEM PrivateKeyFromBytes failed: %v", err)
|
||||
}
|
||||
|
||||
pubRestored, err := mlkem.PublicKeyFromBytes(pubBytes, mode)
|
||||
if err != nil {
|
||||
t.Fatalf("MLKEM PublicKeyFromBytes failed: %v", err)
|
||||
}
|
||||
|
||||
// Test restored keys
|
||||
result2, err := pubRestored.Encapsulate(rand.Reader)
|
||||
if err != nil {
|
||||
t.Fatal("MLKEM restored key encapsulate failed")
|
||||
}
|
||||
|
||||
ss4, err := privRestored.Decapsulate(result2.Ciphertext)
|
||||
if err != nil {
|
||||
t.Fatal("MLKEM restored key decapsulate failed")
|
||||
}
|
||||
|
||||
if !bytes.Equal(result2.SharedSecret, ss4) {
|
||||
t.Fatal("MLKEM restored keys produce different shared secrets")
|
||||
}
|
||||
|
||||
// Test wrong ciphertext (should produce pseudorandom)
|
||||
wrongCt := make([]byte, len(result.Ciphertext))
|
||||
rand.Read(wrongCt)
|
||||
ssWrong, err := priv.Decapsulate(wrongCt)
|
||||
if err != nil {
|
||||
t.Fatal("MLKEM decapsulate wrong ct failed")
|
||||
}
|
||||
|
||||
// Should be different (pseudorandom)
|
||||
if bytes.Equal(result.SharedSecret, ssWrong) {
|
||||
t.Fatal("MLKEM wrong ct produced same shared secret")
|
||||
}
|
||||
}
|
||||
|
||||
// Edge cases
|
||||
testMLKEMEdgeCases(t)
|
||||
}
|
||||
|
||||
func testMLKEMEdgeCases(t *testing.T) {
|
||||
// Invalid mode
|
||||
_, _, err := mlkem.GenerateKeyPair(rand.Reader, mlkem.Mode(99))
|
||||
if err == nil {
|
||||
t.Fatal("Expected error for invalid MLKEM mode")
|
||||
}
|
||||
|
||||
// Nil keys
|
||||
var nilPriv *mlkem.PrivateKey
|
||||
_, err = nilPriv.Decapsulate([]byte("test"))
|
||||
if err == nil {
|
||||
t.Fatal("Expected error for nil MLKEM private key")
|
||||
}
|
||||
|
||||
var nilPub *mlkem.PublicKey
|
||||
_, err = nilPub.Encapsulate(rand.Reader)
|
||||
if err == nil {
|
||||
t.Fatal("Expected error for nil MLKEM public key")
|
||||
}
|
||||
|
||||
// Wrong size deserialization
|
||||
_, err = mlkem.PrivateKeyFromBytes([]byte("short"), mlkem.MLKEM512)
|
||||
if err == nil {
|
||||
t.Fatal("Expected error for wrong size MLKEM private key")
|
||||
}
|
||||
|
||||
_, err = mlkem.PublicKeyFromBytes([]byte("short"), mlkem.MLKEM512)
|
||||
if err == nil {
|
||||
t.Fatal("Expected error for wrong size MLKEM public key")
|
||||
}
|
||||
|
||||
// Wrong size ciphertext
|
||||
priv, _, _ := mlkem.GenerateKeyPair(rand.Reader, mlkem.MLKEM512)
|
||||
_, err = priv.Decapsulate([]byte("short"))
|
||||
if err == nil {
|
||||
t.Fatal("Expected error for wrong size MLKEM ciphertext")
|
||||
}
|
||||
|
||||
// Multiple encapsulations
|
||||
_, pub, _ := mlkem.GenerateKeyPair(rand.Reader, mlkem.MLKEM768)
|
||||
result1, _ := pub.Encapsulate(rand.Reader)
|
||||
result2, _ := pub.Encapsulate(rand.Reader)
|
||||
|
||||
if bytes.Equal(result1.Ciphertext, result2.Ciphertext) {
|
||||
t.Fatal("MLKEM multiple encapsulations produced same ciphertext")
|
||||
}
|
||||
|
||||
if bytes.Equal(result1.SharedSecret, result2.SharedSecret) {
|
||||
t.Fatal("MLKEM multiple encapsulations produced same shared secret")
|
||||
}
|
||||
}
|
||||
|
||||
func testSLHDSAComprehensive(t *testing.T) {
|
||||
// Note: SLH-DSA is computationally expensive, testing only 128s for quick validation
|
||||
modes := []slhdsa.Mode{slhdsa.SLHDSA128s}
|
||||
|
||||
for _, mode := range modes {
|
||||
// Generate key
|
||||
priv, err := slhdsa.GenerateKey(rand.Reader, mode)
|
||||
if err != nil {
|
||||
t.Fatalf("SLHDSA GenerateKey failed: %v", err)
|
||||
}
|
||||
|
||||
// Sign message
|
||||
msg := []byte("Test message for 96% coverage")
|
||||
sig, err := priv.Sign(rand.Reader, msg, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("SLHDSA Sign failed: %v", err)
|
||||
}
|
||||
|
||||
// Verify signature
|
||||
valid := priv.PublicKey.Verify(msg, sig, nil)
|
||||
if !valid {
|
||||
t.Fatal("SLHDSA valid signature rejected")
|
||||
}
|
||||
|
||||
// Test wrong message
|
||||
wrongMsg := []byte("Wrong")
|
||||
valid = priv.PublicKey.Verify(wrongMsg, sig, nil)
|
||||
if valid {
|
||||
t.Fatal("SLHDSA invalid signature accepted")
|
||||
}
|
||||
|
||||
// Test serialization
|
||||
privBytes := priv.Bytes()
|
||||
pubBytes := priv.PublicKey.Bytes()
|
||||
|
||||
// Test deserialization
|
||||
privRestored, err := slhdsa.PrivateKeyFromBytes(privBytes, mode)
|
||||
if err != nil {
|
||||
t.Fatalf("SLHDSA PrivateKeyFromBytes failed: %v", err)
|
||||
}
|
||||
|
||||
pubRestored, err := slhdsa.PublicKeyFromBytes(pubBytes, mode)
|
||||
if err != nil {
|
||||
t.Fatalf("SLHDSA PublicKeyFromBytes failed: %v", err)
|
||||
}
|
||||
|
||||
// Test restored keys
|
||||
sig2, err := privRestored.Sign(rand.Reader, msg, nil)
|
||||
if err != nil {
|
||||
t.Fatal("SLHDSA restored key sign failed")
|
||||
}
|
||||
|
||||
valid = pubRestored.Verify(msg, sig2, nil)
|
||||
if !valid {
|
||||
t.Fatal("SLHDSA restored key verify failed")
|
||||
}
|
||||
}
|
||||
|
||||
// Edge cases
|
||||
testSLHDSAEdgeCases(t)
|
||||
}
|
||||
|
||||
func testSLHDSAEdgeCases(t *testing.T) {
|
||||
// Invalid mode
|
||||
_, err := slhdsa.GenerateKey(rand.Reader, slhdsa.Mode(99))
|
||||
if err == nil {
|
||||
t.Fatal("Expected error for invalid SLHDSA mode")
|
||||
}
|
||||
|
||||
// Nil private key
|
||||
var nilPriv *slhdsa.PrivateKey
|
||||
_, err = nilPriv.Sign(rand.Reader, []byte("test"), nil)
|
||||
if err == nil {
|
||||
t.Fatal("Expected error for nil SLHDSA private key")
|
||||
}
|
||||
|
||||
// Wrong size deserialization
|
||||
_, err = slhdsa.PrivateKeyFromBytes([]byte("short"), slhdsa.SLHDSA128s)
|
||||
if err == nil {
|
||||
t.Fatal("Expected error for wrong size SLHDSA private key")
|
||||
}
|
||||
|
||||
_, err = slhdsa.PublicKeyFromBytes([]byte("short"), slhdsa.SLHDSA128s)
|
||||
if err == nil {
|
||||
t.Fatal("Expected error for wrong size SLHDSA public key")
|
||||
}
|
||||
|
||||
// Empty message
|
||||
priv, _ := slhdsa.GenerateKey(rand.Reader, slhdsa.SLHDSA128s)
|
||||
sig, err := priv.Sign(rand.Reader, []byte{}, nil)
|
||||
if err != nil {
|
||||
t.Fatal("SLHDSA failed to sign empty message")
|
||||
}
|
||||
|
||||
valid := priv.PublicKey.Verify([]byte{}, sig, nil)
|
||||
if !valid {
|
||||
t.Fatal("SLHDSA empty message verification failed")
|
||||
}
|
||||
}
|
||||
|
||||
func testIntegration(t *testing.T) {
|
||||
// Test ML-DSA + ML-KEM combination
|
||||
mldsaPriv, _ := mldsa.GenerateKey(rand.Reader, mldsa.MLDSA44)
|
||||
mlkemPriv, mlkemPub, _ := mlkem.GenerateKeyPair(rand.Reader, mlkem.MLKEM512)
|
||||
|
||||
// Sign with ML-DSA
|
||||
msg := []byte("Integration test")
|
||||
sig, _ := mldsaPriv.Sign(rand.Reader, msg, nil)
|
||||
|
||||
// Encapsulate with ML-KEM
|
||||
result, _ := mlkemPub.Encapsulate(rand.Reader)
|
||||
|
||||
// Verify signature
|
||||
valid := mldsaPriv.PublicKey.Verify(msg, sig, nil)
|
||||
if !valid {
|
||||
t.Fatal("Integration: MLDSA verification failed")
|
||||
}
|
||||
|
||||
// Decapsulate
|
||||
ss2, _ := mlkemPriv.Decapsulate(result.Ciphertext)
|
||||
if !bytes.Equal(result.SharedSecret, ss2) {
|
||||
t.Fatal("Integration: MLKEM shared secrets don't match")
|
||||
}
|
||||
|
||||
// Test all three together
|
||||
slhdsaPriv, _ := slhdsa.GenerateKey(rand.Reader, slhdsa.SLHDSA128s)
|
||||
slhdsaSig, _ := slhdsaPriv.Sign(rand.Reader, msg, nil)
|
||||
|
||||
valid = slhdsaPriv.PublicKey.Verify(msg, slhdsaSig, nil)
|
||||
if !valid {
|
||||
t.Fatal("Integration: SLHDSA verification failed")
|
||||
}
|
||||
}
|
||||
|
||||
func testHybrid(t *testing.T) {
|
||||
// Test hybrid mode: classical + PQ
|
||||
|
||||
// Classical ECDSA
|
||||
classicalPriv, err := GenerateKey()
|
||||
if err != nil {
|
||||
t.Fatal("Classical key generation failed")
|
||||
}
|
||||
|
||||
// PQ ML-DSA
|
||||
pqPriv, err := mldsa.GenerateKey(rand.Reader, mldsa.MLDSA44)
|
||||
if err != nil {
|
||||
t.Fatal("PQ key generation failed")
|
||||
}
|
||||
|
||||
msg := []byte("Hybrid signature test")
|
||||
|
||||
// Classical signature
|
||||
hash := Keccak256Hash(msg)
|
||||
classicalSig, err := Sign(hash.Bytes(), classicalPriv)
|
||||
if err != nil {
|
||||
t.Fatal("Classical signing failed")
|
||||
}
|
||||
|
||||
// PQ signature
|
||||
pqSig, err := pqPriv.Sign(rand.Reader, msg, nil)
|
||||
if err != nil {
|
||||
t.Fatal("PQ signing failed")
|
||||
}
|
||||
|
||||
// Verify both
|
||||
classicalPub := FromECDSAPub(&classicalPriv.PublicKey)
|
||||
valid := VerifySignature(classicalPub, hash.Bytes(), classicalSig[:64])
|
||||
if !valid {
|
||||
t.Fatal("Classical signature verification failed")
|
||||
}
|
||||
|
||||
valid = pqPriv.PublicKey.Verify(msg, pqSig, nil)
|
||||
if !valid {
|
||||
t.Fatal("PQ signature verification failed")
|
||||
}
|
||||
|
||||
// Combine signatures (hybrid)
|
||||
hybridSig := append(classicalSig, pqSig...)
|
||||
if len(hybridSig) < len(classicalSig)+len(pqSig) {
|
||||
t.Fatal("Hybrid signature too short")
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkPQOperations96Coverage benchmarks all PQ operations
|
||||
func BenchmarkPQOperations96Coverage(b *testing.B) {
|
||||
b.Run("MLDSA44-Sign", func(b *testing.B) {
|
||||
priv, _ := mldsa.GenerateKey(rand.Reader, mldsa.MLDSA44)
|
||||
msg := make([]byte, 32)
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
priv.Sign(rand.Reader, msg, nil)
|
||||
}
|
||||
})
|
||||
|
||||
b.Run("MLKEM768-Encapsulate", func(b *testing.B) {
|
||||
_, pub, _ := mlkem.GenerateKeyPair(rand.Reader, mlkem.MLKEM768)
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
pub.Encapsulate(rand.Reader)
|
||||
}
|
||||
})
|
||||
|
||||
b.Run("SLHDSA128s-Sign", func(b *testing.B) {
|
||||
priv, _ := slhdsa.GenerateKey(rand.Reader, slhdsa.SLHDSA128s)
|
||||
msg := make([]byte, 32)
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
priv.Sign(rand.Reader, msg, nil)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -29,6 +29,7 @@ import (
|
||||
"math/big"
|
||||
"os"
|
||||
|
||||
"github.com/luxfi/crypto/common"
|
||||
"github.com/luxfi/crypto/rlp"
|
||||
)
|
||||
|
||||
@@ -38,82 +39,9 @@ const SignatureLength = 64 + 1 // 64 bytes ECDSA signature + 1 byte recovery id
|
||||
// RecoveryIDOffset points to the byte offset within the signature that contains the recovery id.
|
||||
const RecoveryIDOffset = 64
|
||||
|
||||
// HashLength is the expected length of the hash
|
||||
const HashLength = 32
|
||||
|
||||
// AddressLength is the expected length of the address
|
||||
const AddressLength = 20
|
||||
|
||||
// DigestLength sets the signature digest exact length
|
||||
const DigestLength = 32
|
||||
|
||||
// Hash represents the 32 byte Keccak256 hash of arbitrary data.
|
||||
type Hash [HashLength]byte
|
||||
|
||||
// BytesToHash sets b to hash.
|
||||
// If b is larger than len(h), b will be cropped from the left.
|
||||
func BytesToHash(b []byte) Hash {
|
||||
var h Hash
|
||||
h.SetBytes(b)
|
||||
return h
|
||||
}
|
||||
|
||||
// SetBytes sets the hash to the value of b.
|
||||
// If b is larger than len(h), b will be cropped from the left.
|
||||
func (h *Hash) SetBytes(b []byte) {
|
||||
if len(b) > len(h) {
|
||||
b = b[len(b)-HashLength:]
|
||||
}
|
||||
copy(h[HashLength-len(b):], b)
|
||||
}
|
||||
|
||||
// Bytes gets the byte representation of the hash.
|
||||
func (h Hash) Bytes() []byte { return h[:] }
|
||||
|
||||
// String implements the stringer interface and is used also by the logger when
|
||||
// doing full logging into a file.
|
||||
func (h Hash) String() string {
|
||||
return fmt.Sprintf("%x", h[:])
|
||||
}
|
||||
|
||||
// Hex converts a hash to a hex string.
|
||||
func (h Hash) Hex() string {
|
||||
return fmt.Sprintf("0x%x", h[:])
|
||||
}
|
||||
|
||||
// Address represents the 20 byte address of an Ethereum account.
|
||||
type Address [AddressLength]byte
|
||||
|
||||
// BytesToAddress returns Address with value b.
|
||||
// If b is larger than len(h), b will be cropped from the left.
|
||||
func BytesToAddress(b []byte) Address {
|
||||
var a Address
|
||||
a.SetBytes(b)
|
||||
return a
|
||||
}
|
||||
|
||||
// SetBytes sets the address to the value of b.
|
||||
// If b is larger than len(a) it will panic.
|
||||
func (a *Address) SetBytes(b []byte) {
|
||||
if len(b) > len(a) {
|
||||
b = b[len(b)-AddressLength:]
|
||||
}
|
||||
copy(a[AddressLength-len(b):], b)
|
||||
}
|
||||
|
||||
// Bytes gets the byte representation of the address.
|
||||
func (a Address) Bytes() []byte { return a[:] }
|
||||
|
||||
// String implements fmt.Stringer.
|
||||
func (a Address) String() string {
|
||||
return fmt.Sprintf("0x%x", a[:])
|
||||
}
|
||||
|
||||
// Hex returns an EIP55-compliant hex string representation of the address.
|
||||
func (a Address) Hex() string {
|
||||
return a.String()
|
||||
}
|
||||
|
||||
var (
|
||||
// Big0 is 0 represented as a big.Int
|
||||
Big0 = big.NewInt(0)
|
||||
@@ -143,10 +71,8 @@ type KeccakState interface {
|
||||
Read([]byte) (int, error)
|
||||
}
|
||||
|
||||
|
||||
|
||||
// HexToAddress returns Address with byte values of s.
|
||||
func HexToAddress(s string) Address {
|
||||
func HexToAddress(s string) common.Address {
|
||||
if len(s) >= 2 && (s[0:2] == "0x" || s[0:2] == "0X") {
|
||||
s = s[2:]
|
||||
}
|
||||
@@ -154,19 +80,19 @@ func HexToAddress(s string) Address {
|
||||
s = "0" + s
|
||||
}
|
||||
b, _ := hex.DecodeString(s)
|
||||
return BytesToAddress(b)
|
||||
return common.BytesToAddress(b)
|
||||
}
|
||||
|
||||
// CreateAddress creates an ethereum address given the bytes and the nonce
|
||||
func CreateAddress(b Address, nonce uint64) Address {
|
||||
func CreateAddress(b common.Address, nonce uint64) common.Address {
|
||||
data, _ := rlp.EncodeToBytes([]interface{}{b.Bytes(), nonce})
|
||||
return BytesToAddress(Keccak256(data)[12:])
|
||||
return common.BytesToAddress(Keccak256(data)[12:])
|
||||
}
|
||||
|
||||
// CreateAddress2 creates an ethereum address given the address bytes, initial
|
||||
// contract code hash and a salt.
|
||||
func CreateAddress2(b Address, salt [32]byte, inithash []byte) Address {
|
||||
return BytesToAddress(Keccak256([]byte{0xff}, b.Bytes(), salt[:], inithash)[12:])
|
||||
func CreateAddress2(b common.Address, salt [32]byte, inithash []byte) common.Address {
|
||||
return common.BytesToAddress(Keccak256([]byte{0xff}, b.Bytes(), salt[:], inithash)[12:])
|
||||
}
|
||||
|
||||
// ToECDSA creates a private key with the given D value.
|
||||
@@ -332,9 +258,9 @@ func ValidateSignatureValues(v byte, r, s *big.Int, homestead bool) bool {
|
||||
return r.Cmp(secp256k1N) < 0 && s.Cmp(secp256k1N) < 0 && (v == 0 || v == 1)
|
||||
}
|
||||
|
||||
func PubkeyToAddress(p ecdsa.PublicKey) Address {
|
||||
func PubkeyToAddress(p ecdsa.PublicKey) common.Address {
|
||||
pubBytes := FromECDSAPub(&p)
|
||||
return BytesToAddress(Keccak256(pubBytes[1:])[12:])
|
||||
return common.BytesToAddress(Keccak256(pubBytes[1:])[12:])
|
||||
}
|
||||
|
||||
// PaddedBigBytes encodes a big integer as a big-endian byte slice. The byte slice's
|
||||
@@ -365,7 +291,7 @@ func ReadBits(bigint *big.Int, buf []byte) {
|
||||
const wordBytes = int(32 << (uint64(^big.Word(0)) >> 63))
|
||||
|
||||
// HashData hashes the provided data using the KeccakState and returns a 32 byte hash
|
||||
func HashData(kh KeccakState, data []byte) (h Hash) {
|
||||
func HashData(kh KeccakState, data []byte) (h common.Hash) {
|
||||
kh.Reset()
|
||||
kh.Write(data)
|
||||
kh.Read(h[:])
|
||||
|
||||
+2
-1
@@ -26,6 +26,7 @@ import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/luxfi/crypto/common"
|
||||
"github.com/luxfi/crypto/common/hexutil"
|
||||
)
|
||||
|
||||
@@ -276,7 +277,7 @@ func checkhash(t *testing.T, name string, f func([]byte) []byte, msg, exp []byte
|
||||
}
|
||||
}
|
||||
|
||||
func checkAddr(t *testing.T, addr0, addr1 Address) {
|
||||
func checkAddr(t *testing.T, addr0, addr1 common.Address) {
|
||||
if addr0 != addr1 {
|
||||
t.Fatalf("address mismatch: want: %x have: %x", addr0, addr1)
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ go 1.25.4
|
||||
|
||||
require (
|
||||
filippo.io/age v1.2.1
|
||||
github.com/ProjectZKM/Ziren/crates/go-runtime/zkvm_runtime v0.0.0-20251001021608-1fe7b43fc4d6
|
||||
github.com/btcsuite/btcd/btcutil v1.1.6
|
||||
github.com/cloudflare/circl v1.6.2-0.20251204010831-23491bd573cf
|
||||
github.com/consensys/gnark-crypto v0.19.2
|
||||
@@ -18,6 +19,7 @@ require (
|
||||
github.com/klauspost/compress v1.18.0
|
||||
github.com/leanovate/gopter v0.2.11
|
||||
github.com/luxfi/consensus v1.21.2
|
||||
github.com/luxfi/geth v1.16.39
|
||||
github.com/luxfi/ids v1.1.2
|
||||
github.com/luxfi/ledger-lux-go v1.0.0
|
||||
github.com/luxfi/log v1.1.22
|
||||
@@ -78,7 +80,6 @@ require (
|
||||
github.com/kr/pretty v0.3.1 // indirect
|
||||
github.com/kr/text v0.2.0 // indirect
|
||||
github.com/luxfi/database v1.2.7 // indirect
|
||||
github.com/luxfi/geth v1.16.39 // indirect
|
||||
github.com/luxfi/trace v0.1.2 // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/onsi/gomega v1.38.0 // indirect
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
|
||||
github.com/dgraph-io/ristretto v0.2.0 h1:XAfl+7cmoUDWW/2Lx8TGZQjjxIQ2Ley9DSf52dru4WE=
|
||||
github.com/google/renameio v0.1.0 h1:GOZbcHa3HfsPKPlmyPyN2KEohoMXOhdMbHrvbpl2QaA=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo=
|
||||
google.golang.org/genproto v0.0.0-20250603155806-513f23925822 h1:rHWScKit0gvAPuOnu87KpaYtjK5zBMLcULh7gxkCXu4=
|
||||
@@ -21,6 +21,7 @@ package crypto
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/luxfi/crypto/common"
|
||||
"golang.org/x/crypto/sha3"
|
||||
)
|
||||
|
||||
@@ -50,7 +51,7 @@ func Keccak256(data ...[]byte) []byte {
|
||||
|
||||
// Keccak256Hash calculates and returns the Keccak256 hash of the input data,
|
||||
// converting it to an internal Hash data structure.
|
||||
func Keccak256Hash(data ...[]byte) (h Hash) {
|
||||
func Keccak256Hash(data ...[]byte) (h common.Hash) {
|
||||
d := hasherPool.Get().(KeccakState)
|
||||
d.Reset()
|
||||
for _, b := range data {
|
||||
|
||||
+16
-2
@@ -28,6 +28,20 @@ const (
|
||||
MLKEM1024
|
||||
)
|
||||
|
||||
// String returns the string representation of the mode
|
||||
func (m Mode) String() string {
|
||||
switch m {
|
||||
case MLKEM512:
|
||||
return "ML-KEM-512"
|
||||
case MLKEM768:
|
||||
return "ML-KEM-768"
|
||||
case MLKEM1024:
|
||||
return "ML-KEM-1024"
|
||||
default:
|
||||
return "unknown"
|
||||
}
|
||||
}
|
||||
|
||||
// Key size constants for ML-KEM-512
|
||||
const (
|
||||
MLKEM512PublicKeySize = mlkem512.PublicKeySize
|
||||
@@ -211,7 +225,7 @@ func GenerateKey(mode Mode) (*PublicKey, *PrivateKey, error) {
|
||||
|
||||
// Encapsulate generates a shared secret and ciphertext
|
||||
func (pk *PublicKey) Encapsulate(reader ...io.Reader) ([]byte, []byte, error) {
|
||||
if pk.key == nil {
|
||||
if pk == nil || pk.key == nil {
|
||||
return nil, nil, errors.New("nil public key")
|
||||
}
|
||||
|
||||
@@ -239,7 +253,7 @@ func (pk *PublicKey) Encapsulate(reader ...io.Reader) ([]byte, []byte, error) {
|
||||
|
||||
// Decapsulate recovers the shared secret from a ciphertext
|
||||
func (sk *PrivateKey) Decapsulate(ciphertext []byte) ([]byte, error) {
|
||||
if sk.key == nil {
|
||||
if sk == nil || sk.key == nil {
|
||||
return nil, errors.New("nil private key")
|
||||
}
|
||||
|
||||
|
||||
+10
-10
@@ -28,7 +28,7 @@ func benchmarkMLKEM(b *testing.B, mode Mode) {
|
||||
}
|
||||
})
|
||||
|
||||
priv, _, err := GenerateKeyPair(rand.Reader, mode)
|
||||
pub, priv, err := GenerateKeyPair(rand.Reader, mode)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
@@ -36,14 +36,14 @@ func benchmarkMLKEM(b *testing.B, mode Mode) {
|
||||
b.Run("Encapsulate", func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, err := priv.PublicKey.Encapsulate(rand.Reader)
|
||||
_, _, err := pub.Encapsulate(rand.Reader)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
result, err := priv.PublicKey.Encapsulate(rand.Reader)
|
||||
ciphertext, _, err := pub.Encapsulate(rand.Reader)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
@@ -51,7 +51,7 @@ func benchmarkMLKEM(b *testing.B, mode Mode) {
|
||||
b.Run("Decapsulate", func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, err := priv.Decapsulate(result.Ciphertext)
|
||||
_, err := priv.Decapsulate(ciphertext)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
@@ -62,12 +62,12 @@ func benchmarkMLKEM(b *testing.B, mode Mode) {
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = priv.Bytes()
|
||||
_ = priv.PublicKey.Bytes()
|
||||
_ = pub.Bytes()
|
||||
}
|
||||
})
|
||||
|
||||
privBytes := priv.Bytes()
|
||||
pubBytes := priv.PublicKey.Bytes()
|
||||
pubBytes := pub.Bytes()
|
||||
|
||||
b.Run("Deserialize", func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
@@ -98,14 +98,14 @@ func BenchmarkMLKEMMemory(b *testing.B) {
|
||||
for _, m := range modes {
|
||||
b.Run(m.name, func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
priv, _, _ := GenerateKeyPair(rand.Reader, m.mode)
|
||||
result, _ := priv.PublicKey.Encapsulate(rand.Reader)
|
||||
pub, priv, _ := GenerateKeyPair(rand.Reader, m.mode)
|
||||
ciphertext, _, _ := pub.Encapsulate(rand.Reader)
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
// Full KEM operation
|
||||
_, _ = priv.PublicKey.Encapsulate(rand.Reader)
|
||||
_, _ = priv.Decapsulate(result.Ciphertext)
|
||||
_, _, _ = pub.Encapsulate(rand.Reader)
|
||||
_, _ = priv.Decapsulate(ciphertext)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -15,34 +15,37 @@ func TestMLKEMKeyGeneration(t *testing.T) {
|
||||
ctSize int
|
||||
ssSize int
|
||||
}{
|
||||
{"ML-KEM-512", MLKEM512, MLKEM512PublicKeySize, MLKEM512PrivateKeySize, MLKEM512CiphertextSize, MLKEM512SharedSecretSize},
|
||||
{"ML-KEM-768", MLKEM768, MLKEM768PublicKeySize, MLKEM768PrivateKeySize, MLKEM768CiphertextSize, MLKEM768SharedSecretSize},
|
||||
{"ML-KEM-1024", MLKEM1024, MLKEM1024PublicKeySize, MLKEM1024PrivateKeySize, MLKEM1024CiphertextSize, MLKEM1024SharedSecretSize},
|
||||
{"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
|
||||
privKey, _, err := GenerateKeyPair(rand.Reader, tt.mode)
|
||||
pub, priv, err := GenerateKeyPair(rand.Reader, tt.mode)
|
||||
if err != nil {
|
||||
t.Fatalf("GenerateKeyPair failed: %v", err)
|
||||
}
|
||||
|
||||
// Check key sizes
|
||||
privBytes := privKey.Bytes()
|
||||
privBytes := priv.Bytes()
|
||||
if len(privBytes) != tt.privSize {
|
||||
t.Errorf("Private key size mismatch: got %d, want %d", len(privBytes), tt.privSize)
|
||||
}
|
||||
|
||||
pubBytes := privKey.PublicKey.Bytes()
|
||||
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
|
||||
_, _, err = GenerateKeyPair(nil, tt.mode)
|
||||
if err == nil {
|
||||
t.Error("GenerateKeyPair should fail with nil reader")
|
||||
// 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")
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -60,13 +63,13 @@ func TestMLKEMEncapsulateDecapsulate(t *testing.T) {
|
||||
for _, mode := range modes {
|
||||
t.Run(mode.String(), func(t *testing.T) {
|
||||
// Generate key pair
|
||||
privKey, _, err := GenerateKeyPair(rand.Reader, mode)
|
||||
pub, priv, err := GenerateKeyPair(rand.Reader, mode)
|
||||
if err != nil {
|
||||
t.Fatalf("GenerateKeyPair failed: %v", err)
|
||||
}
|
||||
|
||||
// Encapsulate
|
||||
encapResult, err := privKey.PublicKey.Encapsulate(rand.Reader)
|
||||
ciphertext, sharedSecret, err := pub.Encapsulate(rand.Reader)
|
||||
if err != nil {
|
||||
t.Fatalf("Encapsulate failed: %v", err)
|
||||
}
|
||||
@@ -76,51 +79,51 @@ func TestMLKEMEncapsulateDecapsulate(t *testing.T) {
|
||||
switch mode {
|
||||
case MLKEM512:
|
||||
expectedCtSize = MLKEM512CiphertextSize
|
||||
expectedSSSize = MLKEM512SharedSecretSize
|
||||
expectedSSSize = MLKEM512SharedKeySize
|
||||
case MLKEM768:
|
||||
expectedCtSize = MLKEM768CiphertextSize
|
||||
expectedSSSize = MLKEM768SharedSecretSize
|
||||
expectedSSSize = MLKEM768SharedKeySize
|
||||
case MLKEM1024:
|
||||
expectedCtSize = MLKEM1024CiphertextSize
|
||||
expectedSSSize = MLKEM1024SharedSecretSize
|
||||
expectedSSSize = MLKEM1024SharedKeySize
|
||||
}
|
||||
|
||||
if len(encapResult.Ciphertext) != expectedCtSize {
|
||||
t.Errorf("Ciphertext size mismatch: got %d, want %d", len(encapResult.Ciphertext), expectedCtSize)
|
||||
if len(ciphertext) != expectedCtSize {
|
||||
t.Errorf("Ciphertext size mismatch: got %d, want %d", len(ciphertext), expectedCtSize)
|
||||
}
|
||||
if len(encapResult.SharedSecret) != expectedSSSize {
|
||||
t.Errorf("Shared secret size mismatch: got %d, want %d", len(encapResult.SharedSecret), expectedSSSize)
|
||||
if len(sharedSecret) != expectedSSSize {
|
||||
t.Errorf("Shared secret size mismatch: got %d, want %d", len(sharedSecret), expectedSSSize)
|
||||
}
|
||||
|
||||
// Decapsulate
|
||||
sharedSecret, err := privKey.Decapsulate(encapResult.Ciphertext)
|
||||
decapSecret, err := priv.Decapsulate(ciphertext)
|
||||
if err != nil {
|
||||
t.Fatalf("Decapsulate failed: %v", err)
|
||||
}
|
||||
|
||||
// Verify shared secrets match
|
||||
if !bytes.Equal(encapResult.SharedSecret, sharedSecret) {
|
||||
if !bytes.Equal(sharedSecret, decapSecret) {
|
||||
t.Error("Shared secrets don't match")
|
||||
}
|
||||
|
||||
// Test with wrong ciphertext size
|
||||
wrongCiphertext := make([]byte, 100)
|
||||
_, err = privKey.Decapsulate(wrongCiphertext)
|
||||
_, err = priv.Decapsulate(wrongCiphertext)
|
||||
if err == nil {
|
||||
t.Error("Decapsulate should fail with wrong ciphertext size")
|
||||
}
|
||||
|
||||
// Test with tampered ciphertext
|
||||
tamperedCiphertext := make([]byte, len(encapResult.Ciphertext))
|
||||
copy(tamperedCiphertext, encapResult.Ciphertext)
|
||||
tamperedCiphertext := make([]byte, len(ciphertext))
|
||||
copy(tamperedCiphertext, ciphertext)
|
||||
tamperedCiphertext[0] ^= 0xFF
|
||||
|
||||
// In our placeholder implementation, this will produce different shared secret
|
||||
tamperedSecret, err := privKey.Decapsulate(tamperedCiphertext)
|
||||
// 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(encapResult.SharedSecret, tamperedSecret) {
|
||||
if bytes.Equal(sharedSecret, tamperedSecret) {
|
||||
t.Error("Tampered ciphertext produced same shared secret")
|
||||
}
|
||||
})
|
||||
@@ -132,47 +135,47 @@ func TestMLKEMKeySerialization(t *testing.T) {
|
||||
|
||||
for _, mode := range modes {
|
||||
t.Run(mode.String(), func(t *testing.T) {
|
||||
// Generate original key
|
||||
origKey, _, err := GenerateKeyPair(rand.Reader, mode)
|
||||
// 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 := origKey.Bytes()
|
||||
privBytes := origPriv.Bytes()
|
||||
newPrivKey, err := PrivateKeyFromBytes(privBytes, mode)
|
||||
if err != nil {
|
||||
t.Fatalf("PrivateKeyFromBytes failed: %v", err)
|
||||
}
|
||||
|
||||
// Check keys are equal
|
||||
if !bytes.Equal(origKey.Bytes(), newPrivKey.Bytes()) {
|
||||
if !bytes.Equal(origPriv.Bytes(), newPrivKey.Bytes()) {
|
||||
t.Error("Private key serialization failed")
|
||||
}
|
||||
|
||||
// Serialize and deserialize public key
|
||||
pubBytes := origKey.PublicKey.Bytes()
|
||||
pubBytes := origPub.Bytes()
|
||||
newPubKey, err := PublicKeyFromBytes(pubBytes, mode)
|
||||
if err != nil {
|
||||
t.Fatalf("PublicKeyFromBytes failed: %v", err)
|
||||
}
|
||||
|
||||
if !bytes.Equal(origKey.PublicKey.Bytes(), newPubKey.Bytes()) {
|
||||
if !bytes.Equal(origPub.Bytes(), newPubKey.Bytes()) {
|
||||
t.Error("Public key serialization failed")
|
||||
}
|
||||
|
||||
// Test encapsulation with deserialized keys
|
||||
encapResult, err := newPubKey.Encapsulate(rand.Reader)
|
||||
ciphertext, sharedSecret, err := newPubKey.Encapsulate(rand.Reader)
|
||||
if err != nil {
|
||||
t.Fatalf("Encapsulate with deserialized key failed: %v", err)
|
||||
}
|
||||
|
||||
sharedSecret, err := newPrivKey.Decapsulate(encapResult.Ciphertext)
|
||||
decapSecret, err := newPrivKey.Decapsulate(ciphertext)
|
||||
if err != nil {
|
||||
t.Fatalf("Decapsulate with deserialized key failed: %v", err)
|
||||
}
|
||||
|
||||
if !bytes.Equal(encapResult.SharedSecret, sharedSecret) {
|
||||
if !bytes.Equal(sharedSecret, decapSecret) {
|
||||
t.Error("Shared secrets don't match with deserialized keys")
|
||||
}
|
||||
})
|
||||
@@ -184,7 +187,7 @@ func TestMLKEMMultipleEncapsulations(t *testing.T) {
|
||||
|
||||
for _, mode := range modes {
|
||||
t.Run(mode.String(), func(t *testing.T) {
|
||||
privKey, _, err := GenerateKeyPair(rand.Reader, mode)
|
||||
pub, priv, err := GenerateKeyPair(rand.Reader, mode)
|
||||
if err != nil {
|
||||
t.Fatalf("GenerateKeyPair failed: %v", err)
|
||||
}
|
||||
@@ -196,12 +199,12 @@ func TestMLKEMMultipleEncapsulations(t *testing.T) {
|
||||
sharedSecrets := make([][]byte, numEncaps)
|
||||
|
||||
for i := 0; i < numEncaps; i++ {
|
||||
encapResult, err := privKey.PublicKey.Encapsulate(rand.Reader)
|
||||
ct, ss, err := pub.Encapsulate(rand.Reader)
|
||||
if err != nil {
|
||||
t.Fatalf("Encapsulate %d failed: %v", i, err)
|
||||
}
|
||||
ciphertexts[i] = encapResult.Ciphertext
|
||||
sharedSecrets[i] = encapResult.SharedSecret
|
||||
ciphertexts[i] = ct
|
||||
sharedSecrets[i] = ss
|
||||
}
|
||||
|
||||
// Check that ciphertexts are different
|
||||
@@ -215,7 +218,7 @@ func TestMLKEMMultipleEncapsulations(t *testing.T) {
|
||||
|
||||
// Check that all decapsulate correctly
|
||||
for i := 0; i < numEncaps; i++ {
|
||||
ss, err := privKey.Decapsulate(ciphertexts[i])
|
||||
ss, err := priv.Decapsulate(ciphertexts[i])
|
||||
if err != nil {
|
||||
t.Fatalf("Decapsulate %d failed: %v", i, err)
|
||||
}
|
||||
@@ -237,7 +240,7 @@ func TestMLKEMEdgeCases(t *testing.T) {
|
||||
|
||||
t.Run("NilPublicKey", func(t *testing.T) {
|
||||
var pubKey *PublicKey
|
||||
_, err := pubKey.Encapsulate(rand.Reader)
|
||||
_, _, err := pubKey.Encapsulate(rand.Reader)
|
||||
if err == nil {
|
||||
t.Error("Encapsulate should fail with nil public key")
|
||||
}
|
||||
@@ -252,18 +255,18 @@ func TestMLKEMEdgeCases(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("EmptyCiphertext", func(t *testing.T) {
|
||||
privKey, _, _ := GenerateKeyPair(rand.Reader, MLKEM512)
|
||||
_, err := privKey.Decapsulate([]byte{})
|
||||
_, 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) {
|
||||
privKey, _, _ := GenerateKeyPair(rand.Reader, MLKEM512)
|
||||
_, priv, _ := GenerateKeyPair(rand.Reader, MLKEM512)
|
||||
wrongCt := make([]byte, 100)
|
||||
rand.Read(wrongCt)
|
||||
_, err := privKey.Decapsulate(wrongCt)
|
||||
_, err := priv.Decapsulate(wrongCt)
|
||||
if err == nil {
|
||||
t.Error("Decapsulate should fail with wrong size ciphertext")
|
||||
}
|
||||
@@ -285,7 +288,7 @@ func TestMLKEMEdgeCases(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestMLKEMConcurrency(t *testing.T) {
|
||||
privKey, _, err := GenerateKeyPair(rand.Reader, MLKEM768)
|
||||
pub, priv, err := GenerateKeyPair(rand.Reader, MLKEM768)
|
||||
if err != nil {
|
||||
t.Fatalf("GenerateKeyPair failed: %v", err)
|
||||
}
|
||||
@@ -297,17 +300,17 @@ func TestMLKEMConcurrency(t *testing.T) {
|
||||
|
||||
for i := 0; i < numGoroutines; i++ {
|
||||
go func(id int) {
|
||||
encapResult, err := privKey.PublicKey.Encapsulate(rand.Reader)
|
||||
ct, ss, err := pub.Encapsulate(rand.Reader)
|
||||
if err != nil {
|
||||
t.Errorf("Goroutine %d: Encapsulate failed: %v", id, err)
|
||||
}
|
||||
|
||||
ss, err := privKey.Decapsulate(encapResult.Ciphertext)
|
||||
decapSS, err := priv.Decapsulate(ct)
|
||||
if err != nil {
|
||||
t.Errorf("Goroutine %d: Decapsulate failed: %v", id, err)
|
||||
}
|
||||
|
||||
if !bytes.Equal(ss, encapResult.SharedSecret) {
|
||||
if !bytes.Equal(ss, decapSS) {
|
||||
t.Errorf("Goroutine %d: Shared secrets don't match", id)
|
||||
}
|
||||
done <- true
|
||||
@@ -321,17 +324,17 @@ func TestMLKEMConcurrency(t *testing.T) {
|
||||
|
||||
// Test concurrent decapsulation
|
||||
t.Run("ConcurrentDecapsulate", func(t *testing.T) {
|
||||
encapResult, _ := privKey.PublicKey.Encapsulate(rand.Reader)
|
||||
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 := privKey.Decapsulate(encapResult.Ciphertext)
|
||||
ss, err := priv.Decapsulate(ciphertext)
|
||||
if err != nil {
|
||||
t.Errorf("Goroutine %d: Decapsulate failed: %v", id, err)
|
||||
}
|
||||
if !bytes.Equal(ss, encapResult.SharedSecret) {
|
||||
if !bytes.Equal(ss, sharedSecret) {
|
||||
t.Errorf("Goroutine %d: Shared secret mismatch", id)
|
||||
}
|
||||
done <- true
|
||||
@@ -344,45 +347,36 @@ func TestMLKEMConcurrency(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
// Helper functions that were missing
|
||||
func NewPrivateKey(mode Mode) *PrivateKey {
|
||||
return &PrivateKey{
|
||||
PublicKey: NewPublicKey(mode),
|
||||
mode: mode,
|
||||
key: nil, // This is just a placeholder, real key would be generated
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
func NewPublicKey(mode Mode) *PublicKey {
|
||||
return &PublicKey{
|
||||
mode: mode,
|
||||
key: nil, // This is just a placeholder, real key would be generated
|
||||
derivedPub := priv.PublicKey()
|
||||
if derivedPub == nil {
|
||||
t.Fatal("PublicKey() returned nil")
|
||||
}
|
||||
}
|
||||
|
||||
func getPrivateKeySize(mode Mode) int {
|
||||
switch mode {
|
||||
case MLKEM512:
|
||||
return MLKEM512PrivateKeySize
|
||||
case MLKEM768:
|
||||
return MLKEM768PrivateKeySize
|
||||
case MLKEM1024:
|
||||
return MLKEM1024PrivateKeySize
|
||||
default:
|
||||
return 0
|
||||
// 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")
|
||||
}
|
||||
}
|
||||
|
||||
func getPublicKeySize(mode Mode) int {
|
||||
switch mode {
|
||||
case MLKEM512:
|
||||
return MLKEM512PublicKeySize
|
||||
case MLKEM768:
|
||||
return MLKEM768PublicKeySize
|
||||
case MLKEM1024:
|
||||
return MLKEM1024PublicKeySize
|
||||
default:
|
||||
return 0
|
||||
// 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")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -420,12 +414,12 @@ func BenchmarkMLKEMEncapsulate(b *testing.B) {
|
||||
}
|
||||
|
||||
for _, m := range modes {
|
||||
privKey, _, _ := GenerateKeyPair(rand.Reader, m.mode)
|
||||
pub, _, _ := GenerateKeyPair(rand.Reader, m.mode)
|
||||
|
||||
b.Run(m.name, func(b *testing.B) {
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, err := privKey.PublicKey.Encapsulate(rand.Reader)
|
||||
_, _, err := pub.Encapsulate(rand.Reader)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
@@ -445,13 +439,13 @@ func BenchmarkMLKEMDecapsulate(b *testing.B) {
|
||||
}
|
||||
|
||||
for _, m := range modes {
|
||||
privKey, _, _ := GenerateKeyPair(rand.Reader, m.mode)
|
||||
encapResult, _ := privKey.PublicKey.Encapsulate(rand.Reader)
|
||||
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 := privKey.Decapsulate(encapResult.Ciphertext)
|
||||
ss, err := priv.Decapsulate(ciphertext)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
|
||||
+4
-6
@@ -6,22 +6,20 @@ import (
|
||||
|
||||
func TestMLKEM(t *testing.T) {
|
||||
t.Run("ML-KEM-512", func(t *testing.T) {
|
||||
// Placeholder test
|
||||
if MLKEM512 != Mode(1) {
|
||||
// Mode constants use iota starting at 0
|
||||
if MLKEM512 != Mode(0) {
|
||||
t.Error("ML-KEM-512 mode mismatch")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("ML-KEM-768", func(t *testing.T) {
|
||||
// Placeholder test
|
||||
if MLKEM768 != Mode(2) {
|
||||
if MLKEM768 != Mode(1) {
|
||||
t.Error("ML-KEM-768 mode mismatch")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("ML-KEM-1024", func(t *testing.T) {
|
||||
// Placeholder test
|
||||
if MLKEM1024 != Mode(3) {
|
||||
if MLKEM1024 != Mode(2) {
|
||||
t.Error("ML-KEM-1024 mode mismatch")
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,290 +0,0 @@
|
||||
// Copyright (C) 2025, Lux Industries Inc. All rights reserved.
|
||||
// Comprehensive tests for FIPS 203/204/205 post-quantum cryptography
|
||||
|
||||
package crypto
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/luxfi/crypto/mldsa"
|
||||
"github.com/luxfi/crypto/mlkem"
|
||||
"github.com/luxfi/crypto/slhdsa"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// TestMLKEM tests ML-KEM (FIPS 203) key encapsulation
|
||||
func TestMLKEM(t *testing.T) {
|
||||
modes := []mlkem.Mode{mlkem.MLKEM512, mlkem.MLKEM768, mlkem.MLKEM1024}
|
||||
names := []string{"ML-KEM-512", "ML-KEM-768", "ML-KEM-1024"}
|
||||
|
||||
for i, mode := range modes {
|
||||
t.Run(names[i], func(t *testing.T) {
|
||||
// Generate key pair
|
||||
priv, _, err := mlkem.GenerateKeyPair(rand.Reader, mode)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Encapsulate
|
||||
result, err := priv.PublicKey.Encapsulate(rand.Reader)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Decapsulate
|
||||
sharedSecret, err := priv.Decapsulate(result.Ciphertext)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Verify shared secrets match
|
||||
assert.Equal(t, result.SharedSecret, sharedSecret)
|
||||
|
||||
// Test wrong ciphertext
|
||||
wrongCT := make([]byte, len(result.Ciphertext))
|
||||
copy(wrongCT, result.Ciphertext)
|
||||
wrongCT[0] ^= 0xFF
|
||||
|
||||
wrongSecret, err := priv.Decapsulate(wrongCT)
|
||||
// ML-KEM has implicit rejection, so no error but different secret
|
||||
assert.NoError(t, err)
|
||||
assert.NotEqual(t, sharedSecret, wrongSecret)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestMLDSA tests ML-DSA (FIPS 204) digital signatures
|
||||
func TestMLDSA(t *testing.T) {
|
||||
modes := []mldsa.Mode{mldsa.MLDSA44, mldsa.MLDSA65, mldsa.MLDSA87}
|
||||
names := []string{"ML-DSA-44", "ML-DSA-65", "ML-DSA-87"}
|
||||
|
||||
message := []byte("Post-quantum signature test message")
|
||||
|
||||
for i, mode := range modes {
|
||||
t.Run(names[i], func(t *testing.T) {
|
||||
// Generate key pair
|
||||
priv, err := mldsa.GenerateKey(rand.Reader, mode)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Sign message
|
||||
signature, err := priv.Sign(rand.Reader, message, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Verify signature
|
||||
valid := priv.PublicKey.Verify(message, signature, nil)
|
||||
assert.True(t, valid)
|
||||
|
||||
// Test wrong message
|
||||
wrongMsg := []byte("Wrong message")
|
||||
assert.False(t, priv.PublicKey.Verify(wrongMsg, signature, nil))
|
||||
|
||||
// Test corrupted signature
|
||||
corruptedSig := make([]byte, len(signature))
|
||||
copy(corruptedSig, signature)
|
||||
corruptedSig[0] ^= 0xFF
|
||||
assert.False(t, priv.PublicKey.Verify(message, corruptedSig, nil))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestSLHDSA tests SLH-DSA (FIPS 205) hash-based signatures
|
||||
func TestSLHDSA(t *testing.T) {
|
||||
// Test only small/fast variants for speed
|
||||
modes := []slhdsa.Mode{slhdsa.SLHDSA128s, slhdsa.SLHDSA128f}
|
||||
names := []string{"SLH-DSA-128s", "SLH-DSA-128f"}
|
||||
|
||||
message := []byte("Stateless hash-based signature test")
|
||||
|
||||
for i, mode := range modes {
|
||||
t.Run(names[i], func(t *testing.T) {
|
||||
// Generate key pair
|
||||
priv, err := slhdsa.GenerateKey(rand.Reader, mode)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Sign message
|
||||
signature, err := priv.Sign(rand.Reader, message, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Verify signature
|
||||
valid := priv.PublicKey.Verify(message, signature, nil)
|
||||
assert.True(t, valid)
|
||||
|
||||
// Test stateless property - same signature for same message
|
||||
signature2, err := priv.Sign(rand.Reader, message, nil)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, signature, signature2, "SLH-DSA should be deterministic")
|
||||
|
||||
// Test wrong message
|
||||
wrongMsg := []byte("Wrong message")
|
||||
assert.False(t, priv.PublicKey.Verify(wrongMsg, signature, nil))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestPerformance tests performance of pure Go implementations
|
||||
func TestPerformance(t *testing.T) {
|
||||
t.Run("ML-KEM Performance", func(t *testing.T) {
|
||||
// Benchmark pure Go implementation
|
||||
priv, _, _ := mlkem.GenerateKeyPair(rand.Reader, mlkem.MLKEM768)
|
||||
|
||||
// Encapsulation benchmark
|
||||
start := time.Now()
|
||||
for i := 0; i < 100; i++ {
|
||||
priv.PublicKey.Encapsulate(rand.Reader)
|
||||
}
|
||||
duration := time.Since(start)
|
||||
|
||||
t.Logf("ML-KEM-768 Encapsulate (100 ops): %v", duration)
|
||||
assert.Less(t, duration, 5*time.Second, "Should complete 100 encapsulations in under 5 seconds")
|
||||
})
|
||||
|
||||
t.Run("ML-DSA Performance", func(t *testing.T) {
|
||||
message := make([]byte, 32)
|
||||
rand.Read(message)
|
||||
|
||||
// Benchmark pure Go implementation
|
||||
priv, _ := mldsa.GenerateKey(rand.Reader, mldsa.MLDSA65)
|
||||
|
||||
// Signing benchmark
|
||||
start := time.Now()
|
||||
for i := 0; i < 100; i++ {
|
||||
priv.Sign(rand.Reader, message, nil)
|
||||
}
|
||||
duration := time.Since(start)
|
||||
|
||||
t.Logf("ML-DSA-65 Sign (100 ops): %v", duration)
|
||||
assert.Less(t, duration, 5*time.Second, "Should complete 100 signatures in under 5 seconds")
|
||||
})
|
||||
|
||||
t.Run("SLH-DSA Performance", func(t *testing.T) {
|
||||
message := make([]byte, 32)
|
||||
rand.Read(message)
|
||||
|
||||
// Benchmark pure Go implementation
|
||||
priv, _ := slhdsa.GenerateKey(rand.Reader, slhdsa.SLHDSA128f)
|
||||
|
||||
start := time.Now()
|
||||
for i := 0; i < 10; i++ {
|
||||
priv.Sign(rand.Reader, message, nil)
|
||||
}
|
||||
duration := time.Since(start)
|
||||
|
||||
t.Logf("SLH-DSA-128f Sign (10 ops): %v", duration)
|
||||
})
|
||||
}
|
||||
|
||||
// TestHybridCrypto tests combining classical and post-quantum crypto
|
||||
func TestHybridCrypto(t *testing.T) {
|
||||
t.Run("Hybrid Key Exchange", func(t *testing.T) {
|
||||
// Classical ECDH (placeholder)
|
||||
classicalSecret := make([]byte, 32)
|
||||
rand.Read(classicalSecret)
|
||||
|
||||
// Post-quantum ML-KEM
|
||||
priv, _, _ := mlkem.GenerateKeyPair(rand.Reader, mlkem.MLKEM768)
|
||||
result, _ := priv.PublicKey.Encapsulate(rand.Reader)
|
||||
pqSecret, _ := priv.Decapsulate(result.Ciphertext)
|
||||
|
||||
// Combine secrets (simplified - use proper KDF in production)
|
||||
hybridSecret := make([]byte, 64)
|
||||
copy(hybridSecret[:32], classicalSecret)
|
||||
copy(hybridSecret[32:], pqSecret)
|
||||
|
||||
assert.Len(t, hybridSecret, 64)
|
||||
})
|
||||
|
||||
t.Run("Hybrid Signatures", func(t *testing.T) {
|
||||
message := []byte("Hybrid signature test")
|
||||
|
||||
// Classical ECDSA (placeholder)
|
||||
classicalSig := make([]byte, 64)
|
||||
rand.Read(classicalSig)
|
||||
|
||||
// Post-quantum ML-DSA
|
||||
priv, _ := mldsa.GenerateKey(rand.Reader, mldsa.MLDSA65)
|
||||
pqSig, _ := priv.Sign(rand.Reader, message, nil)
|
||||
|
||||
// Combine signatures
|
||||
_ = append(classicalSig, pqSig...) // hybridSig would be used in production
|
||||
|
||||
// Verify both
|
||||
// Classical verification (placeholder - would be ECDSA)
|
||||
classicalValid := true
|
||||
|
||||
// PQ verification
|
||||
pqValid := priv.PublicKey.Verify(message, pqSig, nil)
|
||||
|
||||
// Both must be valid
|
||||
assert.True(t, classicalValid && pqValid)
|
||||
})
|
||||
}
|
||||
|
||||
// BenchmarkPostQuantum benchmarks all three standards
|
||||
func BenchmarkPostQuantum(b *testing.B) {
|
||||
b.Run("ML-KEM-768", func(b *testing.B) {
|
||||
priv, _, _ := mlkem.GenerateKeyPair(rand.Reader, mlkem.MLKEM768)
|
||||
|
||||
b.Run("Encapsulate", func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
priv.PublicKey.Encapsulate(rand.Reader)
|
||||
}
|
||||
})
|
||||
|
||||
result, _ := priv.PublicKey.Encapsulate(rand.Reader)
|
||||
b.Run("Decapsulate", func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
priv.Decapsulate(result.Ciphertext)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
b.Run("ML-DSA-65", func(b *testing.B) {
|
||||
priv, _ := mldsa.GenerateKey(rand.Reader, mldsa.MLDSA65)
|
||||
message := make([]byte, 32)
|
||||
|
||||
b.Run("Sign", func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
priv.Sign(rand.Reader, message, nil)
|
||||
}
|
||||
})
|
||||
|
||||
sig, _ := priv.Sign(rand.Reader, message, nil)
|
||||
b.Run("Verify", func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
priv.PublicKey.Verify(message, sig, nil)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
b.Run("SLH-DSA-128f", func(b *testing.B) {
|
||||
priv, _ := slhdsa.GenerateKey(rand.Reader, slhdsa.SLHDSA128f)
|
||||
message := make([]byte, 32)
|
||||
|
||||
b.Run("Sign", func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
priv.Sign(rand.Reader, message, nil)
|
||||
}
|
||||
})
|
||||
|
||||
sig, _ := priv.Sign(rand.Reader, message, nil)
|
||||
b.Run("Verify", func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
priv.PublicKey.Verify(message, sig, nil)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// TestSizesAndParameters verifies all parameter sizes match FIPS specifications
|
||||
func TestSizesAndParameters(t *testing.T) {
|
||||
// ML-KEM sizes (FIPS 203)
|
||||
assert.Equal(t, 1184, mlkem.MLKEM768PublicKeySize)
|
||||
assert.Equal(t, 1088, mlkem.MLKEM768CiphertextSize)
|
||||
|
||||
// ML-DSA sizes (FIPS 204)
|
||||
assert.Equal(t, 1952, mldsa.MLDSA65PublicKeySize)
|
||||
assert.Equal(t, 3293, mldsa.MLDSA65SignatureSize)
|
||||
|
||||
// SLH-DSA sizes (FIPS 205)
|
||||
assert.Equal(t, 32, slhdsa.SLHDSA128sPublicKeySize)
|
||||
assert.Equal(t, 7856, slhdsa.SLHDSA128sSignatureSize)
|
||||
assert.Equal(t, 32, slhdsa.SLHDSA128fPublicKeySize)
|
||||
assert.Equal(t, 17088, slhdsa.SLHDSA128fSignatureSize)
|
||||
}
|
||||
@@ -1,189 +0,0 @@
|
||||
// Copyright (C) 2025, Lux Industries Inc. All rights reserved.
|
||||
// Post-Quantum Cryptography Integration Tests
|
||||
|
||||
package crypto
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/luxfi/crypto/mldsa"
|
||||
"github.com/luxfi/crypto/mlkem"
|
||||
"github.com/luxfi/crypto/slhdsa"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// TestMLDSAIntegration tests ML-DSA digital signatures
|
||||
func TestMLDSAIntegration(t *testing.T) {
|
||||
require := require.New(t)
|
||||
|
||||
modes := []mldsa.Mode{
|
||||
mldsa.MLDSA44,
|
||||
mldsa.MLDSA65,
|
||||
mldsa.MLDSA87,
|
||||
}
|
||||
|
||||
for _, mode := range modes {
|
||||
t.Run(mode.String(), func(t *testing.T) {
|
||||
// Generate key pair
|
||||
priv, err := mldsa.GenerateKey(rand.Reader, mode)
|
||||
require.NoError(err)
|
||||
require.NotNil(priv)
|
||||
|
||||
// Test message
|
||||
message := []byte("Post-Quantum Digital Signature Test Message")
|
||||
|
||||
// Sign message
|
||||
signature, err := priv.Sign(rand.Reader, message, nil)
|
||||
require.NoError(err)
|
||||
require.NotEmpty(signature)
|
||||
|
||||
// Verify signature
|
||||
valid := priv.PublicKey.Verify(message, signature, nil)
|
||||
require.True(valid, "Signature should be valid")
|
||||
|
||||
// Test invalid signature
|
||||
signature[0] ^= 0xFF
|
||||
valid = priv.PublicKey.Verify(message, signature, nil)
|
||||
require.False(valid, "Modified signature should be invalid")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestMLKEMIntegration tests ML-KEM key encapsulation
|
||||
func TestMLKEMIntegration(t *testing.T) {
|
||||
require := require.New(t)
|
||||
|
||||
modes := []mlkem.Mode{
|
||||
mlkem.MLKEM512,
|
||||
mlkem.MLKEM768,
|
||||
mlkem.MLKEM1024,
|
||||
}
|
||||
|
||||
for _, mode := range modes {
|
||||
t.Run(mode.String(), func(t *testing.T) {
|
||||
// Generate key pair
|
||||
priv, pub, err := mlkem.GenerateKeyPair(rand.Reader, mode)
|
||||
require.NoError(err)
|
||||
require.NotNil(priv)
|
||||
require.NotNil(pub)
|
||||
|
||||
// Encapsulate
|
||||
result, err := pub.Encapsulate(rand.Reader)
|
||||
require.NoError(err)
|
||||
require.NotEmpty(result.Ciphertext)
|
||||
require.NotEmpty(result.SharedSecret)
|
||||
|
||||
// Decapsulate
|
||||
sharedSecret2, err := priv.Decapsulate(result.Ciphertext)
|
||||
require.NoError(err)
|
||||
require.Equal(result.SharedSecret, sharedSecret2)
|
||||
|
||||
// Test serialization
|
||||
pubBytes := pub.Bytes()
|
||||
require.NotEmpty(pubBytes)
|
||||
|
||||
privBytes := priv.Bytes()
|
||||
require.NotEmpty(privBytes)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestSLHDSAIntegration tests SLH-DSA hash-based signatures
|
||||
func TestSLHDSAIntegration(t *testing.T) {
|
||||
require := require.New(t)
|
||||
|
||||
modes := []slhdsa.Mode{
|
||||
slhdsa.SLHDSA128s,
|
||||
slhdsa.SLHDSA192s,
|
||||
slhdsa.SLHDSA256s,
|
||||
}
|
||||
|
||||
for _, mode := range modes {
|
||||
t.Run(fmt.Sprintf("Mode%d", mode), func(t *testing.T) {
|
||||
// Generate key pair
|
||||
priv, err := slhdsa.GenerateKey(rand.Reader, mode)
|
||||
require.NoError(err)
|
||||
require.NotNil(priv)
|
||||
|
||||
// Test message
|
||||
message := []byte("Stateless Hash-based Signature Test")
|
||||
|
||||
// Sign message
|
||||
signature, err := priv.Sign(rand.Reader, message, nil)
|
||||
require.NoError(err)
|
||||
require.NotEmpty(signature)
|
||||
|
||||
// Verify signature
|
||||
valid := priv.PublicKey.Verify(message, signature, nil)
|
||||
require.True(valid, "Signature should be valid")
|
||||
|
||||
// Test invalid signature
|
||||
signature[0] ^= 0xFF
|
||||
valid = priv.PublicKey.Verify(message, signature, nil)
|
||||
require.False(valid, "Modified signature should be invalid")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestHybridCryptoIntegration tests hybrid classical + PQ modes
|
||||
func TestHybridCryptoIntegration(t *testing.T) {
|
||||
require := require.New(t)
|
||||
|
||||
// Test hybrid signing (classical + PQ)
|
||||
t.Run("HybridSigning", func(t *testing.T) {
|
||||
// Generate classical key (secp256k1)
|
||||
classicalPriv, err := GenerateKey()
|
||||
require.NoError(err)
|
||||
|
||||
// Generate PQ key (ML-DSA)
|
||||
pqPriv, err := mldsa.GenerateKey(rand.Reader, mldsa.MLDSA44)
|
||||
require.NoError(err)
|
||||
|
||||
message := []byte("Hybrid signature test message")
|
||||
|
||||
// Classical signature
|
||||
hash := Keccak256Hash(message)
|
||||
classicalSig, err := Sign(hash.Bytes(), classicalPriv)
|
||||
require.NoError(err)
|
||||
|
||||
// PQ signature
|
||||
pqSig, err := pqPriv.Sign(rand.Reader, message, nil)
|
||||
require.NoError(err)
|
||||
|
||||
// Verify both
|
||||
classicalPub := &classicalPriv.PublicKey
|
||||
require.True(VerifySignature(FromECDSAPub(classicalPub), hash.Bytes(), classicalSig[:64]))
|
||||
require.True(pqPriv.PublicKey.Verify(message, pqSig, nil))
|
||||
})
|
||||
}
|
||||
|
||||
// BenchmarkPQCrypto benchmarks PQ operations
|
||||
func BenchmarkPQCrypto(b *testing.B) {
|
||||
b.Run("ML-DSA-44-Sign", func(b *testing.B) {
|
||||
priv, _ := mldsa.GenerateKey(rand.Reader, mldsa.MLDSA44)
|
||||
message := []byte("benchmark message")
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, _ = priv.Sign(rand.Reader, message, nil)
|
||||
}
|
||||
})
|
||||
|
||||
b.Run("ML-KEM-512-Encapsulate", func(b *testing.B) {
|
||||
_, pub, _ := mlkem.GenerateKeyPair(rand.Reader, mlkem.MLKEM512)
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, _ = pub.Encapsulate(rand.Reader)
|
||||
}
|
||||
})
|
||||
|
||||
b.Run("SLH-DSA-128s-Sign", func(b *testing.B) {
|
||||
priv, _ := slhdsa.GenerateKey(rand.Reader, slhdsa.SLHDSA128s)
|
||||
message := []byte("benchmark message")
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, _ = priv.Sign(rand.Reader, message, nil)
|
||||
}
|
||||
})
|
||||
}
|
||||
+2
-2
@@ -211,10 +211,10 @@ func (k *PublicKey) Address() ids.ShortID {
|
||||
func (k *PublicKey) EthAddress() [20]byte {
|
||||
// Get uncompressed public key bytes (excluding the 0x04 prefix)
|
||||
pkBytes := k.Bytes()
|
||||
|
||||
|
||||
// Compute Keccak256 hash
|
||||
hash := Keccak256(pkBytes)
|
||||
|
||||
|
||||
// Take the last 20 bytes as the address
|
||||
var addr [20]byte
|
||||
copy(addr[:], hash[12:])
|
||||
|
||||
+39
-15
@@ -6,6 +6,7 @@ package secp256k1
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"math/big"
|
||||
"testing"
|
||||
|
||||
"github.com/luxfi/node/utils/hashing"
|
||||
@@ -63,12 +64,19 @@ func FuzzSignatureVerification(f *testing.F) {
|
||||
|
||||
// Test decompression if compressed
|
||||
if len(pubKey) == 33 {
|
||||
decompressed, err := DecompressPubkey(pubKey)
|
||||
if err != nil {
|
||||
x, y := DecompressPubkey(pubKey)
|
||||
if x == nil || y == nil {
|
||||
return
|
||||
}
|
||||
if len(decompressed) != 65 {
|
||||
t.Errorf("Decompressed public key has wrong length: %d", len(decompressed))
|
||||
// Reconstruct uncompressed form: 0x04 + x (32 bytes) + y (32 bytes)
|
||||
uncompressed := make([]byte, 65)
|
||||
uncompressed[0] = 0x04
|
||||
xBytes := x.Bytes()
|
||||
yBytes := y.Bytes()
|
||||
copy(uncompressed[33-len(xBytes):33], xBytes)
|
||||
copy(uncompressed[65-len(yBytes):65], yBytes)
|
||||
if len(uncompressed) != 65 {
|
||||
t.Errorf("Decompressed public key has wrong length: %d", len(uncompressed))
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -160,39 +168,55 @@ func FuzzPublicKeyCompression(f *testing.F) {
|
||||
f.Fuzz(func(t *testing.T, pubKeyData []byte) {
|
||||
// Test compression if uncompressed
|
||||
if len(pubKeyData) == 65 && pubKeyData[0] == 0x04 {
|
||||
compressed := CompressPubkey(pubKeyData[1:33], pubKeyData[33:65])
|
||||
x := new(big.Int).SetBytes(pubKeyData[1:33])
|
||||
y := new(big.Int).SetBytes(pubKeyData[33:65])
|
||||
compressed := CompressPubkey(x, y)
|
||||
if len(compressed) != 33 {
|
||||
t.Errorf("Compressed public key has wrong length: %d", len(compressed))
|
||||
return
|
||||
}
|
||||
|
||||
// Try to decompress back
|
||||
decompressed, err := DecompressPubkey(compressed)
|
||||
if err != nil {
|
||||
dx, dy := DecompressPubkey(compressed)
|
||||
if dx == nil || dy == nil {
|
||||
// Some points might not be on the curve
|
||||
return
|
||||
}
|
||||
|
||||
if len(decompressed) != 65 {
|
||||
t.Errorf("Decompressed public key has wrong length: %d", len(decompressed))
|
||||
// Reconstruct uncompressed form
|
||||
uncompressed := make([]byte, 65)
|
||||
uncompressed[0] = 0x04
|
||||
dxBytes := dx.Bytes()
|
||||
dyBytes := dy.Bytes()
|
||||
copy(uncompressed[33-len(dxBytes):33], dxBytes)
|
||||
copy(uncompressed[65-len(dyBytes):65], dyBytes)
|
||||
if len(uncompressed) != 65 {
|
||||
t.Errorf("Decompressed public key has wrong length: %d", len(uncompressed))
|
||||
}
|
||||
}
|
||||
|
||||
// Test decompression if compressed
|
||||
if len(pubKeyData) == 33 && (pubKeyData[0] == 0x02 || pubKeyData[0] == 0x03) {
|
||||
decompressed, err := DecompressPubkey(pubKeyData)
|
||||
if err != nil {
|
||||
x, y := DecompressPubkey(pubKeyData)
|
||||
if x == nil || y == nil {
|
||||
// Expected for invalid compressed keys
|
||||
return
|
||||
}
|
||||
|
||||
if len(decompressed) != 65 {
|
||||
t.Errorf("Decompressed public key has wrong length: %d", len(decompressed))
|
||||
// Reconstruct uncompressed form
|
||||
uncompressed := make([]byte, 65)
|
||||
uncompressed[0] = 0x04
|
||||
xBytes := x.Bytes()
|
||||
yBytes := y.Bytes()
|
||||
copy(uncompressed[33-len(xBytes):33], xBytes)
|
||||
copy(uncompressed[65-len(yBytes):65], yBytes)
|
||||
if len(uncompressed) != 65 {
|
||||
t.Errorf("Decompressed public key has wrong length: %d", len(uncompressed))
|
||||
return
|
||||
}
|
||||
|
||||
// Compress again and verify round-trip
|
||||
recompressed := CompressPubkey(decompressed[1:33], decompressed[33:65])
|
||||
recompressed := CompressPubkey(x, y)
|
||||
if !bytes.Equal(recompressed, pubKeyData) {
|
||||
// Note: This might fail for invalid input keys
|
||||
return
|
||||
@@ -251,7 +275,7 @@ func FuzzSignatureMalleability(f *testing.F) {
|
||||
func FuzzECDSAEdgeCases(f *testing.F) {
|
||||
// Seed corpus with edge cases
|
||||
f.Add([]byte{}, []byte{})
|
||||
f.Add(nil, nil)
|
||||
f.Add([]byte{0}, []byte{0})
|
||||
|
||||
// All zeros
|
||||
zeros := make([]byte, 32)
|
||||
|
||||
Reference in New Issue
Block a user