mirror of
https://github.com/luxfi/crypto.git
synced 2026-07-27 01:54:50 +00:00
333 lines
8.0 KiB
Plaintext
333 lines
8.0 KiB
Plaintext
// Copyright (C) 2025, Lux Industries Inc. All rights reserved.
|
|
// Package slhdsa provides SLH-DSA (FIPS 205) stateless hash-based signatures
|
|
// This is a placeholder implementation for CI testing
|
|
|
|
package slhdsa
|
|
|
|
import (
|
|
"crypto"
|
|
"crypto/sha256"
|
|
"errors"
|
|
"io"
|
|
)
|
|
|
|
// Security parameters for SLH-DSA (Stateless Hash-Based Digital Signature Algorithm)
|
|
const (
|
|
// SLH-DSA-SHA2-128s (Small signatures, Level 1 security)
|
|
SLHDSA128sPublicKeySize = 32 // bytes
|
|
SLHDSA128sPrivateKeySize = 64 // bytes
|
|
SLHDSA128sSignatureSize = 7856 // bytes
|
|
|
|
// SLH-DSA-SHA2-128f (Fast signing, Level 1 security)
|
|
SLHDSA128fPublicKeySize = 32 // bytes
|
|
SLHDSA128fPrivateKeySize = 64 // bytes
|
|
SLHDSA128fSignatureSize = 17088 // bytes
|
|
|
|
// SLH-DSA-SHA2-192s (Small signatures, Level 3 security)
|
|
SLHDSA192sPublicKeySize = 48 // bytes
|
|
SLHDSA192sPrivateKeySize = 96 // bytes
|
|
SLHDSA192sSignatureSize = 16224 // bytes
|
|
|
|
// SLH-DSA-SHA2-192f (Fast signing, Level 3 security)
|
|
SLHDSA192fPublicKeySize = 48 // bytes
|
|
SLHDSA192fPrivateKeySize = 96 // bytes
|
|
SLHDSA192fSignatureSize = 35664 // bytes
|
|
|
|
// SLH-DSA-SHA2-256s (Small signatures, Level 5 security)
|
|
SLHDSA256sPublicKeySize = 64 // bytes
|
|
SLHDSA256sPrivateKeySize = 128 // bytes
|
|
SLHDSA256sSignatureSize = 29792 // bytes
|
|
|
|
// SLH-DSA-SHA2-256f (Fast signing, Level 5 security)
|
|
SLHDSA256fPublicKeySize = 64 // bytes
|
|
SLHDSA256fPrivateKeySize = 128 // bytes
|
|
SLHDSA256fSignatureSize = 49856 // bytes
|
|
)
|
|
|
|
// Mode represents the SLH-DSA parameter set
|
|
type Mode int
|
|
|
|
const (
|
|
SLHDSA128s Mode = iota + 1
|
|
SLHDSA128f
|
|
SLHDSA192s
|
|
SLHDSA192f
|
|
SLHDSA256s
|
|
SLHDSA256f
|
|
)
|
|
|
|
// PublicKey represents an SLH-DSA public key
|
|
type PublicKey struct {
|
|
mode Mode
|
|
data []byte
|
|
}
|
|
|
|
// PrivateKey represents an SLH-DSA private key
|
|
type PrivateKey struct {
|
|
PublicKey
|
|
data []byte
|
|
}
|
|
|
|
// GenerateKey generates a new SLH-DSA key pair
|
|
func GenerateKey(rand io.Reader, mode Mode) (*PrivateKey, error) {
|
|
var pubKeySize, privKeySize int
|
|
|
|
switch mode {
|
|
case SLHDSA128s:
|
|
pubKeySize = SLHDSA128sPublicKeySize
|
|
privKeySize = SLHDSA128sPrivateKeySize
|
|
case SLHDSA128f:
|
|
pubKeySize = SLHDSA128fPublicKeySize
|
|
privKeySize = SLHDSA128fPrivateKeySize
|
|
case SLHDSA192s:
|
|
pubKeySize = SLHDSA192sPublicKeySize
|
|
privKeySize = SLHDSA192sPrivateKeySize
|
|
case SLHDSA192f:
|
|
pubKeySize = SLHDSA192fPublicKeySize
|
|
privKeySize = SLHDSA192fPrivateKeySize
|
|
case SLHDSA256s:
|
|
pubKeySize = SLHDSA256sPublicKeySize
|
|
privKeySize = SLHDSA256sPrivateKeySize
|
|
case SLHDSA256f:
|
|
pubKeySize = SLHDSA256fPublicKeySize
|
|
privKeySize = SLHDSA256fPrivateKeySize
|
|
default:
|
|
return nil, errors.New("invalid SLH-DSA mode")
|
|
}
|
|
|
|
// Check for nil random source
|
|
if rand == nil {
|
|
return nil, errors.New("random source is nil")
|
|
}
|
|
|
|
// Placeholder implementation - generate random keys
|
|
// In real SLH-DSA, public key is derived from private key
|
|
privBytes := make([]byte, privKeySize)
|
|
if _, err := io.ReadFull(rand, privBytes); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Derive public key from private key for consistency
|
|
h := sha256.New()
|
|
h.Write(privBytes[:32]) // Use first part as seed
|
|
h.Write([]byte("slhdsa-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
|
|
}
|
|
|
|
// Sign signs a message using the private key
|
|
func (priv *PrivateKey) Sign(rand io.Reader, message []byte, opts crypto.SignerOpts) ([]byte, error) {
|
|
if priv == nil {
|
|
return nil, errors.New("private key is nil")
|
|
}
|
|
|
|
var sigSize int
|
|
|
|
switch priv.PublicKey.mode {
|
|
case SLHDSA128s:
|
|
sigSize = SLHDSA128sSignatureSize
|
|
case SLHDSA128f:
|
|
sigSize = SLHDSA128fSignatureSize
|
|
case SLHDSA192s:
|
|
sigSize = SLHDSA192sSignatureSize
|
|
case SLHDSA192f:
|
|
sigSize = SLHDSA192fSignatureSize
|
|
case SLHDSA256s:
|
|
sigSize = SLHDSA256sSignatureSize
|
|
case SLHDSA256f:
|
|
sigSize = SLHDSA256fSignatureSize
|
|
default:
|
|
return nil, errors.New("invalid SLH-DSA mode")
|
|
}
|
|
|
|
// Placeholder: create deterministic signature that can be verified
|
|
// Start with hash of public key and message (what Verify expects)
|
|
h := sha256.New()
|
|
h.Write(priv.PublicKey.data)
|
|
h.Write(message)
|
|
hash := h.Sum(nil)
|
|
|
|
signature := make([]byte, sigSize)
|
|
// Copy the hash to beginning of signature
|
|
copy(signature[:32], hash)
|
|
|
|
// Fill rest with deterministic data based on private key
|
|
// SLH-DSA is stateless so signature should be deterministic
|
|
h.Reset()
|
|
h.Write(priv.data)
|
|
h.Write(message)
|
|
privHash := h.Sum(nil)
|
|
|
|
for i := 32; i < sigSize; i += len(privHash) {
|
|
end := i + len(privHash)
|
|
if end > sigSize {
|
|
end = sigSize
|
|
}
|
|
copy(signature[i:end], privHash)
|
|
h.Write(privHash) // Generate more data
|
|
privHash = h.Sum(nil)
|
|
}
|
|
|
|
return signature, nil
|
|
}
|
|
|
|
// Verify verifies a signature using the public key
|
|
func (pub *PublicKey) Verify(message, signature []byte, opts crypto.SignerOpts) bool {
|
|
if pub == nil {
|
|
return false
|
|
}
|
|
|
|
var expectedSigSize int
|
|
|
|
switch pub.mode {
|
|
case SLHDSA128s:
|
|
expectedSigSize = SLHDSA128sSignatureSize
|
|
case SLHDSA128f:
|
|
expectedSigSize = SLHDSA128fSignatureSize
|
|
case SLHDSA192s:
|
|
expectedSigSize = SLHDSA192sSignatureSize
|
|
case SLHDSA192f:
|
|
expectedSigSize = SLHDSA192fSignatureSize
|
|
case SLHDSA256s:
|
|
expectedSigSize = SLHDSA256sSignatureSize
|
|
case SLHDSA256f:
|
|
expectedSigSize = SLHDSA256fSignatureSize
|
|
default:
|
|
return false
|
|
}
|
|
|
|
// Check signature size
|
|
if len(signature) != expectedSigSize {
|
|
return false
|
|
}
|
|
|
|
// Placeholder verification for SLH-DSA
|
|
// Recompute expected signature start based on public key and message
|
|
h := sha256.New()
|
|
h.Write(pub.data)
|
|
h.Write(message)
|
|
expectedSigStart := h.Sum(nil)
|
|
|
|
// Check if first 32 bytes match
|
|
if len(signature) < 32 {
|
|
return false
|
|
}
|
|
|
|
// Compare first 32 bytes
|
|
for i := 0; i < 32; i++ {
|
|
if signature[i] != expectedSigStart[i] {
|
|
return false
|
|
}
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
// 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 SLHDSA128s, SLHDSA128f:
|
|
expectedSize = SLHDSA128sPublicKeySize
|
|
case SLHDSA192s, SLHDSA192f:
|
|
expectedSize = SLHDSA192sPublicKeySize
|
|
case SLHDSA256s, SLHDSA256f:
|
|
expectedSize = SLHDSA256sPublicKeySize
|
|
default:
|
|
return nil, errors.New("invalid SLH-DSA 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 expectedSize int
|
|
var pubKeySize int
|
|
|
|
switch mode {
|
|
case SLHDSA128s, SLHDSA128f:
|
|
expectedSize = SLHDSA128sPrivateKeySize
|
|
pubKeySize = SLHDSA128sPublicKeySize
|
|
case SLHDSA192s, SLHDSA192f:
|
|
expectedSize = SLHDSA192sPrivateKeySize
|
|
pubKeySize = SLHDSA192sPublicKeySize
|
|
case SLHDSA256s, SLHDSA256f:
|
|
expectedSize = SLHDSA256sPrivateKeySize
|
|
pubKeySize = SLHDSA256sPublicKeySize
|
|
default:
|
|
return nil, errors.New("invalid SLH-DSA mode")
|
|
}
|
|
|
|
if len(data) != expectedSize {
|
|
return nil, errors.New("invalid private key size")
|
|
}
|
|
|
|
// Derive public key from private key (same as GenerateKey)
|
|
h := sha256.New()
|
|
h.Write(data[:32]) // Use first part as seed
|
|
h.Write([]byte("slhdsa-public"))
|
|
pubSeed := h.Sum(nil)
|
|
|
|
pubData := 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(pubData[i:end], hash)
|
|
}
|
|
|
|
return &PrivateKey{
|
|
PublicKey: PublicKey{
|
|
mode: mode,
|
|
data: pubData,
|
|
},
|
|
data: data,
|
|
}, nil
|
|
}
|