Files
zeekay eaf864890e feat: release v7.0.0 with GPU acceleration and module update
- Update module path to github.com/luxfi/lattice/v7
- Add GPU acceleration with Metal (macOS) and CUDA backends
- Enhance threshold FHE operations
- Revert to vanilla Apache 2.0 license (remove tfhe)
- Update Go version to 1.25.5
- Merge upstream lattigo improvements
2025-12-30 15:37:37 -08:00

189 lines
5.4 KiB
Go

package inverse
import (
"flag"
"fmt"
"math"
"math/big"
"testing"
"github.com/stretchr/testify/require"
"github.com/luxfi/lattice/v7/circuits/ckks/bootstrapping"
"github.com/luxfi/lattice/v7/circuits/ckks/comparison"
"github.com/luxfi/lattice/v7/circuits/ckks/minimax"
"github.com/luxfi/lattice/v7/core/rlwe"
"github.com/luxfi/lattice/v7/ring"
"github.com/luxfi/lattice/v7/schemes/ckks"
"github.com/luxfi/lattice/v7/utils/bignum"
)
var printPrecisionStats = flag.Bool("print-precision", false, "print precision stats")
func TestInverse(t *testing.T) {
paramsLiteral := testInsecurePrec90
for _, ringType := range []ring.Type{ring.Standard, ring.ConjugateInvariant} {
paramsLiteral.RingType = ringType
// paramsLiteral.LogN = 10
tc := ckks.NewTestContext(paramsLiteral)
btp := bootstrapping.NewSecretKeyBootstrapper(tc.Params, tc.Sk)
logmin := -30.0
logmax := 10.0
// 2^{-r}
min := math.Exp2(float64(logmin))
// 2^{r}
max := math.Exp2(float64(logmax))
var galKeys []*rlwe.GaloisKey
if tc.Params.RingType() == ring.Standard {
galKeys = append(galKeys, tc.Kgen.GenGaloisKeyNew(tc.Params.GaloisElementForComplexConjugation(), tc.Sk))
}
evk := rlwe.NewMemEvaluationKeySet(tc.Kgen.GenRelinearizationKeyNew(tc.Sk), galKeys...)
eval := tc.Evl.WithKey(evk)
t.Run(name("GoldschmidtDivisionNew", tc), func(t *testing.T) {
t.Parallel()
values, _, ciphertext := tc.NewTestVector(complex(min, 0), complex(2-min, 0))
one := new(big.Float).SetInt64(1)
for i := range values {
values[i][0].Quo(one, values[i][0])
}
minEvl := minimax.NewEvaluator(tc.Params, eval, btp)
invEval := NewEvaluator(tc.Params, minEvl)
var err error
if ciphertext, err = invEval.GoldschmidtDivisionNew(ciphertext, logmin); err != nil {
t.Fatal(err)
}
ckks.VerifyTestVectors(tc.Params, tc.Ecd, tc.Dec, values, ciphertext, 70, 0, *printPrecisionStats, t)
})
t.Run(name("PositiveDomain", tc), func(t *testing.T) {
t.Parallel()
eval := tc.Evl.WithKey(evk)
values, _, ct := tc.NewTestVector(complex(0, 0), complex(max, 0))
minEvl := minimax.NewEvaluator(tc.Params, eval, btp)
invEval := NewEvaluator(tc.Params, minEvl)
cInv, err := invEval.EvaluatePositiveDomainNew(ct, logmin, logmax)
require.NoError(t, err)
have := make([]*big.Float, tc.Params.MaxSlots())
require.NoError(t, tc.Ecd.Decode(tc.Dec.DecryptNew(cInv), have))
want := make([]*big.Float, tc.Params.MaxSlots())
threshold := bignum.NewFloat(min, tc.Params.EncodingPrecision())
for i := range have {
if new(big.Float).Abs(values[i][0]).Cmp(threshold) == -1 {
want[i] = have[i] // Ignores values outside of the interval
} else {
want[i] = new(big.Float).Quo(bignum.NewFloat(1, tc.Params.EncodingPrecision()), values[i][0])
}
}
ckks.VerifyTestVectors(tc.Params, tc.Ecd, nil, want, have, 70, 0, *printPrecisionStats, t)
})
t.Run(name("NegativeDomain", tc), func(t *testing.T) {
t.Parallel()
eval := tc.Evl.WithKey(evk)
values, _, ct := tc.NewTestVector(complex(-max, 0), complex(0, 0))
minEvl := minimax.NewEvaluator(tc.Params, eval, btp)
invEval := NewEvaluator(tc.Params, minEvl)
cInv, err := invEval.EvaluateNegativeDomainNew(ct, logmin, logmax)
require.NoError(t, err)
have := make([]*big.Float, tc.Params.MaxSlots())
require.NoError(t, tc.Ecd.Decode(tc.Dec.DecryptNew(cInv), have))
want := make([]*big.Float, tc.Params.MaxSlots())
threshold := bignum.NewFloat(min, tc.Params.EncodingPrecision())
for i := range have {
if new(big.Float).Abs(values[i][0]).Cmp(threshold) == -1 {
want[i] = have[i] // Ignores values outside of the interval
} else {
want[i] = new(big.Float).Quo(bignum.NewFloat(1, tc.Params.EncodingPrecision()), values[i][0])
}
}
ckks.VerifyTestVectors(tc.Params, tc.Ecd, nil, want, have, 70, 0, *printPrecisionStats, t)
})
t.Run(name("FullDomain", tc), func(t *testing.T) {
t.Parallel()
values, _, ct := tc.NewTestVector(complex(-max, 0), complex(max, 0))
minEvl := minimax.NewEvaluator(tc.Params, eval, btp)
invEval := NewEvaluator(tc.Params, minEvl)
cInv, err := invEval.EvaluateFullDomainNew(ct, logmin, logmax, minimax.NewPolynomial(comparison.DefaultCompositePolynomialForSign))
require.NoError(t, err)
have := make([]*big.Float, tc.Params.MaxSlots())
require.NoError(t, tc.Ecd.Decode(tc.Dec.DecryptNew(cInv), have))
want := make([]*big.Float, tc.Params.MaxSlots())
threshold := bignum.NewFloat(min, tc.Params.EncodingPrecision())
for i := range have {
if new(big.Float).Abs(values[i][0]).Cmp(threshold) == -1 {
want[i] = have[i] // Ignores values outside of the interval
} else {
want[i] = new(big.Float).Quo(bignum.NewFloat(1, tc.Params.EncodingPrecision()), values[i][0])
}
}
ckks.VerifyTestVectors(tc.Params, tc.Ecd, nil, want, have, 70, 0, *printPrecisionStats, t)
})
}
}
func name(opname string, tc *ckks.TestContext) string {
return fmt.Sprintf("%s/RingType=%s/logN=%d/logQP=%d/Qi=%d/Pi=%d/LogScale=%d",
opname,
tc.Params.RingType(),
tc.Params.LogN(),
int(math.Round(tc.Params.LogQP())),
tc.Params.QCount(),
tc.Params.PCount(),
int(math.Log2(tc.Params.DefaultScale().Float64())))
}
// testInsecurePrec90 are insecure parameters used for the sole purpose of fast testing.
var testInsecurePrec90 = ckks.ParametersLiteral{
LogN: 10,
LogQ: []int{55, 55, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45},
LogP: []int{60, 60},
LogDefaultScale: 90,
}