mirror of
https://github.com/luxfi/crypto.git
synced 2026-07-27 01:54:50 +00:00
280 lines
6.5 KiB
Plaintext
280 lines
6.5 KiB
Plaintext
// Copyright (C) 2025, Lux Industries Inc. All rights reserved.
|
|
// Package mlkem provides ML-KEM (FIPS 203) key encapsulation mechanism
|
|
// This is a placeholder implementation for CI testing
|
|
|
|
package mlkem
|
|
|
|
import (
|
|
"crypto/sha256"
|
|
"errors"
|
|
"io"
|
|
)
|
|
|
|
// Security parameters for ML-KEM (Module Lattice Key Encapsulation Mechanism)
|
|
const (
|
|
// ML-KEM-512 (Level 1 security)
|
|
MLKEM512PublicKeySize = 800
|
|
MLKEM512PrivateKeySize = 1632
|
|
MLKEM512CiphertextSize = 768
|
|
MLKEM512SharedSecretSize = 32
|
|
|
|
// ML-KEM-768 (Level 3 security)
|
|
MLKEM768PublicKeySize = 1184
|
|
MLKEM768PrivateKeySize = 2400
|
|
MLKEM768CiphertextSize = 1088
|
|
MLKEM768SharedSecretSize = 32
|
|
|
|
// ML-KEM-1024 (Level 5 security)
|
|
MLKEM1024PublicKeySize = 1568
|
|
MLKEM1024PrivateKeySize = 3168
|
|
MLKEM1024CiphertextSize = 1568
|
|
MLKEM1024SharedSecretSize = 32
|
|
)
|
|
|
|
// Mode represents the ML-KEM parameter set
|
|
type Mode int
|
|
|
|
const (
|
|
MLKEM512 Mode = iota + 1
|
|
MLKEM768
|
|
MLKEM1024
|
|
)
|
|
|
|
// PublicKey represents an ML-KEM public key
|
|
type PublicKey struct {
|
|
mode Mode
|
|
data []byte
|
|
}
|
|
|
|
// PrivateKey represents an ML-KEM private key
|
|
type PrivateKey struct {
|
|
PublicKey PublicKey
|
|
data []byte
|
|
}
|
|
|
|
// EncapsulationResult contains the ciphertext and shared secret
|
|
type EncapsulationResult struct {
|
|
Ciphertext []byte
|
|
SharedSecret []byte
|
|
}
|
|
|
|
// GenerateKeyPair generates a new ML-KEM key pair
|
|
func GenerateKeyPair(rand io.Reader, mode Mode) (*PrivateKey, error) {
|
|
var pubKeySize, privKeySize int
|
|
|
|
switch mode {
|
|
case MLKEM512:
|
|
pubKeySize = MLKEM512PublicKeySize
|
|
privKeySize = MLKEM512PrivateKeySize
|
|
case MLKEM768:
|
|
pubKeySize = MLKEM768PublicKeySize
|
|
privKeySize = MLKEM768PrivateKeySize
|
|
case MLKEM1024:
|
|
pubKeySize = MLKEM1024PublicKeySize
|
|
privKeySize = MLKEM1024PrivateKeySize
|
|
default:
|
|
return nil, errors.New("invalid ML-KEM mode")
|
|
}
|
|
|
|
// Check for nil random source
|
|
if rand == nil {
|
|
return nil, errors.New("random source is nil")
|
|
}
|
|
|
|
// Placeholder implementation - generate random private key
|
|
privBytes := make([]byte, privKeySize)
|
|
if _, err := io.ReadFull(rand, privBytes); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Derive public key from private key deterministically
|
|
h := sha256.New()
|
|
h.Write(privBytes[:32]) // Use first 32 bytes as seed
|
|
h.Write([]byte("public"))
|
|
pubSeed := h.Sum(nil)
|
|
|
|
pubBytes := make([]byte, pubKeySize)
|
|
// Fill public key with deterministic data
|
|
for i := 0; i < pubKeySize; i += 32 {
|
|
h.Reset()
|
|
h.Write(pubSeed)
|
|
h.Write([]byte{byte(i / 32)})
|
|
hash := h.Sum(nil)
|
|
end := i + 32
|
|
if end > pubKeySize {
|
|
end = pubKeySize
|
|
}
|
|
copy(pubBytes[i:end], hash)
|
|
}
|
|
|
|
return &PrivateKey{
|
|
PublicKey: PublicKey{
|
|
mode: mode,
|
|
data: pubBytes,
|
|
},
|
|
data: privBytes,
|
|
}, nil
|
|
}
|
|
|
|
// Encapsulate generates a shared secret and ciphertext
|
|
func (pub *PublicKey) Encapsulate(rand io.Reader) (*EncapsulationResult, error) {
|
|
if pub == nil {
|
|
return nil, errors.New("public key is nil")
|
|
}
|
|
|
|
var ctSize int
|
|
|
|
switch pub.mode {
|
|
case MLKEM512:
|
|
ctSize = MLKEM512CiphertextSize
|
|
case MLKEM768:
|
|
ctSize = MLKEM768CiphertextSize
|
|
case MLKEM1024:
|
|
ctSize = MLKEM1024CiphertextSize
|
|
default:
|
|
return nil, errors.New("invalid ML-KEM mode")
|
|
}
|
|
|
|
// Placeholder: generate random ciphertext
|
|
ct := make([]byte, ctSize)
|
|
if _, err := io.ReadFull(rand, ct); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Placeholder: derive shared secret deterministically
|
|
// Use SHA256(pubkey || ciphertext) for consistency
|
|
h := sha256.New()
|
|
h.Write(pub.data)
|
|
h.Write(ct)
|
|
ss := h.Sum(nil)
|
|
|
|
// Store the ciphertext hash for later decapsulation
|
|
// In real implementation, this would be proper KEM
|
|
|
|
return &EncapsulationResult{
|
|
Ciphertext: ct,
|
|
SharedSecret: ss,
|
|
}, nil
|
|
}
|
|
|
|
// Decapsulate recovers the shared secret from ciphertext
|
|
func (priv *PrivateKey) Decapsulate(ciphertext []byte) ([]byte, error) {
|
|
if priv == nil {
|
|
return nil, errors.New("private key is nil")
|
|
}
|
|
|
|
var expectedCtSize int
|
|
|
|
switch priv.PublicKey.mode {
|
|
case MLKEM512:
|
|
expectedCtSize = MLKEM512CiphertextSize
|
|
case MLKEM768:
|
|
expectedCtSize = MLKEM768CiphertextSize
|
|
case MLKEM1024:
|
|
expectedCtSize = MLKEM1024CiphertextSize
|
|
default:
|
|
return nil, errors.New("invalid ML-KEM mode")
|
|
}
|
|
|
|
if len(ciphertext) != expectedCtSize {
|
|
return nil, errors.New("invalid ciphertext size")
|
|
}
|
|
|
|
// Placeholder: derive shared secret deterministically
|
|
// Use same formula as Encapsulate: SHA256(pubkey || ciphertext)
|
|
// This ensures matching shared secrets
|
|
h := sha256.New()
|
|
h.Write(priv.PublicKey.data)
|
|
h.Write(ciphertext)
|
|
ss := h.Sum(nil)
|
|
|
|
return ss, nil
|
|
}
|
|
|
|
// Bytes returns the public key as bytes
|
|
func (pub *PublicKey) Bytes() []byte {
|
|
return pub.data
|
|
}
|
|
|
|
// Bytes returns the private key as bytes
|
|
func (priv *PrivateKey) Bytes() []byte {
|
|
return priv.data
|
|
}
|
|
|
|
// PublicKeyFromBytes reconstructs a public key from bytes
|
|
func PublicKeyFromBytes(data []byte, mode Mode) (*PublicKey, error) {
|
|
var expectedSize int
|
|
|
|
switch mode {
|
|
case MLKEM512:
|
|
expectedSize = MLKEM512PublicKeySize
|
|
case MLKEM768:
|
|
expectedSize = MLKEM768PublicKeySize
|
|
case MLKEM1024:
|
|
expectedSize = MLKEM1024PublicKeySize
|
|
default:
|
|
return nil, errors.New("invalid ML-KEM mode")
|
|
}
|
|
|
|
if len(data) != expectedSize {
|
|
return nil, errors.New("invalid public key size")
|
|
}
|
|
|
|
return &PublicKey{
|
|
mode: mode,
|
|
data: data,
|
|
}, nil
|
|
}
|
|
|
|
// PrivateKeyFromBytes reconstructs a private key from bytes
|
|
func PrivateKeyFromBytes(data []byte, mode Mode) (*PrivateKey, error) {
|
|
var expectedPrivSize, expectedPubSize int
|
|
|
|
switch mode {
|
|
case MLKEM512:
|
|
expectedPrivSize = MLKEM512PrivateKeySize
|
|
expectedPubSize = MLKEM512PublicKeySize
|
|
case MLKEM768:
|
|
expectedPrivSize = MLKEM768PrivateKeySize
|
|
expectedPubSize = MLKEM768PublicKeySize
|
|
case MLKEM1024:
|
|
expectedPrivSize = MLKEM1024PrivateKeySize
|
|
expectedPubSize = MLKEM1024PublicKeySize
|
|
default:
|
|
return nil, errors.New("invalid ML-KEM mode")
|
|
}
|
|
|
|
if len(data) != expectedPrivSize {
|
|
return nil, errors.New("invalid private key size")
|
|
}
|
|
|
|
// For placeholder: derive public key from private key deterministically
|
|
// Use first part of private key as seed for public key
|
|
h := sha256.New()
|
|
h.Write(data[:32]) // Use first 32 bytes as seed
|
|
h.Write([]byte("public"))
|
|
pubSeed := h.Sum(nil)
|
|
|
|
pubData := make([]byte, expectedPubSize)
|
|
// Fill public key with deterministic data
|
|
for i := 0; i < expectedPubSize; i += 32 {
|
|
h.Reset()
|
|
h.Write(pubSeed)
|
|
h.Write([]byte{byte(i / 32)})
|
|
hash := h.Sum(nil)
|
|
end := i + 32
|
|
if end > expectedPubSize {
|
|
end = expectedPubSize
|
|
}
|
|
copy(pubData[i:end], hash)
|
|
}
|
|
|
|
return &PrivateKey{
|
|
PublicKey: PublicKey{
|
|
mode: mode,
|
|
data: pubData,
|
|
},
|
|
data: data,
|
|
}, nil
|
|
}
|