Files
lattigo/core/rlwe/scale.go
Romain Bouyé e9e04b1445 applying lattigo-e patch
CHANGELOG:
- Update of `PrecisionStats` in `ckks/precision.go`:
  - precision/error stats computed as log2 of min/max/average/...
  - fields renamed (`MinPrecision` -> `MINLog2Prec`, `MaxPrecision` -> `MAXLog2Prec`, ...)
  - `rlwe.Scale` has a `.Log2()` method
- Update of `mod1.Parameters` fields (made public, some removed)
- Improvement of the relinearization key-generation protocol (reduce the degree of the shares)
- Serialisation of bootstrapping keys
- Lower noise incurred by `ModUp`
- Evaluation keys can be compressed (public element `a` can be generated from a seed)
- More doc formatting
- Fix various bugs:
  - `ShallowCopy` of the CKKS bootstrapping evaluator and BFV evaluator not deep enough.
  - PSI example failing
  - Incorrect reset of pointer in uniform sampler
  - Error when doing inverse NTT with small degree
  - Mod1Evaluator changes the input ciphertext

Co-authored-by: Andrea Caforio <andrea.caforio@protonmail.com>
Co-authored-by: Jean-Philippe Bossuat <jean-philippe@tuneinsight.com>
2024-10-07 11:46:55 +02:00

304 lines
7.6 KiB
Go

package rlwe
import (
"encoding/json"
"fmt"
"math"
"math/big"
"github.com/tuneinsight/lattigo/v6/utils/bignum"
)
const (
// ScalePrecision is the default precision of the scale.
ScalePrecision = uint(128)
)
var ScalePrecisionLog10 = int(math.Ceil(float64(ScalePrecision) / math.Log2(10)))
// Scale is a struct used to track the scaling factor
// of [Plaintext] and [Ciphertext] structs.
// The scale is managed as an 128-bit precision real and can
// be either a floating point value or a mod T
// prime integer, which is determined at instantiation.
type Scale struct {
Value big.Float //`json:",omitempty"`
Mod *big.Int //`json:",omitempty"`
}
// NewScale instantiates a new floating point [Scale].
// Accepted types for s are int, int64, uint64, float64, *[big.Int], *[big.Float] and *[Scale].
// If the input type is not an accepted type, returns an error.
func NewScale(s interface{}) Scale {
v := scaleToBigFloat(s)
return Scale{Value: *v}
}
// NewScaleModT instantiates a new integer mod T [Scale].
// Accepted types for s are int, int64, uint64, float64, *[big.Int], *[big.Float] and *[Scale].
// If the input type is not an accepted type, returns an error.
func NewScaleModT(s interface{}, mod uint64) Scale {
scale := NewScale(s)
if mod != 0 {
scale.Mod = big.NewInt(0).SetUint64(mod)
}
return scale
}
// BigInt returns the scale as a big.Int, truncating the rational part and rounding ot the nearest integer.
// The rounding assumes that the scale is a positive value.
func (s Scale) BigInt() (sInt *big.Int) {
sInt = new(big.Int)
new(big.Float).SetPrec(s.Value.Prec()).Add(&s.Value, new(big.Float).SetFloat64(0.5)).Int(sInt)
return
}
// Float64 returns the underlying scale as a float64 value.
func (s Scale) Float64() float64 {
f64, _ := s.Value.Float64()
return f64
}
func (s Scale) Log2() float64 {
res := new(big.Float).Quo(bignum.Log(&s.Value), bignum.Log2(s.Value.Prec()))
f64, _ := res.Float64()
return f64
}
// Uint64 returns the underlying scale as an uint64 value.
func (s Scale) Uint64() uint64 {
u64, _ := s.Value.Uint64()
return u64
}
// Mul multiplies the target s with s1, returning the result in
// a new [Scale] struct. If mod is specified, performs the multiplication
// modulo mod.
func (s Scale) Mul(s1 Scale) Scale {
res := new(big.Float)
if s.Mod != nil {
si, _ := s.Value.Int(nil)
s1i, _ := s1.Value.Int(nil)
s1i.Mul(si, s1i)
s1i.Mod(s1i, s.Mod)
res.SetPrec(ScalePrecision)
res.SetInt(s1i)
} else {
res.Mul(&s.Value, &s1.Value)
}
return Scale{Value: *res, Mod: s.Mod}
}
// Div multiplies the target s with s1^-1, returning the result in
// a new [Scale] struct. If mod is specified, performs the multiplication
// modulo t with the multiplicative inverse of s1. Otherwise, performs
// the quotient operation.
func (s Scale) Div(s1 Scale) Scale {
res := new(big.Float)
if s.Mod != nil {
s1i, _ := s.Value.Int(nil)
s2i, _ := s1.Value.Int(nil)
s2i.ModInverse(s2i, s.Mod)
s1i.Mul(s1i, s2i)
s1i.Mod(s1i, s.Mod)
res.SetPrec(ScalePrecision)
res.SetInt(s1i)
} else {
res.Quo(&s.Value, &s1.Value)
}
return Scale{Value: *res, Mod: s.Mod}
}
// Cmp compares the target scale with s1.
// Returns 0 if the scales are equal, 1 if
// the target scale is greater and -1 if
// the target scale is smaller.
func (s Scale) Cmp(s1 Scale) (cmp int) {
return s.Value.Cmp(&s1.Value)
}
// Equal returns true if a == b.
func (s Scale) Equal(s1 Scale) bool {
return s.Cmp(s1) == 0
}
// InDelta returns true if abs(a-b) <= 2^{-log2Delta}
func (s Scale) InDelta(s1 Scale, log2Delta float64) bool {
return s.Log2Delta(s1) >= log2Delta
}
// Log2Delta returns -log2(abs(a-b)/max(a, b))
func (s Scale) Log2Delta(s1 Scale) float64 {
d := new(big.Float).Sub(&s.Value, &s1.Value)
d.Abs(d)
max := s.Max(s1)
d.Quo(d, &max.Value)
d.Quo(bignum.Log(d), bignum.Log2(s.Value.Prec()))
d.Neg(d)
f64, _ := d.Float64()
return f64
}
// Max returns the a new scale which is the maximum
// between the target scale and s1.
func (s Scale) Max(s1 Scale) (max Scale) {
if s.Cmp(s1) < 0 {
return s1
}
return s
}
// Min returns the a new scale which is the minimum
// between the target scale and s1.
func (s Scale) Min(s1 Scale) (max Scale) {
if s.Cmp(s1) > 0 {
return s1
}
return s
}
// BinarySize returns the serialized size of the object in bytes.
// Each value is encoded with .Text('e', ceil(ScalePrecision / log2(10))).
func (s Scale) BinarySize() int {
// 21 for JSON formatting plus 2*(6 + ScalePrecisionLog10) for the scales encoding.
return 21 + (ScalePrecisionLog10+6)<<1
}
// MarshalBinary encodes the object into a binary form on a newly allocated slice of bytes.
func (s Scale) MarshalBinary() (p []byte, err error) {
return s.MarshalJSON()
}
// UnmarshalBinary decodes a slice of bytes generated by
// [Scale.MarshalBinary] on the object.
func (s Scale) UnmarshalBinary(p []byte) (err error) {
return s.UnmarshalJSON(p)
}
// MarshalJSON encodes the object into a binary form on a newly allocated slice of bytes.
func (s Scale) MarshalJSON() (p []byte, err error) {
var mod string
if s.Mod != nil {
mod = new(big.Float).SetPrec(ScalePrecision).SetInt(s.Mod).Text('e', ScalePrecisionLog10)
} else {
var m string
for i := 0; i < ScalePrecisionLog10; i++ {
m += "0"
}
mod = "0." + m + "e+00"
}
aux := &struct {
Value string
Mod string
}{
Value: s.Value.Text('e', ScalePrecisionLog10),
Mod: mod,
}
p, err = json.Marshal(aux)
return
}
func (s *Scale) UnmarshalJSON(p []byte) (err error) {
aux := &struct {
Value string
Mod string
}{}
if err = json.Unmarshal(p, aux); err != nil {
return
}
s.Value.SetPrec(ScalePrecision)
s.Value.SetString(aux.Value)
mod, bool := new(big.Float).SetString(aux.Mod)
if mod.Cmp(new(big.Float)) != 0 {
if s.Mod == nil {
s.Mod = new(big.Int)
}
if !bool {
return fmt.Errorf("Scale: UnmarshalJSON: s.Mod != exact")
}
mod.Int(s.Mod)
}
return
}
func scaleToBigFloat(scale interface{}) (s *big.Float) {
switch scale := scale.(type) {
case float64:
if scale < 0 || math.IsNaN(scale) || math.IsInf(scale, 0) {
panic(fmt.Errorf("scale cannot be negative, NaN or Inf, but is %f", scale))
}
s = new(big.Float).SetPrec(ScalePrecision)
s.SetFloat64(scale)
case *big.Float:
if scale.Cmp(new(big.Float).SetFloat64(0)) < 0 || scale.IsInf() {
panic(fmt.Errorf("scale cannot be negative, but is %f", scale))
}
s = new(big.Float).SetPrec(ScalePrecision)
s.Set(scale)
case big.Float:
if scale.Cmp(new(big.Float).SetFloat64(0)) < 0 || scale.IsInf() {
panic(fmt.Errorf("scale cannot be negative, but is %f", &scale))
}
s = new(big.Float).SetPrec(ScalePrecision)
s.Set(&scale)
case *big.Int:
if scale.Cmp(new(big.Int).SetInt64(0)) < 0 {
panic(fmt.Errorf("scale cannot be negative, but is %f", scale))
}
s = new(big.Float).SetPrec(ScalePrecision)
s.SetInt(scale)
case big.Int:
if scale.Cmp(new(big.Int).SetInt64(0)) < 0 {
panic(fmt.Errorf("scale cannot be negative, but is %f", &scale))
}
s = new(big.Float).SetPrec(ScalePrecision)
s.SetInt(&scale)
case int:
return scaleToBigFloat(new(big.Int).SetInt64(int64(scale)))
case int64:
return scaleToBigFloat(new(big.Int).SetInt64(scale))
case uint64:
return scaleToBigFloat(new(big.Int).SetUint64(scale))
case Scale:
return scaleToBigFloat(scale.Value)
default:
panic(fmt.Errorf("invalid scale.(type): must be int, int64, uint64, float64, *big.Int, *big.Float or *Scale but is %T", scale))
}
// Although the big.Float has 128 bits of precision, it will be
// initialized with mant:big.nat{0x0}, i.e. only one mantissa word.
// This forces two mantissa words: mant:big.nat{0x0, 0x0}.
s.SetString(s.Text('x', ScalePrecisionLog10))
return
}