tuneinsight / lattigo

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

Question: Need to check if Evaluator's parameters match those of each operand #413

Closed Maokami closed 10 months ago

Maokami commented 10 months ago

What version of Lattigo are you using?

dev_release_v4.2.0 (commit hash : d2f8555f40307b08217bc62bab617d46fa9b0173)

Does this issue persist with the latest release?

Yes

What were you trying to do?

Hello Lattigo team,

In this issue, I have intentionally created scenarios where the evaluator's parameters differ from those of each operand to test their behavior.

What were you expecting to happen?

I expected that the library would raise an appropriate exception when it detects a discrepancy in the parameters.

What actually happened?

The function returns incorrect random output (for example, [35372266 53616578]).

Reproducibility

// main.go
package main

import (
    "fmt"

    "github.com/tuneinsight/lattigo/v4/core/rlwe"
    "github.com/tuneinsight/lattigo/v4/schemes/bgv"
)

func main() {
    // parameters except for the evaluation
    params, err := bgv.NewParametersFromLiteral(bgv.ParametersLiteral{
        LogN:             14,
        LogQ:             []int{56, 55, 55, 54, 54, 54},
        LogP:             []int{55, 55},
        PlaintextModulus: 0x3ee0001,
    })
    if err != nil {
        panic(err)
    }
    encoder := bgv.NewEncoder(params)
    kgen := rlwe.NewKeyGenerator(params)
    Sk, Pk := kgen.GenKeyPairNew()
    encryptor := rlwe.NewEncryptor(params, Pk)
    decryptor := rlwe.NewDecryptor(params, Sk)

    // parameters for the evaluation
    params_eval, err := bgv.NewParametersFromLiteral(bgv.ParametersLiteral{
        LogN:             14,
        LogQ:             []int{56, 55, 55, 54, 54, 53},
        LogP:             []int{55, 55},
        PlaintextModulus: 0x3ee0001,
    })
    if err != nil {
        panic(err)
    }

    evaluator := bgv.NewEvaluator(params_eval, nil)

    D1 := make([]uint64, 2)
    for i := range D1 {
        D1[i] = 1
    }

    P1 := bgv.NewPlaintext(params, 5)
    if err := encoder.Encode(D1, P1); err != nil {
        panic(err)
    }

    C1, err := encryptor.EncryptNew(P1)
    if err != nil {
        panic(err)
    }

    result := make([]uint64, 2)

    // This function is what I want to test
    C, err := evaluator.AddNew(C1, C1)
    if err != nil {
        panic(err)
    }

    if err := encoder.Decode(decryptor.DecryptNew(C), result); err != nil {
        panic(err)
    }

    fmt.Print(result)
}
Pro7ech commented 10 months ago

Hi @Maokami, this is an intended behavior. This topic has been discussed multiple times over the development cycles of the library and the conclusion was that, since Lattigo is intended to be a backed, this is not something that should be implemented at this level.

Rather we believe it should be implemented at the same level as the layer managing the keys and the parameters or in the high level circuit. It is easier (and much more efficient) to check once, at the beginning of the circuit that the ciphertext is valid, rather than do this check at each low-level operation.

Another reason we did not add such checks is because this only affects correctness, not the security.

Of course, this is something that might change in the future, but at the moment we have no plan to add such checks.

Maokami commented 10 months ago

Hello @Pro7ech,

Thank you very much for your detailed response. I just have one more question to ensure that I fully understand.

I am interested in the input validity checks in HE libraries. When examining the evaluator.AddNew(ciphertext1, ciphertext2) function in Lattigo, I noticed that it ultimately calls the InitOutputBinaryOp function, which performs several checks related to the validity of inputs (such as NTT form, degree, etc.).

Unlike these checks, if I understand correctly, do you believe that the check I mentioned in this issue (evaluator.Q == ciphertext1 and 2.Q) is unnecessary because Lattigo is intended to be used not as a direct user-facing tool, but as a backend for compilers, making situations where the parameter Q changes unlikely?

Pro7ech commented 10 months ago

These are lower level checks to ensure that a ciphertext, within a cryptographic context and homomorphic circuit is valid for the given homomorphic operation. For example, it might check that the ciphertext is in the NTT domain and that it is of degree 1. These are properties that can dynamically vary within the same cryptographic parameters and between homomorphic operations of a circuit.

But the checks that you are referring to happen target higher level properties that are fixed within a given application and I believe they are unnecessary at the level of the library.

Cryptographic parameters are fixed for a given circuit, even if you might have multiple of them. So this check is only needed once at the application level when receiving the ciphertext from a client/server before operating on it, to make sure it is valid within the cryptographic context of the application.

Maokami commented 10 months ago

It makes sense. Thank you once again!