Files
lattigo/rlwe/gadgetciphertext.go
Christian Mouchet 2d7f0e42b1 making the evaluation keys independant of the OperandQP type
As for the public keys, I think it is better to keep evaluation key types
as simple as possible. The "Operand/OperandQP" types are more adapted to
the "data" path, i.e. as operands in a circuit. One obvious exemple is that
there is no point for keys to have the metadata of a ciphertext.Also, we'll
have easier time designing the evaluation logic and evolving the Operand
types if the keys do not depend on them.
2023-06-15 23:04:40 +02:00

229 lines
6.8 KiB
Go

package rlwe
import (
"io"
"github.com/google/go-cmp/cmp"
"github.com/tuneinsight/lattigo/v4/ring"
"github.com/tuneinsight/lattigo/v4/rlwe/ringqp"
"github.com/tuneinsight/lattigo/v4/utils/structs"
)
// GadgetCiphertext is a struct for storing an encrypted
// plaintext times the gadget power matrix.
type GadgetCiphertext struct {
Value structs.Matrix[tupleQP]
}
// NewGadgetCiphertext returns a new Ciphertext key with pre-allocated zero-value.
// Ciphertext is always in the NTT domain.
func NewGadgetCiphertext(params ParametersInterface, levelQ, levelP, decompRNS, decompBIT int) *GadgetCiphertext {
m := make(structs.Matrix[tupleQP], decompRNS)
for i := 0; i < decompRNS; i++ {
m[i] = make([]tupleQP, decompBIT)
for j := range m[i] {
m[i][j] = newTupleQPAtLevel(params, levelQ, levelP)
}
}
return &GadgetCiphertext{Value: m}
}
// LevelQ returns the level of the modulus Q of the target Ciphertext.
func (ct GadgetCiphertext) LevelQ() int {
return ct.Value[0][0][0].LevelQ()
}
// LevelP returns the level of the modulus P of the target Ciphertext.
func (ct GadgetCiphertext) LevelP() int {
return ct.Value[0][0][0].LevelP()
}
// Equal checks two Ciphertexts for equality.
func (ct *GadgetCiphertext) Equal(other *GadgetCiphertext) bool {
return cmp.Equal(ct.Value, other.Value)
}
// CopyNew creates a deep copy of the receiver Ciphertext and returns it.
func (ct *GadgetCiphertext) CopyNew() (ctCopy *GadgetCiphertext) {
if ct == nil || len(ct.Value) == 0 {
return nil
}
v := make(structs.Matrix[tupleQP], len(ct.Value))
for i := range ct.Value {
v[i] = make([]tupleQP, len(ct.Value[0]))
for j, el := range ct.Value[i] {
v[i][j] = el.CopyNew()
}
}
return &GadgetCiphertext{Value: v}
}
// BinarySize returns the serialized size of the object in bytes.
func (ct *GadgetCiphertext) BinarySize() (dataLen int) {
return ct.Value.BinarySize()
}
// WriteTo writes the object on an io.Writer. It implements the io.WriterTo
// interface, and will write exactly object.BinarySize() bytes on w.
//
// Unless w implements the buffer.Writer interface (see lattigo/utils/buffer/writer.go),
// it will be wrapped into a bufio.Writer. Since this requires allocations, it
// is preferable to pass a buffer.Writer directly:
//
// - When writing multiple times to a io.Writer, it is preferable to first wrap the
// io.Writer in a pre-allocated bufio.Writer.
// - When writing to a pre-allocated var b []byte, it is preferable to pass
// buffer.NewBuffer(b) as w (see lattigo/utils/buffer/buffer.go).
func (ct *GadgetCiphertext) WriteTo(w io.Writer) (n int64, err error) {
return ct.Value.WriteTo(w)
}
// ReadFrom reads on the object from an io.Writer. It implements the
// io.ReaderFrom interface.
//
// Unless r implements the buffer.Reader interface (see see lattigo/utils/buffer/reader.go),
// it will be wrapped into a bufio.Reader. Since this requires allocation, it
// is preferable to pass a buffer.Reader directly:
//
// - When reading multiple values from a io.Reader, it is preferable to first
// first wrap io.Reader in a pre-allocated bufio.Reader.
// - When reading from a var b []byte, it is preferable to pass a buffer.NewBuffer(b)
// as w (see lattigo/utils/buffer/buffer.go).
func (ct *GadgetCiphertext) ReadFrom(r io.Reader) (n int64, err error) {
return ct.Value.ReadFrom(r)
}
// MarshalBinary encodes the object into a binary form on a newly allocated slice of bytes.
func (ct *GadgetCiphertext) MarshalBinary() (data []byte, err error) {
return ct.Value.MarshalBinary()
}
// UnmarshalBinary decodes a slice of bytes generated by
// MarshalBinary or WriteTo on the object.
func (ct *GadgetCiphertext) UnmarshalBinary(p []byte) (err error) {
return ct.Value.UnmarshalBinary(p)
}
// AddPolyTimesGadgetVectorToGadgetCiphertext takes a plaintext polynomial and a list of Ciphertexts and adds the
// plaintext times the RNS and BIT decomposition to the i-th element of the i-th Ciphertexts. This method panics if
// len(cts) > 2.
func AddPolyTimesGadgetVectorToGadgetCiphertext(pt *ring.Poly, cts []GadgetCiphertext, ringQP ringqp.Ring, logbase2 int, buff *ring.Poly) {
levelQ := cts[0].LevelQ()
levelP := cts[0].LevelP()
ringQ := ringQP.RingQ.AtLevel(levelQ)
if len(cts) > 2 {
panic("cannot AddPolyTimesGadgetVectorToGadgetCiphertext: len(cts) should be <= 2")
}
if levelP != -1 {
ringQ.MulScalarBigint(pt, ringQP.RingP.AtLevel(levelP).Modulus(), buff) // P * pt
} else {
levelP = 0
if pt != buff {
ring.CopyLvl(levelQ, pt, buff) // 1 * pt
}
}
RNSDecomp := len(cts[0].Value)
BITDecomp := len(cts[0].Value[0])
N := ringQ.N()
var index int
for j := 0; j < BITDecomp; j++ {
for i := 0; i < RNSDecomp; i++ {
// e + (m * P * w^2j) * (q_star * q_tild) mod QP
//
// q_prod = prod(q[i*#Pi+j])
// q_star = Q/qprod
// q_tild = q_star^-1 mod q_prod
//
// Therefore : (pt * P * w^2j) * (q_star * q_tild) = pt*P*w^2j mod q[i*#Pi+j], else 0
for k := 0; k < levelP+1; k++ {
index = i*(levelP+1) + k
// Handle cases where #pj does not divide #qi
if index >= levelQ+1 {
break
}
qi := ringQ.SubRings[index].Modulus
p0tmp := buff.Coeffs[index]
for u, ct := range cts {
p1tmp := ct.Value[i][j][u].Q.Coeffs[index]
for w := 0; w < N; w++ {
p1tmp[w] = ring.CRed(p1tmp[w]+p0tmp[w], qi)
}
}
}
}
// w^2j
ringQ.MulScalar(buff, 1<<logbase2, buff)
}
}
// GadgetPlaintext stores a plaintext value times the gadget vector.
type GadgetPlaintext struct {
Value structs.Vector[ring.Poly]
}
// NewGadgetPlaintext creates a new gadget plaintext from value, which can be either uint64, int64 or *ring.Poly.
// Plaintext is returned in the NTT and Mongtomery domain.
func NewGadgetPlaintext(params Parameters, value interface{}, levelQ, levelP, logBase2, decompBIT int) (pt *GadgetPlaintext) {
ringQ := params.RingQP().RingQ.AtLevel(levelQ)
pt = new(GadgetPlaintext)
pt.Value = make([]ring.Poly, decompBIT)
switch el := value.(type) {
case uint64:
pt.Value[0] = *ringQ.NewPoly()
for i := 0; i < levelQ+1; i++ {
pt.Value[0].Coeffs[i][0] = el
}
case int64:
pt.Value[0] = *ringQ.NewPoly()
if el < 0 {
for i := 0; i < levelQ+1; i++ {
pt.Value[0].Coeffs[i][0] = ringQ.SubRings[i].Modulus - uint64(-el)
}
} else {
for i := 0; i < levelQ+1; i++ {
pt.Value[0].Coeffs[i][0] = uint64(el)
}
}
case *ring.Poly:
pt.Value[0] = *el.CopyNew()
default:
panic("cannot NewGadgetPlaintext: unsupported type, must be wither uint64 or *ring.Poly")
}
if levelP > -1 {
ringQ.MulScalarBigint(&pt.Value[0], params.RingP().AtLevel(levelP).Modulus(), &pt.Value[0])
}
ringQ.NTT(&pt.Value[0], &pt.Value[0])
ringQ.MForm(&pt.Value[0], &pt.Value[0])
for i := 1; i < len(pt.Value); i++ {
pt.Value[i] = *pt.Value[0].CopyNew()
for j := 0; j < i; j++ {
ringQ.MulScalar(&pt.Value[i], 1<<logBase2, &pt.Value[i])
}
}
return
}