mirror of
https://github.com/luxfi/vm.git
synced 2026-10-30 15:13:40 +00:00
435 lines
11 KiB
Go
435 lines
11 KiB
Go
// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
package keychain
|
|
|
|
import (
|
|
"crypto"
|
|
"crypto/rand"
|
|
"crypto/sha256"
|
|
"errors"
|
|
"fmt"
|
|
|
|
// "github.com/luxfi/crypto/bls" // TODO: Implement BLS support
|
|
// "github.com/luxfi/crypto/mlkem" // TODO: Implement ML-KEM support
|
|
// "github.com/luxfi/crypto/corona" // TODO: Implement corona support
|
|
"github.com/luxfi/crypto/mldsa"
|
|
"github.com/luxfi/crypto/secp256k1"
|
|
"github.com/luxfi/crypto/slhdsa"
|
|
"github.com/luxfi/ids"
|
|
"github.com/luxfi/math/set"
|
|
)
|
|
|
|
var (
|
|
ErrInvalidKeyType = errors.New("invalid key type")
|
|
ErrKeyNotFound = errors.New("key not found")
|
|
)
|
|
|
|
// KeyType represents the type of cryptographic key
|
|
type KeyType uint8
|
|
|
|
const (
|
|
// Classical cryptography
|
|
KeyTypeSecp256k1 KeyType = iota
|
|
KeyTypeBLS // BLS signatures for consensus
|
|
|
|
// Post-quantum cryptography (NIST FIPS standards)
|
|
KeyTypeMLDSA44 // FIPS 204 - ML-DSA-44
|
|
KeyTypeMLDSA65 // FIPS 204 - ML-DSA-65
|
|
KeyTypeMLDSA87 // FIPS 204 - ML-DSA-87
|
|
KeyTypeSLHDSA128 // FIPS 205 - SLH-DSA-128
|
|
KeyTypeSLHDSA192 // FIPS 205 - SLH-DSA-192
|
|
KeyTypeSLHDSA256 // FIPS 205 - SLH-DSA-256
|
|
|
|
// Key encapsulation (FIPS 203)
|
|
KeyTypeMLKEM512 // ML-KEM-512
|
|
KeyTypeMLKEM768 // ML-KEM-768
|
|
KeyTypeMLKEM1024 // ML-KEM-1024
|
|
|
|
// Privacy-preserving
|
|
KeyTypeCorona // R-LWE threshold signatures
|
|
|
|
// Hybrid modes (classical + post-quantum)
|
|
KeyTypeHybridSecp256k1MLDSA44
|
|
KeyTypeHybridSecp256k1SLHDSA128
|
|
KeyTypeHybridBLSMLDSA44
|
|
)
|
|
|
|
// PQSigner implements Signer with post-quantum support
|
|
type PQSigner struct {
|
|
keyType KeyType
|
|
address ids.ShortID
|
|
|
|
// Classical keys
|
|
secp256k1Key *secp256k1.PrivateKey
|
|
|
|
// Post-quantum keys
|
|
mldsaKey interface{} // Can be *mldsa.PrivateKey44/65/87
|
|
slhdsaKey interface{} // Can be *slhdsa.PrivateKey128/192/256
|
|
// coronaKey *corona.PrivateKey // TODO: implement when available
|
|
|
|
// For hybrid modes, we store both
|
|
hybridClassical *secp256k1.PrivateKey
|
|
hybridPQ interface{}
|
|
}
|
|
|
|
// SignHash signs a hash with the appropriate algorithm
|
|
func (s *PQSigner) SignHash(hash []byte) ([]byte, error) {
|
|
switch s.keyType {
|
|
case KeyTypeSecp256k1:
|
|
if s.secp256k1Key == nil {
|
|
return nil, ErrInvalidKeyType
|
|
}
|
|
return s.secp256k1Key.SignHash(hash)
|
|
|
|
case KeyTypeMLDSA44, KeyTypeMLDSA65, KeyTypeMLDSA87:
|
|
if key, ok := s.mldsaKey.(*mldsa.PrivateKey); ok {
|
|
sig, err := key.Sign(rand.Reader, hash, crypto.Hash(0))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return sig, nil
|
|
}
|
|
return nil, ErrInvalidKeyType
|
|
|
|
case KeyTypeSLHDSA128, KeyTypeSLHDSA192, KeyTypeSLHDSA256:
|
|
if key, ok := s.slhdsaKey.(*slhdsa.PrivateKey); ok {
|
|
sig, err := key.Sign(rand.Reader, hash, crypto.Hash(0))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return sig, nil
|
|
}
|
|
return nil, ErrInvalidKeyType
|
|
|
|
// TODO: implement corona when available
|
|
// case KeyTypeCorona:
|
|
// if s.coronaKey == nil {
|
|
// return nil, ErrInvalidKeyType
|
|
// }
|
|
// // Corona requires ring members for signing
|
|
// // For now, we'll use a simple signature
|
|
// return s.coronaKey.Sign(hash), nil
|
|
|
|
case KeyTypeHybridSecp256k1MLDSA44:
|
|
// Hybrid mode: concatenate both signatures
|
|
if s.hybridClassical == nil || s.hybridPQ == nil {
|
|
return nil, ErrInvalidKeyType
|
|
}
|
|
|
|
classicalSig, err := s.hybridClassical.SignHash(hash)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if key, ok := s.hybridPQ.(*mldsa.PrivateKey); ok {
|
|
pqSig, err := key.Sign(rand.Reader, hash, crypto.Hash(0))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
// Concatenate signatures with length prefixes
|
|
result := make([]byte, 0, 2+len(classicalSig)+2+len(pqSig))
|
|
result = append(result, byte(len(classicalSig)>>8), byte(len(classicalSig)))
|
|
result = append(result, classicalSig...)
|
|
result = append(result, byte(len(pqSig)>>8), byte(len(pqSig)))
|
|
result = append(result, pqSig...)
|
|
return result, nil
|
|
}
|
|
return nil, ErrInvalidKeyType
|
|
|
|
default:
|
|
return nil, ErrInvalidKeyType
|
|
}
|
|
}
|
|
|
|
// Sign signs a message with the appropriate algorithm
|
|
func (s *PQSigner) Sign(msg []byte) ([]byte, error) {
|
|
switch s.keyType {
|
|
case KeyTypeSecp256k1:
|
|
// secp256k1 needs a 32-byte hash
|
|
if s.secp256k1Key == nil {
|
|
return nil, ErrInvalidKeyType
|
|
}
|
|
hash := sha256.New()
|
|
hash.Write(msg)
|
|
return s.secp256k1Key.SignHash(hash.Sum(nil))
|
|
|
|
case KeyTypeHybridSecp256k1MLDSA44, KeyTypeHybridSecp256k1SLHDSA128:
|
|
// For hybrid, we need to hash for the classical part
|
|
hash := sha256.New()
|
|
hash.Write(msg)
|
|
hashBytes := hash.Sum(nil)
|
|
|
|
// Sign with both algorithms
|
|
if s.hybridClassical == nil || s.hybridPQ == nil {
|
|
return nil, ErrInvalidKeyType
|
|
}
|
|
|
|
classicalSig, err := s.hybridClassical.SignHash(hashBytes)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var pqSig []byte
|
|
switch pq := s.hybridPQ.(type) {
|
|
case *mldsa.PrivateKey:
|
|
pqSig, err = pq.Sign(rand.Reader, msg, crypto.Hash(0))
|
|
case *slhdsa.PrivateKey:
|
|
pqSig, err = pq.Sign(rand.Reader, msg, crypto.Hash(0))
|
|
default:
|
|
return nil, ErrInvalidKeyType
|
|
}
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Concatenate signatures with length prefixes
|
|
result := make([]byte, 0, 2+len(classicalSig)+2+len(pqSig))
|
|
result = append(result, byte(len(classicalSig)>>8), byte(len(classicalSig)))
|
|
result = append(result, classicalSig...)
|
|
result = append(result, byte(len(pqSig)>>8), byte(len(pqSig)))
|
|
result = append(result, pqSig...)
|
|
return result, nil
|
|
|
|
default:
|
|
// PQ algorithms sign the message directly
|
|
return s.SignHash(msg)
|
|
}
|
|
}
|
|
|
|
// Address returns the address associated with this signer
|
|
func (s *PQSigner) Address() ids.ShortID {
|
|
return s.address
|
|
}
|
|
|
|
// PQKeychain implements Keychain with post-quantum support
|
|
type PQKeychain struct {
|
|
keysByAddress map[ids.ShortID]*PQSigner
|
|
addressSet set.Set[ids.ShortID]
|
|
defaultType KeyType
|
|
}
|
|
|
|
// NewPQKeychain creates a new post-quantum keychain
|
|
func NewPQKeychain(defaultType KeyType) *PQKeychain {
|
|
return &PQKeychain{
|
|
keysByAddress: make(map[ids.ShortID]*PQSigner),
|
|
addressSet: set.NewSet[ids.ShortID](0),
|
|
defaultType: defaultType,
|
|
}
|
|
}
|
|
|
|
// AddSecp256k1 adds a secp256k1 key to the keychain
|
|
func (kc *PQKeychain) AddSecp256k1(key *secp256k1.PrivateKey) ids.ShortID {
|
|
pk := key.PublicKey()
|
|
addr := pk.Address()
|
|
shortAddr, _ := ids.ToShortID(addr[:])
|
|
|
|
signer := &PQSigner{
|
|
keyType: KeyTypeSecp256k1,
|
|
address: shortAddr,
|
|
secp256k1Key: key,
|
|
}
|
|
|
|
kc.keysByAddress[shortAddr] = signer
|
|
kc.addressSet.Add(shortAddr)
|
|
return shortAddr
|
|
}
|
|
|
|
// AddMLDSA adds an ML-DSA key to the keychain
|
|
func (kc *PQKeychain) AddMLDSA(key *mldsa.PrivateKey, keyType KeyType) ids.ShortID {
|
|
// Generate address from public key bytes
|
|
pubKeyBytes := key.Bytes()
|
|
addrBytes := ids.ShortID{}
|
|
copy(addrBytes[:], pubKeyBytes[:20]) // Use first 20 bytes as address
|
|
|
|
signer := &PQSigner{
|
|
keyType: keyType,
|
|
address: addrBytes,
|
|
mldsaKey: key,
|
|
}
|
|
|
|
kc.keysByAddress[addrBytes] = signer
|
|
kc.addressSet.Add(addrBytes)
|
|
return addrBytes
|
|
}
|
|
|
|
// AddSLHDSA adds an SLH-DSA key to the keychain
|
|
func (kc *PQKeychain) AddSLHDSA(key *slhdsa.PrivateKey, keyType KeyType) ids.ShortID {
|
|
pubKeyBytes := key.Bytes()
|
|
addrBytes := ids.ShortID{}
|
|
copy(addrBytes[:], pubKeyBytes[:20])
|
|
|
|
signer := &PQSigner{
|
|
keyType: keyType,
|
|
address: addrBytes,
|
|
slhdsaKey: key,
|
|
}
|
|
|
|
kc.keysByAddress[addrBytes] = signer
|
|
kc.addressSet.Add(addrBytes)
|
|
return addrBytes
|
|
}
|
|
|
|
// TODO: implement when corona is available
|
|
// AddCorona adds a corona key to the keychain
|
|
// func (kc *PQKeychain) AddCorona(key *corona.PrivateKey) ids.ShortID {
|
|
// pubKey := key.PublicKey()
|
|
// addrBytes := ids.ShortID{}
|
|
// copy(addrBytes[:], pubKey.Bytes()[:20])
|
|
//
|
|
// signer := &PQSigner{
|
|
// keyType: KeyTypeCorona,
|
|
// address: addrBytes,
|
|
// coronaKey: key,
|
|
// }
|
|
//
|
|
// kc.keysByAddress[addrBytes] = signer
|
|
// kc.addressSet.Add(addrBytes)
|
|
// return addrBytes
|
|
// }
|
|
|
|
// AddHybrid adds a hybrid classical+PQ key pair
|
|
func (kc *PQKeychain) AddHybrid(classical *secp256k1.PrivateKey, pq interface{}) ids.ShortID {
|
|
// Generate address from classical key for compatibility
|
|
pk := classical.PublicKey()
|
|
addr := pk.Address()
|
|
shortAddr, _ := ids.ToShortID(addr[:])
|
|
|
|
var keyType KeyType
|
|
switch pq.(type) {
|
|
case *mldsa.PrivateKey:
|
|
keyType = KeyTypeHybridSecp256k1MLDSA44
|
|
case *slhdsa.PrivateKey:
|
|
keyType = KeyTypeHybridSecp256k1SLHDSA128
|
|
default:
|
|
return ids.ShortEmpty
|
|
}
|
|
|
|
signer := &PQSigner{
|
|
keyType: keyType,
|
|
address: shortAddr,
|
|
hybridClassical: classical,
|
|
hybridPQ: pq,
|
|
}
|
|
|
|
kc.keysByAddress[shortAddr] = signer
|
|
kc.addressSet.Add(shortAddr)
|
|
return shortAddr
|
|
}
|
|
|
|
// GenerateKey generates a new key of the default type
|
|
func (kc *PQKeychain) GenerateKey() (ids.ShortID, error) {
|
|
switch kc.defaultType {
|
|
case KeyTypeSecp256k1:
|
|
key, err := secp256k1.NewPrivateKey()
|
|
if err != nil {
|
|
return ids.ShortEmpty, err
|
|
}
|
|
return kc.AddSecp256k1(key), nil
|
|
|
|
case KeyTypeMLDSA44:
|
|
key, err := mldsa.GenerateKey(rand.Reader, mldsa.MLDSA44)
|
|
if err != nil {
|
|
return ids.ShortEmpty, err
|
|
}
|
|
return kc.AddMLDSA(key, KeyTypeMLDSA44), nil
|
|
|
|
case KeyTypeMLDSA65:
|
|
key, err := mldsa.GenerateKey(rand.Reader, mldsa.MLDSA65)
|
|
if err != nil {
|
|
return ids.ShortEmpty, err
|
|
}
|
|
return kc.AddMLDSA(key, KeyTypeMLDSA65), nil
|
|
|
|
case KeyTypeMLDSA87:
|
|
key, err := mldsa.GenerateKey(rand.Reader, mldsa.MLDSA87)
|
|
if err != nil {
|
|
return ids.ShortEmpty, err
|
|
}
|
|
return kc.AddMLDSA(key, KeyTypeMLDSA87), nil
|
|
|
|
case KeyTypeSLHDSA128:
|
|
key, err := slhdsa.GenerateKey(rand.Reader, slhdsa.SHA2_128s)
|
|
if err != nil {
|
|
return ids.ShortEmpty, err
|
|
}
|
|
return kc.AddSLHDSA(key, KeyTypeSLHDSA128), nil
|
|
|
|
case KeyTypeSLHDSA192:
|
|
key, err := slhdsa.GenerateKey(rand.Reader, slhdsa.SHA2_192s)
|
|
if err != nil {
|
|
return ids.ShortEmpty, err
|
|
}
|
|
return kc.AddSLHDSA(key, KeyTypeSLHDSA192), nil
|
|
|
|
case KeyTypeSLHDSA256:
|
|
key, err := slhdsa.GenerateKey(rand.Reader, slhdsa.SHA2_256s)
|
|
if err != nil {
|
|
return ids.ShortEmpty, err
|
|
}
|
|
return kc.AddSLHDSA(key, KeyTypeSLHDSA256), nil
|
|
|
|
// TODO: implement when corona is available
|
|
// case KeyTypeCorona:
|
|
// key, err := corona.GenerateKey(rand.Reader)
|
|
// if err != nil {
|
|
// return ids.ShortEmpty, err
|
|
// }
|
|
// return kc.AddCorona(key), nil
|
|
|
|
case KeyTypeHybridSecp256k1MLDSA44:
|
|
classical, err := secp256k1.NewPrivateKey()
|
|
if err != nil {
|
|
return ids.ShortEmpty, err
|
|
}
|
|
pq, err := mldsa.GenerateKey(rand.Reader, mldsa.MLDSA44)
|
|
if err != nil {
|
|
return ids.ShortEmpty, err
|
|
}
|
|
return kc.AddHybrid(classical, pq), nil
|
|
|
|
case KeyTypeHybridSecp256k1SLHDSA128:
|
|
classical, err := secp256k1.NewPrivateKey()
|
|
if err != nil {
|
|
return ids.ShortEmpty, err
|
|
}
|
|
pq, err := slhdsa.GenerateKey(rand.Reader, slhdsa.SHA2_128s)
|
|
if err != nil {
|
|
return ids.ShortEmpty, err
|
|
}
|
|
return kc.AddHybrid(classical, pq), nil
|
|
|
|
default:
|
|
return ids.ShortEmpty, fmt.Errorf("unsupported key type: %v", kc.defaultType)
|
|
}
|
|
}
|
|
|
|
// Addresses returns all addresses in the keychain
|
|
func (kc *PQKeychain) Addresses() []ids.ShortID {
|
|
addrs := make([]ids.ShortID, 0, kc.addressSet.Len())
|
|
for addr := range kc.addressSet {
|
|
addrs = append(addrs, addr)
|
|
}
|
|
return addrs
|
|
}
|
|
|
|
// Get returns the signer for the given address
|
|
func (kc *PQKeychain) Get(addr ids.ShortID) (Signer, bool) {
|
|
signer, exists := kc.keysByAddress[addr]
|
|
if !exists {
|
|
return nil, false
|
|
}
|
|
return signer, true
|
|
}
|
|
|
|
// GetPQSigner returns the PQ signer for advanced operations
|
|
func (kc *PQKeychain) GetPQSigner(addr ids.ShortID) (*PQSigner, bool) {
|
|
signer, exists := kc.keysByAddress[addr]
|
|
return signer, exists
|
|
}
|
|
|
|
// SetDefaultType sets the default key type for new keys
|
|
func (kc *PQKeychain) SetDefaultType(keyType KeyType) {
|
|
kc.defaultType = keyType
|
|
}
|