mirror of
https://github.com/tuneinsight/lattigo.git
synced 2025-09-13 03:27:14 +00:00
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.
229 lines
6.8 KiB
Go
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
|
|
}
|