s0l0ist / node-seal

Homomorphic Encryption for TypeScript or JavaScript - Microsoft SEAL
https://s0l0ist.github.io/node-seal/
MIT License
191 stars 24 forks source link

Rescaling in CKKS for substraction #127

Closed patriciaOrtuno28 closed 2 years ago

patriciaOrtuno28 commented 2 years ago

The issue:

I am trying to substract rB from rA, but I'm having an issue with the scales. The cipherTextSxy was previously relinearized and rescaled in other steps of the code. I have already tried to set rA's scale to rB's scale but then we'd lose the real value of rA and the substraction, even though it would not fail, would not be realistic. I have also tried to turn N's plaintext to a ciphertext and work with the evaluator.multiply() function, but there is also an scale issue with that approach.

Screenshot of the scales' values:
Scales of cipherTextSxy and NPlainText:

image

Scales of rA and rB:

image

My code:
    // Turn N into a PlainText
    const NPlaintext = seal.PlainText();
    const NArray = Float64Array.from([N]);
    encoder.encode(NArray, scale, NPlaintext);

    // Compute rA = N*Sxy
    let rA = seal.CipherText();
    evaluator.multiplyPlain(cipherTextSxy, NPlaintext, rA);
    evaluator.relinearize(rA, relinKey);
    evaluator.rescaleToNext(rA);

    // Compute rB = Sx*Sy
    let rB = seal.CipherText();
    evaluator.multiply(cipherTextSx, cipherTextSy, rB);
    evaluator.relinearize(rB, relinKey);
    evaluator.rescaleToNext(rB);

    // Compute rAB = rA - rB
    let rAB = seal.CipherText();
    //rA.setScale(rB.scale);
    console.log(`Scale A: ${rA.scale}`);
    console.log(`Scale B: ${rB.scale}`);
    console.log(`Scale AB: ${rAB.scale}`);
    evaluator.sub(rA, rB, rAB);
Encryption parameters:
    /**************************************************
     * SCHEME PARAMETERS
     **************************************************/
    const schemeType = seal.SchemeType.ckks;
    const securityLevel = seal.SecurityLevel.tc128;
    const polyModulusDegree = 8192;
    const bitSizes = [60, 40, 40, 60];
    const bitSize = 40;

    const parms = seal.EncryptionParameters(schemeType);

    /**************************************************
     * POLY MODULUS DEGREE
     **************************************************/
    parms.setPolyModulusDegree(polyModulusDegree);

    /**************************************************
     * COEFF MODULUS PRIMES
     **************************************************/
    parms.setCoeffModulus(
        seal.CoeffModulus.Create(polyModulusDegree, Int32Array.from(bitSizes))
    );

    /**************************************************
     * CREATE CONTEXT
     **************************************************/
    const context = seal.Context(
        parms, // Encryption Parameters
        true, // ExpandModChain
        securityLevel // Enforce a security level
    );
s0l0ist commented 2 years ago

Hi @patriciaOrtuno28,

It looks like there's a bug in the way you're performing relinearization and rescaling. Specifically, the library has an 'overload' option for most functions that can accept a destination parameter or, if not provided, returns a corresponding CipherText or PlainText containing the result of the function called.

For example, when you perform relinearization you're not specifying a destination parameter nor capturing the output of the function. So your call:

evaluator.relinearize(rA, relinKey);

Is internally creating a copy rA, performs relinearization on the copy, and returns the copy - but you're not capturing it (thrown away).

For both of your calls to relinearize and rescaleToNext, you should either pass in a destination parameter or use the output that it returns (if you don't supply a destination parameter). For example:

const rARelinDest = seal.CipherText();
evaluator.relinearize(rA, relinKey, rARelinDest);
// or 
const rARelinDest = evaluator.relinearize(rA, relinKey);
patriciaOrtuno28 commented 2 years ago

Hi @s0l0ist ,

Thank you for your help, it worked perfectly fine. I was also missing the modulus switching for the parmsId. I'll attach the new code just in case someone has the same doubt in the future.

    // Turn N into a PlainText
    const NPlaintext = seal.PlainText();
    const NArray = Float64Array.from([N]);
    encoder.encode(NArray, scale, NPlaintext);

    // Compute rA = N*Sxy
    NPlaintext.setScale(cipherTextSxy.scale);
    const NPlaintextModSwitch = evaluator.plainModSwitchToNext(NPlaintext);
    let rA = seal.CipherText();
    evaluator.multiplyPlain(cipherTextSxy, NPlaintextModSwitch, rA);
    const rARelin = evaluator.relinearize(rA, relinKey);
    const rARescale = evaluator.rescaleToNext(rARelin);

    // Compute rB = Sx*Sy
    let rB = seal.CipherText();
    evaluator.multiply(cipherTextSx, cipherTextSy, rB);
    const rBRelin = evaluator.relinearize(rB, relinKey);
    const rBRescale = evaluator.rescaleToNext(rBRelin);

    // Compute rAB = rA - rB
    let rAB = seal.CipherText();
    rBRescale.setScale(rARescale.scale);
    const rBRescaleModSwitch = evaluator.cipherModSwitchTo(rBRescale, rARescale.parmsId);
    evaluator.sub(rARescale, rBRescaleModSwitch, rAB);