Bug[BGV]: panic when encoding `N` values with `IsBatched=false`
What version of Lattigo are you using?
v5.0.2
Does this issue persist with the latest release?
yes
What were you trying to do?
Encode params.N() values with the flag IsBatched=false
What were you expecting to happen?
No error
What actually happened?
panic: cannot Encode (TimeDomain): len(values)=16384 > N=128
Reproducibility
// Package main is a template encrypted modular arithmetic integers, with a set of example parameters, key generation, encoding, encryption, decryption and decoding.
package main
import (
"fmt"
"math/rand"
"github.com/tuneinsight/lattigo/v5/core/rlwe"
"github.com/tuneinsight/lattigo/v5/he/heint"
"github.com/tuneinsight/lattigo/v5/utils"
)
func main() {
var err error
var params heint.Parameters
// 128-bit secure parameters enabling depth-7 circuits.
// LogN:14, LogQP: 431.
if params, err = heint.NewParametersFromLiteral(
heint.ParametersLiteral{
LogN: 14, // log2(ring degree)
LogQ: []int{55, 45, 45, 45, 45, 45, 45, 45}, // log2(primes Q) (ciphertext modulus)
LogP: []int{61}, // log2(primes P) (auxiliary modulus)
PlaintextModulus: 0x101, // log2(scale)
}); err != nil {
panic(err)
}
// Key Generator
kgen := rlwe.NewKeyGenerator(params)
// Secret Key
sk := kgen.GenSecretKeyNew()
// Encoder
ecd := heint.NewEncoder(params)
// Encryptor
enc := rlwe.NewEncryptor(params, sk)
// Decryptor
dec := rlwe.NewDecryptor(params, sk)
// Vector of plaintext values
values := make([]uint64, params.N())
// Source for sampling random plaintext values (not cryptographically secure)
/* #nosec G404 */
r := rand.New(rand.NewSource(0))
// Populates the vector of plaintext values
T := params.PlaintextModulus()
for i := range values {
values[i] = r.Uint64() % T
}
// Allocates a plaintext at the max level.
// Default rlwe.MetaData:
// - IsBatched = true (slots encoding)
// - Scale = params.DefaultScale()
pt := heint.NewPlaintext(params, params.MaxLevel())
pt.IsBatched = false
// Encodes the vector of plaintext values
if err = ecd.Encode(values, pt); err != nil {
panic(err)
}
// Encrypts the vector of plaintext values
var ct *rlwe.Ciphertext
if ct, err = enc.EncryptNew(pt); err != nil {
panic(err)
}
// Allocates a vector for the reference values
want := make([]uint64, params.MaxSlots())
copy(want, values)
PrintPrecisionStats(params, ct, want, ecd, dec)
}
// PrintPrecisionStats decrypts, decodes and prints the precision stats of a ciphertext.
func PrintPrecisionStats(params heint.Parameters, ct *rlwe.Ciphertext, want []uint64, ecd *heint.Encoder, dec *rlwe.Decryptor) {
var err error
// Decrypts the vector of plaintext values
pt := dec.DecryptNew(ct)
// Decodes the plaintext
have := make([]uint64, params.MaxSlots())
if err = ecd.Decode(pt, have); err != nil {
panic(err)
}
// Pretty prints some values
fmt.Printf("Have: ")
for i := 0; i < 4; i++ {
fmt.Printf("%d ", have[i])
}
fmt.Printf("...\n")
fmt.Printf("Want: ")
for i := 0; i < 4; i++ {
fmt.Printf("%d ", want[i])
}
fmt.Printf("...\n")
if !utils.EqualSlice(want, have) {
panic("wrong result: bad decryption or encrypted/plaintext circuits do not match")
}
}
Hey, this seems to be an API issue as it is not documented what happens if the plaintext modulus t does not respect t = 1 (mod 2N).
Internally, the current implementation will find a ring degree for the T polynomial that abides by this rule. In the snippet t = 257, the largest ring degree that accommodates this choice is 128 which is also the number of slots available. As a result, params.N() does not correspond to the actual number of slots available.
For example see, testEncoder in schemes/bgv/bgv_test.go which handles this case correctly.
If batching is set to false, it should be possible to encode N values modulo a any t since this doesn't require to call a DFT/IDFT.