[circuits/float]: API improvements based on feedback

This commit is contained in:
Jean-Philippe Bossuat
2023-09-25 23:07:25 +02:00
parent affdeef37f
commit 3bbdfb3e50
7 changed files with 47 additions and 89 deletions

View File

@@ -19,7 +19,8 @@ type ComparisonEvaluator struct {
// This polynomial will define the internal precision of all computation performed by this evaluator and it can be obtained with the function
// GenMinimaxCompositePolynomialForSign.
//
// If no MinimaxCompositePolynomial is given, then it will use by default the variable DefaultMinimaxCompositePolynomialForSign.
// It is highly recommended to use GenMinimaxCompositePolynomialForSign to generate an approximation optimized for the circuit requiring comparisons.
// However, if no MinimaxCompositePolynomial is given, then it will use by default the variable DefaultMinimaxCompositePolynomialForSign.
// See the doc of DefaultMinimaxCompositePolynomialForSign for additional information about the capabilities of this approximation.
func NewComparisonEvaluator(eval *MinimaxCompositePolynomialEvaluator, signPoly ...MinimaxCompositePolynomial) *ComparisonEvaluator {

View File

@@ -12,7 +12,7 @@ import (
// EvaluatorForInverse defines a set of common and scheme agnostic
// method that are necessary to instantiate an InverseEvaluator.
type EvaluatorForInverse interface {
circuits.Evaluator
EvaluatorForMinimaxCompositePolynomial
SetScale(ct *rlwe.Ciphertext, scale rlwe.Scale) (err error)
}
@@ -21,41 +21,21 @@ type InverseEvaluator struct {
EvaluatorForInverse
*MinimaxCompositePolynomialEvaluator
circuits.Bootstrapper[rlwe.Ciphertext]
Parameters ckks.Parameters
Log2Min, Log2Max float64
SignMinimaxCompositePolynomial MinimaxCompositePolynomial
Parameters ckks.Parameters
}
// NewInverseEvaluator instantiates a new InverseEvaluator from an EvaluatorForInverse.
// This method is allocation free.
//
// The evaluator can be used to compute the inverse of values:
// EvaluateFullDomainNew: [-2^{log2max}, -2^{log2min}] U [2^{log2min}, 2^{log2max}]
// EvaluatePositiveDomainNew: [2^{log2min}, 2^{log2max}]
// EvaluateNegativeDomainNew: [-2^{log2max}, -2^{log2min}]
// GoldschmidtDivisionNew: [0, 2]
//
// A minimax composite polynomial (signMCP) for the sign function in the interval [-1-e, -2^{log2min}] U [2^{log2min}, 1+e]
// (where e is an upperbound on the scheme error) is required for the full domain inverse.
func NewInverseEvaluator(params ckks.Parameters, log2min, log2max float64, signMCP MinimaxCompositePolynomial, evalInv EvaluatorForInverse, evalPWF EvaluatorForMinimaxCompositePolynomial, btp circuits.Bootstrapper[rlwe.Ciphertext]) InverseEvaluator {
var MCPEval *MinimaxCompositePolynomialEvaluator
if evalPWF != nil {
MCPEval = NewMinimaxCompositePolynomialEvaluator(params, evalPWF, btp)
}
func NewInverseEvaluator(params ckks.Parameters, eval EvaluatorForInverse, btp circuits.Bootstrapper[rlwe.Ciphertext]) InverseEvaluator {
return InverseEvaluator{
EvaluatorForInverse: evalInv,
MinimaxCompositePolynomialEvaluator: MCPEval,
EvaluatorForInverse: eval,
MinimaxCompositePolynomialEvaluator: NewMinimaxCompositePolynomialEvaluator(params, eval, btp),
Bootstrapper: btp,
Parameters: params,
Log2Min: log2min,
Log2Max: log2max,
SignMinimaxCompositePolynomial: signMCP,
}
}
// EvaluateFullDomainNew computes 1/x for x in [-max, -min] U [min, max].
// EvaluateFullDomainNew computes 1/x for x in [-2^{log2max}, -2^{log2min}] U [2^{log2min}, 2^{log2max}].
// 1. Reduce the interval from [-max, -min] U [min, max] to [-1, -min] U [min, 1] by computing an approximate
// inverse c such that |c * x| <= 1. For |x| > 1, c tends to 1/x while for |x| < c tends to 1.
// This is done by using the work Efficient Homomorphic Evaluation on Large Intervals (https://eprint.iacr.org/2022/280.pdf).
@@ -63,43 +43,54 @@ func NewInverseEvaluator(params ckks.Parameters, log2min, log2max float64, signM
// 3. Compute y' = 1/(|c * x|) with the iterative Goldschmidt division algorithm.
// 4. Compute y = y' * c * sign(x * c)
//
// Note that the precision of sign(x * c) does not impact the circuit precision since this value ends up being both at
// The user can provide a minimax composite polynomial (signMinimaxPoly) for the sign function in the interval
// [-1-e, -2^{log2min}] U [2^{log2min}, 1+e] (where e is an upperbound on the scheme error).
// If no such polynomial is provided, then the DefaultMinimaxCompositePolynomialForSign is used by default.
// Note that the precision of the output of sign(x * c) does not impact the circuit precision since this value ends up being both at
// the numerator and denominator, thus cancelling itself.
func (eval InverseEvaluator) EvaluateFullDomainNew(ct *rlwe.Ciphertext) (cInv *rlwe.Ciphertext, err error) {
return eval.evaluateNew(ct, true)
func (eval InverseEvaluator) EvaluateFullDomainNew(ct *rlwe.Ciphertext, log2min, log2max float64, signMinimaxPoly ...MinimaxCompositePolynomial) (cInv *rlwe.Ciphertext, err error) {
var poly MinimaxCompositePolynomial
if len(signMinimaxPoly) == 1 {
poly = signMinimaxPoly[0]
} else {
poly = NewMinimaxCompositePolynomial(DefaultMinimaxCompositePolynomialForSign)
}
return eval.evaluateNew(ct, log2min, log2max, true, poly)
}
// EvaluatePositiveDomainNew computes 1/x for x in [min, max].
// EvaluatePositiveDomainNew computes 1/x for x in [2^{log2min}, 2^{log2max}].
// 1. Reduce the interval from [min, max] to [min, 1] by computing an approximate
// inverse c such that |c * x| <= 1. For |x| > 1, c tends to 1/x while for |x| < c tends to 1.
// This is done by using the work Efficient Homomorphic Evaluation on Large Intervals (https://eprint.iacr.org/2022/280.pdf).
// 2. Compute y' = 1/(c * x) with the iterative Goldschmidt division algorithm.
// 3. Compute y = y' * c
func (eval InverseEvaluator) EvaluatePositiveDomainNew(ct *rlwe.Ciphertext) (cInv *rlwe.Ciphertext, err error) {
return eval.evaluateNew(ct, false)
func (eval InverseEvaluator) EvaluatePositiveDomainNew(ct *rlwe.Ciphertext, log2min, log2max float64) (cInv *rlwe.Ciphertext, err error) {
return eval.evaluateNew(ct, log2min, log2max, false, nil)
}
// EvaluateNegativeDomainNew computes 1/x for x in [-max, -min].
// EvaluateNegativeDomainNew computes 1/x for x in [-2^{log2max}, -2^{log2min}].
// 1. Reduce the interval from [-max, -min] to [-1, -min] by computing an approximate
// inverse c such that |c * x| <= 1. For |x| > 1, c tends to 1/x while for |x| < c tends to 1.
// This is done by using the work Efficient Homomorphic Evaluation on Large Intervals (https://eprint.iacr.org/2022/280.pdf).
// 2. Compute y' = 1/(c * x) with the iterative Goldschmidt division algorithm.
// 3. Compute y = y' * c
func (eval InverseEvaluator) EvaluateNegativeDomainNew(ct *rlwe.Ciphertext) (cInv *rlwe.Ciphertext, err error) {
func (eval InverseEvaluator) EvaluateNegativeDomainNew(ct *rlwe.Ciphertext, log2min, log2max float64) (cInv *rlwe.Ciphertext, err error) {
var ctNeg *rlwe.Ciphertext
if ctNeg, err = eval.MulNew(ct, -1); err != nil {
return
}
if cInv, err = eval.EvaluatePositiveDomainNew(ctNeg); err != nil {
if cInv, err = eval.EvaluatePositiveDomainNew(ctNeg, log2min, log2max); err != nil {
return
}
return cInv, eval.Mul(cInv, -1, cInv)
}
func (eval InverseEvaluator) evaluateNew(ct *rlwe.Ciphertext, fulldomain bool) (cInv *rlwe.Ciphertext, err error) {
func (eval InverseEvaluator) evaluateNew(ct *rlwe.Ciphertext, log2min, log2max float64, fulldomain bool, signMinimaxPoly MinimaxCompositePolynomial) (cInv *rlwe.Ciphertext, err error) {
params := eval.Parameters
@@ -111,9 +102,9 @@ func (eval InverseEvaluator) evaluateNew(ct *rlwe.Ciphertext, fulldomain bool) (
// If max > 1, then normalizes the ciphertext interval from [-max, -min] U [min, max]
// to [-1, -min] U [min, 1], and returns the encrypted normalization factor.
if eval.Log2Max > 0 {
if log2max > 0 {
if cInv, normalizationfactor, err = eval.IntervalNormalization(ct, eval.Log2Max, btp); err != nil {
if cInv, normalizationfactor, err = eval.IntervalNormalization(ct, log2max, btp); err != nil {
return nil, fmt.Errorf("preprocessing: normalizationfactor: %w", err)
}
@@ -130,7 +121,7 @@ func (eval InverseEvaluator) evaluateNew(ct *rlwe.Ciphertext, fulldomain bool) (
}
// Computes the sign with precision [-1, -2^-a] U [2^-a, 1]
if sign, err = eval.MinimaxCompositePolynomialEvaluator.Evaluate(cInv, eval.SignMinimaxCompositePolynomial); err != nil {
if sign, err = eval.MinimaxCompositePolynomialEvaluator.Evaluate(cInv, signMinimaxPoly); err != nil {
return nil, fmt.Errorf("preprocessing: fulldomain: true -> sign: %w", err)
}
@@ -159,7 +150,7 @@ func (eval InverseEvaluator) evaluateNew(ct *rlwe.Ciphertext, fulldomain bool) (
}
// Computes the inverse of x in [min = 2^-a, 1]
if cInv, err = eval.GoldschmidtDivisionNew(cInv, eval.Log2Min); err != nil {
if cInv, err = eval.GoldschmidtDivisionNew(cInv, log2min); err != nil {
return nil, fmt.Errorf("division: GoldschmidtDivisionNew: %w", err)
}
@@ -212,15 +203,15 @@ func (eval InverseEvaluator) evaluateNew(ct *rlwe.Ciphertext, fulldomain bool) (
return cInv, nil
}
// GoldschmidtDivisionNew homomorphically computes 1/x.
// input: ct: Enc(x) with values in the interval [0+minvalue, 2-minvalue].
// GoldschmidtDivisionNew homomorphically computes 1/x in the domain [0, 2].
// input: ct: Enc(x) with values in the interval [0+2^{-log2min}, 2-2^{-log2min}].
// output: Enc(1/x - e), where |e| <= (1-x)^2^(#iterations+1) -> the bit-precision doubles after each iteration.
// This method automatically estimates how many iterations are needed to
// achieve the optimal precision, which is derived from the plaintext scale.
// This method will return an error if the input ciphertext does not have enough
// remaining level and if the InverseEvaluator was instantiated with no bootstrapper.
// This method will return an error if something goes wrong with the bootstrapping or the rescaling operations.
func (eval InverseEvaluator) GoldschmidtDivisionNew(ct *rlwe.Ciphertext, log2Min float64) (ctInv *rlwe.Ciphertext, err error) {
func (eval InverseEvaluator) GoldschmidtDivisionNew(ct *rlwe.Ciphertext, log2min float64) (ctInv *rlwe.Ciphertext, err error) {
btp := eval.Bootstrapper
@@ -230,7 +221,7 @@ func (eval InverseEvaluator) GoldschmidtDivisionNew(ct *rlwe.Ciphertext, log2Min
prec := float64(params.N()/2) / ct.Scale.Float64()
// Estimates the number of iterations required to achieve the desired precision, given the interval [min, 2-min]
start := 1 - math.Exp2(log2Min)
start := 1 - math.Exp2(log2min)
var iters = 1
for start >= prec {
start *= start // Doubles the bit-precision at each iteration

View File

@@ -42,8 +42,6 @@ func TestInverse(t *testing.T) {
btp := bootstrapper.NewSecretKeyBootstrapper(params, sk)
minimaxpolysign := float.NewMinimaxCompositePolynomial(float.DefaultMinimaxCompositePolynomialForSign)
logmin := -30.0
logmax := 10.0
@@ -60,8 +58,7 @@ func TestInverse(t *testing.T) {
evk := rlwe.NewMemEvaluationKeySet(kgen.GenRelinearizationKeyNew(sk), galKeys...)
evalInverse := tc.evaluator.WithKey(evk)
evalMinimaxPoly := evalInverse
eval := tc.evaluator.WithKey(evk)
t.Run(GetTestName(params, "GoldschmidtDivisionNew"), func(t *testing.T) {
@@ -72,7 +69,7 @@ func TestInverse(t *testing.T) {
values[i][0].Quo(one, values[i][0])
}
invEval := float.NewInverseEvaluator(params, logmin, logmax, nil, evalInverse, nil, btp)
invEval := float.NewInverseEvaluator(params, eval, btp)
var err error
if ciphertext, err = invEval.GoldschmidtDivisionNew(ciphertext, logmin); err != nil {
@@ -86,9 +83,9 @@ func TestInverse(t *testing.T) {
values, _, ct := newCKKSTestVectors(tc, enc, complex(0, 0), complex(max, 0), t)
invEval := float.NewInverseEvaluator(params, logmin, logmax, nil, evalInverse, nil, btp)
invEval := float.NewInverseEvaluator(params, eval, btp)
cInv, err := invEval.EvaluatePositiveDomainNew(ct)
cInv, err := invEval.EvaluatePositiveDomainNew(ct, logmin, logmax)
require.NoError(t, err)
have := make([]*big.Float, params.MaxSlots())
@@ -113,9 +110,9 @@ func TestInverse(t *testing.T) {
values, _, ct := newCKKSTestVectors(tc, enc, complex(-max, 0), complex(0, 0), t)
invEval := float.NewInverseEvaluator(params, logmin, logmax, nil, evalInverse, nil, btp)
invEval := float.NewInverseEvaluator(params, eval, btp)
cInv, err := invEval.EvaluateNegativeDomainNew(ct)
cInv, err := invEval.EvaluateNegativeDomainNew(ct, logmin, logmax)
require.NoError(t, err)
have := make([]*big.Float, params.MaxSlots())
@@ -140,9 +137,9 @@ func TestInverse(t *testing.T) {
values, _, ct := newCKKSTestVectors(tc, enc, complex(-max, 0), complex(max, 0), t)
invEval := float.NewInverseEvaluator(params, logmin, logmax, minimaxpolysign, evalInverse, evalMinimaxPoly, btp)
invEval := float.NewInverseEvaluator(params, eval, btp)
cInv, err := invEval.EvaluateFullDomainNew(ct)
cInv, err := invEval.EvaluateFullDomainNew(ct, logmin, logmax, float.NewMinimaxCompositePolynomial(float.DefaultMinimaxCompositePolynomialForSign))
require.NoError(t, err)
have := make([]*big.Float, params.MaxSlots())

View File

@@ -19,7 +19,7 @@ type EvaluatorForMinimaxCompositePolynomial interface {
// MinimaxCompositePolynomialEvaluator is an evaluator used to evaluate composite polynomials on ciphertexts.
type MinimaxCompositePolynomialEvaluator struct {
EvaluatorForMinimaxCompositePolynomial
*PolynomialEvaluator
PolynomialEvaluator
circuits.Bootstrapper[rlwe.Ciphertext]
Parameters ckks.Parameters
}
@@ -27,7 +27,7 @@ type MinimaxCompositePolynomialEvaluator struct {
// NewMinimaxCompositePolynomialEvaluator instantiates a new MinimaxCompositePolynomialEvaluator.
// This method is allocation free.
func NewMinimaxCompositePolynomialEvaluator(params ckks.Parameters, eval EvaluatorForMinimaxCompositePolynomial, bootstrapper circuits.Bootstrapper[rlwe.Ciphertext]) *MinimaxCompositePolynomialEvaluator {
return &MinimaxCompositePolynomialEvaluator{eval, NewPolynomialEvaluator(params, eval), bootstrapper, params}
return &MinimaxCompositePolynomialEvaluator{eval, *NewPolynomialEvaluator(params, eval), bootstrapper, params}
}
func (eval MinimaxCompositePolynomialEvaluator) Evaluate(ct *rlwe.Ciphertext, mcp MinimaxCompositePolynomial) (res *rlwe.Ciphertext, err error) {

View File

@@ -32,15 +32,6 @@ func NewPolynomialEvaluator(params ckks.Parameters, eval circuits.Evaluator) *Po
}
}
// NewCustomPolynomialEvaluator instantiates a new PolynomialEvaluator from a circuit.EvaluatorForPolynomial.
// This constructor is primarily indented for custom implementations.
func NewCustomPolynomialEvaluator(params ckks.Parameters, eval circuits.EvaluatorForPolynomial) *PolynomialEvaluator {
return &PolynomialEvaluator{
Parameters: params,
EvaluatorForPolynomial: eval,
}
}
// Evaluate evaluates a polynomial on the input Ciphertext in ceil(log2(deg+1)) levels.
// Returns an error if the input ciphertext does not have enough level to carry out the full polynomial evaluation.
// Returns an error if something is wrong with the scale.

View File

@@ -72,16 +72,6 @@ func NewLinearTransformationEvaluator(eval circuits.EvaluatorForLinearTransforma
}
}
// NewCustomLinearTransformationEvaluator instantiates a new LinearTransformationEvaluator from a
// circuits.EvaluatorForLinearTransformation and circuits.EvaluatorForDiagonalMatrix.
// This constructor is primarily indented for custom implementations.
func NewCustomLinearTransformationEvaluator(evalLT circuits.EvaluatorForLinearTransformation, evalMat circuits.EvaluatorForDiagonalMatrix) (linTransEval *LinearTransformationEvaluator) {
return &LinearTransformationEvaluator{
EvaluatorForLinearTransformation: evalLT,
EvaluatorForDiagonalMatrix: evalMat,
}
}
// EvaluateNew takes as input a ciphertext ctIn and a linear transformation M and evaluate and returns opOut: M(ctIn).
func (eval LinearTransformationEvaluator) EvaluateNew(ctIn *rlwe.Ciphertext, linearTransformation LinearTransformation) (opOut *rlwe.Ciphertext, err error) {
ops, err := eval.EvaluateManyNew(ctIn, []LinearTransformation{linearTransformation})

View File

@@ -56,18 +56,6 @@ func NewPolynomialEvaluator(params bgv.Parameters, eval circuits.Evaluator, Inva
}
}
// NewCustomPolynomialEvaluator instantiates a new PolynomialEvaluator from a circuit.EvaluatorForPolynomial.
// This constructor is primarily indented for custom implementations.
// InvariantTensoring is a boolean that specifies if the evaluator performes the invariant tensoring (BFV-style) or
// the regular tensoring (BGB-style).
func NewCustomPolynomialEvaluator(params bgv.Parameters, eval circuits.EvaluatorForPolynomial, InvariantTensoring bool) *PolynomialEvaluator {
return &PolynomialEvaluator{
Parameters: params,
EvaluatorForPolynomial: eval,
InvariantTensoring: InvariantTensoring,
}
}
// Evaluate evaluates a polynomial on the input Ciphertext in ceil(log2(deg+1)) levels.
// Returns an error if the input ciphertext does not have enough level to carry out the full polynomial evaluation.
// Returns an error if something is wrong with the scale.