Files
MP-SPDZ/FHE/NTL-Subs.cpp
2019-06-07 15:26:28 +10:00

775 lines
20 KiB
C++

#include "FHE/NTL-Subs.h"
#include "Math/Setup.h"
#include "Math/gfp.h"
#include "Math/gf2n.h"
#include "FHE/P2Data.h"
#include "FHE/QGroup.h"
#include "FHE/NoiseBounds.h"
#include "Tools/mkpath.h"
#include "FHEOffline/Proof.h"
#include <fstream>
using namespace std;
#include <NTL/ZZ.h>
#include <NTL/ZZX.h>
#include <NTL/GF2EXFactoring.h>
#include <NTL/GF2XFactoring.h>
NTL_CLIENT
#include "FHEOffline/DataSetup.h"
template <>
void generate_setup(int n_parties, int plaintext_length, int sec,
FHE_Params& params, FFT_Data& FTD, int slack, bool round_up)
{
Ring Rp;
bigint p0p,p1p,p;
SPDZ_Data_Setup_Char_p(Rp, FTD, p0p, p1p, n_parties, plaintext_length, sec,
slack, round_up);
params.set(Rp, {p0p, p1p});
}
template <>
void generate_setup(int n_parties, int plaintext_length, int sec,
FHE_Params& params, P2Data& P2D, int slack, bool round_up)
{
Ring R;
bigint pr0,pr1;
SPDZ_Data_Setup_Char_2(R, P2D, pr0, pr1, n_parties, plaintext_length, sec,
slack, round_up);
params.set(R, {pr0, pr1});
}
void generate_setup(int n, int lgp, int lg2, int sec, bool skip_2,
int slack, bool round_up)
{
DataSetup setup;
// do the full setup for SHE data
generate_setup(n, lgp, sec, setup.setup_p.params, setup.setup_p.FieldD, slack, round_up);
if (!skip_2)
generate_setup(n, lg2, sec, setup.setup_2.params, setup.setup_2.FieldD, slack, round_up);
setup.write_setup(skip_2);
}
bool same_word_length(int l1, int l2)
{
return l1 / 64 == l2 / 64;
}
template <>
int generate_semi_setup(int plaintext_length, int sec,
FHE_Params& params, FFT_Data& FTD, bool round_up)
{
int m = 1024;
int lgp = plaintext_length;
bigint p;
generate_prime(p, lgp, m);
int lgp0, lgp1;
while (true)
{
SemiHomomorphicNoiseBounds nb(p, phi_N(m), 1, sec,
numBits(NonInteractiveProof::slack(sec, phi_N(m))), true);
bigint p1 = 2 * p * m, p0 = p;
while (nb.min_p0(params.n_mults() > 0, p1) > p0)
{
p0 *= 2;
}
if (phi_N(m) < nb.min_phi_m(numBits(p0 * (params.n_mults() > 0 ? p1 : 1))))
{
m *= 2;
generate_prime(p, lgp, m);
}
else
{
lgp0 = numBits(p0) + 1;
lgp1 = numBits(p1) + 1;
break;
}
}
int extra_slack = common_semi_setup(params, m, p, lgp0, lgp1, round_up);
FTD.init(params.get_ring(), p);
gfp::init_field(p);
return extra_slack;
}
template <>
int generate_semi_setup(int plaintext_length, int sec,
FHE_Params& params, P2Data& P2D, bool round_up)
{
if (params.n_mults() > 0)
throw runtime_error("only implemented for 0-level BGV");
gf2n_short::init_field(plaintext_length);
int m;
char_2_dimension(m, plaintext_length);
SemiHomomorphicNoiseBounds nb(2, phi_N(m), 1, sec,
numBits(NonInteractiveProof::slack(sec, phi_N(m))), true);
int lgp0 = numBits(nb.min_p0(false, 0));
int extra_slack = common_semi_setup(params, m, 2, lgp0, -1, round_up);
load_or_generate(P2D, params.get_ring());
return extra_slack;
}
int common_semi_setup(FHE_Params& params, int m, bigint p, int lgp0, int lgp1, bool round_up)
{
cout << "Need ciphertext modulus of length " << lgp0;
if (params.n_mults() > 0)
cout << "+" << lgp1;
cout << " and " << phi_N(m) << " slots" << endl;
int extra_slack = 0;
if (round_up)
{
int i;
for (i = 0; i <= 20; i++)
{
if (SemiHomomorphicNoiseBounds::min_phi_m(lgp0 + i) > phi_N(m))
break;
if (not same_word_length(lgp0, lgp0 + i))
break;
}
extra_slack = i - 1;
lgp0 += extra_slack;
cout << "Rounding up to " << lgp0 << ", giving extra slack of "
<< extra_slack << " bits" << endl;
}
Ring R;
::init(R, m);
bigint p0, p1 = 1;
if (params.n_mults() > 0)
{
generate_moduli(p0, p1, m, p, lgp0, lgp1);
params.set(R, {p0, p1});
}
else
{
generate_modulus(p0, m, p, lgp0);
params.set(R, {p0});
}
return extra_slack;
}
int finalize_lengths(int& lg2p0, int& lg2p1, int n, int m, int* lg2pi, bool round_up)
{
if (n >= 2 and n <= 10)
cout << "Difference to suggestion for p0: " << lg2p0 - lg2pi[n - 2]
<< ", for p1: " << lg2p1 - lg2pi[9 + n - 2] << endl;
cout << "p0 needs " << int(ceil(1. * lg2p0 / 64)) << " words" << endl;
cout << "p1 needs " << int(ceil(1. * lg2p1 / 64)) << " words" << endl;
int extra_slack = 0;
if (round_up)
{
int i = 0;
for (i = 0; i < 10; i++)
{
if (phi_N(m) < NoiseBounds::min_phi_m(lg2p0 + lg2p1 + 2 * i))
break;
if (not same_word_length(lg2p0 + i, lg2p0))
break;
if (not same_word_length(lg2p1 + i, lg2p1))
break;
}
i--;
extra_slack = 2 * i;
lg2p0 += i;
lg2p1 += i;
cout << "Rounding up to " << lg2p0 << "+" << lg2p1
<< ", giving extra slack of " << extra_slack << " bits" << endl;
}
cout << "Total length: " << lg2p0 + lg2p1 << endl;
return extra_slack;
}
/******************************************************************************
* Here onwards needs NTL
******************************************************************************/
/*
* Subroutine for creating the FHE parameters
*/
int SPDZ_Data_Setup_Char_p_Sub(Ring& R, bigint& pr0, bigint& pr1, int n,
int idx, int& m, bigint& p, int sec, int slack = 0, bool round_up = false)
{
int lg2pi[5][2][9]
= { { {130,132,132,132,132,132,132,132,132},
{104,104,104,106,106,108,108,110,110} },
{ {196,196,196,196,198,198,198,198,198},
{136,138,140,142,140,140,140,142,142} },
{ {325,325,325,325,330,330,330,330,330},
{205,205,205,210,205,205,205,205,210} },
{ {580,585,585,585,585,585,585,585,585},
{330,330,330,335,335,335,335,335,335} },
{ {1095,1095,1095,1095,1095,1095,1095,1095,1095},
{590,590,590,590,590,595,595,595,595} }
};
int lg2p0 = 0, lg2p1 = 0;
if (n >= 2 and n <= 10)
{
lg2p0=lg2pi[idx][0][n-2];
lg2p1=lg2pi[idx][1][n-2];
}
else if (sec == -1)
throw runtime_error("no precomputed parameters available");
while (sec != -1)
{
double phi_m_bound =
NoiseBounds(p, phi_N(m), n, sec, slack).optimize(lg2p0, lg2p1);
cout << "Trying primes of length " << lg2p0 << " and " << lg2p1 << endl;
if (phi_N(m) < phi_m_bound)
{
int old_m = m;
m = 2 << int(ceil(log2(phi_m_bound)));
cout << "m = " << old_m << " too small, increasing it to " << m << endl;
generate_prime(p, numBits(p), m);
}
else
break;
}
init(R,m);
int extra_slack = finalize_lengths(lg2p0, lg2p1, n, m, lg2pi[idx][0],
round_up);
generate_moduli(pr0, pr1, m, p, lg2p0, lg2p1);
return extra_slack;
}
void generate_moduli(bigint& pr0, bigint& pr1, const int m, const bigint p,
const int lg2p0, const int lg2p1)
{
generate_modulus(pr0, m, p, lg2p0, "0");
generate_modulus(pr1, m, p, lg2p1, "1", pr0);
}
void generate_modulus(bigint& pr, const int m, const bigint p, const int lg2pr,
const string& i, const bigint& pr0)
{
int ex;
if (lg2pr==0) { throw invalid_params(); }
bigint step=m;
bigint twop=1<<(numBits(m)+1);
bigint gc=gcd(step,twop);
step=step*twop/gc;
ex=lg2pr-numBits(p)-numBits(step)+1;
if (ex<0) { cout << "Something wrong in lg2p" << i << " = " << lg2pr << endl; abort(); }
pr=1; pr=(pr<<ex)*p*step+1;
while (!probPrime(pr) || pr==pr0 || numBits(pr)<lg2pr) { pr=pr+p*step; }
cout << "\t pr" << i << " = " << pr << " : " << numBits(pr) << endl;
cout << "\t\tFollowing should be both 1" << endl;
cout << "\t\tpr" << i << " mod m = " << pr%m << endl;
cout << "\t\tpr" << i << " mod p = " << pr%p << endl;
cout << "Minimal MAX_MOD_SZ = " << int(ceil(1. * lg2pr / 64)) << endl;
}
/*
* Create the char p FHE parameters
*/
void SPDZ_Data_Setup_Char_p(Ring& R, FFT_Data& FTD, bigint& pr0, bigint& pr1,
int n, int lgp, int sec, int slack, bool round_up)
{
bigint p;
int idx, m;
SPDZ_Data_Setup_Primes(p, lgp, idx, m);
SPDZ_Data_Setup_Char_p_Sub(R, pr0, pr1, n, idx, m, p, sec, slack, round_up);
Zp_Data Zp(p);
gfp::init_field(p);
FTD.init(R,Zp);
}
/* Compute Phi(N) */
int phi_N(int N)
{
int phiN=1,p,e;
PrimeSeq s;
while (N!=1)
{ p=s.next();
e=0;
while ((N%p)==0) { N=N/p; e++; }
if (e!=0)
{ phiN=phiN*(p-1)*power_long(p,e-1); }
}
return phiN;
}
/* Compute mobius function (naive method as n is small) */
int mobius(int n)
{
int p,e,arity=0;
PrimeSeq s;
while (n!=1)
{ p=s.next();
e=0;
while ((n%p)==0) { n=n/p; e++; }
if (e>1) { return 0; }
if (e!=0) { arity^=1; }
}
if (arity==0) { return 1; }
return -1;
}
/* Compute cyclotomic polynomial */
ZZX Cyclotomic(int N)
{
ZZX Num,Den,G,F;
NTL::set(Num); NTL::set(Den);
int m,d;
for (d=1; d<=N; d++)
{ if ((N%d)==0)
{ clear(G);
SetCoeff(G,N/d,1); SetCoeff(G,0,-1);
m=mobius(d);
if (m==1) { Num*=G; }
else if (m==-1) { Den*=G; }
}
}
F=Num/Den;
return F;
}
void init(Ring& Rg,int m)
{
Rg.mm=m;
Rg.phim=phi_N(Rg.mm);
Rg.pi.resize(Rg.phim); Rg.pi_inv.resize(Rg.mm);
for (int i=0; i<Rg.mm; i++) { Rg.pi_inv[i]=-1; }
int k=0;
for (int i=1; i<Rg.mm; i++)
{ if (gcd(i,Rg.mm)==1)
{ Rg.pi[k]=i;
Rg.pi_inv[i]=k;
k++;
}
}
ZZX P=Cyclotomic(Rg.mm);
Rg.poly.resize(Rg.phim+1);
for (int i=0; i<Rg.phim+1; i++)
{ Rg.poly[i]=to_int(coeff(P,i)); }
}
// Computes a(b) mod c
GF2X Subs_Mod(const GF2X& a,const GF2X& b,const GF2X& c)
{
GF2X ans,pb,bb=b%c;
ans=to_GF2X(coeff(a,0));
pb=bb;
for (int i=1; i<=deg(a); i++)
{ ans=ans+pb*coeff(a,i);
pb=MulMod(pb,bb,c);
}
return ans;
}
// Computes a(x^pow) mod c where x^m=1
GF2X Subs_PowX_Mod(const GF2X& a,int pow,int m,const GF2X& c)
{
GF2X ans; ans.SetMaxLength(m);
for (int i=0; i<=deg(a); i++)
{ int j=MulMod(i,pow,m);
if (IsOne(coeff(a,i))) { SetCoeff(ans,j,1); }
}
ans=ans%c;
return ans;
}
void init(P2Data& P2D,const Ring& Rg)
{
GF2X G,F;
SetCoeff(G,gf2n_short::degree(),1);
SetCoeff(G,0,1);
for (int i=0; i<gf2n_short::get_nterms(); i++)
{ SetCoeff(G,gf2n_short::get_t(i),1); }
//cout << "G = " << G << endl;
for (int i=0; i<=Rg.phi_m(); i++)
{ if (((Rg.Phi()[i])%2)!=0)
{ SetCoeff(F,i,1); }
}
//cout << "F = " << F << endl;
// seed randomness to achieve same result for all players
// randomness is used in SFCanZass and FindRoot
SetSeed(ZZ(0));
// Now factor F modulo 2
vec_GF2X facts=SFCanZass(F);
// Check all is compatible
int d=deg(facts[0]);
if (d%deg(G)!=0)
{ throw invalid_params(); }
// Compute the quotient group
QGroup QGrp;
int Gord=-1,e=Rg.phi_m()/d; // e = # of plaintext slots, phi(m)/degree
int seed=1;
while (Gord!=e)
{ QGrp.assign(Rg.m(),seed); // QGrp encodes the the quotient group Z_m^*/<2>
Gord=QGrp.order();
if (Gord!=e) { cout << "Group order wrong, need to repeat the Haf-Mc algorithm" << endl; seed++; }
}
//cout << " l = " << Gord << " , d = " << d << endl;
if ((Gord*gf2n_short::degree())!=Rg.phi_m())
{ cout << "Plaintext type requires Gord*gf2n_short::degree == phi_m" << endl;
throw not_implemented();
}
vector<GF2X> Fi(Gord);
vector<GF2X> Rts(Gord);
vector<GF2X> u(Gord);
/*
Find map from Type 0 (mod G) -> Type 1 (mod Fi)
for the first of the Fi's only
*/
Fi[0]=facts[0];
GF2E::init(facts[0]); // work with the extension field GF_2[X]/Fi[0]
GF2EX Ga=to_GF2EX(G); // represent G as a polynomial over the extension field
Rts[0]=rep(FindRoot(Ga)); // Find a roof of G in this field
cout << "Fixing field ordering and the maps (Need to count to " << Gord << " here)\n\t";
GF2E::init(G);
GF2X g;
vector<int> used(facts.length());
for (int i=0; i<facts.length(); i++) { used[i]=0; }
used[0]=1;
for (int i=0; i<Gord; i++)
{ cout << i << " " << flush;
if (i!=0)
{ int hpow=QGrp.nth_element(i);
Rts[i]=Subs_PowX_Mod(Rts[0],hpow,Rg.m(),F);
bool flag=false;
for (int j=0; j<facts.length(); j++)
{ if (used[j]==0)
{ g=Subs_PowX_Mod(facts[0],hpow,Rg.m(),facts[j]);
if (IsZero(g))
{ g=Subs_Mod(G,Rts[i],facts[j]);
if (!IsZero(g))
{ cout << "Something wrong - G" << endl;
throw invalid_params();
}
Fi[i]=facts[j];
used[j]=1;
flag=true;
break;
}
}
}
if (flag==false)
{ cout << "Something gone wrong" << endl;
throw invalid_params();
}
Rts[i]=Rts[i]%Fi[i];
}
// Now sort out the projection map (for CRT reconstruction)
GF2X te=(F/Fi[i]);
GF2X tei=InvMod(te%Fi[i],Fi[i]);
u[i]=MulMod(te,tei,F); // u[i] = \prod_{j!=i} F[j]*(F[j]^{-1} mod F[i])
}
cout << endl;
// Make the forward matrix
// This is a deg(F) x (deg(G)*Gord) matrix which maps elements
// vectors in the SIMD representation into plaintext vectors
imatrix A;
A.resize(Rg.phi_m(), imatrix::value_type(Gord*gf2n_short::degree()));
P2D.A.resize(A[0].size(), imatrix::value_type(A.size()));
for (int slot=0; slot<Gord; slot++)
{ for (int co=0; co<gf2n_short::degree(); co++)
{ // Work out how x^co in given slot maps to plaintext vector
GF2X av;
SetCoeff(av,co,1);
// av is mod G, now map to mod Fi
av=Subs_Mod(av,Rts[slot],Fi[slot]);
// Now need to map using CRT to the plaintext vector
av=MulMod(av,u[slot],F);
//cout << slot << " " << co << " : " << av << endl;
for (int k=0; k<Rg.phi_m(); k++)
{
int i = slot*gf2n_short::degree()+co;
if (IsOne(coeff(av,k)))
{ A[k][i]=1; }
else
{ A[k][i]=0; }
P2D.A[i][k] = A[k][i];
}
}
}
//cout << "Forward Matrix : " << endl; print(P2D.A);
// Find pseudo inverse modulo 2
pinv(P2D.Ai, A);
P2D.Ai.resize(Gord*gf2n_short::degree());
//cout << "Inverse Matrix : " << endl; print(P2D.Ai);
P2D.slots=Gord;
}
/*
* Create the FHE parameters
*/
void char_2_dimension(int& m, int& lg2)
{
switch (lg2)
{ case -1:
m=17;
lg2=8;
break;
case 40:
m=13175;
break;
case -40:
m=5797;
lg2=40;
break;
default:
throw runtime_error("field size not supported");
break;
}
}
void SPDZ_Data_Setup_Char_2(Ring& R, P2Data& P2D, bigint& pr0, bigint& pr1,
int n, int lg2, int sec, int slack, bool round_up)
{
int lg2pi[2][9]
= { {70,70,70,70,70,70,70,70,70},
{70,75,75,75,75,80,80,80,80}
};
cout << "Setting up parameters\n";
if ((n<2 || n>10) and sec == -1) { throw invalid_params(); }
int m,lg2p0,lg2p1,ex;
char_2_dimension(m, lg2);
if (sec == -1)
{
lg2p0=lg2pi[0][n-2];
lg2p1=lg2pi[1][n-2];
}
else
{
NoiseBounds(2, phi_N(m), n, sec, slack).optimize(lg2p0, lg2p1);
finalize_lengths(lg2p0, lg2p1, n, m, lg2pi[0], round_up);
}
if (NoiseBounds::min_phi_m(lg2p0 + lg2p1) > phi_N(m))
throw runtime_error("number of slots too small");
cout << "m = " << m << endl;
init(R,m);
if (lg2p0==0 || lg2p1==0) { throw invalid_params(); }
// We want pr0=pr1=1 mod (m*twop) where twop is the smallest
// power of two bigger than 2*m. This means we have m'th roots of
// unity and twop'th roots of unity. This means FFT's are easier
// to implement
int lg2m=numBits(m);
bigint step=m<<(lg2m+1);
ex=lg2p0-2*lg2m;
pr0=1; pr0=(pr0<<ex)*step+1;
while (!probPrime(pr0)) { pr0=pr0+step; }
cout << "\t pr0 = " << pr0 << " : " << numBits(pr0) << endl;
ex=lg2p1-2*lg2m;
pr1=1; pr1=(pr1<<ex)*step+1;
while (!probPrime(pr1) || pr1==pr0) { pr1=pr1+step; }
cout << "\t pr1 = " << pr1 << " : " << numBits(pr1) << endl;
cout << "\t\tFollowing should be both 1" << endl;
cout << "\t\tpr1 mod m = " << pr1%m << endl;
cout << "\t\tpr1 mod 2^lg2m = " << pr1%(1<<lg2m) << endl;
gf2n_short::init_field(lg2);
load_or_generate(P2D, R);
}
void load_or_generate(P2Data& P2D, const Ring& R)
{
try
{
P2D.load(R);
}
catch (...)
{
cout << "Loading failed" << endl;
init(P2D,R);
P2D.store(R);
}
}
/*
* Create FHE parameters for a general plaintext modulus p
* Basically this is for general large primes only
*/
void SPDZ_Data_Setup_Char_p_General(Ring& R, PPData& PPD, bigint& pr0,
bigint& pr1, int n, int sec, bigint& p)
{
cout << "Setting up parameters" << endl;
int lgp=numBits(p);
int mm,idx;
// mm is the minimum value of m we will accept
if (lgp<48)
{ mm=100; // Test case
idx=0;
}
else if (lgp <96)
{ mm=8192;
idx=1;
}
else if (lgp<192)
{ mm=16384;
idx=2;
}
else if (lgp<384)
{ mm=16384;
idx=3;
}
else if (lgp<768)
{ mm=32768;
idx=4;
}
else
{ throw invalid_params(); }
// Now find the small factors of p-1 and their exponents
bigint t=p-1;
vector<long> primes(100),exp(100);
PrimeSeq s;
long pr;
pr=s.next();
int len=0;
while (pr<2*mm)
{ int e=0;
while ((t%pr)==0)
{ e++;
t=t/pr;
}
if (e!=0)
{ primes[len]=pr;
exp[len]=e;
if (len!=0) { cout << " * "; }
cout << pr << "^" << e << flush;
len++;
}
pr=s.next();
}
cout << endl;
// We want to find the best m which divides pr-1, such that
// - 2*m > phi(m) > mm
// - m has the smallest number of factors
vector<int> ee;
ee.resize(len);
for (int i=0; i<len; i++) { ee[i]=0; }
int min_hwt=-1,m=-1,bphi_m=-1,bmx=-1;
bool flag=true;
while (flag)
{ int cand_m=1,hwt=0,mx=0;
for (int i=0; i<len; i++)
{ //cout << ee[i] << " ";
if (ee[i]!=0)
{ hwt++;
for (int j=0; j<ee[i]; j++)
{ cand_m*=primes[i]; }
if (ee[i]>mx) { mx=ee[i]; }
}
}
// Put "if" here to stop searching for things which will never work
if (cand_m>1 && cand_m<4*mm)
{ //cout << " : " << cand_m << " : " << hwt << flush;
int phim=phi_N(cand_m);
//cout << " : " << phim << " : " << mm << endl;
if (phim>mm && phim<3*mm)
{ if (m==-1 || hwt<min_hwt || (hwt==min_hwt && mx<bmx) || (hwt==min_hwt && mx==bmx && phim<bphi_m))
{ m=cand_m;
min_hwt=hwt;
bphi_m=phim;
bmx=mx;
//cout << "\t OK" << endl;
}
}
}
else
{ //cout << endl;
}
int i=0;
ee[i]=ee[i]+1;
while (ee[i]>exp[i] && flag)
{ ee[i]=0;
i++;
if (i==len) { flag=false; i=0; }
else { ee[i]=ee[i]+1; }
}
}
if (m==-1)
{ throw bad_value(); }
cout << "Chosen value of m=" << m << "\t\t phi(m)=" << bphi_m << " : " << min_hwt << " : " << bmx << endl;
SPDZ_Data_Setup_Char_p_Sub(R,pr0,pr1,n,idx,m,p,sec);
int mx=0;
for (int i=0; i<R.phi_m(); i++)
{ if (mx<R.Phi()[i]) { mx=R.Phi()[i]; } }
cout << "Max Coeff = " << mx << endl;
Zp_Data Zp(p);
gfp::init_field(p);
PPD.init(R,Zp);
}