mirror of
https://github.com/luxfi/fhe.git
synced 2026-07-26 23:16:08 +00:00
Restore core FHE Go files that were removed during cleanup. These are required for precompile/fhe to build.
189 lines
5.1 KiB
Go
189 lines
5.1 KiB
Go
// Copyright (c) 2025, Lux Industries Inc
|
|
// SPDX-License-Identifier: BSD-3-Clause
|
|
|
|
package fhe
|
|
|
|
import (
|
|
"crypto/sha256"
|
|
"encoding/binary"
|
|
"fmt"
|
|
)
|
|
|
|
// FheRNG generates encrypted random numbers for FHE computations.
|
|
// Uses a deterministic PRNG seeded with a nonce for consensus compatibility.
|
|
// The generated random values are encrypted and remain hidden until decryption.
|
|
type FheRNG struct {
|
|
params Parameters
|
|
enc *BitwiseEncryptor
|
|
state [32]byte // SHA256 state
|
|
counter uint64
|
|
}
|
|
|
|
// NewFheRNG creates a new encrypted random number generator.
|
|
// The seed should be derived from blockchain state (e.g., block hash + tx hash)
|
|
// to ensure deterministic but unpredictable randomness.
|
|
func NewFheRNG(params Parameters, sk *SecretKey, seed []byte) *FheRNG {
|
|
state := sha256.Sum256(seed)
|
|
return &FheRNG{
|
|
params: params,
|
|
enc: NewBitwiseEncryptor(params, sk),
|
|
state: state,
|
|
counter: 0,
|
|
}
|
|
}
|
|
|
|
// FheRNGPublic generates encrypted random numbers using public key encryption.
|
|
// This allows the RNG to run on nodes that don't have access to the secret key.
|
|
type FheRNGPublic struct {
|
|
params Parameters
|
|
enc *BitwisePublicEncryptor
|
|
state [32]byte
|
|
counter uint64
|
|
}
|
|
|
|
// NewFheRNGPublic creates a new encrypted random number generator using public key.
|
|
func NewFheRNGPublic(params Parameters, pk *PublicKey, seed []byte) *FheRNGPublic {
|
|
state := sha256.Sum256(seed)
|
|
return &FheRNGPublic{
|
|
params: params,
|
|
enc: NewBitwisePublicEncryptor(params, pk),
|
|
state: state,
|
|
counter: 0,
|
|
}
|
|
}
|
|
|
|
// advance advances the PRNG state and returns the next 32 bytes of randomness
|
|
func (rng *FheRNG) advance() [32]byte {
|
|
// Combine state with counter
|
|
var data [40]byte
|
|
copy(data[:32], rng.state[:])
|
|
binary.LittleEndian.PutUint64(data[32:], rng.counter)
|
|
rng.counter++
|
|
|
|
// Update state
|
|
rng.state = sha256.Sum256(data[:])
|
|
return rng.state
|
|
}
|
|
|
|
// advance advances the public RNG state
|
|
func (rng *FheRNGPublic) advance() [32]byte {
|
|
var data [40]byte
|
|
copy(data[:32], rng.state[:])
|
|
binary.LittleEndian.PutUint64(data[32:], rng.counter)
|
|
rng.counter++
|
|
rng.state = sha256.Sum256(data[:])
|
|
return rng.state
|
|
}
|
|
|
|
// RandomBit generates a single encrypted random bit
|
|
// Note: Uses secret key encryption which cannot fail with valid parameters
|
|
func (rng *FheRNG) RandomBit() *Ciphertext {
|
|
random := rng.advance()
|
|
bit := (random[0] & 1) == 1
|
|
return rng.enc.enc.Encrypt(bit)
|
|
}
|
|
|
|
// RandomBit generates a single encrypted random bit using public key
|
|
func (rng *FheRNGPublic) RandomBit() (*Ciphertext, error) {
|
|
random := rng.advance()
|
|
bit := (random[0] & 1) == 1
|
|
ct, err := rng.enc.Encrypt(bit)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("random bit encrypt: %w", err)
|
|
}
|
|
return ct, nil
|
|
}
|
|
|
|
// RandomUint generates an encrypted random integer of the specified type
|
|
// Note: Uses secret key encryption which cannot fail with valid parameters
|
|
func (rng *FheRNG) RandomUint(t FheUintType) *BitCiphertext {
|
|
numBits := t.NumBits()
|
|
bits := make([]*Ciphertext, numBits)
|
|
|
|
// Get enough random bytes
|
|
bytesNeeded := (numBits + 7) / 8
|
|
var randomBytes []byte
|
|
|
|
for len(randomBytes) < bytesNeeded {
|
|
random := rng.advance()
|
|
randomBytes = append(randomBytes, random[:]...)
|
|
}
|
|
|
|
// Encrypt each bit
|
|
for i := 0; i < numBits; i++ {
|
|
byteIdx := i / 8
|
|
bitIdx := i % 8
|
|
bit := (randomBytes[byteIdx] >> bitIdx) & 1
|
|
bits[i] = rng.enc.enc.Encrypt(bit == 1)
|
|
}
|
|
|
|
return &BitCiphertext{
|
|
bits: bits,
|
|
numBits: numBits,
|
|
fheType: t,
|
|
}
|
|
}
|
|
|
|
// RandomUint generates an encrypted random integer using public key
|
|
func (rng *FheRNGPublic) RandomUint(t FheUintType) (*BitCiphertext, error) {
|
|
numBits := t.NumBits()
|
|
bits := make([]*Ciphertext, numBits)
|
|
|
|
bytesNeeded := (numBits + 7) / 8
|
|
var randomBytes []byte
|
|
|
|
for len(randomBytes) < bytesNeeded {
|
|
random := rng.advance()
|
|
randomBytes = append(randomBytes, random[:]...)
|
|
}
|
|
|
|
for i := 0; i < numBits; i++ {
|
|
byteIdx := i / 8
|
|
bitIdx := i % 8
|
|
bit := (randomBytes[byteIdx] >> bitIdx) & 1
|
|
ct, err := rng.enc.Encrypt(bit == 1)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("random uint encrypt bit %d: %w", i, err)
|
|
}
|
|
bits[i] = ct
|
|
}
|
|
|
|
return &BitCiphertext{
|
|
bits: bits,
|
|
numBits: numBits,
|
|
fheType: t,
|
|
}, nil
|
|
}
|
|
|
|
// RandomBounded generates an encrypted random integer in range [0, bound)
|
|
// Uses rejection sampling to ensure uniform distribution
|
|
// Note: This reveals the number of attempts but not the final value
|
|
func (rng *FheRNG) RandomBounded(t FheUintType, bound uint64) *BitCiphertext {
|
|
// For now, just generate a random value and let the caller handle modular reduction
|
|
// True rejection sampling would require homomorphic comparison which is expensive
|
|
// The caller can use eval.Mod() or similar operations if needed
|
|
return rng.RandomUint(t)
|
|
}
|
|
|
|
// Counter returns the current RNG counter (for verification/debugging)
|
|
func (rng *FheRNG) Counter() uint64 {
|
|
return rng.counter
|
|
}
|
|
|
|
// Counter returns the current RNG counter
|
|
func (rng *FheRNGPublic) Counter() uint64 {
|
|
return rng.counter
|
|
}
|
|
|
|
// Reseed reseeds the RNG with a new seed
|
|
func (rng *FheRNG) Reseed(seed []byte) {
|
|
rng.state = sha256.Sum256(seed)
|
|
rng.counter = 0
|
|
}
|
|
|
|
// Reseed reseeds the public RNG with a new seed
|
|
func (rng *FheRNGPublic) Reseed(seed []byte) {
|
|
rng.state = sha256.Sum256(seed)
|
|
rng.counter = 0
|
|
}
|