Files
Zach Kelling 1ed73f84d7 fix: restore FHE Go implementation files
Restore core FHE Go files that were removed during cleanup.
These are required for precompile/fhe to build.
2026-01-03 19:11:38 -08:00

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
}