mirror of
https://github.com/luxfi/fhe.git
synced 2026-07-26 23:16:08 +00:00
security: fix FHE encryptor panic and NTT timing side-channel
F13: Add EncryptSafe(bool) (*Ciphertext, error) that returns errors instead of panicking. Existing Encrypt(bool) is preserved but deprecated -- it now calls EncryptSafe internally. F07: Remove data-dependent branch (if hi == 0) from mulModBarrett. Always execute the full 128-bit reduction path to eliminate timing side-channel.
This commit is contained in:
+17
-5
@@ -4,6 +4,8 @@
|
|||||||
package fhe
|
package fhe
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"github.com/luxfi/lattice/v7/core/rlwe"
|
"github.com/luxfi/lattice/v7/core/rlwe"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -23,9 +25,9 @@ func NewEncryptor(params Parameters, sk *SecretKey) *Encryptor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Encrypt encrypts a boolean value
|
// EncryptSafe encrypts a boolean value, returning an error on failure.
|
||||||
// Note: Panics on error (should not happen with valid parameters)
|
// Prefer this over Encrypt for production code paths.
|
||||||
func (enc *Encryptor) Encrypt(value bool) *Ciphertext {
|
func (enc *Encryptor) EncryptSafe(value bool) (*Ciphertext, error) {
|
||||||
pt := rlwe.NewPlaintext(enc.params.paramsLWE, enc.params.paramsLWE.MaxLevel())
|
pt := rlwe.NewPlaintext(enc.params.paramsLWE, enc.params.paramsLWE.MaxLevel())
|
||||||
|
|
||||||
q := enc.params.QLWE()
|
q := enc.params.QLWE()
|
||||||
@@ -46,10 +48,20 @@ func (enc *Encryptor) Encrypt(value bool) *Ciphertext {
|
|||||||
|
|
||||||
ct := rlwe.NewCiphertext(enc.params.paramsLWE, 1, enc.params.paramsLWE.MaxLevel())
|
ct := rlwe.NewCiphertext(enc.params.paramsLWE, 1, enc.params.paramsLWE.MaxLevel())
|
||||||
if err := enc.encryptor.Encrypt(pt, ct); err != nil {
|
if err := enc.encryptor.Encrypt(pt, ct); err != nil {
|
||||||
panic(err) // Should not happen with valid parameters
|
return nil, fmt.Errorf("fhe encrypt: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return &Ciphertext{ct}
|
return &Ciphertext{ct}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encrypt encrypts a boolean value.
|
||||||
|
// Deprecated: Use EncryptSafe for production code. This method panics on error.
|
||||||
|
func (enc *Encryptor) Encrypt(value bool) *Ciphertext {
|
||||||
|
ct, err := enc.EncryptSafe(value)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return ct
|
||||||
}
|
}
|
||||||
|
|
||||||
// EncryptBit is an alias for Encrypt
|
// EncryptBit is an alias for Encrypt
|
||||||
|
|||||||
+5
-7
@@ -333,15 +333,13 @@ func (e *NTTEngine) mulMod(a, b uint64) uint64 {
|
|||||||
return div128(hi, lo, e.Q)
|
return div128(hi, lo, e.Q)
|
||||||
}
|
}
|
||||||
|
|
||||||
// mulModBarrett computes (a * b) mod Q using Barrett reduction
|
// mulModBarrett computes (a * b) mod Q using Barrett reduction.
|
||||||
// For Q up to ~58 bits, we use a two-word Barrett with mu = floor(2^(2k)/Q)
|
// Constant-time: always executes the full 128-bit path regardless of input.
|
||||||
// where k = number of bits in Q. This gives enough precision for the quotient.
|
|
||||||
func (e *NTTEngine) mulModBarrett(a, b uint64) uint64 {
|
func (e *NTTEngine) mulModBarrett(a, b uint64) uint64 {
|
||||||
hi, lo := mul64(a, b)
|
hi, lo := mul64(a, b)
|
||||||
if hi == 0 {
|
// Always execute the full path to avoid timing side-channel on hi == 0
|
||||||
return lo % e.Q
|
_, r := bits.Div64(hi%e.Q, lo, e.Q)
|
||||||
}
|
return r
|
||||||
return div128(hi, lo, e.Q)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// mul64 multiplies two 64-bit integers and returns 128-bit result as (hi, lo)
|
// mul64 multiplies two 64-bit integers and returns 128-bit result as (hi, lo)
|
||||||
|
|||||||
Reference in New Issue
Block a user