initial commit
This commit is contained in:
137
README
Normal file
137
README
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
An implementation of the DGHV fully homomorphic scheme
|
||||||
|
|
||||||
|
This is an implementation of the DGHV fully homomorphic scheme with
|
||||||
|
compressed public-key. This implementation is described in the
|
||||||
|
following article:
|
||||||
|
|
||||||
|
[1] J.S. Coron, D. Naccache and M. Tibouchi, "Public-key Compression and
|
||||||
|
Modulus Switching for Fully Homomorphic Encryption over the Integers",
|
||||||
|
Proceedings of Eurocrypt 2012.
|
||||||
|
|
||||||
|
available at http://eprint.iacr.org/2011/440
|
||||||
|
|
||||||
|
The implementation is done with the SAGE 4.7.2 mathematical library
|
||||||
|
under Python, available at http://www.sagemath.org/.
|
||||||
|
|
||||||
|
WHAT IS FULLY HOMOMORPHIC ENCRYPTION ?
|
||||||
|
--------------------------------------
|
||||||
|
|
||||||
|
An encryption scheme is fully homomorphic when it is possible to
|
||||||
|
perform implicit addition and multiplication of plaintexts while
|
||||||
|
manipulating only ciphertexts. The first construction of a fully
|
||||||
|
homomorphic encryption (FHE) scheme was described by Gentry in 2009.
|
||||||
|
|
||||||
|
WHAT IS THE DGHV SCHEME ?
|
||||||
|
-------------------------
|
||||||
|
|
||||||
|
The DGHV scheme is a FHE scheme published in:
|
||||||
|
|
||||||
|
[2] M. van Dijk, C. Gentry, S. Halevi and V. Vaikuntanathan, "Fully
|
||||||
|
Homomorphic Encryption over the Integers". Proceedings of Eurocrypt
|
||||||
|
2010.
|
||||||
|
|
||||||
|
and available at http://eprint.iacr.org/2009/616
|
||||||
|
|
||||||
|
In DGHV a ciphertext has the form:
|
||||||
|
|
||||||
|
c= q*p + 2*r + m
|
||||||
|
|
||||||
|
where p is the secret-key, m is the bit plaintext (m=0 or 1), q is a
|
||||||
|
large random, and r is a small random.
|
||||||
|
|
||||||
|
To decrypt, compute m=(c mod p) mod 2. It is easy to see that
|
||||||
|
decryption works as long as the noise 2*r is smaller than p.
|
||||||
|
|
||||||
|
Given two ciphertexts
|
||||||
|
c1= q1*p + 2*r1 + m1
|
||||||
|
c2= q2*p + 2*r2 + m2
|
||||||
|
|
||||||
|
we have:
|
||||||
|
|
||||||
|
c1+c2=(q1+q2)*p+2*(r1+r2)+m1+m2
|
||||||
|
|
||||||
|
Therefore one can obtain the encryption of m1+m2 (mod 2)=m1 xor m2 simply by
|
||||||
|
computing c1+c2.
|
||||||
|
|
||||||
|
Similarly we have:
|
||||||
|
|
||||||
|
c1*c2=q12*p+2*(2*r1*r2+r1*m2+r2*m1)+m1*m2
|
||||||
|
|
||||||
|
Therefore one can obtain the encryption of m1*m2 simply by computing
|
||||||
|
c1*c2. However one gets a new ciphertext with noise roughly
|
||||||
|
twice larger than in the original ciphertexts c1 and c2. Since the
|
||||||
|
noise must remain below p, the number of permitted multiplications on
|
||||||
|
ciphertexts is therefore limited. This is called a somewhat
|
||||||
|
homomorphic encryption scheme.
|
||||||
|
|
||||||
|
To obtain a fully homomorphic encryption scheme, i.e. unlimited
|
||||||
|
addition and multiplication on ciphertexts, one must be able to reduce
|
||||||
|
the amount of noise in a ciphertext; this is called a ciphertext
|
||||||
|
refresh.
|
||||||
|
|
||||||
|
Gentry's key idea to refresh a ciphertext is to homomorphically
|
||||||
|
evaluate the decryption circuit on the ciphertext bits, using an
|
||||||
|
encryption of the secret-key bits. This is called bootstrapping.
|
||||||
|
Then instead of getting the plaintext bit (as we would get if we
|
||||||
|
would evaluate the decryption circuit with the secret-key bits in
|
||||||
|
clear), we get an __encryption__ of the plaintext bit, i.e. a new
|
||||||
|
ciphertext for the same plaintext. Now if the decryption circuit has a
|
||||||
|
small enough depth, then the amount of noise in the new ciphertext can
|
||||||
|
be actually smaller than in the original plaintext, hence a ciphertext refresh.
|
||||||
|
|
||||||
|
However the previous decryption algorithm m=(c mod p) mod 2 does not
|
||||||
|
have a small depth, therefore the decryption procedure must be "squashed" so
|
||||||
|
that it can be expressed as a low depth circuit. How this is done in
|
||||||
|
explained in [2].
|
||||||
|
|
||||||
|
So far we have only described a secret-key encryption scheme, i.e. to
|
||||||
|
encrypt one must know the secret-key p. However it is easy to obtain a
|
||||||
|
public-key encryption scheme. For this generate a public set of
|
||||||
|
ciphertexts xi which are all different encryptions of 0:
|
||||||
|
|
||||||
|
xi=qi*p+2*ri
|
||||||
|
|
||||||
|
and to encrypt a bit m compute:
|
||||||
|
|
||||||
|
c=m+2*r+random_subset_sum(xi)
|
||||||
|
|
||||||
|
It is easy to see that c is indeed an encryption of the bit m.
|
||||||
|
|
||||||
|
WHAT IS IMPLEMENTED ?
|
||||||
|
---------------------
|
||||||
|
|
||||||
|
We provide an implementation of the DGHV scheme with the fully
|
||||||
|
homomorphic capability, i.e. we implement the key generation,
|
||||||
|
encryption, decryption, add, mult and ciphertext refresh procedures.
|
||||||
|
|
||||||
|
The implementation is done using the Sage 4.7.2 mathematical library
|
||||||
|
under Python, available at http://www.sagemath.org/
|
||||||
|
|
||||||
|
First run sage, then type:
|
||||||
|
|
||||||
|
sage: load "dghv.sage"
|
||||||
|
sage: testPkRecrypt()
|
||||||
|
|
||||||
|
The testPkRecrypt() function demonstrates key generation, addition and
|
||||||
|
multiplication of ciphertexts, and ciphertext refresh. It runs for
|
||||||
|
four sets of parameters (toy, small, medium and large), as described
|
||||||
|
in [1].
|
||||||
|
|
||||||
|
|
||||||
|
WHAT IS A COMPRESSED PUBLIC-KEY ?
|
||||||
|
---------------------------------
|
||||||
|
|
||||||
|
In DGHV the ciphertext size must be huge to prevent attacks based on
|
||||||
|
lattice reduction algorithms; at least 10^7 bits. The secret p is
|
||||||
|
comparatively smaller, roughly 2000 bits. Since roughly 10^4
|
||||||
|
ciphertexts xi must be included in the public-key, that would give a
|
||||||
|
public-key size of 10^11 bits, i.e. 12.5 GB.
|
||||||
|
|
||||||
|
To reduce the public-key size we implement the following technique
|
||||||
|
described in [1]. Instead of generating xi as xi=qi*p+2*ri, one first
|
||||||
|
generates a pseudo-random Xi of the same size, and computes a small
|
||||||
|
correction di such that xi=Xi-di is small modulo p. Then only these
|
||||||
|
small corrections need to be stored in the public-key, with the seed
|
||||||
|
of the PRNG. Using this compression technique the public-key size
|
||||||
|
becomes 10^4*(2*10^3)=2*10^7 bits, i.e. 2.5 MB, which is more
|
||||||
|
manageable.
|
1
README.md
Normal file
1
README.md
Normal file
@ -0,0 +1 @@
|
|||||||
|
DGHV sage implementation ported to python3 from the original python2 version available here: https://github.com/coron/fhe
|
234
dghv.sage
Normal file
234
dghv.sage
Normal file
@ -0,0 +1,234 @@
|
|||||||
|
#
|
||||||
|
# dghv.sage: an implementation of DGHV-FHE with compressed public-key
|
||||||
|
#
|
||||||
|
# as described in:
|
||||||
|
#
|
||||||
|
# J.S. Coron, D. Naccache and M. Tibouchi, "Public-key Compression and Modulus
|
||||||
|
# Switching for Fully Homomorphic Encryption over the Integers"
|
||||||
|
#
|
||||||
|
# available at http://eprint.iacr.org/2011/440
|
||||||
|
#
|
||||||
|
# Copyright (c) 2012 Jean-Sebastien Coron <jean-sebastien.coron@uni.lu>
|
||||||
|
# and Mehdi Tibouchi <mehdi.tibouchi@normalesup.org>
|
||||||
|
#
|
||||||
|
# This program is free software; you can redistribute it and/or modify it
|
||||||
|
# under the terms of the GNU General Public License version 2 as published
|
||||||
|
# by the Free Software Foundation.
|
||||||
|
|
||||||
|
load scalprod.spyx
|
||||||
|
load utils.sage
|
||||||
|
from itertools import chain, islice, count
|
||||||
|
from time import time
|
||||||
|
|
||||||
|
theta = 15
|
||||||
|
n = 4
|
||||||
|
|
||||||
|
class Pk(object):
|
||||||
|
"The PK of the DGHV somewhat homomorphic scheme, without the xi's"
|
||||||
|
def __init__(self, rho, eta, gam, modx0=True, verbose=True, *args, **kwargs):
|
||||||
|
print(" Pk: generation of p")
|
||||||
|
self.rho, self.eta, self.gam, self.modx0 = rho, eta, gam, modx0
|
||||||
|
self.p = random_prime(2^self.eta, lbound=2^(self.eta-1), proof=False)
|
||||||
|
if self.modx0:
|
||||||
|
self.x0 = self.p * RandomOdd(self.gam - self.eta)
|
||||||
|
|
||||||
|
def encrypt(self, m):
|
||||||
|
return self.p * ZZ.random_element(2^(self.gam - self.eta - 1)) + 2 * ZZ.random_element(-2^self.rho + 1, 2^self.rho) + m
|
||||||
|
|
||||||
|
def noise(self, c):
|
||||||
|
return modNear(c, self.p)
|
||||||
|
|
||||||
|
def decrypt(self, c):
|
||||||
|
return mod(self.noise(c), 2)
|
||||||
|
|
||||||
|
def add(self, c1, c2):
|
||||||
|
return self.modx0 and mod(c1 + c2, self.x0).lift() or (c1 + c2)
|
||||||
|
|
||||||
|
def mult(self, c1, c2):
|
||||||
|
return self.modx0 and mod(c1 * c2, self.x0).lift() or (c1 * c2)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "<Pk with rho=%d, eta=%d, gam=%d>" % (self.rho, self.eta, self.gam)
|
||||||
|
|
||||||
|
class Ciphertext():
|
||||||
|
def __init__(self, val_, pk_, degree_=1):
|
||||||
|
self.val, self.pk, self.degree = val_, pk_, degree_
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def encrypt(pk, m=None):
|
||||||
|
if m == None:
|
||||||
|
m = ZZ.random_element(2)
|
||||||
|
return Ciphertext(pk.encrypt(m), pk)
|
||||||
|
|
||||||
|
def noise(self):
|
||||||
|
return self.pk.noise(self.val)
|
||||||
|
|
||||||
|
def decrypt(self, verbose=False):
|
||||||
|
t = cputime(subprocesses=True)
|
||||||
|
m = self.pk.decrypt(self.val)
|
||||||
|
if verbose:
|
||||||
|
print("Decrypt", cputime(subprocesses=True) - t)
|
||||||
|
return m
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "<Ciphertext with m=%d size=%d noise=%d deg=%d>" % (self.decrypt(), self.val.nbits(), self.noise().nbits(), self.degree)
|
||||||
|
|
||||||
|
def __add__(self, x):
|
||||||
|
return self.__class__(self.pk.add(self.val, x.val), self.pk, max(self.degree, x.degree))
|
||||||
|
|
||||||
|
def __mul__(self, x):
|
||||||
|
return self.__class__(self.pk.mult(self.val, x.val), self.pk, self.degree + x.degree)
|
||||||
|
|
||||||
|
def scalmult(self, x):
|
||||||
|
if isinstance(x, array):
|
||||||
|
return array([self.__class__(self.val * xi, self.pk, self.degree) for xi in x])
|
||||||
|
else:
|
||||||
|
return self.__class__(self.val * x, self.pk, self.degree)
|
||||||
|
|
||||||
|
def expand(self):
|
||||||
|
return self.pk.expand(self.val)
|
||||||
|
|
||||||
|
class PRIntegers:
|
||||||
|
"A list of pseudo-random integers."
|
||||||
|
def __init__(self, gam, ell):
|
||||||
|
self.gam, self.ell = gam, ell
|
||||||
|
self.li = [None for i in range(self.ell)]
|
||||||
|
set_random_seed()
|
||||||
|
self.se = initial_seed()
|
||||||
|
|
||||||
|
def __getitem__(self, i):
|
||||||
|
return self.li[i]
|
||||||
|
|
||||||
|
def __setitem__(self, i, val):
|
||||||
|
self.li[i] = val
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
set_random_seed(self.se)
|
||||||
|
for i in range(self.ell):
|
||||||
|
a = ZZ.random_element(2^self.gam)
|
||||||
|
if self.li[i] != None:
|
||||||
|
yield self.li[i]
|
||||||
|
else:
|
||||||
|
yield a
|
||||||
|
set_random_seed()
|
||||||
|
|
||||||
|
class PRIntegersDelta(PRIntegers):
|
||||||
|
"""A list of pseudo-random integers, with their delta corrections"""
|
||||||
|
def __iter__(self):
|
||||||
|
return (c + d for c, d in zip(PRIntegers.__iter__(self), self.delta))
|
||||||
|
|
||||||
|
def ciphertexts(self, pk):
|
||||||
|
return (Ciphertext(cd, pk) for cd in self)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def encrypt(pk, v):
|
||||||
|
pr = PRIntegersDelta(pk.gam, len(v))
|
||||||
|
r = [ZZ.random_element(-2^pk.rho + 1, 2^pk.rho) for i in range(len(v))]
|
||||||
|
pr.delta = [0 for i in range(len(v))]
|
||||||
|
|
||||||
|
temp = [-mod(xi, pk.p) for xi in PRIntegers.__iter__(pr)]
|
||||||
|
pr.delta = [te.lift() + 2 * ri + vi for te, ri, vi in zip(temp, r, v)]
|
||||||
|
return pr
|
||||||
|
|
||||||
|
class PkRecrypt(Pk):
|
||||||
|
"The Pk of the DGHV scheme, with the xi's, the yi's and the encrypted secret-key bits"
|
||||||
|
def __init__(self, rho, eta, gam, Theta, pkRecrypt=None, *args, **kwargs):
|
||||||
|
t = cputime(subprocesses=True)
|
||||||
|
super(PkRecrypt, self).__init__(rho, eta, gam, *args, **kwargs)
|
||||||
|
self.kap = 64 * (self.gam // 64 + 1) - 1
|
||||||
|
self.Theta = Theta
|
||||||
|
self.alpha, self.tau = kwargs['alpha'], kwargs['tau']
|
||||||
|
|
||||||
|
xp = QuotientNear(2^self.kap, self.p)
|
||||||
|
B = self.Theta // theta
|
||||||
|
|
||||||
|
assert self.Theta % theta == 0, "Theta must be a multiple of theta"
|
||||||
|
print(" PkRecrypt: generation of s")
|
||||||
|
self.s = [1] + [0 for i in range(B - 1)] + sum2([randSparse(B, 1) for i in range(theta - 1)])
|
||||||
|
|
||||||
|
t2 = cputime(subprocesses=True)
|
||||||
|
self.y = PRIntegers(self.kap, self.Theta)
|
||||||
|
self.y[0] = 0
|
||||||
|
self.y[0] = mod(xp - prodScal(self.s, self.y), 2^(self.kap + 1)).lift()
|
||||||
|
assert mod(prodScal(self.s, self.y), 2^(self.kap + 1)) == mod(xp, 2^(self.kap + 1)), "Equality not valid"
|
||||||
|
print(" PkRecrypt: generation of yis", cputime(subprocesses=True) - t2)
|
||||||
|
|
||||||
|
t2 = cputime(subprocesses=True)
|
||||||
|
self.x = PRIntegersDelta.encrypt(self, [0 for i in range(self.tau)])
|
||||||
|
print(" PkRecrypt: generation of xis", cputime(subprocesses=True) - t2)
|
||||||
|
|
||||||
|
t2 = cputime(subprocesses=True)
|
||||||
|
self.pkRecrypt = pkRecrypt
|
||||||
|
if not self.pkRecrypt:
|
||||||
|
self.pkRecrypt = self
|
||||||
|
self.se = PRIntegersDelta.encrypt(self.pkRecrypt, self.s)
|
||||||
|
print(" PkRecrypt: generation of seis", cputime(subprocesses=True) - t2)
|
||||||
|
|
||||||
|
print(" PkRecrypt: genkey", cputime(subprocesses=True) - t)
|
||||||
|
|
||||||
|
def encrypt_pk(self, m=None, verbose=True):
|
||||||
|
t = cputime(subprocesses=True)
|
||||||
|
if m == None:
|
||||||
|
m = ZZ.random_element(2)
|
||||||
|
rhop = self.eta - self.rho
|
||||||
|
|
||||||
|
f = [Ciphertext(ZZ.random_element(2^self.alpha), self) for i in range(self.tau)]
|
||||||
|
|
||||||
|
c = sum2(ci * fi for ci, fi in zip(self.x.ciphertexts(self), f)) + Ciphertext(ZZ.random_element(2^rhop), self) + Ciphertext(m, self)
|
||||||
|
if verbose:
|
||||||
|
print("Encrypt", cputime(subprocesses=True) - t)
|
||||||
|
c.degree = 1
|
||||||
|
return c
|
||||||
|
|
||||||
|
def expand(self, c, verbose=True):
|
||||||
|
m = 2^(n + 1) - 1
|
||||||
|
t = cputime(subprocesses=True)
|
||||||
|
|
||||||
|
ce = [(((directProd(c, yi, self.kap) >> (32 - (n + 2))) + 1) >> 1) & m for yi in self.y]
|
||||||
|
|
||||||
|
if verbose:
|
||||||
|
print("expand", cputime(subprocesses=True) - t)
|
||||||
|
return ce
|
||||||
|
|
||||||
|
def recrypt(self, c, verbose=False):
|
||||||
|
t = cputime(subprocesses=True)
|
||||||
|
B = self.Theta // theta
|
||||||
|
cebin = [array(toBinary(cei, n + 1)) for cei in c.expand()]
|
||||||
|
|
||||||
|
sec = (c.__class__(ci, self.pkRecrypt) for ci in self.se)
|
||||||
|
|
||||||
|
li = (ski.scalmult(cei) for ski, cei in zip(sec, cebin))
|
||||||
|
ly = [sum2(islice(li, B)) for i in range(theta)]
|
||||||
|
|
||||||
|
res = reduceSteps(sumBinary, ly, verbose)
|
||||||
|
v = res[-1] + res[-2]
|
||||||
|
v.val += c.val & 1
|
||||||
|
if verbose:
|
||||||
|
print("recrypt", cputime(subprocesses=True) - t)
|
||||||
|
return v
|
||||||
|
|
||||||
|
def testPkRecrypt():
|
||||||
|
toy = {'ty': "toy", 'lam': 42, 'rho': 26, 'eta': 988, 'gam': 147456, 'Theta': 150, 'pksize': 0.076519, 'seclevel': 42.0, 'alpha': 936, 'tau': 158}
|
||||||
|
small = {'ty': "small", 'lam': 52, 'rho': 41, 'eta': 1558, 'gam': 843033, 'Theta': 555, 'pksize': 0.437567, 'seclevel': 52.0, 'alpha': 1476, 'tau': 572}
|
||||||
|
medium = {'ty': "medium", 'lam': 62, 'rho': 56, 'eta': 2128, 'gam': 4251866, 'Theta': 2070, 'pksize': 2.207241, 'seclevel': 62.0, 'alpha': 2016, 'tau': 2110}
|
||||||
|
large = {'ty': "large", 'lam': 72, 'rho': 71, 'eta': 2698, 'gam': 19575950, 'Theta': 7965, 'pksize': 10.303797, 'seclevel': 72.0, 'alpha': 2556, 'tau': 7659}
|
||||||
|
|
||||||
|
for param in [toy, small, medium, large]:
|
||||||
|
ty, rho, eta, gam, Theta = [param[x] for x in ['ty', 'rho', 'eta', 'gam', 'Theta']]
|
||||||
|
print("type=", ty, "lam=", param['lam'], "rho=", rho, "eta=", eta, "gamma=", gam, "Theta=", Theta)
|
||||||
|
pk = PkRecrypt(rho, eta, gam, Theta, tau=param['tau'], alpha=param['alpha'])
|
||||||
|
|
||||||
|
c1 = Ciphertext.encrypt(pk)
|
||||||
|
c2 = Ciphertext.encrypt(pk)
|
||||||
|
print("c1=", c1)
|
||||||
|
print("c2=", c2)
|
||||||
|
print("c1+c2=", c1 + c2)
|
||||||
|
t = cputime(subprocesses=True)
|
||||||
|
print("c1*c2=", c1 * c2)
|
||||||
|
print("tmult:", cputime(subprocesses=True) - t)
|
||||||
|
|
||||||
|
c = pk.encrypt_pk()
|
||||||
|
c.decrypt(verbose=True)
|
||||||
|
print("c=", c)
|
||||||
|
newc = pk.recrypt(c, verbose=True)
|
||||||
|
print("newc=", newc)
|
47
directscal.c
Normal file
47
directscal.c
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
/*
|
||||||
|
* directscale.c: faster implementation of ciphertext expand
|
||||||
|
*
|
||||||
|
* Copyright (c) 2012 Mehdi Tibouchi <mehdi.tibouchi@normalesup.org>
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License version 2 as published
|
||||||
|
* by the Free Software Foundation.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <gmp.h>
|
||||||
|
|
||||||
|
#define w (GMP_NUMB_BITS/2)
|
||||||
|
#define _BOTMASK ((1ul << w)-1)
|
||||||
|
#define _TOPMASK (~_BOTMASK)
|
||||||
|
#define BOT(x) ((x) & _BOTMASK)
|
||||||
|
#define TOP(x) ((x) >> w)
|
||||||
|
#define LIMB(z,i) (((i)<((z)->_mp_size))?((z)->_mp_d[i]):(0L))
|
||||||
|
#define BOTL(z,i) (BOT(LIMB(z,i)))
|
||||||
|
#define TOPL(z,i) (TOP(LIMB(z,i)))
|
||||||
|
#define HLIMB(z,j) ((j&1)?(TOPL(z,j>>1)):(BOTL(z,j>>1)))
|
||||||
|
|
||||||
|
unsigned getGMP_NUMB_BITS()
|
||||||
|
{
|
||||||
|
return GMP_NUMB_BITS;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned long directScal(unsigned long kap, mpz_t cz, mpz_t yz)
|
||||||
|
{
|
||||||
|
unsigned long nW=(kap+1)/(2*w), val=0, i;
|
||||||
|
|
||||||
|
if(nW*w*2 != kap+1)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
for(i = 0; i < nW-1; i++) {
|
||||||
|
val += BOTL(cz,i) * LIMB(yz,nW-1-i);
|
||||||
|
val += (BOTL(cz,i) * TOPL(yz,nW-2-i)) >> w;
|
||||||
|
val += TOPL(cz,i) * ((BOTL(yz,nW-1-i) << w) + TOPL(yz,nW-2-i));
|
||||||
|
val += (TOPL(cz,i) * BOTL(yz,nW-2-i)) >> w;
|
||||||
|
}
|
||||||
|
|
||||||
|
val += BOTL(cz,nW-1) * LIMB(yz,0);
|
||||||
|
val += TOPL(cz,nW-1) * (BOTL(yz,0) << w);
|
||||||
|
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
55
scalprod.spyx
Normal file
55
scalprod.spyx
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
#
|
||||||
|
# scalprod.spyx: faster implementation of ciphertext expand
|
||||||
|
#
|
||||||
|
# Copyright (c) 2012 Jean-Sebastien Coron <jean-sebastien.coron@uni.lu>
|
||||||
|
# and Mehdi Tibouchi <mehdi.tibouchi@normalesup.org>
|
||||||
|
#
|
||||||
|
# This program is free software; you can redistribute it and/or modify it
|
||||||
|
# under the terms of the GNU General Public License version 2 as published
|
||||||
|
# by the Free Software Foundation.
|
||||||
|
|
||||||
|
from sage.libs.gmp.all cimport mpz_t, mpz_export
|
||||||
|
from sage.rings.integer cimport Integer
|
||||||
|
from sage.misc.misc import cputime
|
||||||
|
from sage.all import ZZ
|
||||||
|
from libc.stdlib cimport calloc, free
|
||||||
|
|
||||||
|
cdef extern from "directscal.c":
|
||||||
|
unsigned long directScal(unsigned long kap, mpz_t c, mpz_t y)
|
||||||
|
unsigned getGMP_NUMB_BITS()
|
||||||
|
|
||||||
|
def directProd(c, y, kap):
|
||||||
|
cdef Integer yz = <Integer>y
|
||||||
|
cdef Integer cz = <Integer>c
|
||||||
|
return ZZ(directScal(kap, cz.value, yz.value)) >> (getGMP_NUMB_BITS() - 32)
|
||||||
|
|
||||||
|
def partialProd(c, y, kap):
|
||||||
|
cdef int w = sizeof(int) * 4 # 16 bits
|
||||||
|
cdef int nw = (kap + 1) // w
|
||||||
|
assert nw * w == kap + 1
|
||||||
|
|
||||||
|
cdef Integer yz = <Integer>y
|
||||||
|
cdef size_t ny
|
||||||
|
cdef unsigned int* yw = <unsigned int*> calloc(nw, sizeof(int))
|
||||||
|
|
||||||
|
cdef int order = -1 # least significant word first
|
||||||
|
cdef int endian = 0
|
||||||
|
cdef size_t nails = sizeof(int) * 4
|
||||||
|
mpz_export(yw, &ny, order, sizeof(int), endian, nails, yz.value)
|
||||||
|
assert ny <= nw
|
||||||
|
|
||||||
|
cdef Integer cz = <Integer>c
|
||||||
|
cdef size_t nc
|
||||||
|
cdef unsigned int* cw = <unsigned int*> calloc(nw, sizeof(int))
|
||||||
|
mpz_export(cw, &nc, order, sizeof(int), endian, nails, cz.value)
|
||||||
|
assert nc <= nw
|
||||||
|
|
||||||
|
cdef unsigned int val = 0
|
||||||
|
for i in range(nw - 2):
|
||||||
|
val += cw[i] * ((yw[nw-1-i] << w) + yw[nw-2-i]) + ((cw[i] * yw[nw-3-i]) >> w)
|
||||||
|
|
||||||
|
val += cw[nw-2] * ((yw[1] << w) + yw[0]) + cw[nw-1] * (yw[0] << w)
|
||||||
|
|
||||||
|
free(cw)
|
||||||
|
free(yw)
|
||||||
|
return ZZ(val)
|
86
utils.sage
Normal file
86
utils.sage
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
#
|
||||||
|
# utils.sage
|
||||||
|
#
|
||||||
|
# Copyright (c) 2012 Jean-Sebastien Coron <jean-sebastien.coron@uni.lu>
|
||||||
|
#
|
||||||
|
# This program is free software; you can redistribute it and/or modify it
|
||||||
|
# under the terms of the GNU General Public License version 2 as published
|
||||||
|
# by the Free Software Foundation.
|
||||||
|
|
||||||
|
|
||||||
|
from itertools import chain, islice, zip_longest
|
||||||
|
from sys import stdout
|
||||||
|
|
||||||
|
def RandomOdd(n):
|
||||||
|
return 2 * (2^(n-2) + ZZ.random_element(2^(n-2))) + 1
|
||||||
|
|
||||||
|
def sum2(li):
|
||||||
|
empty = True
|
||||||
|
for x in li:
|
||||||
|
if empty:
|
||||||
|
res = x
|
||||||
|
empty = False
|
||||||
|
else:
|
||||||
|
res = res + x
|
||||||
|
if empty:
|
||||||
|
return 0
|
||||||
|
else:
|
||||||
|
return res
|
||||||
|
|
||||||
|
class array(list):
|
||||||
|
def __mul__(self, x):
|
||||||
|
return array([x * ci for ci in self])
|
||||||
|
|
||||||
|
def __add__(self, xx):
|
||||||
|
return array([ai + xi for ai, xi in zip(self, xx)])
|
||||||
|
|
||||||
|
def reduceSteps(f, li, verbose=True):
|
||||||
|
i = 0
|
||||||
|
res = li[0]
|
||||||
|
if verbose:
|
||||||
|
print(i, end=' ')
|
||||||
|
for x in li[1:]:
|
||||||
|
stdout.flush()
|
||||||
|
i = i + 1
|
||||||
|
res = f(res, x)
|
||||||
|
if verbose:
|
||||||
|
print(i, end=' ')
|
||||||
|
if verbose:
|
||||||
|
print()
|
||||||
|
return res
|
||||||
|
|
||||||
|
def sumBinary(a, b):
|
||||||
|
"Computes the sum of the binary vectors a and b, modulo 2^n where n is the length of the vectors a and b"
|
||||||
|
c = [a[0] + b[0]]
|
||||||
|
carry = a[0] * b[0]
|
||||||
|
|
||||||
|
for i in range(1, len(a) - 1):
|
||||||
|
carry2 = (a[i] + b[i]) * carry + a[i] * b[i]
|
||||||
|
c.append(a[i] + b[i] + carry)
|
||||||
|
carry = carry2
|
||||||
|
|
||||||
|
c.append(a[-1] + b[-1] + carry)
|
||||||
|
return c
|
||||||
|
|
||||||
|
def QuotientNear(a, b):
|
||||||
|
"Gives the nearest integer to a/b"
|
||||||
|
return (2 * a + b) // (2 * b)
|
||||||
|
|
||||||
|
def modNear(a, b):
|
||||||
|
"Computes a mod b with a in ]-b/2,b/2]"
|
||||||
|
return a - b * QuotientNear(a, b)
|
||||||
|
|
||||||
|
def toBinary(x, l):
|
||||||
|
"Converts a positive integer x into binary with l digits"
|
||||||
|
return (x + 2^l).digits(2)[:-1]
|
||||||
|
|
||||||
|
def prodScal(x, y):
|
||||||
|
return sum2((xi * yi for xi, yi in zip(x, y)))
|
||||||
|
|
||||||
|
def randSparse(n, h):
|
||||||
|
v = [0 for i in range(n)]
|
||||||
|
while sum(v) < h:
|
||||||
|
i = int(ZZ.random_element(n))
|
||||||
|
if v[i] == 0:
|
||||||
|
v[i] = 1
|
||||||
|
return v
|
Reference in New Issue
Block a user