tuneinsight / lattigo

A library for lattice-based multiparty homomorphic encryption in Go
Apache License 2.0
1.13k stars 172 forks source link

Bug[BGV]: panic when encoding `N` values with `IsBatched=false` #478

Open Pro7ech opened 1 month ago

Pro7ech commented 1 month ago

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")
    }
}
qantik commented 1 month ago

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.

Pro7ech commented 1 month ago

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.