I am currently studying the bootstrapping process of the TFHE (Fully Homomorphic Encryption) scheme and have developed a theoretical understanding of its working. However, while reviewing the implementation of the bootstrapping process, I am having difficulty fully comprehending certain aspects of the code.
For example, in the function BootstrapGateCore
(shown below), there are sections involving the accumulation process and sparse embedding that I find challenging to understand. Could you recommend any articles or resources that offer a detailed explanation of this code or provide insights into the underlying principles?
Here is a portion of the code I am working on:
RLWECiphertext BinFHEScheme::BootstrapGateCore(const std::shared_ptr<BinFHECryptoParams>& params, BINGATE gate,
ConstRingGSWACCKey& ek, ConstLWECiphertext& ct) const {
if (ek == nullptr) {
std::string errMsg =
"Bootstrapping keys have not been generated. Please call BTKeyGen "
"before calling bootstrapping.";
OPENFHE_THROW(config_error, errMsg);
}
auto& LWEParams = params->GetLWEParams();
auto& RGSWParams = params->GetRingGSWParams();
auto polyParams = RGSWParams->GetPolyParams();
// Specifies the range [q1,q2) that will be used for mapping
NativeInteger p = ct->GetptModulus(); //4 明文模数
NativeInteger q = ct->GetModulus();
uint32_t qHalf = q.ConvertToInt() >> 1;
NativeInteger q1 = RGSWParams->GetGateConst()[static_cast<size_t>(gate)]; //3/8q
NativeInteger q2 = q1.ModAddFast(NativeInteger(qHalf), q); //7/8
// depending on whether the value is the range, it will be set
// to either Q/8 or -Q/8 to match binary arithmetic
NativeInteger Q = LWEParams->GetQ();
NativeInteger Q2p = Q / NativeInteger(2 * p) + 1; //Q/8+1
NativeInteger Q2pNeg = Q - Q2p; //7/8Q-1
uint32_t N = LWEParams->GetN();
NativeVector m(N, Q);
// Since q | (2*N), we deal with a sparse embedding of Z_Q[x]/(X^{q/2}+1) to
// Z_Q[x]/(X^N+1)
uint32_t factor = (2 * N / q.ConvertToInt());
const NativeInteger& b = ct->GetB();
for (size_t j = 0; j < qHalf; ++j) {
NativeInteger temp = b.ModSub(j, q);
if (q1 < q2)
m[j * factor] = ((temp >= q1) && (temp < q2)) ? Q2pNeg : Q2p;
else
m[j * factor] = ((temp >= q2) && (temp < q1)) ? Q2p : Q2pNeg;
}
//m(x)-m(x^w)
std::vector<NativePoly> res(2);
// no need to do NTT as all coefficients of this poly are zero
res[0] = NativePoly(polyParams, Format::EVALUATION, true);
res[1] = NativePoly(polyParams, Format::COEFFICIENT, false);
res[1].SetValues(std::move(m), Format::COEFFICIENT);
res[1].SetFormat(Format::EVALUATION);
// main accumulation computation
// the following loop is the bottleneck of bootstrapping/binary gate
// evaluation
auto acc = std::make_shared<RLWECiphertextImpl>(std::move(res));
ACCscheme->EvalAcc(RGSWParams, ek, acc, ct->GetA());
return acc;
}
especial below code:
for (size_t j = 0; j < qHalf; ++j) {
NativeInteger temp = b.ModSub(j, q);
if (q1 < q2)
m[j * factor] = ((temp >= q1) && (temp < q2)) ? Q2pNeg : Q2p;
else
m[j * factor] = ((temp >= q2) && (temp < q1)) ? Q2p : Q2pNeg;
}
Any guidance or explanations regarding this would be greatly appreciated.
Thank you for your time and assistance.