Skip to content

Commit c9b03d8

Browse files
committed
Distributed key generation for homomorphic encryption with active security.
1 parent 08a80cf commit c9b03d8

136 files changed

Lines changed: 2117 additions & 492 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CHANGELOG.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,14 @@
11
The changelog explains changes pulled through from the private development repository. Bug fixes and small enhancements are committed between releases and not documented here.
22

3-
## 0.2.2 (Jan 21, 2020)
3+
## 0.2.3 (Feb 23, 2021)
4+
5+
- Distributed key generation for homomorphic encryption with active security similar to [Rotaru et al.](https://eprint.iacr.org/2019/1300)
6+
- Homomorphic encryption parameters more similar to SCALE-MAMBA
7+
- Fixed security bug: all-zero secret keys in homomorphic encryption
8+
- Fixed security bug: missing check in binary Rep4
9+
- Fixed security bug: insufficient "blaming" (covert security) in CowGear and HighGear due to low default security parameter
10+
11+
## 0.2.2 (Jan 21, 2021)
412

513
- Infrastructure for random element generation
614
- Programs generating as much preprocessing data as required by a particular high-level program

Compiler/GC/instructions.py

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -490,6 +490,9 @@ class bitb(NonVectorInstruction):
490490
code = opcodes['BITB']
491491
arg_format = ['sbw']
492492

493+
def add_usage(self, req_node):
494+
req_node.increment(('bit', 'bit'), 1)
495+
493496
class reveal(BinaryVectorInstruction, base.VarArgsInstruction, base.Mergeable):
494497
""" Reveal secret bit register vectors and copy result to clear bit
495498
register vectors.
@@ -519,6 +522,10 @@ class inputb(base.DoNotEliminateInstruction, base.VarArgsInstruction):
519522
arg_format = tools.cycle(['p','int','int','sbw'])
520523
is_vec = lambda self: True
521524

525+
def add_usage(self, req_node):
526+
for i in range(0, len(self.args), 4):
527+
req_node.increment(('bit', 'input', self.args[0]), self.args[1])
528+
522529
class inputbvec(base.DoNotEliminateInstruction, base.VarArgsInstruction,
523530
base.Mergeable):
524531
""" Copy private input to secret bit registers bit by bit. The input is
@@ -538,17 +545,26 @@ class inputbvec(base.DoNotEliminateInstruction, base.VarArgsInstruction,
538545

539546
def __init__(self, *args, **kwargs):
540547
self.arg_format = []
548+
for x in self.get_arg_tuples(args):
549+
self.arg_format += ['int', 'int', 'p'] + ['sbw'] * (x[0] - 3)
550+
super(inputbvec, self).__init__(*args, **kwargs)
551+
552+
@staticmethod
553+
def get_arg_tuples(args):
541554
i = 0
542555
while i < len(args):
543-
self.arg_format += ['int', 'int', 'p'] + ['sbw'] * (args[i] - 3)
556+
yield args[i:i+args[i]]
544557
i += args[i]
545558
assert i == len(args)
546-
super(inputbvec, self).__init__(*args, **kwargs)
547559

548560
def merge(self, other):
549561
self.args += other.args
550562
self.arg_format += other.arg_format
551563

564+
def add_usage(self, req_node):
565+
for x in self.get_arg_tuples(self.args):
566+
req_node.increment(('bit', 'input', x[2]), x[0] - 3)
567+
552568
class print_regb(base.VectorInstruction, base.IOInstruction):
553569
""" Debug output of clear bit register.
554570

Compiler/allocator.py

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -371,6 +371,14 @@ def mem_access(n, instr, last_access_this_kind, last_access_other_kind):
371371
# hack
372372
warned_about_mem.append(True)
373373

374+
def strict_mem_access(n, last_this_kind, last_other_kind):
375+
if last_other_kind and last_this_kind and \
376+
last_other_kind[-1] > last_this_kind[-1]:
377+
last_this_kind[:] = []
378+
last_this_kind.append(n)
379+
for i in last_other_kind:
380+
add_edge(i, n)
381+
374382
def keep_order(instr, n, t, arg_index=None):
375383
if arg_index is None:
376384
player = None
@@ -444,26 +452,17 @@ def keep_merged_order(instr, n, t):
444452

445453
if isinstance(instr, ReadMemoryInstruction):
446454
if options.preserve_mem_order:
447-
if last_mem_write and last_mem_read and last_mem_write[-1] > last_mem_read[-1]:
448-
last_mem_read[:] = []
449-
last_mem_read.append(n)
450-
for i in last_mem_write:
451-
add_edge(i, n)
455+
strict_mem_access(n, last_mem_read, last_mem_write)
452456
else:
453457
mem_access(n, instr, last_mem_read_of, last_mem_write_of)
454458
elif isinstance(instr, WriteMemoryInstruction):
455459
if options.preserve_mem_order:
456-
if last_mem_write and last_mem_read and last_mem_write[-1] < last_mem_read[-1]:
457-
last_mem_write[:] = []
458-
last_mem_write.append(n)
459-
for i in last_mem_read:
460-
add_edge(i, n)
460+
strict_mem_access(n, last_mem_write, last_mem_read)
461461
else:
462462
mem_access(n, instr, last_mem_write_of, last_mem_read_of)
463463
elif isinstance(instr, matmulsm):
464464
if options.preserve_mem_order:
465-
for i in last_mem_write:
466-
add_edge(i, n)
465+
strict_mem_access(n, last_mem_read, last_mem_write)
467466
else:
468467
for i in last_mem_write_of.values():
469468
for j in i:

Compiler/comparison.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -452,6 +452,11 @@ def CarryOutRaw(a, b, c=0):
452452
assert len(a) == len(b)
453453
k = len(a)
454454
from . import types
455+
if program.linear_rounds():
456+
carry = 0
457+
for (ai, bi) in zip(a, b):
458+
carry = bi.carry_out(ai, carry)
459+
return carry
455460
d = [program.curr_block.new_reg('s') for i in range(k)]
456461
s = [program.curr_block.new_reg('s') for i in range(3)]
457462
for i in range(k):

Compiler/ml.py

Lines changed: 57 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -627,15 +627,14 @@ class Dense(DenseBase):
627627
:param d_out: output dimension
628628
"""
629629
def __init__(self, N, d_in, d_out, d=1, activation='id', debug=False):
630-
self.activation = activation
631630
if activation == 'id':
632-
self.f = lambda x: x
631+
self.activation_layer = None
633632
elif activation == 'relu':
634-
self.f = relu
635-
self.f_prime = relu_prime
636-
elif activation == 'sigmoid':
637-
self.f = sigmoid
638-
self.f_prime = sigmoid_prime
633+
self.activation_layer = Relu([N, d, d_out])
634+
elif activation == 'square':
635+
self.activation_layer = Square([N, d, d_out])
636+
else:
637+
raise CompilerError('activation not supported: %s', activation)
639638

640639
self.N = N
641640
self.d_in = d_in
@@ -652,10 +651,16 @@ def __init__(self, N, d_in, d_out, d=1, activation='id', debug=False):
652651
self.nabla_W = sfix.Matrix(d_in, d_out)
653652
self.nabla_b = sfix.Array(d_out)
654653

655-
self.f_input = MultiArray([N, d, d_out], sfix)
656-
657654
self.debug = debug
658655

656+
l = self.activation_layer
657+
if l:
658+
self.f_input = l.X
659+
l.Y = self.Y
660+
l.nabla_Y = self.nabla_Y
661+
else:
662+
self.f_input = self.Y
663+
659664
def reset(self):
660665
d_in = self.d_in
661666
d_out = self.d_out
@@ -699,10 +704,8 @@ def _(i):
699704

700705
def forward(self, batch=None):
701706
self.compute_f_input(batch=batch)
702-
@multithread(self.n_threads, len(batch), 128)
703-
def _(base, size):
704-
self.Y.assign_part_vector(self.f(
705-
self.f_input.get_part_vector(base, size)), base)
707+
if self.activation_layer:
708+
self.activation_layer.forward(batch)
706709
if self.debug:
707710
limit = self.debug
708711
@for_range_opt(len(batch))
@@ -731,26 +734,11 @@ def backward(self, compute_nabla_X=True, batch=None):
731734
nabla_W = self.nabla_W
732735
nabla_b = self.nabla_b
733736

734-
if self.activation == 'id':
735-
f_schur_Y = nabla_Y
737+
if self.activation_layer:
738+
self.activation_layer.backward(batch)
739+
f_schur_Y = self.activation_layer.nabla_X
736740
else:
737-
f_prime_bit = MultiArray([N, d, d_out], sint)
738-
f_schur_Y = MultiArray([N, d, d_out], sfix)
739-
740-
@multithread(self.n_threads, f_prime_bit.total_size())
741-
def _(base, size):
742-
f_prime_bit.assign_vector(
743-
self.f_prime(self.f_input.get_vector(base, size)), base)
744-
745-
progress('f prime')
746-
747-
@multithread(self.n_threads, f_prime_bit.total_size())
748-
def _(base, size):
749-
f_schur_Y.assign_vector(nabla_Y.get_vector(base, size) *
750-
f_prime_bit.get_vector(base, size),
751-
base)
752-
753-
progress('f prime schur Y')
741+
f_schur_Y = nabla_Y
754742

755743
if compute_nabla_X:
756744
@multithread(self.n_threads, N)
@@ -841,35 +829,55 @@ def _(k):
841829
def backward(self):
842830
self.nabla_X = self.nabla_Y.schur(self.B)
843831

844-
class Relu(NoVariableLayer):
845-
""" Fixed-point ReLU layer.
846-
847-
:param shape: input/output shape (tuple/list of int)
848-
"""
832+
class ElementWiseLayer(NoVariableLayer):
849833
def __init__(self, shape, inputs=None):
850834
self.X = Tensor(shape, sfix)
851835
self.Y = Tensor(shape, sfix)
836+
self.nabla_X = Tensor(shape, sfix)
837+
self.nabla_Y = Tensor(shape, sfix)
852838
self.inputs = inputs
853839

854840
def forward(self, batch=[0]):
855-
assert len(batch) == 1
856-
@multithread(self.n_threads, self.X[batch[0]].total_size())
841+
@multithread(self.n_threads, len(batch), 128)
857842
def _(base, size):
858-
tmp = relu(self.X[batch[0]].get_vector(base, size))
859-
self.Y[batch[0]].assign_vector(tmp, base)
843+
self.Y.assign_part_vector(self.f(
844+
self.X.get_part_vector(base, size)), base)
860845

861-
class Square(NoVariableLayer):
862-
""" Fixed-point square layer.
846+
def backward(self, batch):
847+
f_prime_bit = MultiArray(self.X.sizes, self.prime_type)
848+
849+
@multithread(self.n_threads, f_prime_bit.total_size())
850+
def _(base, size):
851+
f_prime_bit.assign_vector(
852+
self.f_prime(self.X.get_vector(base, size)), base)
853+
854+
progress('f prime')
855+
856+
@multithread(self.n_threads, f_prime_bit.total_size())
857+
def _(base, size):
858+
self.nabla_X.assign_vector(self.nabla_Y.get_vector(base, size) *
859+
f_prime_bit.get_vector(base, size),
860+
base)
861+
862+
progress('f prime schur Y')
863+
864+
class Relu(ElementWiseLayer):
865+
""" Fixed-point ReLU layer.
863866
864867
:param shape: input/output shape (tuple/list of int)
865868
"""
866-
def __init__(self, shape):
867-
self.X = MultiArray(shape, sfix)
868-
self.Y = MultiArray(shape, sfix)
869+
f = staticmethod(relu)
870+
f_prime = staticmethod(relu_prime)
871+
prime_type = sint
869872

870-
def forward(self, batch=[0]):
871-
assert len(batch) == 1
872-
self.Y.assign_vector(self.X.get_part_vector(batch[0]) ** 2)
873+
class Square(ElementWiseLayer):
874+
""" Fixed-point square layer.
875+
876+
:param shape: input/output shape (tuple/list of int)
877+
"""
878+
f = staticmethod(lambda x: x ** 2)
879+
f_prime = staticmethod(lambda x: cfix(2, size=x.size) * x)
880+
prime_type = sfix
873881

874882
class MaxPool(NoVariableLayer):
875883
""" Fixed-point MaxPool layer.

Compiler/program.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,7 @@ def __init__(self, args, options=defaults):
150150
self.use_split(int(options.split))
151151
self._square = False
152152
self._always_raw = False
153+
self._linear_rounds = False
153154
self.warn_about_mem = [True]
154155
Program.prog = self
155156
from . import instructions_base, instructions, types, comparison
@@ -461,6 +462,12 @@ def always_raw(self, change=None):
461462
else:
462463
self._always_raw = change
463464

465+
def linear_rounds(self, change=None):
466+
if change is None:
467+
return self._linear_rounds
468+
else:
469+
self._linear_rounds = change
470+
464471
def options_from_args(self):
465472
""" Set a number of options from the command-line arguments. """
466473
if 'trunc_pr' in self.args:
@@ -475,6 +482,8 @@ def options_from_args(self):
475482
self.always_raw(True)
476483
if 'edabit' in self.args:
477484
self.use_edabit(True)
485+
if 'linear_rounds' in self.args:
486+
self.linear_rounds(True)
478487

479488
def disable_memory_warnings(self):
480489
self.warn_about_mem.append(False)
@@ -855,7 +864,9 @@ def write_bytes(self, filename=None):
855864
filename = self.program.programs_dir + '/Bytecode/' + filename
856865
print('Writing to', filename)
857866
f = open(filename, 'wb')
858-
f.write(self.get_bytes())
867+
for i in self._get_instructions():
868+
if i is not None:
869+
f.write(i.get_bytes())
859870
f.close()
860871

861872
def new_reg(self, reg_type, size=None):

Compiler/types.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -267,7 +267,7 @@ def __mul__(self, other):
267267
def __pow__(self, exp):
268268
""" Exponentation through square-and-multiply.
269269
270-
:param exp: compile-time (int) """
270+
:param exp: any type allowing bit decomposition """
271271
if isinstance(exp, int) and exp >= 0:
272272
if exp == 0:
273273
return self.__class__(1)
@@ -425,6 +425,10 @@ def half_adder(self, other):
425425
:rtype: depending on inputs (secret if any of them is) """
426426
return self ^ other, self & other
427427

428+
def carry_out(self, a, b):
429+
s = a ^ b
430+
return a ^ (s & (self ^ a))
431+
428432
class _gf2n(_bit):
429433
""" :math:`\mathrm{GF}(2^n)` functionality. """
430434

@@ -5247,6 +5251,7 @@ def reveal(self):
52475251
bit_decompose = lambda self,*args,**kwargs: self.read().bit_decompose(*args, **kwargs)
52485252

52495253
if_else = lambda self,*args,**kwargs: self.read().if_else(*args, **kwargs)
5254+
bit_and = lambda self,other: self.read().bit_and(other)
52505255

52515256
def expand_to_vector(self, size=None):
52525257
if program.curr_block == self.last_write_block:

FHE/DiscreteGauss.cpp

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,13 +35,14 @@ int DiscreteGauss::sample(PRNG &G, int stretch) const
3535
void RandomVectors::set(int nn,int hh,double R)
3636
{
3737
n=nn;
38-
if (hh > 0)
39-
h=hh;
38+
h=hh;
4039
DG.set(R);
41-
assert(h > 0);
4240
}
4341

44-
42+
void RandomVectors::set_n(int nn)
43+
{
44+
n = nn;
45+
}
4546

4647
vector<bigint> RandomVectors::sample_Gauss(PRNG& G, int stretch) const
4748
{
@@ -54,8 +55,7 @@ vector<bigint> RandomVectors::sample_Gauss(PRNG& G, int stretch) const
5455

5556
vector<bigint> RandomVectors::sample_Hwt(PRNG& G) const
5657
{
57-
assert(h > 0);
58-
if (h>n/2) { return sample_Gauss(G); }
58+
if (h > n/2 or h <= 0) { return sample_Gauss(G); }
5959
vector<bigint> ans(n);
6060
for (int i=0; i<n; i++) { ans[i]=0; }
6161
int cnt=0,j=0;

FHE/DiscreteGauss.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,10 +48,11 @@ class RandomVectors
4848
public:
4949

5050
void set(int nn,int hh,double R); // R is input STANDARD DEVIATION
51+
void set_n(int nn);
5152

5253
void pack(octetStream& o) const { o.store(n); o.store(h); DG.pack(o); }
5354
void unpack(octetStream& o)
54-
{ o.get(n); o.get(h); DG.unpack(o); if(h <= 0) throw exception(); }
55+
{ o.get(n); o.get(h); DG.unpack(o); }
5556

5657
RandomVectors(int h, double R) : RandomVectors(0, h, R) {}
5758
RandomVectors(int nn,int hh,double R) : DG(R) { set(nn,hh,R); }
@@ -61,6 +62,7 @@ class RandomVectors
6162

6263
double get_R() const { return DG.get_R(); }
6364
DiscreteGauss get_DG() const { return DG; }
65+
int get_h() const { return h; }
6466

6567
// Sample from Discrete Gauss distribution
6668
vector<bigint> sample_Gauss(PRNG& G, int stretch = 1) const;

0 commit comments

Comments
 (0)