From 799929b8010035e665f02b64152b3b6a1278b8e8 Mon Sep 17 00:00:00 2001 From: Marcel Keller Date: Fri, 17 Sep 2021 14:29:28 +1000 Subject: [PATCH] Optimized matrix multiplication in Hemi. --- BMR/RealProgramParty.hpp | 5 +- BMR/network/Client.cpp | 5 + BMR/network/Client.h | 4 + BMR/network/Node.cpp | 2 +- BMR/network/Node.h | 2 +- CHANGELOG.md | 12 + Compiler/GC/instructions.py | 13 +- Compiler/GC/types.py | 58 +- Compiler/allocator.py | 2 +- Compiler/compilerLib.py | 56 +- Compiler/floatingpoint.py | 4 +- Compiler/instructions.py | 45 +- Compiler/instructions_base.py | 11 +- Compiler/library.py | 72 +- Compiler/ml.py | 60 +- Compiler/mpc_math.py | 4 +- Compiler/oram.py | 2 +- Compiler/program.py | 46 +- Compiler/types.py | 390 +++- ECDSA/fake-spdz-ecdsa-party.cpp | 2 +- ECDSA/hm-ecdsa-party.hpp | 2 +- ECDSA/ot-ecdsa-party.hpp | 2 +- ExternalIO/Client.h | 31 + ExternalIO/Client.hpp | 126 ++ ExternalIO/bankers-bonus-client.cpp | 119 +- FHE/AddableVector.h | 10 + FHE/Diagonalizer.cpp | 71 + FHE/Diagonalizer.h | 36 + FHE/FFT.cpp | 3 +- FHE/FHE_Keys.cpp | 8 + FHEOffline/Multiplier.cpp | 20 +- FHEOffline/Multiplier.h | 2 + FHEOffline/PairwiseMachine.cpp | 2 +- FHEOffline/SimpleGenerator.h | 2 +- FHEOffline/SimpleMachine.cpp | 2 +- GC/AtlasSecret.cpp | 1 + GC/CcdPrep.h | 4 +- GC/FakeSecret.h | 3 +- GC/Instruction.h | 1 + GC/MaliciousRepSecret.h | 6 - GC/NoShare.h | 2 +- GC/Processor.h | 1 + GC/Processor.hpp | 13 + GC/Rep4Secret.h | 2 +- GC/SemiPrep.cpp | 7 +- GC/SemiPrep.h | 2 +- GC/ShareParty.hpp | 9 +- GC/ShareSecret.h | 4 +- GC/ShareThread.h | 4 +- GC/Thread.h | 2 +- GC/Thread.hpp | 9 +- GC/ThreadMaster.hpp | 6 +- GC/TinierSharePrep.h | 2 +- GC/TinierSharePrep.hpp | 8 +- GC/TinySecret.h | 9 +- GC/instructions.h | 2 + Machines/OTMachine.cpp | 5 +- Machines/TripleMachine.cpp | 7 +- Machines/hemi-party.cpp | 3 + Makefile | 60 +- Math/BitVec.h | 6 +- Math/Integer.h | 3 + Math/Z2k.hpp | 3 +- Math/Zp_Data.h | 10 +- Math/gfp.hpp | 3 +- Networking/CryptoPlayer.cpp | 18 +- Networking/CryptoPlayer.h | 15 +- Networking/Player.cpp | 98 +- Networking/Player.h | 238 ++- Networking/Server.cpp | 2 +- Networking/ServerSocket.cpp | 32 +- Networking/ServerSocket.h | 21 +- OT/NPartyTripleGenerator.h | 12 - OT/NPartyTripleGenerator.hpp | 2 +- Processor/BaseMachine.cpp | 5 +- Processor/Binary_File_IO.h | 2 + Processor/Binary_File_IO.hpp | 7 + Processor/Data_Files.h | 5 +- Processor/ExternalClients.cpp | 8 +- Processor/Instruction.h | 1 + Processor/Instruction.hpp | 62 +- Processor/Machine.h | 6 +- Processor/Machine.hpp | 81 +- Processor/OfflineMachine.hpp | 2 +- Processor/Online-Thread.hpp | 28 +- Processor/OnlineMachine.h | 3 +- Processor/OnlineMachine.hpp | 23 +- Processor/OnlineOptions.cpp | 23 +- Processor/OnlineOptions.h | 1 + Processor/Processor.h | 14 +- Processor/Processor.hpp | 111 +- Processor/Program.cpp | 3 + Processor/Program.h | 4 +- Processor/RingMachine.hpp | 1 + Processor/ThreadJob.h | 2 + Processor/ThreadQueues.cpp | 18 +- Processor/instructions.h | 2 +- Programs/Source/bankers_bonus.mpc | 8 +- Programs/Source/gc_oram.mpc | 2 + Programs/Source/logreg.mpc | 28 +- Programs/Source/mnist_full_C.mpc | 5 + Programs/Source/mnist_logreg.mpc | 3 + Programs/Source/test_gc.mpc | 3 + Protocols/Beaver.hpp | 3 +- Protocols/ChaiGearPrep.hpp | 2 +- Protocols/CowGearOptions.cpp | 3 +- Protocols/CowGearPrep.hpp | 2 +- Protocols/Hemi.h | 35 + Protocols/Hemi.hpp | 206 ++ Protocols/HemiMatrixPrep.h | 50 + Protocols/HemiMatrixPrep.hpp | 201 ++ Protocols/HemiOptions.h | 42 + Protocols/HemiPrep.h | 6 + Protocols/HemiPrep.hpp | 12 +- Protocols/HemiShare.h | 7 +- Protocols/MascotPrep.h | 1 - Protocols/MascotPrep.hpp | 15 +- Protocols/Replicated.h | 9 + Protocols/SemiShare.h | 3 +- Protocols/ShareInterface.cpp | 2 + Protocols/ShareInterface.h | 2 + Protocols/ShareMatrix.h | 233 +++ Protocols/SohoPrep.hpp | 4 +- Protocols/Spdz2kPrep.h | 2 +- Protocols/Spdz2kPrep.hpp | 6 +- Scripts/direct_compilation_example.py | 3 + Scripts/yao.sh | 4 +- Tools/OfflineMachineBase.cpp | 6 +- Tools/OfflineMachineBase.h | 1 - Tools/WaitQueue.h | 2 +- Tools/octetStream.cpp | 11 + Tools/octetStream.h | 62 +- Tools/parse.h | 2 + Utils/paper-example.cpp | 3 +- Yao/YaoEvaluator.cpp | 2 +- Yao/YaoEvaluator.h | 4 +- Yao/YaoGarbler.cpp | 6 +- Yao/YaoGarbler.h | 2 +- Yao/YaoPlayer.cpp | 5 +- Yao/YaoPlayer.h | 1 - azure-pipelines.yml | 4 +- compile.py | 2 + doc/Compiler.rst | 3 +- doc/Doxyfile | 2579 +++++++++++++++++++++++++ doc/conf.py | 8 +- doc/index.rst | 8 + doc/io.rst | 18 +- doc/low-level.rst | 5 +- doc/networking.rst | 66 +- doc/requirements.txt | 1 + doc/troubleshooting.rst | 27 +- 151 files changed, 5261 insertions(+), 747 deletions(-) create mode 100644 ExternalIO/Client.h create mode 100644 ExternalIO/Client.hpp create mode 100644 FHE/Diagonalizer.cpp create mode 100644 FHE/Diagonalizer.h create mode 100644 Protocols/Hemi.h create mode 100644 Protocols/Hemi.hpp create mode 100644 Protocols/HemiMatrixPrep.h create mode 100644 Protocols/HemiMatrixPrep.hpp create mode 100644 Protocols/HemiOptions.h create mode 100644 Protocols/ShareMatrix.h create mode 100644 doc/Doxyfile create mode 100644 doc/requirements.txt diff --git a/BMR/RealProgramParty.hpp b/BMR/RealProgramParty.hpp index a5022f4a1..22438deb4 100644 --- a/BMR/RealProgramParty.hpp +++ b/BMR/RealProgramParty.hpp @@ -79,7 +79,7 @@ RealProgramParty::RealProgramParty(int argc, const char** argv) : auto& MC = this->MC; this->_id = online_opts.playerno + 1; - Server* server = Server::start_networking(N, online_opts.playerno, nparties, + Server::start_networking(N, online_opts.playerno, nparties, network_opts.hostname, network_opts.portnum_base); if (T::dishonest_majority) P = new PlainPlayer(N, 0); @@ -159,8 +159,7 @@ RealProgramParty::RealProgramParty(int argc, const char** argv) : MC->Check(*P); data_sent = P->comm_stats.total_data() + prep->data_sent(); - if (server) - delete server; + this->machine.write_memory(this->N.my_num()); } template diff --git a/BMR/network/Client.cpp b/BMR/network/Client.cpp index 829ac42a2..fbe95b9ef 100644 --- a/BMR/network/Client.cpp +++ b/BMR/network/Client.cpp @@ -25,6 +25,9 @@ static void throw_bad_ip(const char* ip) { throw std::invalid_argument( "bad ip" ); } +namespace BIU +{ + Client::Client(endpoint_t* endpoints, int numservers, ClientUpdatable* updatable, unsigned int max_message_size) :_max_msg_sz(max_message_size), _numservers(numservers), @@ -205,3 +208,5 @@ void Client::_send_blocking(SendBuffer& msg, int id) { fflush(0); #endif } + +} diff --git a/BMR/network/Client.h b/BMR/network/Client.h index 9b446814c..14415c714 100644 --- a/BMR/network/Client.h +++ b/BMR/network/Client.h @@ -28,6 +28,8 @@ class ClientUpdatable { +namespace BIU +{ class Client { public: @@ -61,4 +63,6 @@ class Client { boost::thread_group threads; }; +} + #endif /* NETWORK_INC_CLIENT_H_ */ diff --git a/BMR/network/Node.cpp b/BMR/network/Node.cpp index 41e55d1ad..0f28914ba 100644 --- a/BMR/network/Node.cpp +++ b/BMR/network/Node.cpp @@ -35,7 +35,7 @@ Node::Node(const char* netmap_file, int my_id, NodeUpdatable* updatable, int num _ready_nodes = new bool[_numparties](); //initialized to false _clients_connected = new bool[_numparties](); _server = new BIU::Server(_port, _numparties-1, this, max_message_size); - _client = new Client(_endpoints, _numparties-1, this, max_message_size); + _client = new BIU::Client(_endpoints, _numparties-1, this, max_message_size); } Node::~Node() { diff --git a/BMR/network/Node.h b/BMR/network/Node.h index da22c27d3..69d3dfcb3 100644 --- a/BMR/network/Node.h +++ b/BMR/network/Node.h @@ -69,7 +69,7 @@ class Node : public ServerUpdatable, public ClientUpdatable { int _numparties; endpoint_t* _endpoints; - Client* _client; + BIU::Client* _client; BIU::Server* _server; bool* _ready_nodes; volatile bool _connected_to_servers; diff --git a/CHANGELOG.md b/CHANGELOG.md index 7a97b0f6b..9d4850732 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,17 @@ The changelog explains changes pulled through from the private development repository. Bug fixes and small enhancements are committed between releases and not documented here. +## 0.2.7 (Sep 17, 2021) + +- Optimized matrix multiplication in Hemi +- Improved client communication +- Private integer division as per `Veugen and Abspoel + ` +- Compiler option to translate some Python control flow instructions + to run-time instructions +- Functionality to break out of run-time loops +- Run-time range check of data structure accesses +- Improved documentation of network infrastructure + ## 0.2.6 (Aug 6, 2021) - [ATLAS](https://eprint.iacr.org/2021/833) diff --git a/Compiler/GC/instructions.py b/Compiler/GC/instructions.py index ac207347e..f1b5ad236 100644 --- a/Compiler/GC/instructions.py +++ b/Compiler/GC/instructions.py @@ -52,6 +52,7 @@ class ClearBitsAF(base.RegisterArgFormat): CONVCBIT2S = 0x249, XORCBI = 0x210, BITDECC = 0x211, + NOTCB = 0x212, CONVCINT = 0x213, REVEAL = 0x214, STMSDCI = 0x215, @@ -190,6 +191,16 @@ class nots(BinaryVectorInstruction): code = opcodes['NOTS'] arg_format = ['int','sbw','sb'] +class notcb(BinaryVectorInstruction): + """ Bitwise NOT of secret register vector. + + :param: number of bits + :param: result (cbit) + :param: operand (cbit) + """ + code = opcodes['NOTCB'] + arg_format = ['int','cbw','cb'] + class addcb(NonVectorInstruction): """ Integer addition two single clear bit registers. @@ -617,4 +628,4 @@ class cond_print_strb(base.IOInstruction): arg_format = ['cb', 'int'] def __init__(self, cond, val): - super(cond_print_str, self).__init__(cond, self.str_to_int(val)) + super(cond_print_strb, self).__init__(cond, self.str_to_int(val)) diff --git a/Compiler/GC/types.py b/Compiler/GC/types.py index b0b18e103..a1384475b 100644 --- a/Compiler/GC/types.py +++ b/Compiler/GC/types.py @@ -169,6 +169,33 @@ def load_other(self, other): (str(other), repr(other), type(other), type(self))) def long_one(self): return 2**self.n - 1 if self.n != None else None + def is_long_one(self, other): + return util.is_all_ones(other, self.n) or \ + (other is None and self.n == None) + def res_type(self, other): + if self.n == None and other.n == None: + n = None + else: + n = max(self.n, other.n) + return self.get_type(n) + @read_mem_value + def __and__(self, other): + if util.is_zero(other): + return 0 + elif self.is_long_one(other): + return self + else: + return self._and(other) + @read_mem_value + def __xor__(self, other): + if util.is_zero(other): + return self + elif self.is_long_one(other): + return ~self + else: + return self._xor(other) + __rand__ = __and__ + __rxor__ = __xor__ def __repr__(self): if self.n != None: suffix = '%d' % self.n @@ -245,19 +272,20 @@ def clear_op(self, other, c_inst, ci_inst, op): self.clear_op(other, inst.addcb, inst.addcbi, operator.add) __sub__ = lambda self, other: \ self.clear_op(-other, inst.addcb, inst.addcbi, operator.add) - def __xor__(self, other): + def _xor(self, other): if isinstance(other, (sbits, sbitvec)): return NotImplemented elif isinstance(other, cbits): - res = cbits.get_type(max(self.n, other.n))() + res = self.res_type(other)() assert res.size == self.size assert res.size == other.size inst.xorcb(res.n, res, self, other) return res else: return self.clear_op(other, None, inst.xorcbi, operator.xor) + def _and(self, other): + return NotImplemented __radd__ = __add__ - __rxor__ = __xor__ def __mul__(self, other): if isinstance(other, cbits): return NotImplemented @@ -278,7 +306,9 @@ def __lshift__(self, other): inst.shlcbi(res, self, other) return res def __invert__(self): - return self ^ self.long_one() + res = type(self)() + inst.notcb(self.n, res, self) + return res def print_reg(self, desc=''): inst.print_regb(self, desc) def print_reg_plain(self): @@ -287,6 +317,8 @@ def print_reg_plain(self): def print_if(self, string): inst.cond_print_strb(self, string) def output_if(self, cond): + if Program.prog.options.binary: + raise CompilerError('conditional output not supported') cint(self).output_if(cond) def reveal(self): return self @@ -423,8 +455,7 @@ def __add__(self, other): __radd__ = __add__ __sub__ = __add__ __rsub__ = __add__ - __xor__ = __add__ - __rxor__ = __add__ + _xor = __add__ @read_mem_value def __mul__(self, other): if isinstance(other, int): @@ -440,13 +471,7 @@ def __mul__(self, other): except AttributeError: return NotImplemented __rmul__ = __mul__ - @read_mem_value - def __and__(self, other): - if util.is_zero(other): - return 0 - elif util.is_all_ones(other, self.n) or \ - (other is None and self.n == None): - return self + def _and(self, other): res = self.new(n=self.n) if not isinstance(other, sbits): other = cbits.get_type(self.n).conv(other) @@ -456,7 +481,6 @@ def __and__(self, other): assert(self.n == other.n) inst.ands(self.n, res, self, other) return res - __rand__ = __and__ def xor_int(self, other): if other == 0: return self @@ -551,12 +575,6 @@ def bit_adder(*args, **kwargs): @staticmethod def ripple_carry_adder(*args, **kwargs): return sbitint.ripple_carry_adder(*args, **kwargs) - def to_sint(self, n_bits): - """ Convert the :py:obj:`n_bits` least significant bits to - :py:obj:`~Compiler.types.sint`. """ - bits = sbitvec.from_vec(sbitvec([self]).v[:n_bits]).elements()[0] - bits = sint(bits, size=n_bits) - return sint.bit_compose(bits) class sbitvec(_vec): """ Vector of registers of secret bits, effectively a matrix of secret bits. diff --git a/Compiler/allocator.py b/Compiler/allocator.py index 45c60e3da..6a1472d7b 100644 --- a/Compiler/allocator.py +++ b/Compiler/allocator.py @@ -591,7 +591,7 @@ def run(self, instructions): elif isinstance(inst, IndirectMemoryInstruction): if inst.args[1] in self.cache: instructions[i] = inst.get_direct(self.cache[inst.args[1]]) - elif isinstance(inst, convint_class): + elif type(inst) == convint_class: if inst.args[1] in self.cache: res = self.cache[inst.args[1]] self.cache[inst.args[0]] = res diff --git a/Compiler/compilerLib.py b/Compiler/compilerLib.py index 1e2bd3519..64e76434c 100644 --- a/Compiler/compilerLib.py +++ b/Compiler/compilerLib.py @@ -2,6 +2,7 @@ from .GC import types as GC_types import sys +import re, tempfile, os def run(args, options): @@ -21,11 +22,62 @@ def run(args, options): del VARS[i] print('Compiling file', prog.infile) - + f = open(prog.infile, 'rb') + + changed = False + if options.flow_optimization: + output = [] + if_stack = [] + for line in open(prog.infile): + if if_stack and not re.match(if_stack[-1][0], line): + if_stack.pop() + m = re.match( + '(\s*)for +([a-zA-Z_]+) +in +range\(([0-9a-zA-Z_]+)\):', + line) + if m: + output.append('%s@for_range_opt(%s)\n' % (m.group(1), + m.group(3))) + output.append('%sdef _(%s):\n' % (m.group(1), m.group(2))) + changed = True + continue + m = re.match('(\s*)if(\W.*):', line) + if m: + if_stack.append((m.group(1), len(output))) + output.append('%s@if_(%s)\n' % (m.group(1), m.group(2))) + output.append('%sdef _():\n' % (m.group(1))) + changed = True + continue + m = re.match('(\s*)elif\s+', line) + if m: + raise CompilerError('elif not supported') + if if_stack: + m = re.match('%selse:' % if_stack[-1][0], line) + if m: + start = if_stack[-1][1] + ws = if_stack[-1][0] + output[start] = re.sub(r'^%s@if_\(' % ws, r'%s@if_e(' % ws, + output[start]) + output.append('%s@else_\n' % ws) + output.append('%sdef _():\n' % ws) + continue + output.append(line) + if changed: + infile = tempfile.NamedTemporaryFile('w+', delete=False) + for line in output: + infile.write(line) + infile.seek(0) + else: + infile = open(prog.infile) + else: + infile = open(prog.infile) + # make compiler modules directly accessible sys.path.insert(0, 'Compiler') # create the tapes - exec(compile(open(prog.infile).read(), prog.infile, 'exec'), VARS) + exec(compile(infile.read(), infile.name, 'exec'), VARS) + + if changed and not options.debug: + os.unlink(infile.name) prog.finalize() diff --git a/Compiler/floatingpoint.py b/Compiler/floatingpoint.py index b6b5ef83b..66de0859a 100644 --- a/Compiler/floatingpoint.py +++ b/Compiler/floatingpoint.py @@ -562,7 +562,7 @@ def SDiv(a, b, l, kappa, round_nearest=False): y = a * w y = y.round(2 * l + 1, l, kappa, round_nearest, signed=False) x2 = types.sint() - comparison.Mod2m(x2, x, 2 * l + 1, l, kappa, False) + comparison.Mod2m(x2, x, 2 * l + 1, l, kappa, True) x1 = comparison.TruncZeros(x - x2, 2 * l + 1, l, True) for i in range(theta-1): y = y * (x1 + two_power(l)) + (y * x2).round(2 * l, l, kappa, @@ -642,7 +642,7 @@ def BitDecFull(a, maybe_mixed=False): b, bbits = sint.get_edabit(logp, True, size=a.size) if logp != bit_length: from .GC.types import sbits - bbits += [sbits.get_type(a.size)(0)] + bbits += [0] else: bbits = [sint.get_random_bit(size=a.size) for i in range(logp)] b = sint.bit_compose(bbits) diff --git a/Compiler/instructions.py b/Compiler/instructions.py index 7d4a0d4d5..9d246b184 100644 --- a/Compiler/instructions.py +++ b/Compiler/instructions.py @@ -409,6 +409,18 @@ class use_edabit(base.Instruction): code = base.opcodes['USE_EDABIT'] arg_format = ['int','int','int'] +class use_matmul(base.Instruction): + """ Matrix multiplication usage. Used for multithreading of + preprocessing. + + :param: number of left-hand rows (int) + :param: number of left-hand columns/right-hand rows (int) + :param: number of right-hand columns (int) + :param: number (int, -1 for unknown) + """ + code = base.opcodes['USE_MATMUL'] + arg_format = ['int','int','int','int'] + class run_tape(base.Instruction): """ Start tape/bytecode file in another thread. @@ -432,7 +444,7 @@ class join_tape(base.Instruction): class crash(base.IOInstruction): """ Crash runtime. """ code = base.opcodes['CRASH'] - arg_format = [] + arg_format = ['ci'] class start_grind(base.IOInstruction): code = base.opcodes['STARTGRIND'] @@ -1559,51 +1571,49 @@ class pubinput(base.PublicFileIOInstruction): code = base.opcodes['PUBINPUT'] arg_format = ['cw'] -@base.vectorize class readsocketc(base.IOInstruction): """ Read a variable number of clear values in internal representation from socket for a specified client id and store them in clear registers. :param: number of arguments to follow / number of inputs minus one (int) :param: client id (regint) + :param: vector size (int) :param: destination (cint) :param: (repeat destination)... """ __slots__ = [] code = base.opcodes['READSOCKETC'] - arg_format = tools.chain(['ci'], itertools.repeat('cw')) + arg_format = tools.chain(['ci','int'], itertools.repeat('cw')) def has_var_args(self): return True -@base.vectorize class readsockets(base.IOInstruction): """Read a variable number of secret shares + MACs from socket for a client id and store in registers""" __slots__ = [] code = base.opcodes['READSOCKETS'] - arg_format = tools.chain(['ci'], itertools.repeat('sw')) + arg_format = tools.chain(['ci','int'], itertools.repeat('sw')) def has_var_args(self): return True -@base.vectorize class readsocketint(base.IOInstruction): """ Read a variable number of 32-bit integers from socket for a specified client id and store them in clear integer registers. :param: number of arguments to follow / number of inputs minus one (int) :param: client id (regint) + :param: vector size (int) :param: destination (regint) :param: (repeat destination)... """ __slots__ = [] code = base.opcodes['READSOCKETINT'] - arg_format = tools.chain(['ci'], itertools.repeat('ciw')) + arg_format = tools.chain(['ci','int'], itertools.repeat('ciw')) def has_var_args(self): return True -@base.vectorize class writesocketc(base.IOInstruction): """ Write a variable number of clear GF(p) values from registers into socket @@ -1611,29 +1621,28 @@ class writesocketc(base.IOInstruction): """ __slots__ = [] code = base.opcodes['WRITESOCKETC'] - arg_format = tools.chain(['ci', 'int'], itertools.repeat('c')) + arg_format = tools.chain(['ci', 'int', 'int'], itertools.repeat('c')) def has_var_args(self): return True -@base.vectorize class writesocketshare(base.IOInstruction): """ Write a variable number of shares (without MACs) from secret registers into socket for a specified client id. :param: client id (regint) :param: message type (must be 0) + :param: vector size (int) :param: source (sint) :param: (repeat source)... """ __slots__ = [] code = base.opcodes['WRITESOCKETSHARE'] - arg_format = tools.chain(['ci', 'int'], itertools.repeat('s')) + arg_format = tools.chain(['ci', 'int', 'int'], itertools.repeat('s')) def has_var_args(self): return True -@base.vectorize class writesocketint(base.IOInstruction): """ Write a variable number of 32-bit ints from registers into socket @@ -1641,7 +1650,7 @@ class writesocketint(base.IOInstruction): """ __slots__ = [] code = base.opcodes['WRITESOCKETINT'] - arg_format = tools.chain(['ci', 'int'], itertools.repeat('ci')) + arg_format = tools.chain(['ci', 'int', 'int'], itertools.repeat('ci')) def has_var_args(self): return True @@ -2266,6 +2275,10 @@ def __init__(self, *args, **kwargs): for i in range(2): assert args[8 + i].size == args[4 + i] + def add_usage(self, req_node): + super(matmulsm, self).add_usage(req_node) + req_node.increment(('matmul', tuple(self.args[3:6])), 1) + class conv2ds(base.DataInstruction): """ Secret 2D convolution. @@ -2301,6 +2314,12 @@ def get_repeat(self): return self.args[3] * self.args[4] * self.args[7] * self.args[8] * \ self.args[11] * self.args[14] + def add_usage(self, req_node): + super(conv2ds, self).add_usage(req_node) + args = self.args + req_node.increment(('matmul', (1, args[7] * args[8] * args[11], + args[14] * args[3] * args[4])), 1) + @base.vectorize class trunc_pr(base.VarArgsInstruction): """ Probabilistic truncation if supported by the protocol. diff --git a/Compiler/instructions_base.py b/Compiler/instructions_base.py index 0e2ff0a62..66b55fd9d 100644 --- a/Compiler/instructions_base.py +++ b/Compiler/instructions_base.py @@ -63,6 +63,7 @@ THRESHOLD = 0xE3, PLAYERID = 0xE4, USE_EDABIT = 0xE5, + USE_MATMUL = 0x1F, # Addition ADDC = 0x20, ADDS = 0x21, @@ -456,7 +457,7 @@ def new_instructions(self, size, regs): reset_global_vector_size() program.curr_tape = old_tape for x, bl in tape.req_bit_length.items(): - old_tape.require_bit_length(bl, x) + old_tape.require_bit_length(bl - 1, x) from Compiler.allocator import Merger merger = Merger(block, program.options, tuple(program.to_merge)) @@ -542,7 +543,13 @@ def __str__(self): MergeCISC.__name__ = function.__name__ def wrapper(*args, **kwargs): - if program.options.cisc: + same_sizes = True + for arg in args: + try: + same_sizes &= arg.size == args[0].size + except: + pass + if program.options.cisc and same_sizes: return MergeCISC(*args, **kwargs) else: return function(*args, **kwargs) diff --git a/Compiler/library.py b/Compiler/library.py index 2be7caf99..529608dc2 100644 --- a/Compiler/library.py +++ b/Compiler/library.py @@ -143,7 +143,6 @@ def print_str_if(cond, ss, *args): assert len(subs) == len(args) + 1 if isinstance(cond, localint): cond = cond._v - cond = cint.conv(cond) for i, s in enumerate(subs): if i != 0: val = args[i - 1] @@ -203,6 +202,27 @@ def runtime_error(msg='', *args): print_ln(msg, *args) crash() +def runtime_error_if(condition, msg='', *args): + """ Conditionally print an error message and abort the runtime. + + :param condition: regint/cint/int/cbit + :param msg: message + :param args: list of public values to fit ``%s`` in the message + + """ + print_ln_if(condition, msg, *args) + crash(condition) + +def crash(condition=None): + """ Crash virtual machine. + + :param condition: crash if true (default: true) + + """ + if condition == None: + condition = regint(1) + instructions.crash(regint.conv(condition)) + def public_input(): """ Public input read from ``Programs/Public-Input/``. """ res = cint() @@ -1209,6 +1229,8 @@ def f(i): return loop_body(base + i) prog = get_program() thread_args = [] + if prog.curr_tape == prog.tapes[0]: + prog.n_running_threads = n_threads if not util.is_zero(thread_rounds): tape = prog.new_tape(f, (0,), 'multithread') for i in range(n_threads - remainder): @@ -1225,6 +1247,7 @@ def f(i): if len(mem_state): args[i][1] = mem_state.address thread_args.append((tape1, i)) + prog.n_running_threads = None threads = prog.run_tapes(thread_args) for thread in threads: prog.join_tape(thread) @@ -1397,6 +1420,7 @@ def _(): # possibly unknown loop count get_tape().open_scope(lambda x: x[0].set_all(float('Inf')), \ name='begin-loop') + get_tape().loop_breaks.append([]) loop_block = instructions.program.curr_block condition = _run_and_link(loop_fn, g) if callable(condition): @@ -1404,8 +1428,15 @@ def _(): branch = instructions.jmpnz(regint.conv(condition), 0, add_to_prog=False) instructions.program.curr_block.set_exit(branch, loop_block) get_tape().close_scope(scope, parent_node, 'end-loop') + for loop_break in get_tape().loop_breaks.pop(): + loop_break.set_exit(jmp(0, add_to_prog=False), get_block()) return loop_fn +def break_loop(): + """ Break out of loop. """ + get_tape().loop_breaks[-1].append(get_block()) + break_point('break') + def if_then(condition): class State: pass state = State() @@ -1483,10 +1514,18 @@ def if_(condition): def _(): ... """ + try: + condition = bool(condition) + except: + pass def decorator(body): - if_then(condition) - _run_and_link(body) - end_if() + if isinstance(condition, bool): + if condition: + _run_and_link(body) + else: + if_then(condition) + _run_and_link(body) + end_if() return decorator def if_e(condition): @@ -1506,15 +1545,30 @@ def _(): def _(): ... """ + try: + condition = bool(condition) + except: + pass def decorator(body): - if_then(condition) - _run_and_link(body) + if isinstance(condition, bool): + get_tape().if_states.append(condition) + if condition: + _run_and_link(body) + else: + if_then(condition) + _run_and_link(body) return decorator def else_(body): - else_then() - _run_and_link(body) - end_if() + if_states = get_tape().if_states + if isinstance(if_states[-1], bool): + if not if_states[-1]: + _run_and_link(body) + if_states.pop() + else: + else_then() + _run_and_link(body) + end_if() def and_(*terms): res = regint(0) diff --git a/Compiler/ml.py b/Compiler/ml.py index 9e147a2c4..42389ae83 100644 --- a/Compiler/ml.py +++ b/Compiler/ml.py @@ -292,7 +292,8 @@ def _(base, size): self.l.write(sum(lse) * \ self.divisor(N, 1)) - def eval(self, size, base=0): + def eval(self, size, base=0, top=False): + assert not top if self.approx: return approx_sigmoid(self.X.get_vector(base, size), self.approx) else: @@ -474,8 +475,14 @@ def _(i): self.true_X[i] = true_X self.l.write(sum(tmp.get_vector(0, N)) / N) - def eval(self, N): + def eval(self, N, top=False): d_out = self.X.sizes[1] + if top: + res = sint.Array(N) + @for_range_opt_multithread(self.n_threads, N) + def _(i): + res[i] = argmax(self.X[i]) + return res res = sfix.Matrix(N, d_out) if self.approx: @for_range_opt_multithread(self.n_threads, N) @@ -1832,7 +1839,7 @@ def batch_for(self, layer, batch): @_no_mem_warnings def forward(self, N=None, batch=None, keep_intermediate=True, - model_from=None, training=False): + model_from=None, training=False, run_last=True): """ Compute graph. :param N: batch size (used if batch not given) @@ -1851,7 +1858,9 @@ def forward(self, N=None, batch=None, keep_intermediate=True, break_point() if self.time_layers: start_timer(100 + i) - layer.forward(batch=self.batch_for(layer, batch), training=training) + if i != len(self.layers) - 1 or run_last: + layer.forward(batch=self.batch_for(layer, batch), + training=training) if self.time_layers: stop_timer(100 + i) break_point() @@ -1862,19 +1871,23 @@ def forward(self, N=None, batch=None, keep_intermediate=True, theta.delete() @_no_mem_warnings - def eval(self, data, batch_size=None): + def eval(self, data, batch_size=None, top=False): """ Compute evaluation after training. :param data: sample data (:py:class:`Compiler.types.Matrix` with one row per sample) + :param top: return top prediction instead of probability distribution """ - if isinstance(self.layers[-1].Y, Array): - res = sfix.Array(len(data)) + if isinstance(self.layers[-1].Y, Array) or top: + if top: + res = sint.Array(len(data)) + else: + res = sfix.Array(len(data)) else: res = sfix.Matrix(len(data), self.layers[-1].d_out) def f(start, batch_size, batch): batch.assign_vector(regint.inc(batch_size, start)) - self.forward(batch=batch) - part = self.layers[-1].eval(batch_size) + self.forward(batch=batch, run_last=not top) + part = self.layers[-1].eval(batch_size, top=top) res.assign_part_vector(part.get_vector(), start) self.run_in_batches(f, data, batch_size or len(self.layers[1].X)) return res @@ -2487,24 +2500,29 @@ def predict(self, x, batch_size=None): batch_size = min(batch_size, self.batch_size) return self.opt.eval(x, batch_size=batch_size) -def solve_linear(A, b, n_iterations, debug=False): +def solve_linear(A, b, n_iterations, progress=False): """ Iterative linear solution approximation. """ assert len(b) == A.sizes[0] x = sfix.Array(A.sizes[1]) x.assign_vector(sfix.get_random(-1, 1, size=len(x))) - At = A.transpose() + AtA = sfix.Matrix(len(x), len(x)) + AtA[:] = A.direct_trans_mul(A) + v = sfix.Array(A.sizes[1]) + v.assign_all(0) + r = Array.create_from(A.transpose() * b - AtA * x) + Av = sfix.Array(len(x)) @for_range(n_iterations) def _(i): - r = At * (b - A * x) - tmp = A * r - tmp = sfix.dot_product(tmp, tmp) - alpha = (tmp == 0).if_else(0, sfix.dot_product(r, r) / tmp) - x.assign(x + alpha * r) - if debug: - print_ln('%s r=%s tmp=%s r*r=%s tmp*tmp=%s alpha=%s x=%s alpha*r=%s', i, - list(r.reveal()), list(tmp.reveal()), - sfix.dot_product(r, r).reveal(), sfix.dot_product(tmp, tmp).reveal(), - alpha.reveal(), x.reveal_list(), list((alpha * r).reveal())) + v[:] = r - sfix.dot_product(r, Av) / sfix.dot_product(v, Av) * v + Av[:] = AtA * v + v_norm = sfix.dot_product(v, Av) + vr = sfix.dot_product(v, r) + alpha = (v_norm == 0).if_else(0, vr / v_norm) + x[:] = x + alpha * v + r[:] = r - alpha * Av + if progress: + print_ln('%s alpha=%s vr=%s v_norm=%s', i, alpha.reveal(), + vr.reveal(), v_norm.reveal()) return x def mr(A, n_iterations): diff --git a/Compiler/mpc_math.py b/Compiler/mpc_math.py index 2c3ec4507..e67f877ba 100644 --- a/Compiler/mpc_math.py +++ b/Compiler/mpc_math.py @@ -266,7 +266,7 @@ def tan(x): @types.vectorize @instructions_base.sfix_cisc -def exp2_fx(a, zero_output=False): +def exp2_fx(a, zero_output=False, as19=False): """ Power of two for fixed-point numbers. @@ -287,7 +287,7 @@ class my_fix(type(a)): g = a._new(whole_exp.TruncMul(e.v, 2 * a.k, n_shift, nearest=a.round_nearest), a.k, a.f) return g - if types.program.options.ring: + if types.program.options.ring and not as19: sint = types.sint intbitint = types.intbitint # how many bits to use from integer part diff --git a/Compiler/oram.py b/Compiler/oram.py index 77d55f788..443d826cd 100644 --- a/Compiler/oram.py +++ b/Compiler/oram.py @@ -947,7 +947,7 @@ def f(): child.output() def random_block(length, value_type): - return sum(value_type.bit_type.get_random_bit() << i for i in range(length)) + return bit_compose(value_type.bit_type.get_random_bit() for i in range(length)) class List(EndRecursiveEviction): """ Debugging only. List which accepts secret values as indices diff --git a/Compiler/program.py b/Compiler/program.py index 4fe3673ab..ebc1601c8 100644 --- a/Compiler/program.py +++ b/Compiler/program.py @@ -150,6 +150,8 @@ def __init__(self, args, options=defaults): self._linear_rounds = False self.warn_about_mem = [True] self.relevant_opts = set() + self.n_running_threads = None + self.input_files = {} Program.prog = self from . import instructions_base, instructions, types, comparison instructions.program = self @@ -370,8 +372,6 @@ def malloc(self, size, mem_type, reg_type=None, creator_tape=None): """ Allocate memory from the top """ if not isinstance(size, int): raise CompilerError('size must be known at compile time') - if not (creator_tape or self.curr_tape).singular: - raise CompilerError('cannot allocate memory outside main thread') if size == 0: return if isinstance(mem_type, type): @@ -383,6 +383,14 @@ def malloc(self, size, mem_type, reg_type=None, creator_tape=None): mem_type = mem_type.reg_type elif reg_type is not None: self.types[mem_type] = reg_type + single_size = None + if not (creator_tape or self.curr_tape).singular: + if self.n_running_threads: + single_size = size + size *= self.n_running_threads + else: + raise CompilerError('cannot allocate memory ' + 'outside main thread') blocks = self.free_mem_blocks[mem_type] addr = blocks.pop(size) if addr is not None: @@ -393,7 +401,13 @@ def malloc(self, size, mem_type, reg_type=None, creator_tape=None): if len(str(addr)) != len(str(addr + size)) and self.verbose: print("Memory of type '%s' now of size %d" % (mem_type, addr + size)) self.allocated_mem_blocks[addr,mem_type] = size - return addr + if single_size: + from .library import get_thread_number, runtime_error_if + tn = get_thread_number() + runtime_error_if(tn > self.n_running_threads, 'malloc') + return addr + single_size * (tn - 1) + else: + return addr def free(self, addr, mem_type): """ Free memory """ @@ -424,7 +438,8 @@ def finalize_memory(self): from . import library self.curr_tape.start_new_basicblock(None, 'memory-usage') # reset register counter to 0 - self.curr_tape.init_registers() + if not self.options.noreallocate: + self.curr_tape.init_registers() for mem_type,size in sorted(self.allocated_mem.items()): if size: #print "Memory of type '%s' of size %d" % (mem_type, size) @@ -586,6 +601,7 @@ def __init__(self, name, program): self.functions = [] self.singular = True self.free_threads = set() + self.loop_breaks = [] class BasicBlock(object): def __init__(self, parent, name, scope, exit_condition=None): @@ -827,8 +843,7 @@ def alloc_loop(block): block = left.popleft() alloc(block) for child in block.children: - if child.instructions: - left.append(child) + left.append(child) for i,block in enumerate(reversed(self.basicblocks)): if len(block.instructions) > 1000000: print('Allocating %s, %d/%d' % \ @@ -878,6 +893,10 @@ def alloc_loop(block): self.basicblocks[-1].instructions.append( Compiler.instructions.use_edabit(True, req[1], num, \ add_to_prog=False)) + elif req[0] == 'matmul': + self.basicblocks[-1].instructions.append( + Compiler.instructions.use_matmul(*req[1], num, \ + add_to_prog=False)) if not self.is_empty(): # bit length requirement @@ -1012,6 +1031,9 @@ def pretty(self): else: eda = 'loose edabits' res += ['%s %s of length %d' % (n, eda, req[1])] + elif domain == 'matmul': + res += ['%s matrix multiplications (%dx%d * %dx%d)' % + (n, req[1][0], req[1][1], req[1][1], req[1][2])] elif req[0] != 'all': res += ['%s %s %ss' % (n, domain, req[1])] if self['all','round']: @@ -1077,7 +1099,10 @@ def close_scope(self, outer_scope, parent_req_node, name): def require_bit_length(self, bit_length, t='p'): if t == 'p': if self.program.prime: - assert bit_length < self.program.prime.bit_length() - 1 + if (bit_length >= self.program.prime.bit_length() - 1): + raise CompilerError( + 'required bit length %d too much for %d' % \ + (bit_length, self.program.prime)) self.req_bit_length[t] = max(bit_length + 1, \ self.req_bit_length[t]) else: @@ -1150,7 +1175,9 @@ def set_vectorbase(self, vectorbase): def _new_by_number(self, i, size=1): return Tape.Register(self.reg_type, self.program, size=size, i=i) - def get_vector(self, base, size): + def get_vector(self, base=0, size=None): + if size == None: + size = self.size if base == 0 and size == self.size: return self if size == 1: @@ -1206,7 +1233,8 @@ def is_clear(self): self.reg_type == RegType.ClearInt def __bool__(self): - raise CompilerError('cannot derive truth value from register') + raise CompilerError('Cannot derive truth value from register, ' + "consider using 'compile.py -l'") def __str__(self): return self.reg_type + str(self.i) diff --git a/Compiler/types.py b/Compiler/types.py index b9c1cb2cc..917eba752 100644 --- a/Compiler/types.py +++ b/Compiler/types.py @@ -508,7 +508,7 @@ def Tensor(cls, shape): a = sfix.Tensor([10, 10]) """ if len(shape) == 1: - return Array(size[0], cls) + return Array(shape[0], cls) else: return MultiArray(shape, cls) @@ -522,6 +522,75 @@ def row_matrix_mul(cls, row, matrix, res_params=None): def mem_size(): return 1 +class _secret_structure(_structure): + @classmethod + def input_tensor_from(cls, player, shape): + """ Input tensor secretly from player. + + :param player: int/regint/cint + :param shape: tensor shape + + """ + res = cls.Tensor(shape) + res.input_from(player) + return res + + @classmethod + def input_tensor_from_client(cls, client_id, shape): + """ Input tensor secretly from client. + + :param client_id: client identifier (public) + :param shape: tensor shape + + """ + res = cls.Tensor(shape) + res.assign_vector(cls.receive_from_client(1, client_id, + size=res.total_size())[0]) + return res + + @classmethod + def input_tensor_via(cls, player, content): + """ + Input tensor-like data via a player. This overwrites the input + file for the relevant player. The following returns an + :py:class:`sint` matrix of dimension 2 by 2:: + + M = [[1, 2], [3, 4]] + sint.input_tensor_via(0, M) + + Make sure to copy ``Player-Data/Input-P-0`` if running + on another host. + + """ + if program.curr_tape != program.tapes[0]: + raise CompilerError('only available in main thread') + shape = [] + tmp = content + while True: + try: + shape.append(len(tmp)) + tmp = tmp[0] + except: + break + if not program.input_files.get(player, None): + program.input_files[player] = open( + 'Player-Data/Input-P%d-0' % player, 'w') + f = program.input_files[player] + def traverse(content, level): + assert len(content) == shape[level] + if level == len(shape) - 1: + for x in content: + f.write(' ') + f.write(str(x)) + else: + for x in content: + traverse(x, level + 1) + traverse(content, 0) + f.write('\n') + res = cls.Tensor(shape) + res.input_from(player) + return res + class _vec(object): def link(self, other): assert len(self.v) == len(other.v) @@ -835,23 +904,26 @@ def read_from_socket(cls, client_id, n=1): :param client_id: Client id (regint) :param n: number of values (default 1) + :param size: vector size (default 1) :returns: cint (if n=1) or list of cint """ res = [cls() for i in range(n)] - readsocketc(client_id, *res) + readsocketc(client_id, get_global_vector_size(), *res) if n == 1: return res[0] else: return res - @vectorized_classmethod + @classmethod def write_to_socket(self, client_id, values, message_type=ClientMessageType.NoType): """ Send a list of clear values to a client. :param client_id: Client id (regint) :param values: list of cint """ - writesocketc(client_id, message_type, *values) + for value in values: + assert(value.size == values[0].size) + writesocketc(client_id, message_type, values[0].size, *values) @vectorized_classmethod def load_mem(cls, address, mem_type=None): @@ -1089,7 +1161,7 @@ def print_if(self, string): cond_print_str(self, string) def output_if(self, cond): - cond_print_plain(cond, self, cint(0)) + cond_print_plain(self.conv(cond), self, cint(0)) class cgf2n(_clear, _gf2n): @@ -1298,23 +1370,26 @@ def read_from_socket(cls, client_id, n=1): :param client_id: Client id (regint) :param n: number of values (default 1) + :param size: vector size (default 1) :returns: regint (if n=1) or list of regint """ res = [cls() for i in range(n)] - readsocketint(client_id, *res) + readsocketint(client_id, get_global_vector_size(), *res) if n == 1: return res[0] else: return res - @vectorized_classmethod + @classmethod def write_to_socket(self, client_id, values, message_type=ClientMessageType.NoType): """ Send a list of clear integers to a client. :param client_id: Client id (regint) :param values: list of regint """ - writesocketint(client_id, message_type, *values) + for value in values: + assert(value.size == values[0].size) + writesocketint(client_id, message_type, values[0].size, *values) @vectorize_init def __init__(self, val=None, size=None): @@ -1388,6 +1463,8 @@ def __floordiv__(self, other): """ Clear integer division (rounding to floor). :param other: regint/cint/int """ + if util.is_constant(other) and other >= 2 ** 64: + return 0 return self.int_op(other, divint) def __rfloordiv__(self, other): @@ -1546,10 +1623,17 @@ def print_if(self, string): """ Output string if value is non-zero. :param string: Python string """ - cint(self).print_if(string) + self._condition().print_if(string) def output_if(self, cond): - cint(self).output_if(cond) + self._condition().output_if(cond) + + def _condition(self): + if program.options.binary: + from GC.types import cbits + return cbits.get_type(64)(self) + else: + return cint(self) def binary_output(self, player=None): """ Write 64-bit signed integer to @@ -1597,6 +1681,9 @@ def __init__(self, player, value): def binary_output(self): self._v.binary_output(self.player) + def bit_decompose(self, length): + return [personal(self.player, x) for x in self._v.bit_decompose(length)] + def _san(self, other): if isinstance(other, personal): assert self.player == other.player @@ -1689,7 +1776,7 @@ def bit_decompose(self, bit_length): res += x.bit_decompose(64) return res[:bit_length] -class _secret(_register): +class _secret(_register, _secret_structure): __slots__ = [] mov = staticmethod(set_instruction_type(movs)) @@ -2022,7 +2109,7 @@ class sint(_secret, _int): the bit length. :param val: initialization (sint/cint/regint/int/cgf2n or list - thereof or sbits/sbitvec) + thereof or sbits/sbitvec/sfix) :param size: vector size (int), defaults to 1 or size of list """ @@ -2130,33 +2217,62 @@ def get_raw_input_from(cls, player): rawinput(player, res) return res - @classmethod + @vectorized_classmethod def receive_from_client(cls, n, client_id, message_type=ClientMessageType.NoType): """ Securely obtain shares of values input by a client. :param n: number of inputs (int) :param client_id: regint + :param size: vector size (default 1) + """ # send shares of a triple to client triples = list(itertools.chain(*(sint.get_random_triple() for i in range(n)))) sint.write_shares_to_socket(client_id, triples, message_type) - received = cint.read_from_socket(client_id, n) + received = util.tuplify(cint.read_from_socket(client_id, n)) y = [0] * n for i in range(n): y[i] = received[i] - triples[i * 3] return y + @classmethod + def reveal_to_clients(cls, clients, values): + """ Reveal securely to clients. + + :param clients: client ids (list or array) + :param values: list of sint to reveal + + """ + set_global_vector_size(values[0].size) + to_send = [] + + for value in values: + assert(value.size == values[0].size) + r = sint.get_random() + to_send += [value, r, value * r] + + if isinstance(clients, Array): + n_clients = clients.length + else: + n_clients = len(clients) + + @library.for_range(n_clients) + def loop_body(i): + sint.write_shares_to_socket(clients[i], to_send) + reset_global_vector_size() + @vectorized_classmethod def read_from_socket(cls, client_id, n=1): """ Receive secret-shared value(s) from client. :param client_id: Client id (regint) :param n: number of values (default 1) + :param size: vector size of values (default 1) :returns: sint (if n=1) or list of sint """ res = [cls() for i in range(n)] - readsockets(client_id, *res) + readsockets(client_id, get_global_vector_size(), *res) if n == 1: return res[0] else: @@ -2165,9 +2281,9 @@ def read_from_socket(cls, client_id, n=1): @vectorize def write_share_to_socket(self, client_id, message_type=ClientMessageType.NoType): """ Send only share to socket """ - writesocketshare(client_id, message_type, self) + writesocketshare(client_id, message_type, self.size, self) - @vectorized_classmethod + @classmethod def write_shares_to_socket(cls, client_id, values, message_type=ClientMessageType.NoType): """ Send shares of a list of values to a specified client socket. @@ -2175,7 +2291,7 @@ def write_shares_to_socket(cls, client_id, values, :param client_id: regint :param values: list of sint """ - writesocketshare(client_id, message_type, *values) + writesocketshare(client_id, message_type, values[0].size, *values) @classmethod def read_from_file(cls, start, n_items): @@ -2227,6 +2343,9 @@ def __init__(self, val=None, size=None): size = val._v.size super(sint, self).__init__('s', size=size) inputpersonal(size, val.player, self, self.clear_type.conv(val._v)) + elif isinstance(val, _fix): + super(sint, self).__init__('s', size=val.v.size) + self.load_other(val.v.round(val.k, val.f)) else: super(sint, self).__init__('s', val=val, size=size) @@ -2498,8 +2617,18 @@ def reveal_to(self, player): def private_division(self, divisor, active=True, dividend_length=None, divisor_length=None): - assert active == False + """ Private integer division as per `Veugen and Abspoel + `_ + :param divisor: public (cint/regint) or personal value thereof + :param active: whether to check on the party knowing the + divisor (active security) + :param dividend_length: bit length of the dividend (default: + global bit length) + :param dividend_length: bit length of the divisor (default: + global bit length) + + """ d = divisor l = divisor_length or program.bit_length m = dividend_length or program.bit_length @@ -2515,11 +2644,28 @@ def private_division(self, divisor, active=True, dividend_length=None, r_prime = sint.get_random_int(m + sigma) r_pprime = sint.get_random_int(l + sigma) - h = (r + (r_prime << (l + sigma))) * sint(d) - z = ((self << (l + sigma)) + h + r_pprime).reveal_to(0) - - y = sint(z // (d << (l + sigma))) - y_prime = sint((z // d) % (2 ** (l + sigma))) + d_shared = sint(d) + h = (r + (r_prime << (l + sigma))) * d_shared + z_shared = ((self << (l + sigma)) + h + r_pprime) + z = z_shared.reveal_to(0) + + if active: + z_prime = [sint(x) for x in (z // d).bit_decompose(min_length)] + check = [(x * (1 - x)).reveal() == 0 for x in z_prime] + z_pp = [sint(x) for x in (z % d).bit_decompose(l)] + check += [(x * (1 - x)).reveal() == 0 for x in z_pp] + library.runtime_error_if(sum(check) != len(check), + 'private division') + z_pp = sint.bit_compose(z_pp) + beta1 = z_pp.less_than(d_shared, l) + beta2 = z_shared - sint.bit_compose(z_prime) * d_shared - z_pp + library.runtime_error_if(beta1.reveal() != 1, 'private div') + library.runtime_error_if(beta2.reveal() != 0, 'private div') + y_prime = sint.bit_compose(z_prime[:l + sigma]) + y = sint.bit_compose(z_prime[l + sigma:]) + else: + y = sint(z // (d << (l + sigma))) + y_prime = sint((z // d) % (2 ** (l + sigma))) b = r.greater_than(y_prime, l + sigma) w = y - b - r_prime @@ -3320,15 +3466,16 @@ def read_from_socket(cls, client_id, n=1): :param client_id: Client id (regint) :param n: number of values (default 1) + :param: vector size (int) :returns: cfix (if n=1) or list of cfix """ - cint_input = cint.read_from_socket(client_id, n) + cint_inputs = cint.read_from_socket(client_id, n) if n == 1: return cfix._new(cint_inputs) else: - return list(map(cfix, cint_inputs)) - - @vectorized_classmethod + return list(map(cfix._new, cint_inputs)) + + @classmethod def write_to_socket(self, client_id, values, message_type=ClientMessageType.NoType): """ Send a list of clear fixed-point values to a client (represented as clear integers). @@ -3336,10 +3483,12 @@ def write_to_socket(self, client_id, values, message_type=ClientMessageType.NoTy :param client_id: Client id (regint) :param values: list of cint """ + for value in values: + assert(value.size == values[0].size) def cfix_to_cint(fix_val): return cint(fix_val.v) cint_values = list(map(cfix_to_cint, values)) - writesocketc(client_id, message_type, *cint_values) + writesocketc(client_id, message_type, values[0].size, *cint_values) @staticmethod def malloc(size, creator_tape=None): @@ -3402,6 +3551,9 @@ def __iter__(self): def __len__(self): return len(self.v) + def __getitem__(self, index): + return self._new(self.v[index], k=self.k, f=self.f) + @vectorize def load_int(self, v): self.v = cint(v) * (2 ** self.f) @@ -3601,7 +3753,7 @@ def print_plain(self): cint(0), cint(0), cint(0)) def output_if(self, cond): - cond_print_plain(cond, self.v, cint(-self.f)) + cond_print_plain(cint.conv(cond), self.v, cint(-self.f)) @vectorize def binary_output(self, player=None): @@ -3614,7 +3766,7 @@ def binary_output(self, player=None): player = -1 floatoutput(player, self.v, cint(-self.f), cint(0), cint(0)) -class _single(_number, _structure): +class _single(_number, _secret_structure): """ Representation as single integer preserving the order """ """ E.g. fixed-point numbers """ __slots__ = ['v'] @@ -3623,7 +3775,7 @@ class _single(_number, _structure): """ Whether to round deterministically to nearest instead of probabilistically, e.g. after fixed-point multiplication. """ - @classmethod + @vectorized_classmethod def receive_from_client(cls, n, client_id, message_type=ClientMessageType.NoType): """ Securely obtain shares of values input by a client. Assumes client @@ -3631,12 +3783,22 @@ def receive_from_client(cls, n, client_id, message_type=ClientMessageType.NoType :param n: number of inputs (int) :param client_id: regint - + :param size: vector size (default 1) """ sint_inputs = cls.int_type.receive_from_client(n, client_id, message_type) return list(map(cls._new, sint_inputs)) + @classmethod + def reveal_to_clients(cls, clients, values): + """ Reveal securely to clients. + + :param clients: client ids (list or array) + :param values: list of values of this class + + """ + cls.int_type.reveal_to_clients(clients, [x.v for x in values]) + @vectorized_classmethod def write_shares_to_socket(cls, client_id, values, message_type=ClientMessageType.NoType): @@ -3926,6 +4088,9 @@ def __init__(self, _v=None, k=None, f=None, size=None): self.v = (1-2*_v.s)*a elif isinstance(_v, type(self)): self.v = _v.v + elif isinstance(_v, cfix): + assert _v.f <= self.f + self.v = self.int_type(_v.v << (self.f - _v.f)) elif isinstance(_v, (MemValue, MemFix)): #this is a memvalue object self.v = type(self)(_v.read()).v @@ -4049,9 +4214,9 @@ class revealed_fix(self.clear_type): return revealed_fix._new(val) class sfix(_fix): - """ Secret fixed-point number represented as secret integer. - This uses integer operations internally, see :py:class:`sint` for security - considerations. + """ Secret fixed-point number represented as secret integer, by + multiplying with ``2^f`` and then rounding. See :py:class:`sint` + for security considerations of the underlying integer operations. It supports basic arithmetic (``+, -, *, /``), returning :py:class:`sfix`, and comparisons (``==, !=, <, <=, >, >=``), @@ -4391,7 +4556,7 @@ def reduce(self, unreduced): shifted = under.if_else(0, shifted) return squant._new(shifted, params=self) -class sfloat(_number, _structure): +class sfloat(_number, _secret_structure): """ Secret floating-point number. Represents :math:`(1 - 2s) \cdot (1 - z)\cdot v \cdot 2^p`. @@ -4705,6 +4870,7 @@ def __rsub__(self, other): return -self + other __rsub__.__doc__ = __sub__.__doc__ + @vectorize def __truediv__(self, other): """ Secret floating-point division. @@ -4857,21 +5023,36 @@ def _get_type(t): else: return t -class Array(object): +class _vectorizable: + def reveal_to_clients(self, clients): + """ Reveal contents to list of clients. + + :param clients: list or array of client identifiers + + """ + self.value_type.reveal_to_clients(clients, [self.get_vector()]) + +class Array(_vectorizable): """ Array accessible by public index. That is, ``a[i]`` works for an array ``a`` and ``i`` being a :py:class:`regint`, - :py:class:`cint`, or a Python integer. ``a[start:stop:step]`` - works as well, and so does iteration over an array. - - Arrays support a number of element-wise operations if the - underlying basic type does so. These are ``+, -, *, **, /``. The - return type of these is a vector, which can be assigned to an - array of a compatible type using :py:func:`assign`. + :py:class:`cint`, or a Python integer. :param length: compile-time integer (int) or :py:obj:`None` for unknown length :param value_type: basic type :param address: if given (regint/int), the array will not be allocated + + You can convert between arrays and register vectors by using slice + indexing. This allows for element-wise operations as long as + supported by the basic type. The following adds 10 secret integers + from the first two parties:: + + a = sint.Array(10) + a.input_from(0) + b = sint.Array(10) + b.input_from(1) + a[:] += b[:] + """ @classmethod def create_from(cls, l): @@ -4900,6 +5081,7 @@ def __init__(self, length, value_type, address=None, debug=None, alloc=True): self.debug = debug self.creator_tape = program.curr_tape self.sink = None + self.check_indices = True if alloc: self.alloc() @@ -4914,11 +5096,17 @@ def delete(self): def get_address(self, index): key = str(index) - if isinstance(index, int) and self.length is not None: - index += self.length * (index < 0) - if index >= self.length or index < 0: - raise IndexError('index %s, length %s' % \ - (str(index), str(self.length))) + if self.length is not None: + from .GC.types import cbits + if isinstance(index, int): + index += self.length * (index < 0) + if index >= self.length or index < 0: + raise IndexError('index %s, length %s' % \ + (str(index), str(self.length))) + elif self.check_indices and not isinstance(index, cbits): + library.runtime_error_if(regint.conv(index) >= self.length, + 'overflow: %s/%s', + index, self.length) if (program.curr_block, key) not in self.address_cache: n = self.value_type.n_elements() length = self.length @@ -4937,21 +5125,24 @@ def get_address(self, index): def get_slice(self, index): if index.stop is None and self.length is None: raise CompilerError('Cannot slice array of unknown length') - return index.start or 0, index.stop or self.length, index.step or 1 + if index.step == 0: + raise CompilerError('slice step cannot be zero') + return index.start or 0, \ + min(index.stop or self.length, self.length), index.step or 1 def __getitem__(self, index): """ Reading from array. :param index: public (regint/cint/int/slice) - :return: array if slice is given, basic type otherwise""" + :return: vector if slice is given, basic type otherwise""" if isinstance(index, slice): start, stop, step = self.get_slice(index) - res_length = (stop - start - 1) // step + 1 - res = Array(res_length, self.value_type) - @library.for_range(res_length) - def f(i): - res[i] = self[start+i*step] - return res + if step == 1: + return self.get_vector(start, stop - start) + else: + res_length = (stop - start - 1) // step + 1 + addresses = regint.inc(res_length, start, step) + return self.get_vector(addresses, res_length) return self._load(self.get_address(index)) def __setitem__(self, index, value): @@ -4961,15 +5152,21 @@ def __setitem__(self, index, value): :param value: convertible for relevant basic type """ if isinstance(index, slice): start, stop, step = self.get_slice(index) - value = Array.create_from(value) - source_index = MemValue(0) - @library.for_range(start, stop, step) - def f(i): - self[i] = value[source_index] - source_index.iadd(1) - return + if step == 1: + return self.assign(value, start) + else: + res_length = (stop - start - 1) // step + 1 + addresses = regint.inc(res_length, start, step) + return self.assign(value, addresses) self._store(value, self.get_address(index)) + def get_sub(self, start, stop=None): + if stop is None: + stop = start + start = 0 + return Array(stop - start, self.value_type, + address=self.address + start) + def maybe_get(self, condition, index): """ Return entry if condition is true. @@ -4989,7 +5186,7 @@ def maybe_set(self, condition, index, value): self.sink = self.value_type.Array( 1, address=self.value_type.malloc(1, creator_tape=program.tapes[0])) addresses = (condition.if_else(x, y) for x, y in - zip(util.tuplify(self.get_address(index)), + zip(util.tuplify(self.get_address(condition * index)), util.tuplify(self.sink.get_address(0)))) self._store(value, util.untuplify(tuple(addresses))) @@ -5034,10 +5231,10 @@ def assign(self, other, base=0): except: pass try: - other.store_in_mem(self.get_address(base)) + self.value_type.conv(other).store_in_mem(self.get_address(base)) if len(self) != None and util.is_constant(base): assert len(self) >= other.size + base - except AttributeError: + except (AttributeError, CompilerError): if isinstance(other, Array): @library.for_range_opt(len(other)) def _(i): @@ -5071,7 +5268,7 @@ def get_vector(self, base=0, size=None): :param base: starting point (regint/cint/int) :param size: length (compile-time int) """ - size = size or self.length + size = size or self.length - base return self.value_type.load_mem(self.get_address(base), size=size) get_part_vector = get_vector @@ -5249,7 +5446,7 @@ def __str__(self): sgf2n.dynamic_array = Array -class SubMultiArray(object): +class SubMultiArray(_vectorizable): """ Multidimensional array functionality. Don't construct this directly, use :py:class:`MultiArray` instead. """ def __init__(self, sizes, value_type, address, index, debug=None): @@ -5261,6 +5458,7 @@ def __init__(self, sizes, value_type, address, index, debug=None): self.address = None self.sub_cache = {} self.debug = debug + self.check_indices = True if debug: library.print_ln_if(self.address + reduce(operator.mul, self.sizes) * self.value_type.n_elements() > program.allocated_mem[self.value_type.reg_type], 'AOF%d:' % len(self.sizes) + self.debug) @@ -5271,11 +5469,17 @@ def __getitem__(self, index): :return: :py:class:`Array` if one-dimensional, :py:class:`SubMultiArray` otherwise""" if util.is_constant(index) and index >= self.sizes[0]: raise StopIteration + if isinstance(index, slice) and index == slice(None): + return self.get_vector() key = program.curr_block, str(index) if key not in self.sub_cache: - if self.debug: - library.print_ln_if(index >= self.sizes[0], \ - 'OF%d:' % len(self.sizes) + self.debug) + if util.is_constant(index) and \ + (index >= self.sizes[0] or index < 0): + raise CompilerError('index out of range') + elif self.check_indices: + library.runtime_error_if(index >= self.sizes[0], + 'overflow: %s/%s', + index, self.sizes) if len(self.sizes) == 2: self.sub_cache[key] = \ Array(self.sizes[1], self.value_type, \ @@ -5293,6 +5497,8 @@ def __setitem__(self, index, other): :param index: public (regint/cint/int) :param other: container of matching size and type """ + if isinstance(index, slice) and index == slice(None): + return self.assign(other) self[index].assign(other) def __len__(self): @@ -5526,13 +5732,18 @@ def iadd(self, other): self.assign_vector(self.get_vector() + other.get_vector()) def __mul__(self, other): + # legacy function + return self.mul(other) + + def mul(self, other, res_params=None): + # legacy function + return self.dot(other, res_params) + + def dot(self, other, res_params=None): """ Matrix-matrix and matrix-vector multiplication. :param self: two-dimensional :param other: Matrix or Array of matching size and type """ - return self.mul(other) - - def mul(self, other, res_params=None): assert len(self.sizes) == 2 if isinstance(other, Array): assert len(other) == self.sizes[1] @@ -5762,11 +5973,16 @@ def transpose(self): assert len(self.sizes) == 2 res = Matrix(self.sizes[1], self.sizes[0], self.value_type) library.break_point() - @library.for_range_opt(self.sizes[1]) - def _(i): + if self.value_type.n_elements() == 1: @library.for_range_opt(self.sizes[0]) def _(j): - res[i][j] = self[j][i] + res.set_column(j, self[j][:]) + else: + @library.for_range_opt(self.sizes[1]) + def _(i): + @library.for_range_opt(self.sizes[0]) + def _(j): + res[i][j] = self[j][i] library.break_point() return res @@ -5809,14 +6025,22 @@ class MultiArray(SubMultiArray): """ Multidimensional array. The access operator (``a[i]``) allows to a multi-dimensional array of dimension one less or a simple array - for a two-dimensional array. Element-wise addition and subtraction - is supported, returning a vector, which can be assigned using - :py:func:`assign`. Matrix-vector and matrix-matrix multiplication - is supported as well. + for a two-dimensional array. :param sizes: shape (compile-time list of integers) :param value_type: basic type of entries + You can convert between arrays and register vectors by using slice + indexing. This allows for element-wise operations as long as + supported by the basic type. The following has the first two parties + input a 10x10 secret integer matrix followed by storing the + element-wise multiplications in the same data structure:: + + a = sint.Tensor([3, 10, 10]) + a[0].input_from(0) + a[1].input_from(1) + a[2][:] = a[0][:] * a[1][:] + """ def __init__(self, sizes, value_type, debug=None, address=None, alloc=True): if isinstance(address, Array): diff --git a/ECDSA/fake-spdz-ecdsa-party.cpp b/ECDSA/fake-spdz-ecdsa-party.cpp index 9fff469ea..6cc2fbcc8 100644 --- a/ECDSA/fake-spdz-ecdsa-party.cpp +++ b/ECDSA/fake-spdz-ecdsa-party.cpp @@ -35,7 +35,7 @@ int main(int argc, const char** argv) int n_tuples = 1000; if (not opt.lastArgs.empty()) n_tuples = atoi(opt.lastArgs[0]->c_str()); - PlainPlayer P(N); + PlainPlayer P(N, "ecdsa"); P256Element::init(); P256Element::Scalar keyp; diff --git a/ECDSA/hm-ecdsa-party.hpp b/ECDSA/hm-ecdsa-party.hpp index d2648fe13..a68f8e833 100644 --- a/ECDSA/hm-ecdsa-party.hpp +++ b/ECDSA/hm-ecdsa-party.hpp @@ -43,7 +43,7 @@ void run(int argc, const char** argv) int n_tuples = 1000; if (not opt.lastArgs.empty()) n_tuples = atoi(opt.lastArgs[0]->c_str()); - CryptoPlayer P(N); + CryptoPlayer P(N, "ecdsa"); P256Element::init(); typedef T pShare; OnlineOptions::singleton.batch_size = 1; diff --git a/ECDSA/ot-ecdsa-party.hpp b/ECDSA/ot-ecdsa-party.hpp index de655e13e..1c4a442ae 100644 --- a/ECDSA/ot-ecdsa-party.hpp +++ b/ECDSA/ot-ecdsa-party.hpp @@ -88,7 +88,7 @@ void run(int argc, const char** argv) int n_tuples = 1000; if (not opt.lastArgs.empty()) n_tuples = atoi(opt.lastArgs[0]->c_str()); - PlainPlayer P(N); + PlainPlayer P(N, "ecdsa"); P256Element::init(); P256Element::Scalar::next::init_field(P256Element::Scalar::pr(), false); diff --git a/ExternalIO/Client.h b/ExternalIO/Client.h new file mode 100644 index 000000000..12ba1c938 --- /dev/null +++ b/ExternalIO/Client.h @@ -0,0 +1,31 @@ +/* + * Client.h + * + */ + +#ifndef EXTERNALIO_CLIENT_H_ +#define EXTERNALIO_CLIENT_H_ + +#include "Networking/ssl_sockets.h" + +class Client +{ + vector plain_sockets; + ssl_ctx ctx; + ssl_service io_service; + +public: + vector sockets; + octetStream specification; + + Client(const vector& hostnames, int port_base, int my_client_id); + ~Client(); + + template + void send_private_inputs(const vector& values); + + template + vector receive_outputs(int n); +}; + +#endif /* EXTERNALIO_CLIENT_H_ */ diff --git a/ExternalIO/Client.hpp b/ExternalIO/Client.hpp new file mode 100644 index 000000000..601d9a486 --- /dev/null +++ b/ExternalIO/Client.hpp @@ -0,0 +1,126 @@ +/* + * Client.cpp + * + */ + +#include "Client.h" + +inline +Client::Client(const vector& hostnames, int port_base, + int my_client_id) : + ctx("C" + to_string(my_client_id)) +{ + bigint::init_thread(); + + // Setup connections from this client to each party socket + int nparties = hostnames.size(); + plain_sockets.resize(nparties); + sockets.resize(nparties); + for (int i = 0; i < nparties; i++) + { + set_up_client_socket(plain_sockets[i], hostnames[i].c_str(), port_base + i); + octetStream(to_string(my_client_id)).Send(plain_sockets[i]); + sockets[i] = new ssl_socket(io_service, ctx, plain_sockets[i], + "P" + to_string(i), "C" + to_string(my_client_id), true); + if (i == 0) + specification.Receive(sockets[0]); + } +} + +inline +Client::~Client() +{ + for (auto& socket : sockets) + { + delete socket; + } +} + +// Send the private inputs masked with a random value. +// Receive shares of a preprocessed triple from each SPDZ engine, combine and check the triples are valid. +// Add the private input value to triple[0] and send to each spdz engine. +template +void Client::send_private_inputs(const vector& values) +{ + int num_inputs = values.size(); + octetStream os; + vector< vector > triples(num_inputs, vector(3)); + vector triple_shares(3); + + // Receive num_inputs triples from SPDZ + for (size_t j = 0; j < sockets.size(); j++) + { + os.reset_write_head(); + os.Receive(sockets[j]); + +#ifdef VERBOSE_COMM + cerr << "received " << os.get_length() << " from " << j << endl; +#endif + + for (int j = 0; j < num_inputs; j++) + { + for (int k = 0; k < 3; k++) + { + triple_shares[k].unpack(os); + triples[j][k] += triple_shares[k]; + } + } + } + + // Check triple relations (is a party cheating?) + for (int i = 0; i < num_inputs; i++) + { + if (T(triples[i][0] * triples[i][1]) != triples[i][2]) + { + cerr << triples[i][2] << " != " << triples[i][0] << " * " << triples[i][1] << endl; + cerr << "Incorrect triple at " << i << ", aborting\n"; + throw mac_fail(); + } + } + // Send inputs + triple[0], so SPDZ can compute shares of each value + os.reset_write_head(); + for (int i = 0; i < num_inputs; i++) + { + T y = values[i] + triples[i][0]; + y.pack(os); + } + + for (auto& socket : sockets) + os.Send(socket); +} + +// Receive shares of the result and sum together. +// Also receive authenticating values. +template +vector Client::receive_outputs(int n) +{ + vector triples(3 * n); + octetStream os; + for (auto& socket : sockets) + { + os.reset_write_head(); + os.Receive(socket); +#ifdef VERBOSE_COMM + cout << "received " << os.get_length() << endl; +#endif + for (int j = 0; j < 3 * n; j++) + { + T value; + value.unpack(os); + triples[j] += value; + } + } + + vector output_values; + for (int i = 0; i < 3 * n; i += 3) + { + if (T(triples[i] * triples[i + 1]) != triples[i + 2]) + { + cerr << "Unable to authenticate output value as correct, aborting." << endl; + throw mac_fail(); + } + output_values.push_back(triples[i]); + } + + return output_values; +} diff --git a/ExternalIO/bankers-bonus-client.cpp b/ExternalIO/bankers-bonus-client.cpp index c3d5aa524..f68384c00 100644 --- a/ExternalIO/bankers-bonus-client.cpp +++ b/ExternalIO/bankers-bonus-client.cpp @@ -39,111 +39,33 @@ #include "Protocols/fake-stuff.h" #include "Math/gfp.hpp" +#include "Client.hpp" #include #include #include #include -// Send the private inputs masked with a random value. -// Receive shares of a preprocessed triple from each SPDZ engine, combine and check the triples are valid. -// Add the private input value to triple[0] and send to each spdz engine. template -void send_private_inputs(const vector& values, vector& sockets, int nparties) -{ - int num_inputs = values.size(); - octetStream os; - vector< vector > triples(num_inputs, vector(3)); - vector triple_shares(3); - - // Receive num_inputs triples from SPDZ - for (int j = 0; j < nparties; j++) - { - os.reset_write_head(); - os.Receive(sockets[j]); - -#ifdef VERBOSE_COMM - cerr << "received " << os.get_length() << " from " << j << endl; -#endif - - for (int j = 0; j < num_inputs; j++) - { - for (int k = 0; k < 3; k++) - { - triple_shares[k].unpack(os); - triples[j][k] += triple_shares[k]; - } - } - } - - // Check triple relations (is a party cheating?) - for (int i = 0; i < num_inputs; i++) - { - if (T(triples[i][0] * triples[i][1]) != triples[i][2]) - { - cerr << triples[i][2] << " != " << triples[i][0] << " * " << triples[i][1] << endl; - cerr << "Incorrect triple at " << i << ", aborting\n"; - throw mac_fail(); - } - } - // Send inputs + triple[0], so SPDZ can compute shares of each value - os.reset_write_head(); - for (int i = 0; i < num_inputs; i++) - { - T y = values[i] + triples[i][0]; - y.pack(os); - } - for (int j = 0; j < nparties; j++) - os.Send(sockets[j]); -} - -// Receive shares of the result and sum together. -// Also receive authenticating values. -template -T receive_result(vector& sockets, int nparties) -{ - vector output_values(3); - octetStream os; - for (int i = 0; i < nparties; i++) - { - os.reset_write_head(); - os.Receive(sockets[i]); - for (unsigned int j = 0; j < 3; j++) - { - T value; - value.unpack(os); - output_values[j] += value; - } - } - - if (T(output_values[0] * output_values[1]) != output_values[2]) - { - cerr << "Unable to authenticate output value as correct, aborting." << endl; - throw mac_fail(); - } - return output_values[0]; -} - -template -void one_run(T salary_value, vector& sockets, int nparties) +void one_run(T salary_value, Client& client) { // Run the computation - send_private_inputs({salary_value}, sockets, nparties); + client.send_private_inputs({salary_value}); cout << "Sent private inputs to each SPDZ engine, waiting for result..." << endl; // Get the result back (client_id of winning client) - T result = receive_result(sockets, nparties); + T result = client.receive_outputs(1)[0]; cout << "Winning client id is : " << result << endl; } template -void run(double salary_value, vector& sockets, int nparties) +void run(double salary_value, Client& client) { // sint - one_run(long(round(salary_value)), sockets, nparties); + one_run(long(round(salary_value)), client); // sfix with f = 16 - one_run(long(round(salary_value * exp2(16))), sockets, nparties); + one_run(long(round(salary_value * exp2(16))), client); } int main(int argc, char** argv) @@ -165,7 +87,7 @@ int main(int argc, char** argv) nparties = atoi(argv[2]); salary_value = atof(argv[3]); finish = atoi(argv[4]); - vector hostnames(nparties, "localhost"); + vector hostnames(nparties, "localhost"); if (argc > 5) { @@ -185,19 +107,11 @@ int main(int argc, char** argv) bigint::init_thread(); // Setup connections from this client to each party socket - vector plain_sockets(nparties); - vector sockets(nparties); - ssl_ctx ctx("C" + to_string(my_client_id)); - ssl_service io_service; - octetStream specification; + Client client(hostnames, port_base, my_client_id); + auto& specification = client.specification; + auto& sockets = client.sockets; for (int i = 0; i < nparties; i++) { - set_up_client_socket(plain_sockets[i], hostnames[i], port_base + i); - send(plain_sockets[i], (octet*) &my_client_id, sizeof(int)); - sockets[i] = new ssl_socket(io_service, ctx, plain_sockets[i], - "P" + to_string(i), "C" + to_string(my_client_id), true); - if (i == 0) - specification.Receive(sockets[0]); octetStream os; os.store(finish); os.Send(sockets[i]); @@ -211,7 +125,7 @@ int main(int argc, char** argv) { gfp::init_field(specification.get()); cerr << "using prime " << gfp::pr() << endl; - run(salary_value, sockets, nparties); + run(salary_value, client); break; } case 'R': @@ -220,13 +134,13 @@ int main(int argc, char** argv) switch (R) { case 64: - run>(salary_value, sockets, nparties); + run>(salary_value, client); break; case 104: - run>(salary_value, sockets, nparties); + run>(salary_value, client); break; case 128: - run>(salary_value, sockets, nparties); + run>(salary_value, client); break; default: cerr << R << "-bit ring not implemented"; @@ -239,8 +153,5 @@ int main(int argc, char** argv) exit(1); } - for (int i = 0; i < nparties; i++) - delete sockets[i]; - return 0; } diff --git a/FHE/AddableVector.h b/FHE/AddableVector.h index bbf0b112e..1efe1e228 100644 --- a/FHE/AddableVector.h +++ b/FHE/AddableVector.h @@ -63,6 +63,16 @@ class AddableVector: public vector return *this; } + AddableVector operator-(const AddableVector& y) const + { + if (this->size() != y.size()) + throw out_of_range("vector length mismatch"); + AddableVector res(y.size()); + for (unsigned int i = 0; i < this->size(); i++) + res[i] = (*this)[i] - y[i]; + return res; + } + void mul(const AddableVector& x, const AddableVector& y) { if (x.size() != y.size()) diff --git a/FHE/Diagonalizer.cpp b/FHE/Diagonalizer.cpp new file mode 100644 index 000000000..9cc1a0840 --- /dev/null +++ b/FHE/Diagonalizer.cpp @@ -0,0 +1,71 @@ +/* + * Diagonalizer.cpp + * + */ + +#include "Diagonalizer.h" + +Diagonalizer::Diagonalizer(const MatrixVector& matrices, + const FFT_Data& FTD, const FHE_PK& pk) : + FTD(FTD) +{ + assert(not matrices.empty()); + for (auto& matrix : matrices) + { + assert(matrix.n_cols == matrices[0].n_cols); + assert(matrix.n_rows == matrices[0].n_rows); + } + + n_rows = matrices[0].n_rows; + n_cols = matrices[0].n_cols; + assert(n_rows * matrices.size() <= size_t(FTD.num_slots())); + for (size_t i = 0; i < n_cols; i++) + { + Plaintext_ plaintext(FTD, Evaluation); + for (size_t k = 0; k < matrices.size(); k++) + { + for (size_t j = 0; j < n_rows; j++) + { + auto entry = matrices.at(k)[{j, (j + i) % n_cols}]; + plaintext.set_element(k * n_rows + j, entry); + } + } + ciphertexts.push_back(pk.encrypt(plaintext)); + } +} + +Plaintext_ Diagonalizer::get_plaintext( + const MatrixVector& matrices, int left_col, + int right_col) +{ + Plaintext_ plaintext(FTD, Evaluation); + for (size_t k = 0; k < matrices.size(); k++) + for (size_t j = 0; j < n_rows; j++) + plaintext.set_element(k * n_rows + j, + matrices.at(k)[{(left_col + j) % n_cols, right_col}]); + return plaintext; +} + +Diagonalizer::MatrixVector Diagonalizer::decrypt( + const vector& products, int n_matrices, FHE_SK& sk) +{ + vector> plaintexts; + for (auto& x : products) + plaintexts.push_back(sk.decrypt(x, FTD)); + return dediag(plaintexts, n_matrices); +} + +Diagonalizer::MatrixVector Diagonalizer::dediag( + const vector>& products, int n_matrices) +{ + int n_cols_out = products.size(); + MatrixVector res(n_matrices, {int(n_rows), n_cols_out}); + for (int i = 0; i < n_cols_out; i++) + { + auto& c = products.at(i); + for (int j = 0; j < n_matrices; j++) + for (size_t k = 0; k < n_rows; k++) + res.at(j)[{k, i}] = c.element(j * n_rows + k); + } + return res; +} diff --git a/FHE/Diagonalizer.h b/FHE/Diagonalizer.h new file mode 100644 index 000000000..6a83ead67 --- /dev/null +++ b/FHE/Diagonalizer.h @@ -0,0 +1,36 @@ +/* + * Diagonalizer.h + * + */ + +#ifndef FHE_DIAGONALIZER_H_ +#define FHE_DIAGONALIZER_H_ + +#include "Math/gfpvar.h" +#include "Ciphertext.h" +#include "Protocols/ShareMatrix.h" + +class Diagonalizer +{ + const FFT_Data& FTD; + + size_t n_rows, n_cols; + +public: + typedef AddableVector> MatrixVector; + + vector ciphertexts; + + Diagonalizer(const MatrixVector& matrices, + const FFT_Data& FTD, const FHE_PK& pk); + + Plaintext_ get_plaintext(const MatrixVector& matrices, + int left_col, int right_col); + + MatrixVector decrypt(const vector&, int n_matrices, FHE_SK& sk); + + MatrixVector dediag(const vector>& plaintexts, + int n_matrices); +}; + +#endif /* FHE_DIAGONALIZER_H_ */ diff --git a/FHE/FFT.cpp b/FHE/FFT.cpp index 7552e5b4a..e8dcc228a 100644 --- a/FHE/FFT.cpp +++ b/FHE/FFT.cpp @@ -185,7 +185,8 @@ void FFT_Iter(vector& ioput, int n, const vector& roots, int start = queues.distribute(job, n / 2); for (int i = start; i < n / 2; i++) FFT_Iter2_body(ioput, alpha2, i, m, PrD); - queues.wrap_up(job); + if (start > 0) + queues.wrap_up(job); } else for (int i = 0; i < n / 2; i++) diff --git a/FHE/FHE_Keys.cpp b/FHE/FHE_Keys.cpp index 8a6580926..2a4d6b123 100644 --- a/FHE/FHE_Keys.cpp +++ b/FHE/FHE_Keys.cpp @@ -359,11 +359,19 @@ void FHE_PK::check(const FHE_Params& params, const bigint& pr) const +template void FHE_PK::encrypt(Ciphertext&, const Plaintext_& mess, + const Random_Coins& rc) const; +template void FHE_PK::encrypt(Ciphertext&, const Plaintext_& mess, + const Random_Coins& rc) const; + template Ciphertext FHE_PK::encrypt(const Plaintext_& mess, const Random_Coins& rc) const; template Ciphertext FHE_PK::encrypt(const Plaintext_& mess) const; template Ciphertext FHE_PK::encrypt(const Plaintext_& mess) const; +template void FHE_SK::decrypt(Plaintext_&, const Ciphertext& c) const; +template void FHE_SK::decrypt(Plaintext_&, const Ciphertext& c) const; + template Plaintext_ FHE_SK::decrypt(const Ciphertext& c, const FFT_Data& FieldD); template Plaintext_ FHE_SK::decrypt(const Ciphertext& c, diff --git a/FHEOffline/Multiplier.cpp b/FHEOffline/Multiplier.cpp index 8d0c49d92..732904b39 100644 --- a/FHEOffline/Multiplier.cpp +++ b/FHEOffline/Multiplier.cpp @@ -44,6 +44,20 @@ void Multiplier::multiply_and_add(Plaintext_& res, template void Multiplier::multiply_and_add(Plaintext_& res, const Ciphertext& enc_a, const Rq_Element& b, OT_ROLE role) +{ + if (role & SENDER) + { + timers["Ciphertext multiplication"].start(); + C.mul(enc_a, b); + timers["Ciphertext multiplication"].stop(); + } + + add(res, C, role); +} + +template +void Multiplier::add(Plaintext_& res, const Ciphertext& c, + OT_ROLE role, int n_summands) { o.reset_write_head(); @@ -51,9 +65,6 @@ void Multiplier::multiply_and_add(Plaintext_& res, { PRNG G; G.ReSeed(); - timers["Ciphertext multiplication"].start(); - C.mul(enc_a, b); - timers["Ciphertext multiplication"].stop(); timers["Mask randomization"].start(); product_share.randomize(G); bigint B = 6 * machine.setup().params.get_R(); @@ -63,12 +74,13 @@ void Multiplier::multiply_and_add(Plaintext_& res, B *= NonInteractiveProof::slack(machine.sec, machine.setup().params.phi_m()); B <<= machine.extra_slack; + B *= n_summands; rc.generateUniform(G, 0, B, B); timers["Mask randomization"].stop(); timers["Encryption"].start(); other_pk.encrypt(mask, product_share, rc); timers["Encryption"].stop(); - mask += C; + mask += c; mask.pack(o); res -= product_share; } diff --git a/FHEOffline/Multiplier.h b/FHEOffline/Multiplier.h index 1a147b918..e2e1ce660 100644 --- a/FHEOffline/Multiplier.h +++ b/FHEOffline/Multiplier.h @@ -47,6 +47,8 @@ class Multiplier const Plaintext_& b); void multiply_and_add(Plaintext_& res, const Ciphertext& C, const Rq_Element& b, OT_ROLE role = BOTH); + void add(Plaintext_& res, const Ciphertext& C, OT_ROLE role = BOTH, + int n_summands = 1); void multiply_alpha_and_add(Plaintext_& res, const Rq_Element& b, OT_ROLE role = BOTH); int get_offset() { return P.get_offset(); } diff --git a/FHEOffline/PairwiseMachine.cpp b/FHEOffline/PairwiseMachine.cpp index 942dd698b..6ac3a82d4 100644 --- a/FHEOffline/PairwiseMachine.cpp +++ b/FHEOffline/PairwiseMachine.cpp @@ -17,7 +17,7 @@ PairwiseMachine::PairwiseMachine(Player& P) : } PairwiseMachine::PairwiseMachine(int argc, const char** argv) : - MachineBase(argc, argv), P(*new PlainPlayer(N, 0xffff << 16)), + MachineBase(argc, argv), P(*new PlainPlayer(N, "pairwise")), other_pks(N.num_players(), {setup_p.params, 0}), pk(other_pks[N.my_num()]), sk(pk) { diff --git a/FHEOffline/SimpleGenerator.h b/FHEOffline/SimpleGenerator.h index 5c4128a86..9cacad697 100644 --- a/FHEOffline/SimpleGenerator.h +++ b/FHEOffline/SimpleGenerator.h @@ -31,7 +31,7 @@ class GeneratorBase map timers; GeneratorBase(int thread_num, const Names& N, Player* player = 0) : - player(player ? 0 : new PlainPlayer(N, thread_num << 16)), + player(player ? 0 : new PlainPlayer(N, to_string(thread_num))), thread_num(thread_num), P(player ? *player : *this->player), thread(0), total(0) { diff --git a/FHEOffline/SimpleMachine.cpp b/FHEOffline/SimpleMachine.cpp index 98bfd2dcf..04d190f1c 100644 --- a/FHEOffline/SimpleMachine.cpp +++ b/FHEOffline/SimpleMachine.cpp @@ -216,7 +216,7 @@ void MultiplicativeMachine::generate_setup(int slack) template void MultiplicativeMachine::fake_keys(int slack) { - PlainPlayer P(N, -1 * N.num_players() * N.num_players()); + PlainPlayer P(N, "fake"); octetStream os; PartSetup& part_setup = setup.part(); if (P.my_num() == 0) diff --git a/GC/AtlasSecret.cpp b/GC/AtlasSecret.cpp index 13e6e6ebb..2c396bfbc 100644 --- a/GC/AtlasSecret.cpp +++ b/GC/AtlasSecret.cpp @@ -6,6 +6,7 @@ #include "AtlasSecret.h" #include "TinyMC.h" +#include "Protocols/Shamir.hpp" #include "Protocols/ShamirMC.hpp" #include "Protocols/MAC_Check_Base.hpp" #include "Secret.hpp" diff --git a/GC/CcdPrep.h b/GC/CcdPrep.h index 003873753..adc44630b 100644 --- a/GC/CcdPrep.h +++ b/GC/CcdPrep.h @@ -92,9 +92,9 @@ class CcdPrep : public BufferPrep } } - size_t data_sent() + NamedCommStats comm_stats() { - return part_prep.data_sent(); + return part_prep.comm_stats(); } }; diff --git a/GC/FakeSecret.h b/GC/FakeSecret.h index f650d4f68..e3820e06d 100644 --- a/GC/FakeSecret.h +++ b/GC/FakeSecret.h @@ -108,7 +108,8 @@ class FakeSecret : public ShareInterface, public BitVec static FakeSecret input(GC::Processor& processor, const InputArgs& args); static FakeSecret input(int from, word input, int n_bits); - static FakeSecret constant(clear value, int = 0, mac_key_type = {}) { return value; } + static FakeSecret constant(clear value, int = 0, mac_key_type = {}, int = -1) + { return value; } FakeSecret() {} template diff --git a/GC/Instruction.h b/GC/Instruction.h index b37091735..f8872f12f 100644 --- a/GC/Instruction.h +++ b/GC/Instruction.h @@ -68,6 +68,7 @@ enum CLEAR_WRITE = 0x210, XORCBI = 0x210, BITDECC = 0x211, + NOTCB = 0x212, CONVCINT = 0x213, REVEAL = 0x214, STMSDCI = 0x215, diff --git a/GC/MaliciousRepSecret.h b/GC/MaliciousRepSecret.h index da2551807..500bbb5af 100644 --- a/GC/MaliciousRepSecret.h +++ b/GC/MaliciousRepSecret.h @@ -84,12 +84,6 @@ class MalRepSecretBase : public ReplicatedSecret return new HashMaliciousRepMC; } - static U constant(const BitVec& other, int my_num, const BitVec& alphai) - { - (void) my_num, (void) alphai; - return other; - } - MalRepSecretBase() {} template MalRepSecretBase(const T& other) : super(other) {} diff --git a/GC/NoShare.h b/GC/NoShare.h index e027e95c1..9bffccd5e 100644 --- a/GC/NoShare.h +++ b/GC/NoShare.h @@ -143,7 +143,7 @@ class NoShare : public ShareInterface static void trans(Processor&, Integer, const vector&) { fail(); } - static NoShare constant(const GC::Clear&, int, mac_key_type) { fail(); return {}; } + static NoShare constant(const GC::Clear&, int, mac_key_type, int = -1) { fail(); return {}; } NoShare() {} diff --git a/GC/Processor.h b/GC/Processor.h index d0a84a6a2..e759cf05f 100644 --- a/GC/Processor.h +++ b/GC/Processor.h @@ -86,6 +86,7 @@ class Processor : public ::ProcessorBase, public GC::RuntimeBranching void xors(const vector& args, size_t start, size_t end); void xorc(const ::BaseInstruction& instruction); void nots(const ::BaseInstruction& instruction); + void notcb(const ::BaseInstruction& instruction); void andm(const ::BaseInstruction& instruction); void and_(const vector& args, bool repeat); void andrs(const vector& args) { and_(args, true); } diff --git a/GC/Processor.hpp b/GC/Processor.hpp index d2151fe2b..b1539b04f 100644 --- a/GC/Processor.hpp +++ b/GC/Processor.hpp @@ -257,6 +257,19 @@ void Processor::nots(const ::BaseInstruction& instruction) } } +template +void Processor::notcb(const ::BaseInstruction& instruction) +{ + int total = instruction.get_n(); + int unit = Clear::N_BITS; + for (int i = 0; i < DIV_CEIL(total, unit); i++) + { + int n = min(unit, total - i * unit); + C[instruction.get_r(0) + i] = + Clear(~C[instruction.get_r(1) + i].get()).mask(n); + } +} + template void Processor::andm(const ::BaseInstruction& instruction) { diff --git a/GC/Rep4Secret.h b/GC/Rep4Secret.h index a708cc315..5b5582296 100644 --- a/GC/Rep4Secret.h +++ b/GC/Rep4Secret.h @@ -30,7 +30,7 @@ class Rep4Secret : public RepSecretBase static MC* new_mc(typename super::mac_key_type) { return new MC; } static This constant(const typename super::clear& constant, int my_num, - typename super::mac_key_type = {}) + typename super::mac_key_type = {}, int = -1) { return Rep4Share::constant(constant, my_num); } diff --git a/GC/SemiPrep.cpp b/GC/SemiPrep.cpp index 41752eab1..32e756096 100644 --- a/GC/SemiPrep.cpp +++ b/GC/SemiPrep.cpp @@ -10,6 +10,7 @@ #include "Protocols/ReplicatedPrep.hpp" #include "Protocols/MAC_Check_Base.hpp" +#include "Protocols/Replicated.hpp" #include "OT/NPartyTripleGenerator.hpp" namespace GC @@ -65,12 +66,12 @@ void SemiPrep::buffer_bits() } } -size_t SemiPrep::data_sent() +NamedCommStats SemiPrep::comm_stats() { if (triple_generator) - return triple_generator->data_sent(); + return triple_generator->comm_stats(); else - return 0; + return {}; } } /* namespace GC */ diff --git a/GC/SemiPrep.h b/GC/SemiPrep.h index 425cf6790..60751c382 100644 --- a/GC/SemiPrep.h +++ b/GC/SemiPrep.h @@ -53,7 +53,7 @@ class SemiPrep : public BufferPrep, ShiftableTripleBuffer::ShareParty(int argc, const char** argv, ez::ezOptionParser& opt, if (not this->machine.use_encryption and not T::dishonest_majority) insecure("unencrypted communication"); - Server* server = network_opts.start_networking(this->N, my_num); + network_opts.start_networking(this->N, my_num); if (online_opts.live_prep) if (T::needs_ot) { Player* P; if (this->machine.use_encryption) - P = new CryptoPlayer(this->N, 0xFFFF); + P = new CryptoPlayer(this->N, "shareparty"); else - P = new PlainPlayer(this->N, 0xFFFF); + P = new PlainPlayer(this->N, "shareparty"); for (int i = 0; i < this->machine.nthreads; i++) this->machine.ot_setups.push_back({*P, true}); delete P; @@ -133,9 +133,6 @@ ShareParty::ShareParty(int argc, const char** argv, ez::ezOptionParser& opt, this->run(); this->machine.write_memory(this->N.my_num()); - - if (server) - delete server; } template diff --git a/GC/ShareSecret.h b/GC/ShareSecret.h index 498b620c9..357f91f08 100644 --- a/GC/ShareSecret.h +++ b/GC/ShareSecret.h @@ -171,8 +171,8 @@ class ReplicatedSecret : public RepSecretBase public: typedef ReplicatedBase Protocol; - static ReplicatedSecret constant(const typename super::clear& value, int my_num, - typename super::mac_key_type) + static ReplicatedSecret constant(const typename super::clear& value, + int my_num, typename super::mac_key_type, int = -1) { ReplicatedSecret res; if (my_num < 2) diff --git a/GC/ShareThread.h b/GC/ShareThread.h index a084f8c79..86b1d95cb 100644 --- a/GC/ShareThread.h +++ b/GC/ShareThread.h @@ -58,8 +58,8 @@ class StandaloneShareThread : public ShareThread, public Thread void pre_run(); void post_run() { ShareThread::post_run(); } - size_t data_sent() - { return Thread::data_sent() + this->DataF.data_sent(); } + NamedCommStats comm_stats() + { return Thread::comm_stats() + this->DataF.comm_stats(); } }; template diff --git a/GC/Thread.h b/GC/Thread.h index d77341655..659c070a0 100644 --- a/GC/Thread.h +++ b/GC/Thread.h @@ -56,7 +56,7 @@ class Thread void join_tape(); void finish(); - virtual size_t data_sent(); + virtual NamedCommStats comm_stats(); }; template diff --git a/GC/Thread.hpp b/GC/Thread.hpp index f53e02d74..38f3d4326 100644 --- a/GC/Thread.hpp +++ b/GC/Thread.hpp @@ -51,10 +51,11 @@ void Thread::run() singleton = this; BaseMachine::s().thread_num = thread_num; secure_prng.ReSeed(); + string id = "T" + to_string(thread_num); if (machine.use_encryption) - P = new CryptoPlayer(N, thread_num << 16); + P = new CryptoPlayer(N, id); else - P = new PlainPlayer(N, thread_num << 16); + P = new PlainPlayer(N, id); processor.open_input_file(N.my_num(), thread_num, master.opts.cmd_private_input_file); processor.out.activate(N.my_num() == 0 or master.opts.interactive); @@ -98,10 +99,10 @@ void Thread::finish() } template -size_t GC::Thread::data_sent() +NamedCommStats Thread::comm_stats() { assert(P); - return P->comm_stats.total_data(); + return P->comm_stats; } } /* namespace GC */ diff --git a/GC/ThreadMaster.hpp b/GC/ThreadMaster.hpp index a0914389c..5f16229c0 100644 --- a/GC/ThreadMaster.hpp +++ b/GC/ThreadMaster.hpp @@ -58,7 +58,7 @@ Thread* ThreadMaster::new_thread(int i) template void ThreadMaster::run() { - P = new PlainPlayer(N, 0xff << 24); + P = new PlainPlayer(N, "main"); machine.load_schedule(progname); @@ -87,12 +87,10 @@ void ThreadMaster::run() NamedCommStats stats = P->comm_stats; ExecutionStats exe_stats; - size_t data_sent = P->comm_stats.total_data(); for (auto thread : threads) { stats += thread->P->comm_stats; exe_stats += thread->processor.stats; - data_sent += thread->data_sent(); delete thread; } @@ -102,7 +100,7 @@ void ThreadMaster::run() stats.print(); cerr << "Time = " << timer.elapsed() << " seconds" << endl; - cerr << "Data sent = " << data_sent * 1e-6 << " MB" << endl; + cerr << "Data sent = " << stats.sent * 1e-6 << " MB" << endl; } } /* namespace GC */ diff --git a/GC/TinierSharePrep.h b/GC/TinierSharePrep.h index cad2e969b..b2fbf9aac 100644 --- a/GC/TinierSharePrep.h +++ b/GC/TinierSharePrep.h @@ -48,7 +48,7 @@ class TinierSharePrep : public PersonalPrep void set_protocol(typename T::Protocol& protocol); - size_t data_sent(); + NamedCommStats comm_stats(); }; } diff --git a/GC/TinierSharePrep.hpp b/GC/TinierSharePrep.hpp index 964e3614a..10711b70b 100644 --- a/GC/TinierSharePrep.hpp +++ b/GC/TinierSharePrep.hpp @@ -92,13 +92,13 @@ void GC::TinierSharePrep::buffer_bits() } template -size_t TinierSharePrep::data_sent() +NamedCommStats TinierSharePrep::comm_stats() { - size_t res = 0; + NamedCommStats res; if (triple_generator) - res += triple_generator->data_sent(); + res += triple_generator->comm_stats(); if (real_triple_generator) - res += real_triple_generator->data_sent(); + res += real_triple_generator->comm_stats(); return res; } diff --git a/GC/TinySecret.h b/GC/TinySecret.h index 632630f13..cbd15ee43 100644 --- a/GC/TinySecret.h +++ b/GC/TinySecret.h @@ -70,11 +70,14 @@ class VectorSecret : public Secret T::reveal_inst(processor, args); } - static This constant(BitVec other, int my_num, mac_key_type alphai) + static This constant(BitVec other, int my_num, mac_key_type alphai, + int n_bits = -1) { + if (n_bits < 0) + n_bits = other.length(); This res; - res.resize_regs(other.length()); - for (int i = 0; i < other.length(); i++) + res.resize_regs(n_bits); + for (int i = 0; i < n_bits; i++) res.get_reg(i) = part_type::constant(other.get_bit(i), my_num, alphai); return res; } diff --git a/GC/instructions.h b/GC/instructions.h index ae395e911..f94da799a 100644 --- a/GC/instructions.h +++ b/GC/instructions.h @@ -43,6 +43,7 @@ X(XORCB, processor.xorc(instruction)) \ X(XORCBI, C0.xor_(PC1, IMM)) \ X(NOTS, processor.nots(INST)) \ + X(NOTCB, processor.notcb(INST)) \ X(ANDRS, T::andrs(PROC, EXTRA)) \ X(ANDS, T::ands(PROC, EXTRA)) \ X(ADDCB, C0 = PC1 + PC2) \ @@ -140,6 +141,7 @@ X(NPLAYERS, I0 = Thread::s().P->num_players()) \ X(THRESHOLD, I0 = T::threshold(Thread::s().P->num_players())) \ X(PLAYERID, I0 = Thread::s().P->my_num()) \ + X(CRASH, if (I0.get()) throw crash_requested()) \ #define INSTRUCTIONS BIT_INSTRUCTIONS GC_INSTRUCTIONS diff --git a/Machines/OTMachine.cpp b/Machines/OTMachine.cpp index f13701bef..351871c9c 100644 --- a/Machines/OTMachine.cpp +++ b/Machines/OTMachine.cpp @@ -226,7 +226,7 @@ OTMachine::OTMachine(int argc, const char** argv) N.push_back(new Names(my_num, portnum_base + 1000 * N.size(), names)); } - P = new RealTwoPartyPlayer(*N[0], 1 - my_num, 500); + P = new RealTwoPartyPlayer(*N[0], 1 - my_num, "machine"); timeval baseOTstart, baseOTend; gettimeofday(&baseOTstart, NULL); @@ -319,7 +319,8 @@ void OTMachine::run() } // now setup resources for each thread // round robin with the names - players[i] = new RealTwoPartyPlayer(*N[i%N.size()], 1 - my_num, (i+1) * 1000); + players[i] = new RealTwoPartyPlayer(*N[i % N.size()], 1 - my_num, + "thread" + to_string(i)); tinfos[i].thread_num = i+1; tinfos[i].other_player_num = 1 - my_num; tinfos[i].nOTs = nOTs; diff --git a/Machines/TripleMachine.cpp b/Machines/TripleMachine.cpp index 5bb4a6e98..b3044d5a4 100644 --- a/Machines/TripleMachine.cpp +++ b/Machines/TripleMachine.cpp @@ -138,7 +138,10 @@ TripleMachine::TripleMachine(int argc, const char** argv) : opt.get("-S")->getInt(z2s); // doesn't work with Montgomery multiplication - gfpvar1::init_field(prime, false); + if (prime) + gfpvar1::init_field(prime, false); + else + gfpvar1::init_default(128, false); gf2n_long::init_field(128); gf2n_short::init_field(40); @@ -175,7 +178,7 @@ void TripleMachine::run() nConnections = 2; } // do the base OTs - PlainPlayer P(N[0], 0xF000); + PlainPlayer P(N[0], "base"); OTTripleSetup setup(P, true); vector generators(nthreads); diff --git a/Machines/hemi-party.cpp b/Machines/hemi-party.cpp index 1eea61360..471862dab 100644 --- a/Machines/hemi-party.cpp +++ b/Machines/hemi-party.cpp @@ -4,6 +4,7 @@ */ #include "Protocols/HemiShare.h" +#include "Protocols/HemiOptions.h" #include "Math/gfp.h" #include "Math/gf2n.h" #include "FHE/P2Data.h" @@ -22,6 +23,7 @@ #include "Protocols/MAC_Check.hpp" #include "Protocols/SemiMC.hpp" #include "Protocols/Beaver.hpp" +#include "Protocols/Hemi.hpp" #include "GC/ShareSecret.hpp" #include "GC/SemiHonestRepPrep.h" #include "Math/gfp.hpp" @@ -29,6 +31,7 @@ int main(int argc, const char** argv) { ez::ezOptionParser opt; + HemiOptions::singleton = {opt, argc, argv}; DishonestMajorityFieldMachine(argc, argv, opt); } diff --git a/Makefile b/Makefile index 930897ac5..fcfbee413 100644 --- a/Makefile +++ b/Makefile @@ -9,7 +9,7 @@ NETWORK = $(patsubst %.cpp,%.o,$(wildcard Networking/*.cpp)) PROCESSOR = $(patsubst %.cpp,%.o,$(wildcard Processor/*.cpp)) -FHEOFFLINE = $(patsubst %.cpp,%.o,$(wildcard FHEOffline/*.cpp FHE/*.cpp)) Protocols/CowGearOptions.o +FHEOBJS = $(patsubst %.cpp,%.o,$(wildcard FHEOffline/*.cpp FHE/*.cpp)) Protocols/CowGearOptions.o GC = $(patsubst %.cpp,%.o,$(wildcard GC/*.cpp)) $(PROCESSOR) GC_SEMI = GC/SemiSecret.o GC/SemiPrep.o GC/square64.o @@ -17,32 +17,38 @@ GC_SEMI = GC/SemiSecret.o GC/SemiPrep.o GC/square64.o OT = $(patsubst %.cpp,%.o,$(wildcard OT/*.cpp)) OT_EXE = ot.x ot-offline.x -COMMON = $(MATH) $(TOOLS) $(NETWORK) GC/square64.o Processor/OnlineOptions.o Processor/BaseMachine.o Processor/DataPositions.o Processor/ThreadQueues.o Processor/ThreadQueue.o +COMMONOBJS = $(MATH) $(TOOLS) $(NETWORK) GC/square64.o Processor/OnlineOptions.o Processor/BaseMachine.o Processor/DataPositions.o Processor/ThreadQueues.o Processor/ThreadQueue.o COMPLETE = $(COMMON) $(PROCESSOR) $(FHEOFFLINE) $(TINYOTOFFLINE) $(GC) $(OT) YAO = $(patsubst %.cpp,%.o,$(wildcard Yao/*.cpp)) $(OT) BMR/Key.o BMR = $(patsubst %.cpp,%.o,$(wildcard BMR/*.cpp BMR/network/*.cpp)) -VM = $(PROCESSOR) $(COMMON) GC/square64.o GC/Instruction.o OT/OTTripleSetup.o OT/BaseOT.o $(LIBSIMPLEOT) +MINI_OT = OT/OTTripleSetup.o OT/BaseOT.o $(LIBSIMPLEOT) +VMOBJS = $(PROCESSOR) $(COMMONOBJS) GC/square64.o GC/Instruction.o OT/OTTripleSetup.o OT/BaseOT.o $(LIBSIMPLEOT) +VM = $(MINI_OT) $(SHAREDLIB) +COMMON = $(SHAREDLIB) LIB = libSPDZ.a +SHAREDLIB = libSPDZ.so +FHEOFFLINE = libFHE.so LIBRELEASE = librelease.a ifeq ($(AVX_OT), 0) VM += ECDSA/P256Element.o OT += ECDSA/P256Element.o +MINI_OT += ECDSA/P256Element.o else LIBSIMPLEOT = SimpleOT/libsimpleot.a endif # used for dependency generation -OBJS = $(BMR) $(FHEOFFLINE) $(TINYOTOFFLINE) $(YAO) $(COMPLETE) $(patsubst %.cpp,%.o,$(wildcard Machines/*.cpp Utils/*.cpp)) +OBJS = $(BMR) $(FHEOBJS) $(TINYOTOFFLINE) $(YAO) $(COMPLETE) $(patsubst %.cpp,%.o,$(wildcard Machines/*.cpp Utils/*.cpp)) DEPS := $(wildcard */*.d */*/*.d) # never delete -.SECONDARY: $(OBJS) +.SECONDARY: $(OBJS) $(patsubst %.cpp,%.o,$(wildcard */*.cpp)) -all: arithmetic binary gen_input online offline externalIO bmr ecdsa doc +all: arithmetic binary gen_input online offline externalIO bmr ecdsa vm: arithmetic binary .PHONY: doc @@ -112,13 +118,22 @@ sy: sy-rep-field-party.x sy-rep-ring-party.x sy-shamir-party.x ecdsa: $(patsubst ECDSA/%.cpp,%.x,$(wildcard ECDSA/*-ecdsa-party.cpp)) Fake-ECDSA.x ecdsa-static: static-dir $(patsubst ECDSA/%.cpp,static/%.x,$(wildcard ECDSA/*-ecdsa-party.cpp)) -$(LIBRELEASE): Protocols/MalRepRingOptions.o $(PROCESSOR) $(COMMON) $(OT) $(GC) +$(LIBRELEASE): Protocols/MalRepRingOptions.o $(PROCESSOR) $(COMMONOBJS) $(OT) $(GC) $(AR) -csr $@ $^ +CFLAGS += -fPIC +LDLIBS += -Wl,-rpath -Wl,$(CURDIR) + +$(SHAREDLIB): $(PROCESSOR) $(COMMONOBJS) GC/square64.o GC/Instruction.o + $(CXX) $(CFLAGS) -shared -o $@ $^ $(LDLIBS) + +$(FHEOFFLINE): $(FHEOBJS) $(SHAREDLIB) + $(CXX) $(CFLAGS) -shared -o $@ $^ $(LDLIBS) + static/%.x: Machines/%.o $(LIBRELEASE) $(LIBSIMPLEOT) $(CXX) $(CFLAGS) -o $@ $^ -Wl,-Map=$<.map -Wl,-Bstatic -static-libgcc -static-libstdc++ $(LIBRELEASE) $(LIBSIMPLEOT) $(BOOST) $(LDLIBS) -Wl,-Bdynamic -ldl -static/%.x: ECDSA/%.o ECDSA/P256Element.o $(VM) $(OT) $(LIBSIMPLEOT) +static/%.x: ECDSA/%.o ECDSA/P256Element.o $(VMOBJS) $(OT) $(LIBSIMPLEOT) $(CXX) $(CFLAGS) -o $@ $^ -Wl,-Map=$<.map -Wl,-Bstatic -static-libgcc -static-libstdc++ $(BOOST) $(LDLIBS) -Wl,-Bdynamic -ldl static-dir: @@ -141,7 +156,7 @@ gc-emulate.x: $(VM) GC/FakeSecret.o GC/square64.o bmr-%.x: $(BMR) $(VM) Machines/bmr-%.cpp $(LIBSIMPLEOT) $(CXX) -o $@ $(CFLAGS) $^ $(BOOST) $(LDLIBS) -%-bmr-party.x: Machines/%-bmr-party.o $(BMR) $(VM) $(LIBSIMPLEOT) +%-bmr-party.x: Machines/%-bmr-party.o $(BMR) $(SHAREDLIB) $(MINI_OT) $(CXX) -o $@ $(CFLAGS) $^ $(BOOST) $(LDLIBS) bmr-clean: @@ -170,14 +185,14 @@ default-prime-length.x: Utils/default-prime-length.o secure.x: Utils/secure.o $(CXX) -o $@ $(CFLAGS) $^ -%.x: Utils/%.o $(COMMON) +Fake-Offline.x: Utils/Fake-Offline.o $(VM) $(CXX) -o $@ $(CFLAGS) $^ $(LDLIBS) -%.x: Machines/%.o $(VM) OT/OTTripleSetup.o OT/BaseOT.o $(LIBSIMPLEOT) +%.x: Utils/%.o $(COMMON) $(CXX) -o $@ $(CFLAGS) $^ $(LDLIBS) -%gear-party.x: Machines/%gear-party.o $(VM) OT/OTTripleSetup.o OT/BaseOT.o $(LIBSIMPLEOT) - $(CXX) -o $@ $(CFLAGS) $^ $(LDLIBS) -lntl +%.x: Machines/%.o $(MINI_OT) $(SHAREDLIB) + $(CXX) -o $@ $(CFLAGS) $^ $(LDLIBS) %-ecdsa-party.x: ECDSA/%-ecdsa-party.o ECDSA/P256Element.o $(VM) $(CXX) -o $@ $(CFLAGS) $^ $(LDLIBS) @@ -188,7 +203,7 @@ replicated-field-party.x: GC/square64.o brain-party.x: GC/square64.o malicious-rep-bin-party.x: GC/square64.o ps-rep-bin-party.x: GC/PostSacriBin.o -semi-bin-party.x: $(VM) $(OT) GC/SemiSecret.o GC/SemiPrep.o GC/square64.o +semi-bin-party.x: $(OT) GC/SemiSecret.o GC/SemiPrep.o GC/square64.o tiny-party.x: $(OT) tinier-party.x: $(OT) spdz2k-party.x: $(OT) $(patsubst %.cpp,%.o,$(wildcard Machines/SPDZ2*.cpp)) @@ -202,12 +217,12 @@ chaigear-party.x: $(FHEOFFLINE) Protocols/CowGearOptions.o $(OT) lowgear-party.x: $(FHEOFFLINE) $(OT) Protocols/CowGearOptions.o Protocols/LowGearKeyGen.o highgear-party.x: $(FHEOFFLINE) $(OT) Protocols/CowGearOptions.o Protocols/HighGearKeyGen.o atlas-party.x: GC/AtlasSecret.o -static/hemi-party.x: $(FHEOFFLINE) -static/soho-party.x: $(FHEOFFLINE) -static/cowgear-party.x: $(FHEOFFLINE) -static/chaigear-party.x: $(FHEOFFLINE) -static/lowgear-party.x: $(FHEOFFLINE) Protocols/CowGearOptions.o Protocols/LowGearKeyGen.o -static/highgear-party.x: $(FHEOFFLINE) Protocols/CowGearOptions.o Protocols/HighGearKeyGen.o +static/hemi-party.x: $(FHEOBJS) +static/soho-party.x: $(FHEOBJS) +static/cowgear-party.x: $(FHEOBJS) +static/chaigear-party.x: $(FHEOBJS) +static/lowgear-party.x: $(FHEOBJS) Protocols/CowGearOptions.o Protocols/LowGearKeyGen.o +static/highgear-party.x: $(FHEOBJS) Protocols/CowGearOptions.o Protocols/HighGearKeyGen.o mascot-party.x: Machines/SPDZ.o $(OT) static/mascot-party.x: Machines/SPDZ.o Player-Online.x: Machines/SPDZ.o $(OT) @@ -226,7 +241,6 @@ real-bmr-party.x: $(OT) paper-example.x: $(VM) $(OT) $(FHEOFFLINE) mascot-offline.x: $(VM) $(OT) cowgear-offline.x: $(OT) $(FHEOFFLINE) -Fake-Offline.x: $(VM) static/rep-bmr-party.x: $(BMR) static/mal-rep-bmr-party.x: $(BMR) static/shamir-bmr-party.x: $(BMR) @@ -269,7 +283,7 @@ mpir: mpir-setup ./configure --enable-cxx --prefix=$(CURDIR)/local $(MAKE) -C mpir install -echo MY_CFLAGS += -I./local/include >> CONFIG.mine - -echo MY_LDLIBS += -Wl,-rpath -Wl,./local/lib -L./local/lib >> CONFIG.mine + -echo MY_LDLIBS += -Wl,-rpath -Wl,$(CURDIR)/local/lib -L$(CURDIR)/local/lib >> CONFIG.mine mac-setup: brew install openssl boost libsodium mpir yasm ntl @@ -281,4 +295,4 @@ simde/simde: git submodule update --init simde clean: - -rm -f */*.o *.o */*.d *.d *.x core.* *.a gmon.out */*/*.o static/*.x + -rm -f */*.o *.o */*.d *.d *.x core.* *.a gmon.out */*/*.o static/*.x *.so diff --git a/Math/BitVec.h b/Math/BitVec.h index fd25e1341..f9e874d14 100644 --- a/Math/BitVec.h +++ b/Math/BitVec.h @@ -52,21 +52,19 @@ class BitVec_ : public IntBase BitVec_& operator-=(const BitVec_& other) { *this ^= other; return *this; } BitVec_ extend_bit() const { return -(this->a & 1); } - BitVec_ mask(int n) const { return n < n_bits ? *this & ((1L << n) - 1) : *this; } void extend_bit(BitVec_& res, int) const { res = extend_bit(); } - void mask(BitVec_& res, int n) const { res = mask(n); } void add(octetStream& os) { *this += os.get(); } void mul(const BitVec_& a, const BitVec_& b) { *this = a * b; } - void randomize(PRNG& G, int n = n_bits) { super::randomize(G); *this = mask(n); } + void randomize(PRNG& G, int n = n_bits) { super::randomize(G); *this = this->mask(n); } void pack(octetStream& os) const { os.store_int(this->a); } void unpack(octetStream& os) { this->a = os.get_int(); } - void pack(octetStream& os, int n) const { os.store_int(mask(n).a, DIV_CEIL(n, 8)); } + void pack(octetStream& os, int n) const { os.store_int(super::mask(n).get(), DIV_CEIL(n, 8)); } void unpack(octetStream& os, int n) { this->a = os.get_int(DIV_CEIL(n, 8)); } static BitVec_ unpack_new(octetStream& os, int n = n_bits) diff --git a/Math/Integer.h b/Math/Integer.h index 59395fff4..5308e6537 100644 --- a/Math/Integer.h +++ b/Math/Integer.h @@ -85,6 +85,9 @@ class IntBase : public ValueInterface T& operator^=(const IntBase& other) { return a ^= other.a; } T& operator&=(const IntBase& other) { return a &= other.a; } + IntBase mask(int n) const { return n < N_BITS ? *this & ((1L << n) - 1) : *this; } + void mask(IntBase& res, int n) const { res = mask(n); } + friend ostream& operator<<(ostream& s, const IntBase& x) { x.output(s, true); return s; } friend istream& operator>>(istream& s, IntBase& x) { x.input(s, true); return s; } diff --git a/Math/Z2k.hpp b/Math/Z2k.hpp index 6734d2ccf..876aef939 100644 --- a/Math/Z2k.hpp +++ b/Math/Z2k.hpp @@ -30,7 +30,8 @@ void Z2::reqbl(int n) } else if (n > 0) { - throw Processor_Error("Program compiled for fields not rings"); + throw Processor_Error("Program compiled for fields not rings, " + "run compile.py with '-R " + to_string(K) + "'"); } } diff --git a/Math/Zp_Data.h b/Math/Zp_Data.h index 335ac9d7f..96deb7951 100644 --- a/Math/Zp_Data.h +++ b/Math/Zp_Data.h @@ -133,7 +133,7 @@ inline void Zp_Data::Add<1>(mp_limb_t* ans,const mp_limb_t* x,const mp_limb_t* y #else *ans = *x + *y; asm goto ("jc %l[sub]" :::: sub); - if (*ans >= *prA) + if (mpn_cmp(ans, prA, 1) >= 0) sub: *ans -= *prA; #endif @@ -251,13 +251,17 @@ inline void Zp_Data::Mont_Mult(mp_limb_t* z,const mp_limb_t* x,const mp_limb_t* break; CASE(1) CASE(2) -#if MAX_MOD_SZ >= 5 +#if MAX_MOD_SZ >= 4 CASE(3) CASE(4) +#endif +#if MAX_MOD_SZ >= 5 CASE(5) #endif -#if MAX_MOD_SZ >= 10 +#if MAX_MOD_SZ >= 6 CASE(6) +#endif +#if MAX_MOD_SZ >= 10 CASE(7) CASE(8) CASE(9) diff --git a/Math/gfp.hpp b/Math/gfp.hpp index acc587007..33ea972cc 100644 --- a/Math/gfp.hpp +++ b/Math/gfp.hpp @@ -166,7 +166,8 @@ void gfp_::reqbl(int n) } else if ((int)n < 0) { - throw Processor_Error("Program compiled for rings not fields"); + throw Processor_Error("Program compiled for rings not fields, " + "run compile.py without -R"); } } diff --git a/Networking/CryptoPlayer.cpp b/Networking/CryptoPlayer.cpp index aee390bbf..3794b69e9 100644 --- a/Networking/CryptoPlayer.cpp +++ b/Networking/CryptoPlayer.cpp @@ -26,9 +26,9 @@ void ssl_error(string side, string pronoun, string other, string server) << "with `Scripts/setup-ssl.sh` expire after a month." << endl; } -CryptoPlayer::CryptoPlayer(const Names& Nms, int id_base) : - MultiPlayer(Nms, id_base), plaintext_player(Nms, id_base), - other_player(Nms, id_base + Nms.num_players()), +CryptoPlayer::CryptoPlayer(const Names& Nms, const string& id_base) : + MultiPlayer(Nms), plaintext_player(Nms, id_base), + other_player(Nms, id_base + "recv"), ctx("P" + to_string(my_num())) { sockets.resize(num_players()); @@ -57,6 +57,11 @@ CryptoPlayer::CryptoPlayer(const Names& Nms, int id_base) : } } +CryptoPlayer::CryptoPlayer(const Names& Nms, int id_base) : + CryptoPlayer(Nms, to_string(id_base)) +{ +} + CryptoPlayer::~CryptoPlayer() { close_client_socket(plaintext_player.socket(my_num())); @@ -124,13 +129,6 @@ void CryptoPlayer::pass_around_no_stats(const octetStream& to_send, } } -template<> -void MultiPlayer::setup_sockets(const vector& names, - const vector& ports, int id_base, ServerSocket& server) -{ - (void)names, (void)ports, (void)id_base, (void)server; -} - void CryptoPlayer::send_receive_all_no_stats(const vector>& channels, const vector& to_send, vector& to_receive) const diff --git a/Networking/CryptoPlayer.h b/Networking/CryptoPlayer.h index ec488a38a..d3bf80bfe 100644 --- a/Networking/CryptoPlayer.h +++ b/Networking/CryptoPlayer.h @@ -12,6 +12,12 @@ #include #include +/** + * Encrypted multi-party communication. + * Uses OpenSSL and certificates issued to "P". + * Sending and receiving is done in separate threads to allow + * for bidirectional communication. + */ class CryptoPlayer : public MultiPlayer { PlainPlayer plaintext_player, other_player; @@ -24,7 +30,14 @@ class CryptoPlayer : public MultiPlayer vector*> receivers; public: - CryptoPlayer(const Names& Nms, int id_base=0); + /** + * Start a new set of encrypted connections. + * @param Nms network setup + * @param id unique identifier + */ + CryptoPlayer(const Names& Nms, const string& id); + // legacy interface + CryptoPlayer(const Names& Nms, int id_base = 0); ~CryptoPlayer(); bool is_encrypted() { return true; } diff --git a/Networking/Player.cpp b/Networking/Player.cpp index 51a143123..61c8fd65c 100644 --- a/Networking/Player.cpp +++ b/Networking/Player.cpp @@ -22,24 +22,20 @@ void Names::init(int player,int pnb,int my_port,const char* servername) setup_server(); } -void Names::init(int player,int pnb,vector Nms) +Names::Names(int player, int nplayers, const string& servername, int pnb, + int my_port) : + Names() { - vector names; - for (auto& name : Nms) - names.push_back((octet*)name.c_str()); - init(player, pnb, names); + Server::start_networking(*this, player, nplayers, servername, pnb, my_port); } -void Names::init(int player,int pnb,vector Nms) +void Names::init(int player,int pnb,vector Nms) { player_no=player; portnum_base=pnb; nplayers=Nms.size(); - names.resize(nplayers); + names=Nms; setup_ports(); - for (int i=0; i -MultiPlayer::MultiPlayer(const Names& Nms, int id) : +MultiPlayer::MultiPlayer(const Names& Nms) : Player(Nms), send_to_self_socket(0) +{ + sockets.resize(Nms.num_players()); +} + + +PlainPlayer::PlainPlayer(const Names& Nms, const string& id) : + MultiPlayer(Nms) { if (Nms.num_players() > 1) setup_sockets(Nms.names, Nms.ports, id, *Nms.server); - else - sockets.resize(Nms.num_players()); } -template<> -MultiPlayer::~MultiPlayer() +PlainPlayer::PlainPlayer(const Names& Nms, int id_base) : + PlainPlayer(Nms, to_string(id_base)) +{ +} + +PlainPlayer::~PlainPlayer() { if (num_players() > 1) { @@ -258,33 +259,38 @@ PlayerBase::~PlayerBase() // Set up nmachines client and server sockets to send data back and fro // A machine is a server between it and player i if i<=my_number // Can also communicate with myself, but only with send_to and receive_from -template<> -void MultiPlayer::setup_sockets(const vector& names,const vector& ports,int id_base,ServerSocket& server) +void PlainPlayer::setup_sockets(const vector& names, + const vector& ports, const string& id_base, ServerSocket& server) { sockets.resize(nplayers); // Set up the client side for (int i=player_no; i>& channels, send_receive_all_no_stats(channels, to_send, to_receive); } -void Player::partial_broadcast(const vector& senders, - vector& os) const +void Player::partial_broadcast(const vector&, + const vector&, vector& os) const { - partial_broadcast(senders, vector(num_players(), senders[my_num()]), - os); + unchecked_broadcast(os); } template @@ -570,7 +575,8 @@ void MultiPlayer::send_receive_all_no_stats( } -ThreadPlayer::ThreadPlayer(const Names& Nms, int id_base) : PlainPlayer(Nms, id_base) +ThreadPlayer::ThreadPlayer(const Names& Nms, const string& id_base) : + PlainPlayer(Nms, id_base) { for (int i = 0; i < Nms.num_players(); i++) { @@ -625,35 +631,41 @@ void ThreadPlayer::send_all(const octetStream& o) const } -RealTwoPartyPlayer::RealTwoPartyPlayer(const Names& Nms, int other_player, int id) : +RealTwoPartyPlayer::RealTwoPartyPlayer(const Names& Nms, int other_player, const string& id) : TwoPartyPlayer(Nms.my_num()), other_player(other_player) { is_server = Nms.my_num() > other_player; setup_sockets(other_player, Nms, Nms.ports[other_player], id); } +RealTwoPartyPlayer::RealTwoPartyPlayer(const Names& Nms, int other_player, + int id_base) : RealTwoPartyPlayer(Nms, other_player, to_string(id_base)) +{ +} + RealTwoPartyPlayer::~RealTwoPartyPlayer() { close_client_socket(socket); } -void RealTwoPartyPlayer::setup_sockets(int other_player, const Names &nms, int portNum, int id) +void RealTwoPartyPlayer::setup_sockets(int other_player, const Names &nms, int portNum, string id) { - id += 0xF << 28; + id += "2"; const char *hostname = nms.names[other_player].c_str(); ServerSocket *server = nms.server; if (is_server) { #ifdef DEBUG_NETWORKING - fprintf(stderr, "Setting up server with id %d\n",id); + fprintf(stderr, "Setting up server with id %s\n", id.c_str()); #endif socket = server->get_connection_socket(id); } else { #ifdef DEBUG_NETWORKING - fprintf(stderr, "Setting up client to %s:%d with id %d\n", hostname, portNum, id); + fprintf(stderr, "Setting up client to %s:%d with id %s\n", hostname, + portNum, id.c_str()); #endif set_up_client_socket(socket, hostname, portNum); - ::send(socket, (unsigned char*)&id, sizeof(id)); + octetStream(id).Send(socket); } } @@ -739,6 +751,10 @@ void TwoPartyPlayer::Broadcast_Receive(vector& o) const o[1 - my_num()] = os[1]; } +NamedCommStats::NamedCommStats() : sent(0) +{ +} + CommStats& CommStats::operator +=(const CommStats& other) { data += other.data; @@ -749,6 +765,7 @@ CommStats& CommStats::operator +=(const CommStats& other) NamedCommStats& NamedCommStats::operator +=(const NamedCommStats& other) { + sent += other.sent; for (auto it = other.begin(); it != other.end(); it++) (*this)[it->first] += it->second; return *this; @@ -772,6 +789,7 @@ CommStats& CommStats::operator -=(const CommStats& other) NamedCommStats NamedCommStats::operator -(const NamedCommStats& other) const { NamedCommStats res = *this; + res.sent = sent - other.sent; for (auto it = other.begin(); it != other.end(); it++) res[it->first] -= it->second; return res; diff --git a/Networking/Player.h b/Networking/Player.h index 4b6cab4ab..668c097a7 100644 --- a/Networking/Player.h +++ b/Networking/Player.h @@ -27,15 +27,22 @@ template class MultiPlayer; class Server; class ServerSocket; -/* Class to get the names off the server */ +/** + * Network setup (hostnames and port numbers) + */ class Names { + friend class Player; + friend class PlainPlayer; + friend class RealTwoPartyPlayer; + vector names; vector ports; int nplayers; int portnum_base; int player_no; - Server* global_server; + + ServerSocket* server; int default_port(int playerno) { return portnum_base + playerno; } void setup_ports(); @@ -48,28 +55,63 @@ class Names static const int DEFAULT_PORT = -1; - mutable ServerSocket* server; - + /** + * Initialize with central server + * @param player my number + * @param pnb base port number (server listens one below) + * @param my_port my port number (`DEFAULT_PORT` for default, + * which is base port number plus player number) + * @param servername location of server + */ void init(int player,int pnb,int my_port,const char* servername); Names(int player,int pnb,int my_port,const char* servername) : Names() { init(player,pnb,my_port,servername); } - // Set up names when we KNOW who we are going to be using before hand - void init(int player,int pnb,vector Nms); - Names(int player,int pnb,vector Nms) : Names() - { init(player,pnb,Nms); } + + /** + * Initialize with central server running on player 0 + * @param player my number + * @param nplayers number of players + * @param servername location of player 0 + * @param pnb base port number + * @param my_port my port number (`DEFAULT_PORT` for default, + * which is base port number plus player number) + */ + Names(int player, int nplayers, const string& servername, int pnb, + int my_port = DEFAULT_PORT); + + /** + * Initialize without central server + * @param player my number + * @param pnb base port number + * @param Nms locations of all parties + */ void init(int player,int pnb,vector Nms); Names(int player,int pnb,vector Nms) : Names() { init(player,pnb,Nms); } - // nplayers = 0 for taking it from hostsfile + + /** + * Initialize from file. One party per line, format ``[:]`` + * @param player my number + * @param pnb base port number + * @param hostsfile filename + * @param players number of players (0 to take from file) + */ void init(int player, int pnb, const string& hostsfile, int players = 0); Names(int player, int pnb, const string& hostsfile) : Names() { init(player, pnb, hostsfile); } - // initialize from command-line options + /** + * Initialize from command-line options + * @param opt option parser instance + * @param argc number of command-line arguments + * @param argv command-line arguments + * @param default_nplayers default number of players + * (used if not given in arguments) + */ Names(ez::ezOptionParser& opt, int argc, const char** argv, int default_nplayers = 2); - Names() : nplayers(1), portnum_base(-1), player_no(0), global_server(0), server(0) { ; } + Names() : nplayers(1), portnum_base(-1), player_no(0), server(0) { ; } Names(const Names& other); ~Names(); @@ -77,11 +119,6 @@ class Names int my_num() const { return player_no; } const string get_name(int i) const { return names[i]; } int get_portnum_base() const { return portnum_base; } - - friend class PlayerBase; - friend class Player; - template friend class MultiPlayer; - friend class RealTwoPartyPlayer; }; @@ -108,6 +145,10 @@ struct CommStats class NamedCommStats : public map { public: + size_t sent; + + NamedCommStats(); + NamedCommStats& operator+=(const NamedCommStats& other); NamedCommStats operator+(const NamedCommStats& other) const; NamedCommStats operator-(const NamedCommStats& other) const; @@ -123,17 +164,20 @@ class NamedCommStats : public map #endif }; +/** + * Abstract class for two- and multi-player communication + */ class PlayerBase { protected: int player_no; public: - mutable size_t sent; + size_t& sent; mutable Timer timer; mutable NamedCommStats comm_stats; - PlayerBase(int player_no) : player_no(player_no), sent(0) {} + PlayerBase(int player_no) : player_no(player_no), sent(comm_stats.sent) {} virtual ~PlayerBase(); int my_real_num() const { return player_no; } @@ -146,6 +190,11 @@ class PlayerBase { Broadcast_Receive(o); } }; +/** + * Abstract class for multi-player communication. + * ``*_no_stats`` functions are called by their equivalents + * after accounting for communications statistics. + */ class Player : public PlayerBase { protected: @@ -159,7 +208,13 @@ class Player : public PlayerBase Player(const Names& Nms); virtual ~Player(); + /** + * Get number of players + */ int num_players() const { return nplayers; } + /** + * Get my player number + */ int my_num() const { return player_no; } int get_offset(int other_player) const { return positive_modulo(other_player - my_num(), num_players()); } @@ -173,61 +228,115 @@ class Player : public PlayerBase // The following functions generally update the statistics // and then call the *_no_stats equivalent specified by a subclass. - // send the same to all other players + /** + * Send the same to all other players + */ virtual void send_all(const octetStream& o) const; - // send to a specific player + /** + * Send to a specific player + */ void send_to(int player,const octetStream& o) const; virtual void send_to_no_stats(int player,const octetStream& o) const = 0; - // receive from all other players + /** + * Receive from all other players. + * Information from player 0 at ``os[0]`` etc. + */ void receive_all(vector& os) const; - // receive from a specific player + /** + * Receive from a specific player + */ void receive_player(int i,octetStream& o) const; virtual void receive_player_no_stats(int i,octetStream& o) const = 0; virtual void receive_player(int i,FlexBuffer& buffer) const; - // Communication relative to my number - // send to all other players by offset + /** + * Send to all other players by offset. + * ``o[0]`` gets sent to the next player etc. + */ void send_relative(const vector& o) const; - // send to other player specified by offset + /* + * Send to other player specified by offset. + * 1 stands for the next player etc. + */ void send_relative(int offset, const octetStream& o) const; - // receive from all other players by offset + /** + * Receive from all other players by offset. + * ``o[0]`` will contain data from the next player etc. + */ void receive_relative(vector& o) const; - // receive from other palyer specified by offset + /** + * Receive from other player specified by offset. + * 1 stands for the next player etc. + */ void receive_relative(int offset, octetStream& o) const; - // exchange data with minimal memory usage - // exchange information with one other party + /** + * Exchange information with one other party, + * reusing the buffer if possible. + */ void exchange(int other, const octetStream& to_send, octetStream& ot_receive) const; virtual void exchange_no_stats(int other, const octetStream& to_send, octetStream& ot_receive) const = 0; + /** + * Exchange information with one other party, reusing the buffer. + */ void exchange(int other, octetStream& o) const; - // exchange with one other partiy specified by offset + /** + * Exchange information with one other party specified by offset, + * reusing the buffer if possible. + */ void exchange_relative(int offset, octetStream& o) const; - // send information to party while receiving from another by offset + /** + * Send information to a party while receiving from another by offset, + * The default is to send to the next party while receiving from the previous. + * The buffer is reused. + */ void pass_around(octetStream& o, int offset = 1) const { pass_around(o, o, offset); } + /** + * Send information to a party while receiving from another by offset. + * The default is to send to the next party while receiving from the previous. + */ void pass_around(octetStream& to_send, octetStream& to_receive, int offset) const; virtual void pass_around_no_stats(const octetStream& to_send, octetStream& to_receive, int offset) const = 0; - /* Broadcast and Receive data to/from all players - * - Assumes o[player_no] contains the thing broadcast by me + /** + * Broadcast and receive data to/from all players. + * Assumes o[player_no] contains the data to be broadcast by me. */ virtual void unchecked_broadcast(vector& o) const; - // broadcast with eventual verification + /** + * Broadcast and receive data to/from all players with eventual verification. + * Assumes o[player_no] contains the data to be broadcast by me. + */ virtual void Broadcast_Receive(vector& o) const; virtual void Broadcast_Receive_no_stats(vector& o) const = 0; - /* Run Protocol To Verify Broadcast Is Correct - * - Resets the blk_SHA_CTX at the same time + /** + * Run protocol to verify broadcast is correct */ virtual void Check_Broadcast() const; - // send something different to all + /** + * Send something different to each player. + */ void send_receive_all(const vector& to_send, vector& to_receive) const; - // specified senders only send something different to all + /** + * Specified senders only send something different to each player. + * @param senders set whether a player sends or not, + * must be equal on all players + * @param to_send data to send by player number + * @param to_receive received data by player number + */ void send_receive_all(const vector& senders, const vector& to_send, vector& to_receive) const; - // send something different only one specified channels + /** + * Send something different only one specified channels. + * @param channels ``channel[i][j]`` indicates whether party ``i`` sends + * to party ``j`` + * @param to_send data to send by player number + * @param to_receive received data by player number + */ void send_receive_all(const vector>& channels, const vector& to_send, vector& to_receive) const; @@ -235,11 +344,15 @@ class Player : public PlayerBase const vector& to_send, vector& to_receive) const = 0; - // specified senders broadcast information + /** + * Specified senders broadcast information to specified receivers. + * @param senders specify which parties do send + * @param receivers specify which parties do send + * @param os data to send at ``os[my_number]``, received data elsewhere + */ virtual void partial_broadcast(const vector& senders, + const vector& receivers, vector& os) const; - virtual void partial_broadcast(const vector&, const vector&, - vector& os) const { unchecked_broadcast(os); } // dummy functions for compatibility virtual void request_receive(int i, octetStream& o) const { (void)i; (void)o; } @@ -247,6 +360,11 @@ class Player : public PlayerBase { receive_player(i, o); } }; +/** + * Multi-player communication helper class. + * ``T = int`` for unencrypted BSD sockets and + * ``T = ssl_socket*`` for Boost SSL streams. + */ template class MultiPlayer : public Player { @@ -254,16 +372,12 @@ class MultiPlayer : public Player vector sockets; T send_to_self_socket; - void setup_sockets(const vector& names,const vector& ports,int id_base,ServerSocket& server); - T socket_to_send(int player) const { return player == player_no ? send_to_self_socket : sockets[player]; } friend class CryptoPlayer; public: - // The offset is used for the multi-threaded call, to ensure different - // portnum bases in each thread - MultiPlayer(const Names& Nms,int id_base=0); + MultiPlayer(const Names& Nms); virtual ~MultiPlayer(); @@ -296,16 +410,34 @@ class MultiPlayer : public Player vector& to_receive) const; }; -typedef MultiPlayer PlainPlayer; +/** + * Plaintext multi-player communication + */ +class PlainPlayer : public MultiPlayer +{ + void setup_sockets(const vector& names, const vector& ports, + const string& id_base, ServerSocket& server); + +public: + /** + * Start a new set of unencrypted connections. + * @param Nms network setup + * @param id unique identifier + */ + PlainPlayer(const Names& Nms, const string& id); + // legacy interface + PlainPlayer(const Names& Nms, int id_base = 0); + ~PlainPlayer(); +}; -class ThreadPlayer : public MultiPlayer +class ThreadPlayer : public PlainPlayer { public: mutable vector*> receivers; mutable vector*> senders; - ThreadPlayer(const Names& Nms,int id_base=0); + ThreadPlayer(const Names& Nms, const string& id_base); virtual ~ThreadPlayer(); void request_receive(int i, octetStream& o) const; @@ -335,14 +467,16 @@ class RealTwoPartyPlayer : public TwoPartyPlayer { private: // setup sockets for comm. with only one other player - void setup_sockets(int other_player, const Names &nms, int portNum, int id); + void setup_sockets(int other_player, const Names &nms, int portNum, string id); int socket; bool is_server; int other_player; public: - RealTwoPartyPlayer(const Names& Nms, int other_player, int pn_offset=0); + RealTwoPartyPlayer(const Names& Nms, int other_player, const string& id); + // legacy + RealTwoPartyPlayer(const Names& Nms, int other_player, int id_base = 0); ~RealTwoPartyPlayer(); void send(octetStream& o) const; diff --git a/Networking/Server.cpp b/Networking/Server.cpp index e70cdcf5d..d9a056dd2 100644 --- a/Networking/Server.cpp +++ b/Networking/Server.cpp @@ -116,7 +116,7 @@ void Server::start() #ifdef DEBUG_NETWORKING cerr << "Waiting for player " << i << endl; #endif - socket_num[i] = server.get_connection_socket(i); + socket_num[i] = server.get_connection_socket("P" + to_string(i)); #ifdef DEBUG_NETWORKING cerr << "Connected to player " << i << endl; #endif diff --git a/Networking/ServerSocket.cpp b/Networking/ServerSocket.cpp index 7ca8060f7..d69fd7b8d 100644 --- a/Networking/ServerSocket.cpp +++ b/Networking/ServerSocket.cpp @@ -7,6 +7,7 @@ #include #include "Tools/Exceptions.h" #include "Tools/time-func.h" +#include "Tools/octetStream.h" #include #include @@ -53,7 +54,10 @@ ServerSocket::ServerSocket(int Portnum) : portnum(Portnum), thread(0) while (fl!=0 and timer.elapsed() < 600) { fl=::bind(main_socket, (struct sockaddr *)&serv, sizeof(struct sockaddr)); if (fl != 0) - { cerr << "Binding to socket on " << my_name << ":" << Portnum << " failed, trying again in a second ..." << endl; + { + cerr << "Binding to socket on " << my_name << ":" << Portnum + << " failed (" << strerror(errno) + << "), trying again in a second ..." << endl; sleep(1); } #ifdef DEBUG_NETWORKING @@ -109,11 +113,11 @@ ServerSocket::~ServerSocket() void ServerSocket::wait_for_client_id(int socket, struct sockaddr dest) { (void) dest; - int client_id; try { - receive(socket, (unsigned char*) &client_id, sizeof(client_id)); - process_connection(socket, client_id); + octetStream client_id; + client_id.Receive(socket); + process_connection(socket, client_id.str()); } catch (closed_connection&) { @@ -138,9 +142,13 @@ void ServerSocket::accept_clients() int consocket = accept(main_socket, (struct sockaddr *)&dest, (socklen_t*) &socksize); if (consocket<0) { error("set_up_socket:accept"); } - int client_id; - if (receive_all_or_nothing(consocket, (unsigned char*)&client_id, sizeof(client_id))) - process_connection(consocket, client_id); + octetStream client_id; + char buf[1]; + if (recv(consocket, buf, 1, MSG_PEEK | MSG_DONTWAIT) > 0) + { + client_id.Receive(consocket); + process_connection(consocket, client_id.str()); + } else { #ifdef DEBUG_NETWORKING @@ -162,7 +170,7 @@ void ServerSocket::accept_clients() } } -void ServerSocket::process_connection(int consocket, int client_id) +void ServerSocket::process_connection(int consocket, const string& client_id) { data_signal.lock(); #ifdef DEBUG_NETWORKING @@ -175,7 +183,7 @@ void ServerSocket::process_connection(int consocket, int client_id) data_signal.unlock(); } -int ServerSocket::get_connection_socket(int id) +int ServerSocket::get_connection_socket(const string& id) { data_signal.lock(); if (used.find(id) != used.end()) @@ -208,14 +216,14 @@ void AnonymousServerSocket::init() pthread_create(&thread, 0, anonymous_accept_thread, this); } -void AnonymousServerSocket::process_client(int client_id) +void AnonymousServerSocket::process_client(const string& client_id) { if (clients.find(client_id) != clients.end()) close_client_socket(clients[client_id]); client_connection_queue.push(client_id); } -int AnonymousServerSocket::get_connection_socket(int& client_id) +int AnonymousServerSocket::get_connection_socket(string& client_id) { data_signal.lock(); @@ -230,7 +238,7 @@ int AnonymousServerSocket::get_connection_socket(int& client_id) return client_socket; } -void AnonymousServerSocket::remove_client(int client_id) +void AnonymousServerSocket::remove_client(const string& client_id) { clients.erase(client_id); } diff --git a/Networking/ServerSocket.h b/Networking/ServerSocket.h index 02f7b6e97..6176df20e 100644 --- a/Networking/ServerSocket.h +++ b/Networking/ServerSocket.h @@ -9,6 +9,7 @@ #include #include #include +#include using namespace std; #include @@ -23,8 +24,8 @@ class ServerSocket { protected: int main_socket, portnum; - map clients; - std::set used; + map clients; + std::set used; Signal data_signal; pthread_t thread; @@ -33,9 +34,9 @@ class ServerSocket // disable copying ServerSocket(const ServerSocket& other); - void process_connection(int socket, int client_id); + void process_connection(int socket, const string& client_id); - virtual void process_client(int) {} + virtual void process_client(const string&) {} public: ServerSocket(int Portnum); @@ -47,9 +48,9 @@ class ServerSocket void wait_for_client_id(int socket, struct sockaddr dest); - // This depends on clients sending their id as int. + // This depends on clients sending their id. // Has to be thread-safe. - int get_connection_socket(int number); + int get_connection_socket(const string& id); }; /* @@ -59,9 +60,9 @@ class AnonymousServerSocket : public ServerSocket { private: // No. of accepted connections in this instance - queue client_connection_queue; + queue client_connection_queue; - void process_client(int client_id); + void process_client(const string& client_id); public: AnonymousServerSocket(int Portnum) : @@ -69,9 +70,9 @@ class AnonymousServerSocket : public ServerSocket void init(); // Get socket and id for the last client who connected - int get_connection_socket(int& client_id); + int get_connection_socket(string& client_id); - void remove_client(int client_id); + void remove_client(const string& client_id); }; #endif /* NETWORKING_SERVERSOCKET_H_ */ diff --git a/OT/NPartyTripleGenerator.h b/OT/NPartyTripleGenerator.h index 87c86d95e..d5981e713 100644 --- a/OT/NPartyTripleGenerator.h +++ b/OT/NPartyTripleGenerator.h @@ -116,7 +116,6 @@ class OTTripleGenerator : public GeneratorThread mac_key_type get_mac_key() const { return mac_key; } - size_t data_sent(); NamedCommStats comm_stats(); }; @@ -210,17 +209,6 @@ class Spdz2kTripleGenerator : public NPartyTripleGenerator void generateTriples(); }; -template -size_t OTTripleGenerator::data_sent() -{ - size_t res = 0; - if (parentPlayer != &globalPlayer) - res = globalPlayer.sent; - for (auto& player : players) - res += player->sent; - return res; -} - template NamedCommStats OTTripleGenerator::comm_stats() { diff --git a/OT/NPartyTripleGenerator.hpp b/OT/NPartyTripleGenerator.hpp index 6ccf7da30..bc799e4a0 100644 --- a/OT/NPartyTripleGenerator.hpp +++ b/OT/NPartyTripleGenerator.hpp @@ -72,7 +72,7 @@ OTTripleGenerator::OTTripleGenerator(const OTTripleSetup& setup, const Names& names, int thread_num, int _nTriples, int nloops, MascotParams& machine, mac_key_type mac_key, Player* parentPlayer) : globalPlayer(parentPlayer ? *parentPlayer : *new PlainPlayer(names, - - thread_num * names.num_players() * names.num_players())), + to_string(thread_num))), parentPlayer(parentPlayer), thread_num(thread_num), mac_key(mac_key), diff --git a/Processor/BaseMachine.cpp b/Processor/BaseMachine.cpp index 7a0385b9a..bc36a8606 100644 --- a/Processor/BaseMachine.cpp +++ b/Processor/BaseMachine.cpp @@ -4,6 +4,7 @@ */ #include "BaseMachine.h" +#include "OnlineOptions.h" #include "Math/Setup.h" #include @@ -90,10 +91,8 @@ void BaseMachine::load_schedule(const string& progname, bool load_bytecode) void BaseMachine::print_compiler() { -#ifdef VERBOSE - if (compiler.size() != 0) + if (compiler.size() != 0 and OnlineOptions::singleton.verbose) cerr << "Compiler: " << compiler << endl; -#endif } void BaseMachine::load_program(const string& threadname, const string& filename) diff --git a/Processor/Binary_File_IO.h b/Processor/Binary_File_IO.h index 11e05a23d..4e38cd161 100644 --- a/Processor/Binary_File_IO.h +++ b/Processor/Binary_File_IO.h @@ -20,6 +20,8 @@ class Binary_File_IO { public: + static string filename(int my_number); + /* * Append the buffer values as binary to the filename. * Throws file_error. diff --git a/Processor/Binary_File_IO.hpp b/Processor/Binary_File_IO.hpp index 65423f3ab..be1fb8fdb 100644 --- a/Processor/Binary_File_IO.hpp +++ b/Processor/Binary_File_IO.hpp @@ -6,6 +6,13 @@ * Intended for application specific file IO. */ +inline string Binary_File_IO::filename(int my_number) +{ + string dir = "Persistence"; + mkdir_p(dir.c_str()); + return dir + "/Transactions-P" + to_string(my_number) + ".data"; +} + template void Binary_File_IO::write_to_file(const string filename, const vector< T >& buffer) { diff --git a/Processor/Data_Files.h b/Processor/Data_Files.h index 36257af74..069a36223 100644 --- a/Processor/Data_Files.h +++ b/Processor/Data_Files.h @@ -58,6 +58,7 @@ class DataPositions vector< array > inputs; array, N_DATA_FIELD_TYPE> extended; map, long long> edabits; + map, long long> matmuls; DataPositions(int num_players = 0); DataPositions(const Player& P) : DataPositions(P.num_players()) {} @@ -126,7 +127,7 @@ class Preprocessing : public PrepBase virtual void prune() {} virtual void purge() {} - virtual size_t data_sent() { return 0; } + virtual size_t data_sent() { return comm_stats().sent; } virtual NamedCommStats comm_stats() { return {}; } virtual void get_three_no_count(Dtype dtype, T& a, T& b, T& c) = 0; @@ -288,7 +289,7 @@ class Data_Files void reset_usage() { usage.reset(); skipped.reset(); } - size_t data_sent() { return DataFp.data_sent() + DataF2.data_sent(); } + NamedCommStats comm_stats() { return DataFp.comm_stats() + DataF2.comm_stats(); } }; template inline diff --git a/Processor/ExternalClients.cpp b/Processor/ExternalClients.cpp index c147bfbd8..65bb4598c 100644 --- a/Processor/ExternalClients.cpp +++ b/Processor/ExternalClients.cpp @@ -46,7 +46,10 @@ int ExternalClients::get_client_connection(int portnum_base) } cerr << "Thread " << this_thread::get_id() << " found server." << endl; int client_id, socket; - socket = client_connection_servers[portnum_base]->get_connection_socket(client_id); + string client; + socket = client_connection_servers[portnum_base]->get_connection_socket( + client); + client_id = stoi(client); if (ctx == 0) ctx = new ssl_ctx("P" + to_string(get_party_num())); external_client_sockets[client_id] = new ssl_socket(io_service, *ctx, socket, @@ -63,7 +66,8 @@ void ExternalClients::close_connection(int client_id) throw runtime_error("client id not active: " + to_string(client_id)); delete external_client_sockets[client_id]; external_client_sockets.erase(client_id); - client_connection_servers[client_ports[client_id]]->remove_client(client_id); + client_connection_servers[client_ports[client_id]]->remove_client( + to_string(client_id)); } int ExternalClients::get_party_num() diff --git a/Processor/Instruction.h b/Processor/Instruction.h index 573429b0e..ad04cefc2 100644 --- a/Processor/Instruction.h +++ b/Processor/Instruction.h @@ -67,6 +67,7 @@ enum THRESHOLD = 0xE3, PLAYERID = 0xE4, USE_EDABIT = 0xE5, + USE_MATMUL = 0x1F, // Addition ADDC = 0x20, ADDS = 0x21, diff --git a/Processor/Instruction.hpp b/Processor/Instruction.hpp index 1dd4fcb8c..3d3db1fcf 100644 --- a/Processor/Instruction.hpp +++ b/Processor/Instruction.hpp @@ -92,9 +92,7 @@ void BaseInstruction::parse_operands(istream& s, int pos, int file_pos) case DIVINT: case CONDPRINTPLAIN: case INPUTMASKREG: - r[0]=get_int(s); - r[1]=get_int(s); - r[2]=get_int(s); + get_ints(r, s, 3); break; // instructions with 2 register operands case LDMCI: @@ -130,8 +128,7 @@ void BaseInstruction::parse_operands(istream& s, int pos, int file_pos) case DABIT: case SHUFFLE: case ACCEPTCLIENTCONNECTION: - r[0]=get_int(s); - r[1]=get_int(s); + get_ints(r, s, 2); break; // instructions with 1 register operand case BIT: @@ -157,6 +154,7 @@ void BaseInstruction::parse_operands(istream& s, int pos, int file_pos) case PLAYERID: case LISTEN: case CLOSECLIENTCONNECTION: + case CRASH: r[0]=get_int(s); break; // instructions with 2 registers + 1 integer operand @@ -205,8 +203,11 @@ void BaseInstruction::parse_operands(istream& s, int pos, int file_pos) case STOPPRIVATEOUTPUT: case GSTOPPRIVATEOUTPUT: case DIGESTC: - r[0]=get_int(s); - r[1]=get_int(s); + get_ints(r, s, 2); + n = get_int(s); + break; + case USE_MATMUL: + get_ints(r, s, 3); n = get_int(s); break; // instructions with 1 register + 1 integer operand @@ -254,7 +255,6 @@ void BaseInstruction::parse_operands(istream& s, int pos, int file_pos) break; // instructions with no operand case TIME: - case CRASH: case STARTGRIND: case STOPGRIND: case CHECK: @@ -320,8 +320,9 @@ void BaseInstruction::parse_operands(istream& s, int pos, int file_pos) case READSOCKETC: case READSOCKETS: case READSOCKETINT: - num_var_args = get_int(s) - 1; + num_var_args = get_int(s) - 2; r[0] = get_int(s); + n = get_int(s); get_vector(num_var_args, start, s); break; @@ -329,9 +330,10 @@ void BaseInstruction::parse_operands(istream& s, int pos, int file_pos) case WRITESOCKETC: case WRITESOCKETSHARE: case WRITESOCKETINT: - num_var_args = get_int(s) - 2; + num_var_args = get_int(s) - 3; r[0] = get_int(s); r[1] = get_int(s); + n = get_int(s); get_vector(num_var_args, start, s); break; case WRITESOCKETS: @@ -383,9 +385,7 @@ void BaseInstruction::parse_operands(istream& s, int pos, int file_pos) // subtract extra argument num_var_args = get_int(s) - 1; s.read((char*)r, sizeof(r)); - start.resize(num_var_args); - for (int i = 0; i < num_var_args; i++) - { start[i] = get_int(s); } + get_vector(num_var_args, start, s); break; case USE_PREP: case GUSE_PREP: @@ -431,6 +431,7 @@ void BaseInstruction::parse_operands(istream& s, int pos, int file_pos) case CONVCBITVEC: case CONVCBIT2S: case NOTS: + case NOTCB: n = get_int(s); get_ints(r, s, 2); break; @@ -497,6 +498,9 @@ bool Instruction::get_offline_data_usage(DataPositions& usage) case USE_EDABIT: usage.edabits[{r[0], r[1]}] = n; return int(n) >= 0; + case USE_MATMUL: + usage.matmuls[{{r[0], r[1], r[2]}}] = n; + return int(n) >= 0; case USE_PREP: usage.extended[DATA_INT][r] = n; return int(n) >= 0; @@ -552,6 +556,7 @@ int BaseInstruction::get_reg_type() const case USE_PREP: case GUSE_PREP: case USE_EDABIT: + case USE_MATMUL: case RUN_TAPE: case CISC: // those use r[] not for registers @@ -709,6 +714,7 @@ unsigned BaseInstruction::get_max_reg(int reg_type) const } case ANDM: case NOTS: + case NOTCB: size = DIV_CEIL(n, 64); break; case CONVCBIT2S: @@ -735,6 +741,14 @@ unsigned BaseInstruction::get_max_reg(int reg_type) const offset = 2; skip = 4; break; + case READSOCKETS: + case READSOCKETC: + case READSOCKETINT: + case WRITESOCKETSHARE: + case WRITESOCKETC: + case WRITESOCKETINT: + size = n; + break; } if (skip > 0) @@ -1016,11 +1030,11 @@ inline void Instruction::execute(Processor& Proc) const Proc.Procp.matmuls(Proc.Procp.get_S(), *this, r[1], r[2]); return; case MATMULSM: - Proc.Procp.matmulsm(Proc.machine.Mp.MS, *this, Proc.read_Ci(r[1]), - Proc.read_Ci(r[2])); + Proc.Procp.protocol.matmulsm(Proc.Procp, Proc.machine.Mp.MS, *this, + Proc.read_Ci(r[1]), Proc.read_Ci(r[2])); return; case CONV2DS: - Proc.Procp.conv2ds(*this); + Proc.Procp.protocol.conv2ds(Proc.Procp, *this); return; case TRUNC_PR: Proc.Procp.protocol.trunc_pr(start, size, Proc.Procp); @@ -1096,6 +1110,7 @@ inline void Instruction::execute(Processor& Proc) const case USE: case USE_INP: case USE_EDABIT: + case USE_MATMUL: case USE_PREP: case GUSE_PREP: break; @@ -1117,7 +1132,8 @@ inline void Instruction::execute(Processor& Proc) const Proc.machine.join_tape(r[0]); break; case CRASH: - throw crash_requested(); + if (Proc.read_Ci(r[0])) + throw crash_requested(); break; case STARTGRIND: CALLGRIND_START_INSTRUMENTATION; @@ -1160,25 +1176,25 @@ inline void Instruction::execute(Processor& Proc) const Proc.external_clients.close_connection(Proc.read_Ci(r[0])); break; case READSOCKETINT: - Proc.read_socket_ints(Proc.read_Ci(r[0]), start); + Proc.read_socket_ints(Proc.read_Ci(r[0]), start, n); break; case READSOCKETC: - Proc.read_socket_vector(Proc.read_Ci(r[0]), start); + Proc.read_socket_vector(Proc.read_Ci(r[0]), start, n); break; case READSOCKETS: // read shares and MAC shares - Proc.read_socket_private(Proc.read_Ci(r[0]), start, true); + Proc.read_socket_private(Proc.read_Ci(r[0]), start, n, true); break; case WRITESOCKETINT: - Proc.write_socket(INT, Proc.read_Ci(r[0]), r[1], start); + Proc.write_socket(INT, Proc.read_Ci(r[0]), r[1], start, n); break; case WRITESOCKETC: - Proc.write_socket(CINT, Proc.read_Ci(r[0]), r[1], start); + Proc.write_socket(CINT, Proc.read_Ci(r[0]), r[1], start, n); break; case WRITESOCKETSHARE: // Send only shares, no MACs // N.B. doesn't make sense to have a corresponding read instruction for this - Proc.write_socket(SINT, Proc.read_Ci(r[0]), r[1], start); + Proc.write_socket(SINT, Proc.read_Ci(r[0]), r[1], start, n); break; case WRITEFILESHARE: // Write shares to file system diff --git a/Processor/Machine.h b/Processor/Machine.h index 5ce56eebc..d6ab84848 100644 --- a/Processor/Machine.h +++ b/Processor/Machine.h @@ -72,7 +72,6 @@ class Machine : public BaseMachine OnlineOptions opts; - atomic data_sent; NamedCommStats comm_stats; ExecutionStats stats; @@ -89,6 +88,11 @@ class Machine : public BaseMachine void fill_buffers(int thread_number, int tape_number, Preprocessing *prep, Preprocessing *bit_prep); + template + void fill_matmul(int thread_numbber, int tape_number, + Preprocessing *prep, true_type); + template + void fill_matmul(int, int, Preprocessing*, false_type) {} DataPositions run_tape(int thread_number, int tape_number, int arg); DataPositions join_tape(int thread_number); void run(); diff --git a/Processor/Machine.hpp b/Processor/Machine.hpp index 64bfae69c..bd08aa39e 100644 --- a/Processor/Machine.hpp +++ b/Processor/Machine.hpp @@ -5,6 +5,7 @@ #include "Memory.hpp" #include "Online-Thread.hpp" +#include "Protocols/Hemi.hpp" #include "Tools/Exceptions.h" @@ -30,8 +31,7 @@ Machine::Machine(int my_number, Names& playerNames, : my_number(my_number), N(playerNames), direct(direct), opening_sum(opening_sum), receive_threads(receive_threads), max_broadcast(max_broadcast), - use_encryption(use_encryption), live_prep(live_prep), opts(opts), - data_sent(0) + use_encryption(use_encryption), live_prep(live_prep), opts(opts) { if (opening_sum < 2) this->opening_sum = N.num_players(); @@ -49,10 +49,11 @@ Machine::Machine(int my_number, Names& playerNames, // make directory for outputs if necessary mkdir_p(PREP_DIR); + string id = "machine"; if (use_encryption) - P = new CryptoPlayer(N, 0xF00); + P = new CryptoPlayer(N, id); else - P = new PlainPlayer(N, 0xF00); + P = new PlainPlayer(N, id); if (opts.live_prep) { @@ -96,6 +97,13 @@ Machine::Machine(int my_number, Names& playerNames, load_schedule(progname_str); + // remove persistence if necessary + for (auto& prog : progs) + { + if (prog.writes_persistance) + ofstream(Binary_File_IO::filename(my_number), ios::out); + } + #ifdef VERBOSE progs[0].print_offline_cost(); #endif @@ -223,6 +231,49 @@ void Machine::fill_buffers(int thread_number, int tape_number, { #ifdef VERBOSE_CENTRAL cerr << "Problem with central bit triple preprocessing: " << e.what() << endl; +#endif + } + } + + if (not HemiOptions::singleton.plain_matmul) + fill_matmul(thread_number, tape_number, prep, sint::triple_matmul); +} + +template +template +void Machine::fill_matmul(int thread_number, int tape_number, + Preprocessing* prep, true_type) +{ + auto usage = progs[tape_number].get_offline_data_used(); + for (auto it = usage.matmuls.begin(); it != usage.matmuls.end(); it++) + { + try + { + auto& source_proc = *dynamic_cast&>(*prep).proc; + int max_inner = opts.batch_size; + int max_cols = opts.batch_size; + for (int j = 0; j < it->first[1]; j += max_inner) + { + for (int k = 0; k < it->first[2]; k += max_cols) + { + auto subdim = it->first; + subdim[1] = min(subdim[1] - j, max_inner); + subdim[2] = min(subdim[2] - k, max_cols); + auto& source = + dynamic_cast&>(source_proc.protocol).get_matrix_prep( + subdim, source_proc); + auto& dest = + dynamic_cast&>(tinfo[thread_number].processor->Procp.protocol).get_matrix_prep( + subdim, tinfo[thread_number].processor->Procp); + for (int i = 0; i < it->second; i++) + dest.push_triple(source.get_triple_no_count(-1)); + } + } + } + catch (bad_cast& e) + { +#ifdef VERBOSE_CENTRAL + cerr << "Problem with central matmul preprocessing: " << e.what() << endl; #endif } } @@ -320,20 +371,27 @@ void Machine::run() for (unsigned int i = 0; i < join_timer.size(); i++) cerr << "Join timer: " << i << " " << join_timer[i].elapsed() << endl; cerr << "Finish timer: " << finish_timer.elapsed() << endl; - cerr << "Process timer: " << proc_timer.elapsed() << endl; #endif + if (opts.verbose) + { + cerr << "Communication details " + "(rounds in parallel threads counted double):" << endl; + comm_stats.print(); + cerr << "CPU time = " << proc_timer.elapsed() << endl; + } + print_timers(); size_t rounds = 0; for (auto& x : comm_stats) rounds += x.second.rounds; - cerr << "Data sent = " << data_sent / 1e6 << " MB in ~" << rounds + cerr << "Data sent = " << comm_stats.sent / 1e6 << " MB in ~" << rounds << " rounds (party " << my_number << ")" << endl; auto& P = *this->P; Bundle bundle(P); - bundle.mine.store(data_sent.load()); + bundle.mine.store(comm_stats.sent); P.Broadcast_Receive_no_stats(bundle); size_t global = 0; for (auto& os : bundle) @@ -384,10 +442,11 @@ void Machine::run() } #endif -#ifdef VERBOSE - cerr << "Actual cost of program:" << endl; - pos.print_cost(); -#endif + if (opts.verbose) + { + cerr << "Actual cost of program:" << endl; + pos.print_cost(); + } if (pos.any_more(progs[0].get_offline_data_used()) and not progs[0].usage_unknown()) diff --git a/Processor/OfflineMachine.hpp b/Processor/OfflineMachine.hpp index 5275e69a3..b9901a0c6 100644 --- a/Processor/OfflineMachine.hpp +++ b/Processor/OfflineMachine.hpp @@ -15,7 +15,7 @@ OfflineMachine::OfflineMachine(int argc, const char** argv, ez::ezOptionParser& opt, OnlineOptions& online_opts, V, int nplayers) : W(argc, argv, opt, online_opts, V(), nplayers), playerNames( - W::playerNames), P(*this->new_player()) + W::playerNames), P(*this->new_player("machine")) { machine.load_schedule(online_opts.progname, false); Program program(playerNames.num_players()); diff --git a/Processor/Online-Thread.hpp b/Processor/Online-Thread.hpp index 7b9a711a7..1ef7da055 100644 --- a/Processor/Online-Thread.hpp +++ b/Processor/Online-Thread.hpp @@ -49,26 +49,27 @@ void thread_info::Sub_Main_Func() fprintf(stderr, "\tI am in thread %d\n",num); #endif Player* player; + string id = "thread" + to_string(num); if (machine.use_encryption) { #ifdef VERBOSE_OPTIONS cerr << "Using encrypted single-threaded communication" << endl; #endif - player = new CryptoPlayer(*(tinfo->Nms), num << 16); + player = new CryptoPlayer(*(tinfo->Nms), id); } else if (!machine.receive_threads or machine.direct) { #ifdef VERBOSE_OPTIONS cerr << "Using single-threaded receiving" << endl; #endif - player = new PlainPlayer(*(tinfo->Nms), num << 16); + player = new PlainPlayer(*(tinfo->Nms), id); } else { #ifdef VERBOSE_OPTIONS cerr << "Using player-specific threads for receiving" << endl; #endif - player = new ThreadPlayer(*(tinfo->Nms), num << 16); + player = new ThreadPlayer(*(tinfo->Nms), id); } Player& P = *player; #ifdef DEBUG_THREADS @@ -238,6 +239,16 @@ void thread_info::Sub_Main_Func() *(Zp_Data*) job.supply); queues->finished(job); } + else if (job.type == CIPHER_PLAIN_MULT_JOB) + { + cipher_plain_mult(job, sint::triple_matmul); + queues->finished(job); + } + else if (job.type == MATRX_RAND_MULT_JOB) + { + matrix_rand_mult(job, sint::triple_matmul); + queues->finished(job); + } else { // RUN PROGRAM #ifdef DEBUG_THREADS @@ -303,16 +314,15 @@ void thread_info::Sub_Main_Func() #endif // wind down thread by thread - size_t prep_sent = Proc.DataF.data_sent(); - prep_sent += Proc.share_thread.DataF.data_sent(); - prep_sent += Proc.Procp.bit_prep.data_sent(); + auto prep_stats = Proc.DataF.comm_stats(); + prep_stats += Proc.share_thread.DataF.comm_stats(); + prep_stats += Proc.Procp.bit_prep.comm_stats(); for (auto& x : Proc.Procp.personal_bit_preps) - prep_sent += x->data_sent(); + prep_stats += x->comm_stats(); machine.stats += Proc.stats; delete processor; - machine.data_sent += P.sent + prep_sent; - machine.comm_stats += P.comm_stats; + machine.comm_stats += P.comm_stats + prep_stats; queues->finished(actual_usage); delete MC2; diff --git a/Processor/OnlineMachine.h b/Processor/OnlineMachine.h index e0a70a4b5..9804828eb 100644 --- a/Processor/OnlineMachine.h +++ b/Processor/OnlineMachine.h @@ -20,7 +20,6 @@ class OnlineMachine int lg2, opening_sum, max_broadcast; Names playerNames; - Server* server; bool use_encryption, receive_threads; @@ -38,7 +37,7 @@ class OnlineMachine template int run(); - Player* new_player(int id_base = 0); + Player* new_player(const string& id_base); }; class DishonestMajorityMachine : public OnlineMachine diff --git a/Processor/OnlineMachine.hpp b/Processor/OnlineMachine.hpp index 1f1b4e531..f728288ff 100644 --- a/Processor/OnlineMachine.hpp +++ b/Processor/OnlineMachine.hpp @@ -28,7 +28,7 @@ template OnlineMachine::OnlineMachine(int argc, const char** argv, ez::ezOptionParser& opt, OnlineOptions& online_opts, int nplayers, V) : argc(argc), argv(argv), online_opts(online_opts), lg2(0), - opening_sum(0), max_broadcast(0), server(0), + opening_sum(0), max_broadcast(0), use_encryption(false), receive_threads(false), opt(opt), nplayers(nplayers) { @@ -192,7 +192,6 @@ void OnlineMachine::start_networking() int mynum = online_opts.playerno; int playerno = online_opts.playerno; - server = 0; if (ipFileName.size() > 0) { if (my_port != Names::DEFAULT_PORT) throw runtime_error("cannot set port number when using IP file"); @@ -204,7 +203,7 @@ void OnlineMachine::start_networking() { if (nplayers == 0) opt.get("-N")->getInt(nplayers); - server = Server::start_networking(playerNames, mynum, nplayers, + Server::start_networking(playerNames, mynum, nplayers, hostname, pnbase, my_port); } else @@ -216,7 +215,7 @@ void OnlineMachine::start_networking() } inline -Player* OnlineMachine::new_player(int id_base) +Player* OnlineMachine::new_player(const string& id_base) { if (use_encryption) return new CryptoPlayer(playerNames, id_base); @@ -238,15 +237,13 @@ int OnlineMachine::run() use_encryption, online_opts.live_prep, online_opts).run(); - if (server) - delete server; - -#ifdef VERBOSE - cerr << "Command line:"; - for (int i = 0; i < argc; i++) - cerr << " " << argv[i]; - cerr << endl; -#endif + if (online_opts.verbose) + { + cerr << "Command line:"; + for (int i = 0; i < argc; i++) + cerr << " " << argv[i]; + cerr << endl; + } } #ifndef INSECURE catch(...) diff --git a/Processor/OnlineOptions.cpp b/Processor/OnlineOptions.cpp index c980b51cd..df0ff7b49 100644 --- a/Processor/OnlineOptions.cpp +++ b/Processor/OnlineOptions.cpp @@ -7,12 +7,14 @@ #include "BaseMachine.h" #include "Math/gfp.h" #include "Math/gfpvar.h" +#include "Protocols/HemiOptions.h" #include "Math/gfp.hpp" using namespace std; OnlineOptions OnlineOptions::singleton; +HemiOptions HemiOptions::singleton; OnlineOptions::OnlineOptions() : playerno(-1) { @@ -26,6 +28,11 @@ OnlineOptions::OnlineOptions() : playerno(-1) bucket_size = 4; cmd_private_input_file = "Player-Data/Input"; cmd_private_output_file = ""; +#ifdef VERBOSE + verbose = true; +#else + verbose = false; +#endif } OnlineOptions::OnlineOptions(ez::ezOptionParser& opt, int argc, @@ -65,7 +72,8 @@ OnlineOptions::OnlineOptions(ez::ezOptionParser& opt, int argc, "Prefix for output file path " "(default: output to stdout for party 0 (silent otherwise " "unless interactive mode is active). " - "Output will be written to {prefix}-P{id}-{thread_id}.", // Help description. + "Output will be written to {prefix}-P{id}-{thread_id}. " + "Use '.' for stdout on all parties.", // Help description. "-OF", // Flag token. "--output-file" // Flag token. ); @@ -171,6 +179,15 @@ OnlineOptions::OnlineOptions(ez::ezOptionParser& opt, int argc, "-B", // Flag token. "--bucket-size" // Flag token. ); + opt.add( + "", // Default. + 0, // Required? + 0, // Number of args expected. + 0, // Delimiter if expecting multiple args. + "Verbose output", // Help description. + "-v", // Flag token. + "--verbose" // Flag token. + ); opt.parse(argc, argv); @@ -198,6 +215,10 @@ OnlineOptions::OnlineOptions(ez::ezOptionParser& opt, int argc, opt.get("--bucket-size")->getInt(bucket_size); +#ifndef VERBOSE + verbose = opt.isSet("--verbose"); +#endif + opt.resetArgs(); } diff --git a/Processor/OnlineOptions.h b/Processor/OnlineOptions.h index 8b34adf0f..615afc4c1 100644 --- a/Processor/OnlineOptions.h +++ b/Processor/OnlineOptions.h @@ -28,6 +28,7 @@ class OnlineOptions int bucket_size; std::string cmd_private_input_file; std::string cmd_private_output_file; + bool verbose; OnlineOptions(); OnlineOptions(ez::ezOptionParser& opt, int argc, const char** argv, diff --git a/Processor/Processor.h b/Processor/Processor.h index 5183c61b7..b62c64c24 100644 --- a/Processor/Processor.h +++ b/Processor/Processor.h @@ -227,13 +227,15 @@ class Processor : public ArithmeticProcessor void split(const Instruction& instruction); // Access to external client sockets for reading clear/shared data - void read_socket_ints(int client_id, const vector& registers); - - void write_socket(const RegType reg_type, - int socket_id, int message_type, const vector& registers); + void read_socket_ints(int client_id, const vector& registers, int size); + + void write_socket(const RegType reg_type, int socket_id, int message_type, + const vector& registers, int size); - void read_socket_vector(int client_id, const vector& registers); - void read_socket_private(int client_id, const vector& registers, bool send_macs); + void read_socket_vector(int client_id, const vector& registers, + int size); + void read_socket_private(int client_id, const vector& registers, + int size, bool send_macs); // Read and write secret numeric data to file (name hardcoded at present) void read_shares_from_file(int start_file_pos, int end_file_pos_register, const vector& data_registers); diff --git a/Processor/Processor.hpp b/Processor/Processor.hpp index cddb65052..dd1e82382 100644 --- a/Processor/Processor.hpp +++ b/Processor/Processor.hpp @@ -95,10 +95,13 @@ Processor::Processor(int thread_num,Player& P, shared_prng.SeedGlobally(P, false); // only output on party 0 if not interactive - bool output = P.my_num() == 0 or machine.opts.interactive; + bool always_stdout = machine.opts.cmd_private_output_file == "."; + bool output = P.my_num() == 0 or machine.opts.interactive or always_stdout; out.activate(output); Procb.out.activate(output); - setup_redirection(P.my_num(), thread_num, opts); + + if (not always_stdout) + setup_redirection(P.my_num(), thread_num, opts); if (stdout_redirect_file.is_open()) { @@ -236,7 +239,8 @@ void Processor::convcbit2s(const Instruction& instruction) for (int i = 0; i < DIV_CEIL(instruction.get_n(), unit); i++) Procb.S[instruction.get_r(0) + i] = sint::bit_type::constant( Procb.C[instruction.get_r(1) + i], P.my_num(), - share_thread.MC->get_alphai()); + share_thread.MC->get_alphai(), + min(unsigned(unit), instruction.get_n() - i * unit)); } template @@ -262,8 +266,8 @@ void Processor::split(const Instruction& instruction) // If message_type is > 0, send message_type in bytes 0 - 3, to allow an external client to // determine the data structure being sent in a message. template -void Processor::write_socket(const RegType reg_type, - int socket_id, int message_type, const vector& registers) +void Processor::write_socket(const RegType reg_type, int socket_id, + int message_type, const vector& registers, int size) { int m = registers.size(); socket_stream.reset_write_head(); @@ -273,28 +277,40 @@ void Processor::write_socket(const RegType reg_type, socket_stream.store(message_type); } - for (int i = 0; i < m; i++) - { - if (reg_type == SINT) { - // Send vector of secret shares - get_Sp_ref(registers[i]).pack(socket_stream, - sint::get_rec_factor(P.my_num(), P.num_players())); - } - else if (reg_type == CINT) { - // Send vector of clear public field elements - get_Cp_ref(registers[i]).pack(socket_stream); - } - else if (reg_type == INT) { - // Send vector of 32-bit clear ints - socket_stream.store((int&)get_Ci_ref(registers[i])); - } - else { - stringstream ss; - ss << "Write socket instruction with unknown reg type " << reg_type << - "." << endl; - throw Processor_Error(ss.str()); + for (int j = 0; j < size; j++) + { + for (int i = 0; i < m; i++) + { + if (reg_type == SINT) + { + // Send vector of secret shares + get_Sp_ref(registers[i] + j).pack(socket_stream, + sint::get_rec_factor(P.my_num(), P.num_players())); + } + else if (reg_type == CINT) + { + // Send vector of clear public field elements + get_Cp_ref(registers[i] + j).pack(socket_stream); + } + else if (reg_type == INT) + { + // Send vector of 32-bit clear ints + socket_stream.store((int&) get_Ci_ref(registers[i] + j)); + } + else + { + stringstream ss; + ss << "Write socket instruction with unknown reg type " + << reg_type << "." << endl; + throw Processor_Error(ss.str()); + } + } } - } + +#ifdef VERBOSE_COMM + cerr << "send " << socket_stream.get_length() << " to client " << socket_id + << endl; +#endif try { socket_stream.Send(external_clients.get_socket(socket_id)); @@ -308,44 +324,47 @@ void Processor::write_socket(const RegType reg_type, // Receive vector of 32-bit clear ints template -void Processor::read_socket_ints(int client_id, const vector& registers) +void Processor::read_socket_ints(int client_id, + const vector& registers, int size) { int m = registers.size(); socket_stream.reset_write_head(); socket_stream.Receive(external_clients.get_socket(client_id)); - for (int i = 0; i < m; i++) - { - int val; - socket_stream.get(val); - write_Ci(registers[i], (long)val); - } + for (int j = 0; j < size; j++) + for (int i = 0; i < m; i++) + { + int val; + socket_stream.get(val); + write_Ci(registers[i] + j, (long) val); + } } // Receive vector of public field elements template -void Processor::read_socket_vector(int client_id, const vector& registers) +void Processor::read_socket_vector(int client_id, + const vector& registers, int size) { int m = registers.size(); socket_stream.reset_write_head(); socket_stream.Receive(external_clients.get_socket(client_id)); - for (int i = 0; i < m; i++) - { - get_Cp_ref(registers[i]) = socket_stream.get(); - } + for (int j = 0; j < size; j++) + for (int i = 0; i < m; i++) + get_Cp_ref(registers[i] + j) = + socket_stream.get(); } // Receive vector of field element shares over private channel template -void Processor::read_socket_private(int client_id, const vector& registers, bool read_macs) +void Processor::read_socket_private(int client_id, + const vector& registers, int size, bool read_macs) { int m = registers.size(); socket_stream.reset_write_head(); socket_stream.Receive(external_clients.get_socket(client_id)); - for (int i = 0; i < m; i++) - { - get_Sp_ref(registers[i]).unpack(socket_stream, read_macs); - } + for (int j = 0; j < size; j++) + for (int i = 0; i < m; i++) + get_Sp_ref(registers[i] + j).unpack(socket_stream, read_macs); } @@ -382,11 +401,7 @@ void Processor::read_shares_from_file(int start_file_posn, int end_ // Append share data in data_registers to end of file. Expects Persistence directory to exist. template void Processor::write_shares_to_file(const vector& data_registers) { - string dir = "Persistence"; - mkdir_p(dir.c_str()); - - string filename; - filename = dir + "/Transactions-P" + to_string(P.my_num()) + ".data"; + string filename = binary_file_io.filename(P.my_num()); unsigned int size = data_registers.size(); diff --git a/Processor/Program.cpp b/Processor/Program.cpp index 0bb4c5bfa..c33039428 100644 --- a/Processor/Program.cpp +++ b/Processor/Program.cpp @@ -23,6 +23,7 @@ void Program::compute_constants() max_mem[reg_type] = max(max_mem[reg_type], p[i].get_mem(RegType(reg_type))); } + writes_persistance |= p[i].opcode == WRITEFILESHARE; } } @@ -41,6 +42,8 @@ void Program::parse(istream& s) s.peek(); while (!s.eof()) { instr.parse(s, p.size()); + if (s.fail()) + throw runtime_error("error while parsing " + to_string(instr.opcode)); p.push_back(instr); //cerr << "\t" << instr << endl; s.peek(); diff --git a/Processor/Program.h b/Processor/Program.h index 55c9b8c45..a41c9e2a6 100644 --- a/Processor/Program.h +++ b/Processor/Program.h @@ -30,8 +30,10 @@ class Program public: + bool writes_persistance; + Program(int nplayers) : offline_data_used(nplayers), - unknown_usage(false) + unknown_usage(false), writes_persistance(false) { compute_constants(); } // Read in a program diff --git a/Processor/RingMachine.hpp b/Processor/RingMachine.hpp index f54aff8b0..dfeb02bfd 100644 --- a/Processor/RingMachine.hpp +++ b/Processor/RingMachine.hpp @@ -74,6 +74,7 @@ HonestMajorityRingMachineWithSecurity::HonestMajorityRingMachineWithSecuri Y(K, 40) \ default: \ cerr << "not compiled for security parameter " << to_string(opts.S) << endl; \ + cerr << "add 'Y(K, " << opts.S << ")' to " __FILE__ ", line 76" << endl; \ exit(1); \ } \ break; diff --git a/Processor/ThreadJob.h b/Processor/ThreadJob.h index fe4f0b6c6..719c16d7d 100644 --- a/Processor/ThreadJob.h +++ b/Processor/ThreadJob.h @@ -23,6 +23,8 @@ enum ThreadJobType TRIPLE_SACRIFICE_JOB, CHECK_JOB, FFT_JOB, + CIPHER_PLAIN_MULT_JOB, + MATRX_RAND_MULT_JOB, NO_JOB }; diff --git a/Processor/ThreadQueues.cpp b/Processor/ThreadQueues.cpp index ea9a6f5be..de0134994 100644 --- a/Processor/ThreadQueues.cpp +++ b/Processor/ThreadQueues.cpp @@ -6,17 +6,21 @@ #include "ThreadQueues.h" #include +#include int ThreadQueues::distribute(ThreadJob job, int n_items, int base, int granularity) { - find_available(); - return distribute_no_setup(job, n_items, base, granularity); + if (find_available() > 0) + return distribute_no_setup(job, n_items, base, granularity); + else + return base; } int ThreadQueues::find_available() { - available.clear(); + if (not available.empty()) + return 0; for (size_t i = 1; i < size(); i++) if (at(i)->available()) available.push_back(i); @@ -28,7 +32,7 @@ int ThreadQueues::find_available() int ThreadQueues::get_n_per_thread(int n_items, int granularity) { - int n_per_thread = n_items / (available.size() + 1) / granularity + int n_per_thread = ceil(n_items / (available.size() + 1.0)) / granularity * granularity; return n_per_thread; } @@ -39,6 +43,11 @@ int ThreadQueues::distribute_no_setup(ThreadJob job, int n_items, int base, int n_per_thread = get_n_per_thread(n_items, granularity); for (size_t i = 0; i < available.size(); i++) { + if (base + (i + 1) * n_per_thread > size_t(n_items)) + { + available.resize(i); + return base + i * n_per_thread; + } if (supplies) job.supply = supplies->at(i); job.begin = base + i * n_per_thread; @@ -52,4 +61,5 @@ void ThreadQueues::wrap_up(ThreadJob job) { for (int i : available) assert(at(i)->result().output == job.output); + available.clear(); } diff --git a/Processor/instructions.h b/Processor/instructions.h index a58f1df20..49901822b 100644 --- a/Processor/instructions.h +++ b/Processor/instructions.h @@ -190,7 +190,7 @@ *dest++ = *op1++ * *op2++) \ X(DIVINT, auto dest = &Proc.get_Ci()[r[0]]; auto op1 = &Proc.get_Ci()[r[1]]; \ auto op2 = &Proc.get_Ci()[r[2]], \ - *dest++ = *op1++ / *op2++) \ + if (*op2 == 0) throw division_by_zero(); *dest++ = *op1++ / *op2++) \ X(INCINT, auto dest = &Proc.get_Ci()[r[0]]; auto base = Proc.get_Ci()[r[1]], \ int inc = (i / start[0]) % start[1]; *dest++ = base + inc * int(n)) \ X(EQZC, auto dest = &Ci[r[0]]; auto source = &Ci[r[1]], *dest++ = *source++ == 0) \ diff --git a/Programs/Source/bankers_bonus.mpc b/Programs/Source/bankers_bonus.mpc index 4826803a7..e3dfc9f92 100644 --- a/Programs/Source/bankers_bonus.mpc +++ b/Programs/Source/bankers_bonus.mpc @@ -67,13 +67,7 @@ def write_winner_to_clients(sockets, number_clients, winning_client_id): # Setup authenticate result using share of random. # client can validate ∑ winning_client_id * ∑ rnd_from_triple = ∑ auth_result - rnd_from_triple = sint.get_random_triple()[0] - auth_result = winning_client_id * rnd_from_triple - - @for_range(number_clients) - def loop_body(i): - sint.write_shares_to_socket(sockets[i], [winning_client_id, rnd_from_triple, auth_result]) - + sint.reveal_to_clients(sockets.get_sub(number_clients), [winning_client_id]) def main(): """Listen in while loop for players to join a game. diff --git a/Programs/Source/gc_oram.mpc b/Programs/Source/gc_oram.mpc index 058a37aa7..fa7ac702f 100644 --- a/Programs/Source/gc_oram.mpc +++ b/Programs/Source/gc_oram.mpc @@ -1,5 +1,7 @@ prog = program +prog.options.binary = -1 + from Compiler.GC.types import * from Compiler.GC.instructions import * diff --git a/Programs/Source/logreg.mpc b/Programs/Source/logreg.mpc index a73153ef2..492e46e02 100644 --- a/Programs/Source/logreg.mpc +++ b/Programs/Source/logreg.mpc @@ -2,7 +2,6 @@ from Compiler import ml debug = False -program.use_edabit(True) program.options_from_args() sfix.set_precision(16, 31) @@ -12,30 +11,39 @@ dim = int(program.args[1]) batch = int(program.args[2]) try: - ml.set_n_threads(int(program.args[3])) + n_iterations = int(program.args[3]) +except: + n_iterations = 1 + +try: + ml.set_n_threads(int(program.args[4])) except: ml.set_n_threads(None) -X_normal = sfix.Matrix(6400, dim) -X_pos = sfix.Matrix(6400, dim) +N = batch * n_iterations + +print('run 1 epoch of logistic regression with %d examples' % (N)) + +dense = ml.Dense(N, dim, 1) +sigmoid = ml.Output(N, debug=debug, approx='approx' in program.args) -dense = ml.Dense(12800, dim, 1) -layers = [dense, ml.Output(12800, debug=debug, approx='approx' in program.args)] +for x in dense.X, sigmoid.Y: + x.assign_all(0) -sgd = ml.SGD(layers, batch // 128 * 10 , debug=debug, report_loss=False) +sgd = ml.SGD([dense, sigmoid], 1, debug=debug, report_loss=False) +sgd.reset() if not ('forward' in program.args or 'backward' in program.args): - sgd.reset([X_normal, X_pos]) sgd.run(batch_size=batch) if 'forward' in program.args: - @for_range(1000) + @for_range(n_iterations) def _(i): sgd.forward(N=batch) if 'backward' in program.args: b = regint.Array(batch) b.assign(regint.inc(batch)) - @for_range(1000) + @for_range(n_iterations) def _(i): sgd.backward(batch=b) diff --git a/Programs/Source/mnist_full_C.mpc b/Programs/Source/mnist_full_C.mpc index e0388a3fa..cd6603ea2 100644 --- a/Programs/Source/mnist_full_C.mpc +++ b/Programs/Source/mnist_full_C.mpc @@ -77,6 +77,11 @@ if 'dropout2' in program.args: elif 'dropout1' in program.args: layers.insert(6, ml.Dropout(N, 800, alpha=0.5)) +if 'no_relu' in program.args: + for x in layers: + if isinstance(x, ml.Relu): + layers.remove(x) + print(layers) Y = sint.Matrix(n_test, 10) diff --git a/Programs/Source/mnist_logreg.mpc b/Programs/Source/mnist_logreg.mpc index 5fe18d9e3..f9d6f9e50 100644 --- a/Programs/Source/mnist_logreg.mpc +++ b/Programs/Source/mnist_logreg.mpc @@ -28,6 +28,9 @@ try: except: batch_size = N +assert batch_size <= N +ml.Layer.back_batch_size = batch_size + try: ml.set_n_threads(int(program.args[3])) except: diff --git a/Programs/Source/test_gc.mpc b/Programs/Source/test_gc.mpc index 268971d6f..bb038e536 100644 --- a/Programs/Source/test_gc.mpc +++ b/Programs/Source/test_gc.mpc @@ -124,3 +124,6 @@ test(c[1], 2 ** 32 - 1) c = (a < b).bit_decompose() test(c[0], 1) test(c[1], 1) + +test(~cbits.get_type(2)(0), 3) +test(~sbits.get_type(64)(0).reveal(), 2 ** 64 - 1) diff --git a/Protocols/Beaver.hpp b/Protocols/Beaver.hpp index d3a7c0815..639930059 100644 --- a/Protocols/Beaver.hpp +++ b/Protocols/Beaver.hpp @@ -78,8 +78,9 @@ T Beaver::finalize_mul(int n) for (int k = 0; k < 2; k++) { masked[k] = *it++; - tmp += (masked[k] * (*triple)[1 - k]); } + tmp += (masked[0] * (*triple)[1]); + tmp += ((*triple)[0] * masked[1]); tmp += T::constant(masked[0] * masked[1], P.my_num(), MC->get_alphai()); triple++; return tmp; diff --git a/Protocols/ChaiGearPrep.hpp b/Protocols/ChaiGearPrep.hpp index a829a2e58..69b16fcf2 100644 --- a/Protocols/ChaiGearPrep.hpp +++ b/Protocols/ChaiGearPrep.hpp @@ -99,7 +99,7 @@ typename ChaiGearPrep::Generator& ChaiGearPrep::get_generator() lock.lock(); if (machine == 0 or machine->setup.part().alphai == 0) { - PlainPlayer P(proc->P.N, T::clear::type_char()); + PlainPlayer P(proc->P.N, "ChaiGear" + T::type_string()); if (machine == 0) basic_setup(P); key_setup(P, proc->MC.get_alphai()); diff --git a/Protocols/CowGearOptions.cpp b/Protocols/CowGearOptions.cpp index a62fb92e7..9212a7e06 100644 --- a/Protocols/CowGearOptions.cpp +++ b/Protocols/CowGearOptions.cpp @@ -88,6 +88,7 @@ CowGearOptions::CowGearOptions(ez::ezOptionParser& opt, int argc, } use_top_gear = not opt.isSet("-J"); if (opt.isSet("-T")) - cerr << "WARNING: Option -T/--top-gear is obsolete." << endl; + cerr << "WARNING: Option -T/--top-gear is obsolete " + "because it is the default now. Use -J to deactivate it." << endl; opt.resetArgs(); } diff --git a/Protocols/CowGearPrep.hpp b/Protocols/CowGearPrep.hpp index 866ec9d43..3b9daae1e 100644 --- a/Protocols/CowGearPrep.hpp +++ b/Protocols/CowGearPrep.hpp @@ -100,7 +100,7 @@ PairwiseGenerator& CowGearPrep::get_generator() lock.lock(); if (pairwise_machine == 0 or pairwise_machine->enc_alphas.empty()) { - PlainPlayer P(proc->P.N, T::clear::type_char()); + PlainPlayer P(proc->P.N, "CowGear" + T::type_string()); if (pairwise_machine == 0) setup(P, proc->MC.get_alphai()); else diff --git a/Protocols/Hemi.h b/Protocols/Hemi.h new file mode 100644 index 000000000..bed380ac8 --- /dev/null +++ b/Protocols/Hemi.h @@ -0,0 +1,35 @@ +/* + * Hemi.h + * + */ + +#ifndef PROTOCOLS_HEMI_H_ +#define PROTOCOLS_HEMI_H_ + +#include "SPDZ.h" +#include "HemiMatrixPrep.h" + +template +class Hemi : public SPDZ +{ + map, HemiMatrixPrep*> matrix_preps; + + ShareMatrix matrix_multiply(const ShareMatrix& A, const ShareMatrix& B, + SubProcessor& processor); + +public: + Hemi(Player& P) : + SPDZ(P) + { + } + ~Hemi(); + + HemiMatrixPrep& get_matrix_prep(const array& dimensions, + SubProcessor& processor); + + void matmulsm(SubProcessor& processor, CheckVector& source, + const Instruction& instruction, int a, int b); + void conv2ds(SubProcessor& processor, const Instruction& instruction); +}; + +#endif /* PROTOCOLS_HEMI_H_ */ diff --git a/Protocols/Hemi.hpp b/Protocols/Hemi.hpp new file mode 100644 index 000000000..d9bca4736 --- /dev/null +++ b/Protocols/Hemi.hpp @@ -0,0 +1,206 @@ +/* + * Hemi.hpp + * + */ + +#ifndef PROTOCOLS_HEMI_HPP_ +#define PROTOCOLS_HEMI_HPP_ + +#include "Hemi.h" +#include "ShareMatrix.h" +#include "HemiOptions.h" + +#include "HemiMatrixPrep.hpp" +#include "HemiPrep.hpp" + +template +Hemi::~Hemi() +{ + for (auto& x : matrix_preps) + delete x.second; +} + +template +HemiMatrixPrep& Hemi::get_matrix_prep(const array& dims, + SubProcessor& processor) +{ + if (matrix_preps.find(dims) == matrix_preps.end()) + matrix_preps.insert({dims, + new HemiMatrixPrep(dims[0], dims[1], dims[2], + dynamic_cast&>(processor.DataF))}); + return *matrix_preps.at(dims); +} + +template +void Hemi::matmulsm(SubProcessor& processor, CheckVector& source, + const Instruction& instruction, int a, int b) +{ + if (HemiOptions::singleton.plain_matmul + or not OnlineOptions::singleton.live_prep) + { + processor.matmulsm(source, instruction, a, b); + return; + } + + auto& dim = instruction.get_start(); + auto& S = processor.get_S(); + auto C = S.begin() + (instruction.get_r(0)); + assert(C + dim[0] * dim[2] <= S.end()); + auto Proc = processor.Proc; + assert(Proc); + + ShareMatrix A(dim[0], dim[1]), B(dim[1], dim[2]); + + for (int i = 0; i < dim[0]; i++) + { + auto ii = Proc->get_Ci().at(dim[3] + i); + for (int j = 0; j < dim[2]; j++) + { + auto jj = Proc->get_Ci().at(dim[6] + j); + for (int k = 0; k < dim[1]; k++) + { + auto kk = Proc->get_Ci().at(dim[4] + k); + auto ll = Proc->get_Ci().at(dim[5] + k); + A[{i, k}] = source.at(a + ii * dim[7] + kk); + B[{k, j}] = source.at(b + ll * dim[8] + jj); + } + } + } + + auto res = matrix_multiply(A, B, processor); + + for (int i = 0; i < dim[0]; i++) + for (int j = 0; j < dim[2]; j++) + *(C + i * dim[2] + j) = res[{i, j}]; +} + +template +ShareMatrix Hemi::matrix_multiply(const ShareMatrix& A, + const ShareMatrix& B, SubProcessor& processor) +{ + Beaver> beaver(this->P); + array dims = {{A.n_rows, A.n_cols, B.n_cols}}; + ShareMatrix C(A.n_rows, B.n_cols); + + int max_inner = OnlineOptions::singleton.batch_size; + int max_cols = OnlineOptions::singleton.batch_size; + for (int i = 0; i < A.n_cols; i += max_inner) + { + for (int j = 0; j < B.n_cols; j += max_cols) + { + auto subdim = dims; + subdim[1] = min(max_inner, A.n_cols - i); + subdim[2] = min(max_cols, B.n_cols - j); + auto& prep = get_matrix_prep(subdim, processor); + MatrixMC mc; + beaver.init_mul(prep, mc); + beaver.prepare_mul(A.from(0, i, subdim.data()), + B.from(i, j, subdim.data() + 1)); + beaver.exchange(); + C.add_from_col(j, beaver.finalize_mul()); + } + } + + return C; +} + +template +void Hemi::conv2ds(SubProcessor& processor, + const Instruction& instruction) +{ + if (HemiOptions::singleton.plain_matmul + or not OnlineOptions::singleton.live_prep) + { + processor.conv2ds(instruction); + return; + } + + auto& args = instruction.get_start(); + int output_h = args[0], output_w = args[1]; + int inputs_h = args[2], inputs_w = args[3]; + int weights_h = args[4], weights_w = args[5]; + int stride_h = args[6], stride_w = args[7]; + int n_channels_in = args[8]; + int padding_h = args[9]; + int padding_w = args[10]; + int batch_size = args[11]; + size_t r0 = instruction.get_r(0); + size_t r1 = instruction.get_r(1); + int r2 = instruction.get_r(2); + int filter_stride_h = 1; + int filter_stride_w = 1; + if (stride_h < 0) + { + filter_stride_h = -stride_h; + stride_h = 1; + } + if (stride_w < 0) + { + filter_stride_w = -stride_w; + stride_w = 1; + } + + auto& S = processor.get_S(); + array dim({{1, weights_h * weights_w * n_channels_in, batch_size * output_h * output_w}}); + ShareMatrix A(dim[0], dim[1]), B(dim[1], dim[2]); + + for (int i_batch = 0; i_batch < batch_size; i_batch ++) + { + size_t base = r1 + i_batch * inputs_w * inputs_h * n_channels_in; + assert(base + inputs_w * inputs_h * n_channels_in <= S.size()); + T* input_base = &S[base]; + for (int out_y = 0; out_y < output_h; out_y++) + for (int out_x = 0; out_x < output_w; out_x++) + { + int in_x_origin = (out_x * stride_w) - padding_w; + int in_y_origin = (out_y * stride_h) - padding_h; + + for (int filter_y = 0; filter_y < weights_h; filter_y++) + { + int in_y = in_y_origin + filter_y * filter_stride_h; + if ((0 <= in_y) and (in_y < inputs_h)) + for (int filter_x = 0; filter_x < weights_w; filter_x++) + { + int in_x = in_x_origin + filter_x * filter_stride_w; + if ((0 <= in_x) and (in_x < inputs_w)) + { + T* pixel_base = &input_base[(in_y * inputs_w + + in_x) * n_channels_in]; + T* weight_base = &S[r2 + + (filter_y * weights_w + filter_x) + * n_channels_in]; + for (int in_c = 0; in_c < n_channels_in; in_c++) +// protocol.prepare_dotprod(pixel_base[in_c], +// weight_base[in_c]) + { + int i_inner = n_channels_in * (filter_x * weights_h + filter_y) + in_c; + B[{i_inner, output_h * (output_w * i_batch + out_x) + out_y}] = pixel_base[in_c]; + A[{0, i_inner}] = weight_base[in_c]; + } + } + } + } +// +// protocol.next_dotprod(); + } + } + + auto C = matrix_multiply(A, B, processor); + + for (int i_batch = 0; i_batch < batch_size; i_batch ++) + { + size_t base = r0 + i_batch * output_h * output_w; + assert(base + output_h * output_w <= S.size()); + T* output_base = &S[base]; + for (int out_y = 0; out_y < output_h; out_y++) + for (int out_x = 0; out_x < output_w; out_x++) + { + output_base[out_y * output_w + out_x] = C[{0, output_h * (output_w * i_batch + out_x) + out_y}]; +// protocol.finalize_dotprod( +// lengths[i_batch][out_y][out_x]); + } + } + +} + +#endif /* PROTOCOLS_HEMI_HPP_ */ diff --git a/Protocols/HemiMatrixPrep.h b/Protocols/HemiMatrixPrep.h new file mode 100644 index 000000000..35db214f7 --- /dev/null +++ b/Protocols/HemiMatrixPrep.h @@ -0,0 +1,50 @@ +/* + * HemiMatrixPrep.h + * + */ + +#ifndef PROTOCOLS_HEMIMATRIXPREP_H_ +#define PROTOCOLS_HEMIMATRIXPREP_H_ + +#include "ShareMatrix.h" +#include "ReplicatedPrep.h" + +template class HemiPrep; + +template +class HemiMatrixPrep : public BufferPrep> +{ + typedef BufferPrep> super; + + int n_rows, n_inner, n_cols; + bool swapped; + DataPositions* usage; + + HemiPrep* prep; + + HemiMatrixPrep(const HemiMatrixPrep&) = delete; + +public: + HemiMatrixPrep(int n_rows, int n_inner, int n_cols, HemiPrep& prep) : + super(*(usage = new DataPositions)), n_rows(n_rows), n_inner(n_inner), + n_cols(n_cols), prep(&prep) + { + swapped = n_rows > n_cols; + if (swapped) + std::swap(this->n_rows, this->n_cols); + assert(this->n_cols >= this->n_rows); + } + + ~HemiMatrixPrep() + { + delete usage; + } + + void set_protocol(typename ShareMatrix::Protocol&) + { + } + + void buffer_triples(); +}; + +#endif /* PROTOCOLS_HEMIMATRIXPREP_H_ */ diff --git a/Protocols/HemiMatrixPrep.hpp b/Protocols/HemiMatrixPrep.hpp new file mode 100644 index 000000000..82b28431c --- /dev/null +++ b/Protocols/HemiMatrixPrep.hpp @@ -0,0 +1,201 @@ +/* + * HemiMatrixPrep.hpp + * + */ + +#include "HemiMatrixPrep.h" +#include "FHE/Diagonalizer.h" + +class CipherPlainMultJob : public ThreadJob +{ +public: + CipherPlainMultJob(vector& products, + const vector& multiplicands, + const vector>& multiplicands2, bool add) + { + type = CIPHER_PLAIN_MULT_JOB; + output = &products; + input = &multiplicands; + supply = &multiplicands2; + length = add; + } +}; + +inline void cipher_plain_mult(ThreadJob job, true_type) +{ +#ifdef VERBOSE_CIPHER_PLAIN_MULT + fprintf(stderr, "multiply %d to %d in thread %lx\n", job.begin, job.end, + pthread_self()); + fflush(stderr); +#endif + for (int i = job.begin; i < job.end; i++) + { + auto prod = ((vector*) job.input)->at(i) + * ((vector>*) job.supply)->at(i); + auto& results = *((vector*) job.output); + + if (job.length) + results[job.begin] += prod; + else + results[i] = prod; + + } +} + +inline void cipher_plain_mult(ThreadJob, false_type) +{ + throw not_implemented(); +} + +class MatrixRandMultJob : public ThreadJob +{ +public: + MatrixRandMultJob(vector>& C, + const vector>& A, + vector>& B) + { + type = MATRX_RAND_MULT_JOB; + output = &C; + input = &A; + supply = &B; + } +}; + +inline void matrix_rand_mult(ThreadJob job, true_type = {}) +{ + auto& C = *(vector>*) job.output; + auto& A = *(vector>*) job.input; + auto& B = *(vector>*) job.supply; + SeededPRNG G; + + for (int i = job.begin; i < job.end; i++) + { + A[i].randomize(G); + B[i].randomize(G); + C[i] = A[i] * B[i]; + } +} + +inline void matrix_rand_mult(ThreadJob, false_type) +{ + throw not_implemented(); +} + +template +void HemiMatrixPrep::buffer_triples() +{ + + assert(prep); + auto& multipliers = prep->get_multipliers(); + assert(prep->pairwise_machine); + auto& FTD = prep->pairwise_machine->setup_p.FieldD; + auto& pk = prep->pairwise_machine->pk; + int n_matrices = FTD.num_slots() / n_rows; +#ifdef VERBOSE + fprintf(stderr, "creating %d %dx%d * %dx%d triples\n", n_matrices, n_rows, n_inner, + n_inner, n_cols); + fflush(stderr); + RunningTimer timer; +#endif + AddableVector> A(n_matrices, {n_rows, n_inner}), + B(n_matrices, {n_inner, n_cols}); + SeededPRNG G; + AddableVector> C(n_matrices); + MatrixRandMultJob job(C, A, B); + + if (BaseMachine::thread_num == 0 and BaseMachine::has_singleton()) + { + auto& queues = BaseMachine::s().queues; + int start = queues.distribute(job, n_matrices); + job.begin = start; + job.end = n_matrices; + matrix_rand_mult(job); + queues.wrap_up(job); + } + else + { + job.begin = 0; + job.end = n_matrices; + matrix_rand_mult(job); + } + +#ifdef VERBOSE_HE + fprintf(stderr, "encrypt at %f\n", timer.elapsed()); + fflush(stderr); +#endif + + Diagonalizer diag(A, FTD, pk); + + vector> products(n_cols, FTD); + assert(prep->proc); + auto& P = prep->proc->P; + + Bundle bundle(P); + bundle.mine.store(diag.ciphertexts); + P.unchecked_broadcast(bundle); + vector> others_ct; + for (auto& os : bundle) + { + others_ct.push_back({}); + os.get(others_ct.back(), Ciphertext(pk)); + } + + for (int j = 0; j < n_cols; j++) + for (auto m : multipliers) + { +#ifdef VERBOSE + fprintf(stderr, "column %d with party offset %d at %f\n", j, + m->get_offset(), timer.elapsed()); + fflush(stderr); +#endif + Ciphertext C(pk); + auto& multiplicands = others_ct[P.get_player(-m->get_offset())]; + if (BaseMachine::thread_num == 0 and BaseMachine::has_singleton()) + { + auto& queues = BaseMachine::s().queues; + vector products(n_inner, pk); + vector> multiplicands2; + for (int i = 0; i < n_inner; i++) + multiplicands2.push_back(diag.get_plaintext(B, i, j)); + CipherPlainMultJob job(products, multiplicands, multiplicands2, true); + int start = queues.distribute(job, n_inner); +#ifdef VERBOSE_HE + fprintf(stderr, "from %d in central thread\n", start); + fflush(stderr); +#endif + for (int i = start; i < n_inner; i++) + products[i] = multiplicands.at(i) * multiplicands2.at(i); + queues.wrap_up(job); +#ifdef VERBOSE_HE + fprintf(stderr, "adding at %f\n", timer.elapsed()); + fflush(stderr); +#endif + for (int i = 0; i < n_inner; i++) + C += products[i]; + } + else + for (int i = 0; i < n_inner; i++) + C += multiplicands.at(i) * diag.get_plaintext(B, i, j); + +#ifdef VERBOSE_HE + fprintf(stderr, "adding column %d with party offset %d at %f\n", j, + m->get_offset(), timer.elapsed()); + fflush(stderr); +#endif + m->add(products[j], C, BOTH, n_inner); + } + + C += diag.dediag(products, n_matrices); + + for (int i = 0; i < n_matrices; i++) + if (swapped) + this->triples.push_back( + {{B[i].transpose(), A[i].transpose(), C[i].transpose()}}); + else + this->triples.push_back({{A[i], B[i], C[i]}}); + +#ifdef VERBOSE_HE + fprintf(stderr, "done at %f\n", timer.elapsed()); + fflush(stderr); +#endif +} diff --git a/Protocols/HemiOptions.h b/Protocols/HemiOptions.h new file mode 100644 index 000000000..9916cb896 --- /dev/null +++ b/Protocols/HemiOptions.h @@ -0,0 +1,42 @@ +/* + * HemiOptions.h + * + */ + +#ifndef PROTOCOLS_HEMIOPTIONS_H_ +#define PROTOCOLS_HEMIOPTIONS_H_ + +#include "Tools/ezOptionParser.h" + +class HemiOptions +{ +public: + static HemiOptions singleton; + + bool plain_matmul; + + + HemiOptions() : + plain_matmul(false) + { + } + + HemiOptions(ez::ezOptionParser& opt, int argc, + const char** argv) + { + opt.add("", // Default. + 0, // Required? + 0, // Number of args expected. + 0, // Delimiter if expecting multiple args. + "Don't use homomorphic matrix multplication", // Help description. + "-M", // Flag token. + "--plain-matmul" // Flag token. + ); + + opt.parse(argc, argv); + plain_matmul = opt.isSet("-M"); + opt.resetArgs(); + } +}; + +#endif /* PROTOCOLS_HEMIOPTIONS_H_ */ diff --git a/Protocols/HemiPrep.h b/Protocols/HemiPrep.h index c301e736a..b140e75ed 100644 --- a/Protocols/HemiPrep.h +++ b/Protocols/HemiPrep.h @@ -9,11 +9,15 @@ #include "ReplicatedPrep.h" #include "FHEOffline/Multiplier.h" +template class HemiMatrixPrep; + template class HemiPrep : public SemiHonestRingPrep { typedef typename T::clear::FD FD; + friend class HemiMatrixPrep; + static PairwiseMachine* pairwise_machine; static Lock lock; @@ -35,6 +39,8 @@ class HemiPrep : public SemiHonestRingPrep } ~HemiPrep(); + vector*>& get_multipliers(); + void buffer_triples(); }; diff --git a/Protocols/HemiPrep.hpp b/Protocols/HemiPrep.hpp index 9aa93e0ea..6cdd75476 100644 --- a/Protocols/HemiPrep.hpp +++ b/Protocols/HemiPrep.hpp @@ -43,7 +43,7 @@ HemiPrep::~HemiPrep() } template -void HemiPrep::buffer_triples() +vector*>& HemiPrep::get_multipliers() { assert(this->proc != 0); auto& P = this->proc->P; @@ -51,7 +51,7 @@ void HemiPrep::buffer_triples() lock.lock(); if (pairwise_machine == 0 or pairwise_machine->enc_alphas.empty()) { - PlainPlayer P(this->proc->P.N, T::clear::type_char()); + PlainPlayer P(this->proc->P.N, "Hemi" + T::type_string()); if (pairwise_machine == 0) basic_setup(P); pairwise_machine->setup().covert_key_generation(P, @@ -64,7 +64,15 @@ void HemiPrep::buffer_triples() for (int i = 1; i < P.num_players(); i++) multipliers.push_back( new Multiplier(i, *pairwise_machine, P, timers)); + return multipliers; +} +template +void HemiPrep::buffer_triples() +{ + assert(this->proc != 0); + auto& P = this->proc->P; + auto& multipliers = get_multipliers(); auto& FieldD = pairwise_machine->setup().FieldD; Plaintext_ a(FieldD), b(FieldD), c(FieldD); a.randomize(G); diff --git a/Protocols/HemiShare.h b/Protocols/HemiShare.h index e51c0f61e..45dbb9949 100644 --- a/Protocols/HemiShare.h +++ b/Protocols/HemiShare.h @@ -9,6 +9,7 @@ #include "SemiShare.h" template class HemiPrep; +template class Hemi; template class HemiShare : public SemiShare @@ -21,10 +22,11 @@ class HemiShare : public SemiShare typedef DirectSemiMC Direct_MC; typedef SemiInput Input; typedef ::PrivateOutput PrivateOutput; - typedef SPDZ Protocol; + typedef typename conditional, Beaver>::type Protocol; typedef HemiPrep LivePrep; static const bool needs_ot = false; + static true_type triple_matmul; HemiShare() { @@ -37,4 +39,7 @@ class HemiShare : public SemiShare }; +template +true_type HemiShare::triple_matmul; + #endif /* PROTOCOLS_HEMISHARE_H_ */ diff --git a/Protocols/MascotPrep.h b/Protocols/MascotPrep.h index abe82baf8..4737f39e2 100644 --- a/Protocols/MascotPrep.h +++ b/Protocols/MascotPrep.h @@ -22,7 +22,6 @@ class OTPrep : public virtual BitPrep void set_protocol(typename T::Protocol& protocol); - size_t data_sent(); NamedCommStats comm_stats(); }; diff --git a/Protocols/MascotPrep.hpp b/Protocols/MascotPrep.hpp index 70ed1fac0..cef603a25 100644 --- a/Protocols/MascotPrep.hpp +++ b/Protocols/MascotPrep.hpp @@ -121,22 +121,13 @@ T Preprocessing::get_random_from_inputs(int nplayers) return res; } -template -size_t OTPrep::data_sent() -{ - size_t res = BitPrep::data_sent(); - if (triple_generator) - res += triple_generator->data_sent(); - return res; -} - template NamedCommStats OTPrep::comm_stats() { + auto res = BitPrep::comm_stats(); if (triple_generator) - return triple_generator->comm_stats(); - else - return {}; + res += triple_generator->comm_stats(); + return res; } #endif diff --git a/Protocols/Replicated.h b/Protocols/Replicated.h index 78e087122..74d0f0f3b 100644 --- a/Protocols/Replicated.h +++ b/Protocols/Replicated.h @@ -88,6 +88,15 @@ class ProtocolBase virtual void randoms(T&, int) { throw runtime_error("randoms not implemented"); } virtual void randoms_inst(vector&, const Instruction&); + template + void matmulsm(SubProcessor & proc, CheckVector& source, + const Instruction& instruction, int a, int b) + { proc.matmulsm(source, instruction, a, b); } + + template + void conv2ds(SubProcessor& proc, const Instruction& instruction) + { proc.conv2ds(instruction); } + virtual void start_exchange() { exchange(); } virtual void stop_exchange() {} diff --git a/Protocols/SemiShare.h b/Protocols/SemiShare.h index 4587b7f31..ed044c461 100644 --- a/Protocols/SemiShare.h +++ b/Protocols/SemiShare.h @@ -83,7 +83,8 @@ class SemiShare : public T, public ShareInterface return nplayers - 1; } - static SemiShare constant(const clear& other, int my_num, const T& alphai = {}) + static SemiShare constant(const clear& other, int my_num, + const T& alphai = {}, int = -1) { return SemiShare(other, my_num, alphai); } diff --git a/Protocols/ShareInterface.cpp b/Protocols/ShareInterface.cpp index c2222e879..065ad3dc5 100644 --- a/Protocols/ShareInterface.cpp +++ b/Protocols/ShareInterface.cpp @@ -6,3 +6,5 @@ #include "ShareInterface.h" const int ShareInterface::default_length; + +const false_type ShareInterface::triple_matmul; diff --git a/Protocols/ShareInterface.h b/Protocols/ShareInterface.h index f9e2d5f23..ae6e7b7dd 100644 --- a/Protocols/ShareInterface.h +++ b/Protocols/ShareInterface.h @@ -35,6 +35,8 @@ class ShareInterface static const bool has_trunc_pr = false; static const bool has_split = false; + static const false_type triple_matmul; + static const int default_length = 1; static string type_short() { return "undef"; } diff --git a/Protocols/ShareMatrix.h b/Protocols/ShareMatrix.h new file mode 100644 index 000000000..b7fdf50be --- /dev/null +++ b/Protocols/ShareMatrix.h @@ -0,0 +1,233 @@ +/* + * ShareMatrix.h + * + */ + +#ifndef PROTOCOLS_SHAREMATRIX_H_ +#define PROTOCOLS_SHAREMATRIX_H_ + +#include +using namespace std; + +#include "Share.h" +#include "FHE/AddableVector.h" + +template class MatrixMC; + +template +class ValueMatrix : public ValueInterface +{ + typedef ValueMatrix This; + +public: + int n_rows, n_cols; + AddableVector entries; + + static DataFieldType field_type() + { + return T::field_type(); + } + + ValueMatrix(int n_rows = 0, int n_cols = 0) : + n_rows(n_rows), n_cols(n_cols), entries(n_rows * n_cols) + { + check(); + } + + template + ValueMatrix(const ValueMatrix& other) : + n_rows(other.n_rows), n_cols(other.n_cols), entries(other.entries) + { + check(); + } + + void check() const + { + assert(entries.size() == size_t(n_rows * n_cols)); + } + + T& operator[](const pair& indices) + { + assert(indices.first < n_rows); + assert(indices.second < n_cols); + return entries.at(indices.first * n_cols + indices.second); + } + + const T& operator[](const pair& indices) const + { + assert(indices.first < n_rows); + assert(indices.second < n_cols); + return entries.at(indices.first * n_cols + indices.second); + } + + This& operator+=(const This& other) + { + entries += other.entries; + check(); + return *this; + } + + This operator-(const This& other) const + { + assert(entries.size() == other.entries.size()); + This res(n_rows, n_cols); + res.entries = entries - other.entries; + res.check(); + return res; + } + + This operator*(const This& other) const + { + assert(n_cols == other.n_rows); + This res(n_rows, other.n_cols); + for (int i = 0; i < n_rows; i++) + for (int j = 0; j < other.n_cols; j++) + for (int k = 0; k < n_cols; k++) + res[{i, j}] += (*this)[{i, k}] * other[{k, j}]; + res.check(); + return res; + } + + bool operator!=(const This& other) const + { + if (n_rows != other.n_rows or n_cols != other.n_cols) + return true; + return entries != other.entries; + } + + void randomize(PRNG& G) + { + entries.randomize(G); + } + + ValueMatrix transpose() const + { + ValueMatrix res(this->n_cols, this->n_rows); + for (int i = 0; i < this->n_rows; i++) + for (int j = 0; j < this->n_cols; j++) + res[{j, i}] = (*this)[{i, j}]; + return res; + } + + friend ostream& operator<<(ostream& o, const This&) + { + return o; + } +}; + +template +class ShareMatrix : public ValueMatrix, public ShareInterface +{ + typedef ShareMatrix This; + typedef ValueMatrix super; + +public: + typedef MatrixMC MAC_Check; + typedef Beaver Protocol; + typedef ::Input Input; + + typedef ValueMatrix clear; + typedef clear open_type; + typedef typename T::clear mac_key_type; + + static string type_string() + { + return "matrix"; + } + + static This constant(const clear& other, int my_num, mac_key_type key) + { + This res(other.n_rows, other.n_cols); + for (size_t i = 0; i < other.entries.size(); i++) + res.entries[i] = T::constant(other.entries[i], my_num, key); + res.check(); + return res; + } + + ShareMatrix(int n_rows = 0, int n_cols = 0) : + ValueMatrix(n_rows, n_cols) + { + } + + template + ShareMatrix(const U& other) : + super(other) + { + } + + ShareMatrix from_row(int start, int size) const + { + ShareMatrix res(min(size, this->n_rows - start), this->n_cols); + for (int i = 0; i < res.n_rows; i++) + for (int j = 0; j < res.n_cols; j++) + res[{i, j}] = (*this)[{start + i, j}]; + return res; + } + + ShareMatrix from_col(int start, int size) const + { + ShareMatrix res(this->n_rows, min(size, this->n_cols - start)); + for (int i = 0; i < res.n_rows; i++) + for (int j = 0; j < res.n_cols; j++) + res[{i, j}] = (*this)[{i, start + j}]; + return res; + } + + ShareMatrix from(int start_row, int start_col, int* sizes) const + { + ShareMatrix res(min(sizes[0], this->n_rows - start_row), + min(sizes[1], this->n_cols - start_col)); + for (int i = 0; i < res.n_rows; i++) + for (int j = 0; j < res.n_cols; j++) + res[{i, j}] = (*this)[{start_row + i, start_col + j}]; + return res; + } + + void add_from_col(int start, const ShareMatrix& other) + { + for (int i = 0; i < this->n_rows; i++) + for (int j = 0; j < other.n_cols; j++) + (*this)[{i, start + j}] += other[{i, j}]; + } +}; + +template +ShareMatrix operator*(const ValueMatrix& a, + const ShareMatrix& b) +{ + assert(a.n_cols == b.n_rows); + ShareMatrix res(a.n_rows, b.n_cols); + for (int i = 0; i < a.n_rows; i++) + for (int j = 0; j < b.n_cols; j++) + for (int k = 0; k < a.n_cols; k++) + res[{i, j}] += a[{i, k}] * b[{k, j}]; + res.check(); + return res; +} + +template +class MatrixMC : public MAC_Check_Base> +{ + typename T::MAC_Check inner; + +public: + void exchange(const Player& P) + { + inner.init_open(P); + for (auto& share : this->secrets) + { + share.check(); + for (auto& entry : share.entries) + inner.prepare_open(entry); + } + inner.exchange(P); + for (auto& share : this->secrets) + { + this->values.push_back({share.n_rows, share.n_cols}); + for (auto& entry : this->values.back().entries) + entry = inner.finalize_open(); + } + } +}; + +#endif /* PROTOCOLS_SHAREMATRIX_H_ */ diff --git a/Protocols/SohoPrep.hpp b/Protocols/SohoPrep.hpp index 331c26258..48deeadc4 100644 --- a/Protocols/SohoPrep.hpp +++ b/Protocols/SohoPrep.hpp @@ -41,7 +41,7 @@ void SohoPrep::buffer_triples() lock.lock(); if (not setup) { - PlainPlayer P(proc->P.N, T::clear::type_char()); + PlainPlayer P(proc->P.N, "Soho" + T::type_string()); basic_setup(P); } lock.unlock(); @@ -83,7 +83,7 @@ void SohoPrep::buffer_squares() lock.lock(); if (not setup) { - PlainPlayer P(proc->P.N, T::clear::type_char()); + PlainPlayer P(proc->P.N, "Soho" + T::type_string()); basic_setup(P); } lock.unlock(); diff --git a/Protocols/Spdz2kPrep.h b/Protocols/Spdz2kPrep.h index 5bcd3c6c0..ab0cc33ea 100644 --- a/Protocols/Spdz2kPrep.h +++ b/Protocols/Spdz2kPrep.h @@ -39,7 +39,7 @@ class Spdz2kPrep : public virtual MaliciousRingPrep, void get_dabit(T& a, GC::TinySecret& b); #endif - size_t data_sent(); + NamedCommStats comm_stats(); }; #endif /* PROTOCOLS_SPDZ2KPREP_H_ */ diff --git a/Protocols/Spdz2kPrep.hpp b/Protocols/Spdz2kPrep.hpp index 7899d26f0..f5c9cdce6 100644 --- a/Protocols/Spdz2kPrep.hpp +++ b/Protocols/Spdz2kPrep.hpp @@ -238,11 +238,11 @@ void MaliciousRingPrep::buffer_edabits_from_personal(bool strict, int n_bits, } template -size_t Spdz2kPrep::data_sent() +NamedCommStats Spdz2kPrep::comm_stats() { - size_t res = OTPrep::data_sent(); + auto res = OTPrep::comm_stats(); if (bit_prep) - res += bit_prep->data_sent(); + res += bit_prep->comm_stats(); return res; } diff --git a/Scripts/direct_compilation_example.py b/Scripts/direct_compilation_example.py index fe2f8ecba..84340569a 100755 --- a/Scripts/direct_compilation_example.py +++ b/Scripts/direct_compilation_example.py @@ -16,3 +16,6 @@ print_ln('%s', (sint(0) < sint(1)).reveal()) prog.finalize() + +import subprocess +subprocess.run(['./emulate.x', 'direct_compilation']) diff --git a/Scripts/yao.sh b/Scripts/yao.sh index 31506505c..d014f8952 100755 --- a/Scripts/yao.sh +++ b/Scripts/yao.sh @@ -6,9 +6,9 @@ port=$[RANDOM+1024] for i in 0 1; do IFS="" - log="yao-$*-$i" + log="$*-$[1-i]" IFS=" " - $prefix ./yao-party.x -p $i -pn $port $* 2>&1 | tee -a logs/$log & true + $prefix ./yao-party.x -p $i -pn $port $* 2>&1 | tee logs/$log & true done wait || exit 1 diff --git a/Tools/OfflineMachineBase.cpp b/Tools/OfflineMachineBase.cpp index 0f86eb518..2c7e56f2e 100644 --- a/Tools/OfflineMachineBase.cpp +++ b/Tools/OfflineMachineBase.cpp @@ -11,15 +11,13 @@ using namespace std; OfflineMachineBase::OfflineMachineBase() : - server(0), my_num(0), nplayers(0), ntriples(0), + my_num(0), nplayers(0), ntriples(0), nTriplesPerThread(0) { } OfflineMachineBase::~OfflineMachineBase() { - if (server) - delete server; } void OfflineMachineBase::parse_options(int argc, const char** argv) @@ -91,5 +89,5 @@ void OfflineMachineBase::parse_options(int argc, const char** argv) void OfflineMachineBase::start_networking_with_server(string hostname, int portnum) { - server = Server::start_networking(N, my_num, nplayers, hostname, portnum); + Server::start_networking(N, my_num, nplayers, hostname, portnum); } diff --git a/Tools/OfflineMachineBase.h b/Tools/OfflineMachineBase.h index c096c78db..0282d72a2 100644 --- a/Tools/OfflineMachineBase.h +++ b/Tools/OfflineMachineBase.h @@ -23,7 +23,6 @@ class OfflineMachineBase : virtual public OfflineParams { protected: ez::ezOptionParser opt; - Server* server; public: Names N; diff --git a/Tools/WaitQueue.h b/Tools/WaitQueue.h index 28e4e9a3f..23dfb2243 100644 --- a/Tools/WaitQueue.h +++ b/Tools/WaitQueue.h @@ -80,7 +80,7 @@ class WaitQueue T pop() { T res; - pop(res); + assert(pop(res)); return res; } diff --git a/Tools/octetStream.cpp b/Tools/octetStream.cpp index 1522b506d..26cb9a6a5 100644 --- a/Tools/octetStream.cpp +++ b/Tools/octetStream.cpp @@ -56,6 +56,11 @@ octetStream::octetStream(size_t len, const octet* source) : append(source, len); } +octetStream::octetStream(const string& other) : + octetStream(other.size(), (const octet*)other.data()) +{ +} + octetStream::octetStream(const octetStream& os) { mxlen=os.mxlen; @@ -75,6 +80,12 @@ octetStream::octetStream(FlexBuffer& buffer) } +string octetStream::str() const +{ + return string((char*) get_data(), get_length()); +} + + void octetStream::hash(octetStream& output) const { assert(output.mxlen >= crypto_generichash_blake2b_BYTES_MIN); diff --git a/Tools/octetStream.h b/Tools/octetStream.h index e4ab090c1..df920a302 100644 --- a/Tools/octetStream.h +++ b/Tools/octetStream.h @@ -34,6 +34,9 @@ using namespace std; class bigint; class FlexBuffer; +/** + * Buffer for networking communication with a pointer for sequential reading + */ class octetStream { friend class FlexBuffer; @@ -46,17 +49,23 @@ class octetStream public: + /// Increase allocation if needed void resize(size_t l); void resize_precise(size_t l); void resize_min(size_t l); void reserve(size_t l); + /// Free memory void clear(); void assign(const octetStream& os); octetStream() : len(0), mxlen(0), ptr(0), data(0) {} + /// Initial allocation octetStream(size_t maxlen); + /// Initial buffer octetStream(size_t len, const octet* source); + /// Initial buffer + octetStream(const string& other); octetStream(FlexBuffer& buffer); octetStream(const octetStream& os); octetStream& operator=(const octetStream& os) @@ -65,43 +74,58 @@ class octetStream } ~octetStream() { if(data) delete[] data; } + /// Number of bytes already read size_t get_ptr() const { return ptr; } + /// Length size_t get_length() const { return len; } + /// Allocation size_t get_max_length() const { return mxlen; } + /// Data pointer octet* get_data() const { return data; } + /// Read pointer octet* get_data_ptr() const { return data + ptr; } + /// Whether done reading bool done() const { return ptr == len; } + /// Whether empty bool empty() const { return len == 0; } + /// Bytes left to read size_t left() const { return len - ptr; } + /// Convert to string + string str() const; + + /// Hash content octetStream hash() const; // output must have length at least HASH_SIZE void hash(octetStream& output) const; // The following produces a check sum for debugging purposes bigint check_sum(int req_bytes=crypto_hash_BYTES) const; + /// Append other buffer void concat(const octetStream& os); + /// Reset reading void reset_read_head() { ptr=0; } - /* If we reset write head then we should reset the read head as well */ + /// Set length to zero but keep allocation void reset_write_head() { len=0; ptr=0; } // Move len back num void rewind_write_head(size_t num) { len-=num; } bool equals(const octetStream& a) const; + /// Equality test bool operator==(const octetStream& a) const { return equals(a); } bool operator!=(const octetStream& a) const { return not equals(a); } - /* Append NUM random bytes from dev/random */ + /// Append ``num`` random bytes void append_random(size_t num); - // Append with no padding for decoding + /// Append ``l`` bytes from ``x`` void append(const octet* x,const size_t l); octet* append(const size_t l); void append_no_resize(const octet* x,const size_t l); - // Read l octets, with no padding for decoding + /// Read ``l`` bytes to ``x`` void consume(octet* x,const size_t l); // Return pointer to next l octets and advance pointer octet* consume(size_t l); @@ -111,30 +135,45 @@ class octetStream void store_bytes(octet* x, const size_t l); //not really "bytes"... void get_bytes(octet* ans, size_t& l); //Assumes enough space in ans + /// Append 4-byte integer void store(unsigned int a) { store_int(a, 4); } + /// Append 4-byte integer void store(int a); + /// Read 4-byte integer void get(unsigned int& a) { a = get_int(4); } + /// Read 4-byte integer void get(int& a); + /// Append 8-byte integer void store(size_t a) { store_int(a, 8); } + /// Read 8-byte integer void get(size_t& a) { a = get_int(8); } + /// Append integer of ``n_bytes`` bytes void store_int(size_t a, int n_bytes); + /// Read integer of ``n_bytes`` bytes size_t get_int(int n_bytes); + /// Append integer of ``N_BYTES`` bytes template void store_int(size_t a); + /// Read integer of ``N_BYTES`` bytes template size_t get_int(); + /// Append big integer void store(const bigint& x); + /// Read big integer void get(bigint& ans); + /// Append instance of type implementing ``pack`` template void store(const T& x); + /// Read instance of type implementing ``unpack`` template T get(); + /// Read instance of type implementing ``unpack`` template void get(T& ans); @@ -144,29 +183,44 @@ class octetStream template void unserialize(T& x) { consume((octet*)&x, sizeof(x)); } + /// Append vector of type implementing ``pack`` template void store(const vector& v); + /// Read vector of type implementing ``unpack`` + /// @param v results + /// @param init initialization if required template void get(vector& v, const T& init = {}); + /// Read vector of type implementing ``unpack`` + /// if vector already has the right size template void get_no_resize(vector& v); + /// Read ``l`` bytes into separate buffer void consume(octetStream& s,size_t l) { s.resize(l); consume(s.data,l); s.len=l; } + /// Send on ``socket_num`` template void Send(T socket_num) const; + /// Receive on ``socket_num``, overwriting current content template void Receive(T socket_num); + /// Input from stream, overwriting current content void input(istream& s); + /// Output to stream void output(ostream& s); + /// Send on ``socket_num`` while receiving on ``receiving_socket``, + /// overwriting current content template void exchange(T send_socket, T receive_socket) { exchange(send_socket, receive_socket, *this); } + /// Send this buffer on ``socket_num`` while receiving + /// to ``receive_stream`` on ``receiving_socket`` template void exchange(T send_socket, T receive_socket, octetStream& receive_stream) const; diff --git a/Tools/parse.h b/Tools/parse.h index 40a59d7c3..af5e6de4c 100644 --- a/Tools/parse.h +++ b/Tools/parse.h @@ -33,6 +33,8 @@ inline void get_ints(int* res, istream& s, int count) inline void get_vector(int m, vector& start, istream& s) { + if (s.fail()) + throw runtime_error("error when parsing vector"); start.resize(m); s.read((char*) start.data(), 4 * m); for (int i = 0; i < m; i++) diff --git a/Utils/paper-example.cpp b/Utils/paper-example.cpp index f04d56673..87247fee8 100644 --- a/Utils/paper-example.cpp +++ b/Utils/paper-example.cpp @@ -68,11 +68,10 @@ template void run(char** argv, int prime_length) { // set up networking on localhost - Names N; int my_number = atoi(argv[1]); int n_parties = atoi(argv[2]); int port_base = 9999; - Server::start_networking(N, my_number, n_parties, "localhost", port_base); + Names N(my_number, n_parties, "localhost", port_base); CryptoPlayer P(N); // initialize fields diff --git a/Yao/YaoEvaluator.cpp b/Yao/YaoEvaluator.cpp index 8653de0f1..0126c8b15 100644 --- a/Yao/YaoEvaluator.cpp +++ b/Yao/YaoEvaluator.cpp @@ -20,7 +20,7 @@ YaoEvaluator::YaoEvaluator(int thread_num, YaoEvalMaster& master) : Thread>(thread_num, master), YaoCommon(master), master(master), - player(N, 0, thread_num << 24), + player(N, 0, "thread" + to_string(thread_num)), ot_ext(OTExtensionWithMatrix::setup(player, {}, RECEIVER, true)) { set_n_program_threads(master.machine.nthreads); diff --git a/Yao/YaoEvaluator.h b/Yao/YaoEvaluator.h index 4a8b24f86..074fb3400 100644 --- a/Yao/YaoEvaluator.h +++ b/Yao/YaoEvaluator.h @@ -59,8 +59,8 @@ class YaoEvaluator: public GC::Thread>, int get_n_worker_threads() { return max(1u, thread::hardware_concurrency() / master.machine.nthreads); } - size_t data_sent() - { return super::data_sent() + player.comm_stats.total_data(); } + NamedCommStats comm_stats() + { return super::comm_stats() + player.comm_stats; } }; inline void YaoEvaluator::load_gate(YaoGate& gate) diff --git a/Yao/YaoGarbler.cpp b/Yao/YaoGarbler.cpp index 2dc1b3fc7..53b0401f4 100644 --- a/Yao/YaoGarbler.cpp +++ b/Yao/YaoGarbler.cpp @@ -23,7 +23,7 @@ YaoGarbler::YaoGarbler(int thread_num, YaoGarbleMaster& master) : master(master), and_proc_timer(CLOCK_PROCESS_CPUTIME_ID), and_main_thread_timer(CLOCK_THREAD_CPUTIME_ID), - player(master.N, 1, thread_num << 24), + player(master.N, 1, "thread" + to_string(thread_num)), ot_ext(OTExtensionWithMatrix::setup(player, master.get_delta().get<__m128i>(), SENDER, true)) { @@ -112,7 +112,7 @@ void YaoGarbler::process_receiver_inputs() } } -size_t YaoGarbler::data_sent() +NamedCommStats YaoGarbler::comm_stats() { - return super::data_sent() + player.comm_stats.total_data(); + return super::comm_stats() + player.comm_stats; } diff --git a/Yao/YaoGarbler.h b/Yao/YaoGarbler.h index f1239c226..038fe432f 100644 --- a/Yao/YaoGarbler.h +++ b/Yao/YaoGarbler.h @@ -72,7 +72,7 @@ class YaoGarbler: public GC::Thread>, long get_gate_id() { return gate_id(thread_num); } - size_t data_sent(); + NamedCommStats comm_stats(); }; inline YaoGarbler& YaoGarbler::s() diff --git a/Yao/YaoPlayer.cpp b/Yao/YaoPlayer.cpp index c67f577b0..fcebe2875 100644 --- a/Yao/YaoPlayer.cpp +++ b/Yao/YaoPlayer.cpp @@ -71,7 +71,6 @@ YaoPlayer::YaoPlayer(int argc, const char** argv) } else { - throw exception(); string usage; opt.getUsage(usage); cerr << usage; @@ -94,7 +93,7 @@ YaoPlayer::YaoPlayer(int argc, const char** argv) else master = new YaoEvalMaster(continuous, online_opts); - server = Server::start_networking(master->N, my_num, 2, hostname, pnb); + Server::start_networking(master->N, my_num, 2, hostname, pnb); master->run(progname); if (my_num == 1) @@ -105,6 +104,4 @@ YaoPlayer::YaoPlayer(int argc, const char** argv) YaoPlayer::~YaoPlayer() { - if (server) - delete server; } diff --git a/Yao/YaoPlayer.h b/Yao/YaoPlayer.h index 6404d1070..a7196e981 100644 --- a/Yao/YaoPlayer.h +++ b/Yao/YaoPlayer.h @@ -13,7 +13,6 @@ class YaoPlayer { string progname; Names N; - Server* server; public: YaoPlayer(int argc, const char** argv); diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 77df49704..8aa623c88 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -5,10 +5,8 @@ trigger: - master -- cnn -pool: - vmImage: 'ubuntu-18.04' +vmImage: 'ubuntu-18.04' steps: - script: | diff --git a/compile.py b/compile.py index aeac32e43..eea78ec8d 100755 --- a/compile.py +++ b/compile.py @@ -80,6 +80,8 @@ def main(): help="faster CISC compilation mode") parser.add_option("-K", "--keep-cisc", action="store_true", dest="keep_cisc", help="don't translate CISC instructions") + parser.add_option("-l", "--flow-optimization", action="store_true", + dest="flow_optimization", help="optimize control flow") parser.add_option("-v", "--verbose", action="store_true", dest="verbose", help="more verbose output") options,args = parser.parse_args() diff --git a/doc/Compiler.rst b/doc/Compiler.rst index 78e871762..df3e13f5c 100644 --- a/doc/Compiler.rst +++ b/doc/Compiler.rst @@ -18,7 +18,8 @@ Compiler.types module reg_type, int_type, clear_type, float_type, basic_type, default_type, unreduced_type, bit_type, dynamic_array, squant, mov, - write_share_to_socket, add, mul, sintbit, from_sint + write_share_to_socket, add, mul, sintbit, from_sint, + SubMultiArray .. autoclass:: sintbit Compiler.GC.types module diff --git a/doc/Doxyfile b/doc/Doxyfile new file mode 100644 index 000000000..7fa2bafe7 --- /dev/null +++ b/doc/Doxyfile @@ -0,0 +1,2579 @@ +# Doxyfile 1.8.17 + +# This file describes the settings to be used by the documentation system +# doxygen (www.doxygen.org) for a project. +# +# All text after a double hash (##) is considered a comment and is placed in +# front of the TAG it is preceding. +# +# All text after a single hash (#) is considered a comment and will be ignored. +# The format is: +# TAG = value [value, ...] +# For lists, items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (\" \"). + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- + +# This tag specifies the encoding used for all characters in the configuration +# file that follow. The default is UTF-8 which is also the encoding used for all +# text before the first occurrence of this tag. Doxygen uses libiconv (or the +# iconv built into libc) for the transcoding. See +# https://www.gnu.org/software/libiconv/ for the list of possible encodings. +# The default value is: UTF-8. + +DOXYFILE_ENCODING = UTF-8 + +# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by +# double-quotes, unless you are using Doxywizard) that should identify the +# project for which the documentation is generated. This name is used in the +# title of most generated pages and in a few other places. +# The default value is: My Project. + +PROJECT_NAME = "My Project" + +# The PROJECT_NUMBER tag can be used to enter a project or revision number. This +# could be handy for archiving the generated documentation or if some version +# control system is used. + +PROJECT_NUMBER = + +# Using the PROJECT_BRIEF tag one can provide an optional one line description +# for a project that appears at the top of each page and should give viewer a +# quick idea about the purpose of the project. Keep the description short. + +PROJECT_BRIEF = + +# With the PROJECT_LOGO tag one can specify a logo or an icon that is included +# in the documentation. The maximum height of the logo should not exceed 55 +# pixels and the maximum width should not exceed 200 pixels. Doxygen will copy +# the logo to the output directory. + +PROJECT_LOGO = + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path +# into which the generated documentation will be written. If a relative path is +# entered, it will be relative to the location where doxygen was started. If +# left blank the current directory will be used. + +OUTPUT_DIRECTORY = + +# If the CREATE_SUBDIRS tag is set to YES then doxygen will create 4096 sub- +# directories (in 2 levels) under the output directory of each output format and +# will distribute the generated files over these directories. Enabling this +# option can be useful when feeding doxygen a huge amount of source files, where +# putting all generated files in the same directory would otherwise causes +# performance problems for the file system. +# The default value is: NO. + +CREATE_SUBDIRS = NO + +# If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII +# characters to appear in the names of generated files. If set to NO, non-ASCII +# characters will be escaped, for example _xE3_x81_x84 will be used for Unicode +# U+3044. +# The default value is: NO. + +ALLOW_UNICODE_NAMES = NO + +# The OUTPUT_LANGUAGE tag is used to specify the language in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all constant output in the proper language. +# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Catalan, Chinese, +# Chinese-Traditional, Croatian, Czech, Danish, Dutch, English (United States), +# Esperanto, Farsi (Persian), Finnish, French, German, Greek, Hungarian, +# Indonesian, Italian, Japanese, Japanese-en (Japanese with English messages), +# Korean, Korean-en (Korean with English messages), Latvian, Lithuanian, +# Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, Romanian, Russian, +# Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, Swedish, Turkish, +# Ukrainian and Vietnamese. +# The default value is: English. + +OUTPUT_LANGUAGE = English + +# The OUTPUT_TEXT_DIRECTION tag is used to specify the direction in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all generated output in the proper direction. +# Possible values are: None, LTR, RTL and Context. +# The default value is: None. + +OUTPUT_TEXT_DIRECTION = None + +# If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member +# descriptions after the members that are listed in the file and class +# documentation (similar to Javadoc). Set to NO to disable this. +# The default value is: YES. + +BRIEF_MEMBER_DESC = YES + +# If the REPEAT_BRIEF tag is set to YES, doxygen will prepend the brief +# description of a member or function before the detailed description +# +# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# brief descriptions will be completely suppressed. +# The default value is: YES. + +REPEAT_BRIEF = YES + +# This tag implements a quasi-intelligent brief description abbreviator that is +# used to form the text in various listings. Each string in this list, if found +# as the leading text of the brief description, will be stripped from the text +# and the result, after processing the whole list, is used as the annotated +# text. Otherwise, the brief description is used as-is. If left blank, the +# following values are used ($name is automatically replaced with the name of +# the entity):The $name class, The $name widget, The $name file, is, provides, +# specifies, contains, represents, a, an and the. + +ABBREVIATE_BRIEF = "The $name class" \ + "The $name widget" \ + "The $name file" \ + is \ + provides \ + specifies \ + contains \ + represents \ + a \ + an \ + the + +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# doxygen will generate a detailed section even if there is only a brief +# description. +# The default value is: NO. + +ALWAYS_DETAILED_SEC = NO + +# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all +# inherited members of a class in the documentation of that class as if those +# members were ordinary class members. Constructors, destructors and assignment +# operators of the base classes will not be shown. +# The default value is: NO. + +INLINE_INHERITED_MEMB = NO + +# If the FULL_PATH_NAMES tag is set to YES, doxygen will prepend the full path +# before files name in the file list and in the header files. If set to NO the +# shortest path that makes the file name unique will be used +# The default value is: YES. + +FULL_PATH_NAMES = YES + +# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path. +# Stripping is only done if one of the specified strings matches the left-hand +# part of the path. The tag can be used to show relative paths in the file list. +# If left blank the directory from which doxygen is run is used as the path to +# strip. +# +# Note that you can specify absolute paths here, but also relative paths, which +# will be relative from the directory where doxygen is started. +# This tag requires that the tag FULL_PATH_NAMES is set to YES. + +STRIP_FROM_PATH = + +# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the +# path mentioned in the documentation of a class, which tells the reader which +# header file to include in order to use a class. If left blank only the name of +# the header file containing the class definition is used. Otherwise one should +# specify the list of include paths that are normally passed to the compiler +# using the -I flag. + +STRIP_FROM_INC_PATH = + +# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but +# less readable) file names. This can be useful is your file systems doesn't +# support long names like on DOS, Mac, or CD-ROM. +# The default value is: NO. + +SHORT_NAMES = NO + +# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the +# first line (until the first dot) of a Javadoc-style comment as the brief +# description. If set to NO, the Javadoc-style will behave just like regular Qt- +# style comments (thus requiring an explicit @brief command for a brief +# description.) +# The default value is: NO. + +JAVADOC_AUTOBRIEF = NO + +# If the JAVADOC_BANNER tag is set to YES then doxygen will interpret a line +# such as +# /*************** +# as being the beginning of a Javadoc-style comment "banner". If set to NO, the +# Javadoc-style will behave just like regular comments and it will not be +# interpreted by doxygen. +# The default value is: NO. + +JAVADOC_BANNER = NO + +# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first +# line (until the first dot) of a Qt-style comment as the brief description. If +# set to NO, the Qt-style will behave just like regular Qt-style comments (thus +# requiring an explicit \brief command for a brief description.) +# The default value is: NO. + +QT_AUTOBRIEF = NO + +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a +# multi-line C++ special comment block (i.e. a block of //! or /// comments) as +# a brief description. This used to be the default behavior. The new default is +# to treat a multi-line C++ comment block as a detailed description. Set this +# tag to YES if you prefer the old behavior instead. +# +# Note that setting this tag to YES also means that rational rose comments are +# not recognized any more. +# The default value is: NO. + +MULTILINE_CPP_IS_BRIEF = NO + +# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the +# documentation from any documented member that it re-implements. +# The default value is: YES. + +INHERIT_DOCS = YES + +# If the SEPARATE_MEMBER_PAGES tag is set to YES then doxygen will produce a new +# page for each member. If set to NO, the documentation of a member will be part +# of the file/class/namespace that contains it. +# The default value is: NO. + +SEPARATE_MEMBER_PAGES = NO + +# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen +# uses this value to replace tabs by spaces in code fragments. +# Minimum value: 1, maximum value: 16, default value: 4. + +TAB_SIZE = 4 + +# This tag can be used to specify a number of aliases that act as commands in +# the documentation. An alias has the form: +# name=value +# For example adding +# "sideeffect=@par Side Effects:\n" +# will allow you to put the command \sideeffect (or @sideeffect) in the +# documentation, which will result in a user-defined paragraph with heading +# "Side Effects:". You can put \n's in the value part of an alias to insert +# newlines (in the resulting output). You can put ^^ in the value part of an +# alias to insert a newline as if a physical newline was in the original file. +# When you need a literal { or } or , in the value part of an alias you have to +# escape them by means of a backslash (\), this can lead to conflicts with the +# commands \{ and \} for these it is advised to use the version @{ and @} or use +# a double escape (\\{ and \\}) + +ALIASES = + +# This tag can be used to specify a number of word-keyword mappings (TCL only). +# A mapping has the form "name=value". For example adding "class=itcl::class" +# will allow you to use the command class in the itcl::class meaning. + +TCL_SUBST = + +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources +# only. Doxygen will then generate output that is more tailored for C. For +# instance, some of the names that are used will be different. The list of all +# members will be omitted, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_FOR_C = NO + +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or +# Python sources only. Doxygen will then generate output that is more tailored +# for that language. For instance, namespaces will be presented as packages, +# qualified scopes will look different, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_JAVA = NO + +# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran +# sources. Doxygen will then generate output that is tailored for Fortran. +# The default value is: NO. + +OPTIMIZE_FOR_FORTRAN = NO + +# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL +# sources. Doxygen will then generate output that is tailored for VHDL. +# The default value is: NO. + +OPTIMIZE_OUTPUT_VHDL = NO + +# Set the OPTIMIZE_OUTPUT_SLICE tag to YES if your project consists of Slice +# sources only. Doxygen will then generate output that is more tailored for that +# language. For instance, namespaces will be presented as modules, types will be +# separated into more groups, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_SLICE = NO + +# Doxygen selects the parser to use depending on the extension of the files it +# parses. With this tag you can assign which parser to use for a given +# extension. Doxygen has a built-in mapping, but you can override or extend it +# using this tag. The format is ext=language, where ext is a file extension, and +# language is one of the parsers supported by doxygen: IDL, Java, JavaScript, +# Csharp (C#), C, C++, D, PHP, md (Markdown), Objective-C, Python, Slice, +# Fortran (fixed format Fortran: FortranFixed, free formatted Fortran: +# FortranFree, unknown formatted Fortran: Fortran. In the later case the parser +# tries to guess whether the code is fixed or free formatted code, this is the +# default for Fortran type files), VHDL, tcl. For instance to make doxygen treat +# .inc files as Fortran files (default is PHP), and .f files as C (default is +# Fortran), use: inc=Fortran f=C. +# +# Note: For files without extension you can use no_extension as a placeholder. +# +# Note that for custom extensions you also need to set FILE_PATTERNS otherwise +# the files are not read by doxygen. + +EXTENSION_MAPPING = + +# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments +# according to the Markdown format, which allows for more readable +# documentation. See https://daringfireball.net/projects/markdown/ for details. +# The output of markdown processing is further processed by doxygen, so you can +# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in +# case of backward compatibilities issues. +# The default value is: YES. + +MARKDOWN_SUPPORT = YES + +# When the TOC_INCLUDE_HEADINGS tag is set to a non-zero value, all headings up +# to that level are automatically included in the table of contents, even if +# they do not have an id attribute. +# Note: This feature currently applies only to Markdown headings. +# Minimum value: 0, maximum value: 99, default value: 5. +# This tag requires that the tag MARKDOWN_SUPPORT is set to YES. + +TOC_INCLUDE_HEADINGS = 5 + +# When enabled doxygen tries to link words that correspond to documented +# classes, or namespaces to their corresponding documentation. Such a link can +# be prevented in individual cases by putting a % sign in front of the word or +# globally by setting AUTOLINK_SUPPORT to NO. +# The default value is: YES. + +AUTOLINK_SUPPORT = YES + +# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want +# to include (a tag file for) the STL sources as input, then you should set this +# tag to YES in order to let doxygen match functions declarations and +# definitions whose arguments contain STL classes (e.g. func(std::string); +# versus func(std::string) {}). This also make the inheritance and collaboration +# diagrams that involve STL classes more complete and accurate. +# The default value is: NO. + +BUILTIN_STL_SUPPORT = NO + +# If you use Microsoft's C++/CLI language, you should set this option to YES to +# enable parsing support. +# The default value is: NO. + +CPP_CLI_SUPPORT = NO + +# Set the SIP_SUPPORT tag to YES if your project consists of sip (see: +# https://www.riverbankcomputing.com/software/sip/intro) sources only. Doxygen +# will parse them like normal C++ but will assume all classes use public instead +# of private inheritance when no explicit protection keyword is present. +# The default value is: NO. + +SIP_SUPPORT = NO + +# For Microsoft's IDL there are propget and propput attributes to indicate +# getter and setter methods for a property. Setting this option to YES will make +# doxygen to replace the get and set methods by a property in the documentation. +# This will only work if the methods are indeed getting or setting a simple +# type. If this is not the case, or you want to show the methods anyway, you +# should set this option to NO. +# The default value is: YES. + +IDL_PROPERTY_SUPPORT = YES + +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES then doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default +# all members of a group must be documented explicitly. +# The default value is: NO. + +DISTRIBUTE_GROUP_DOC = NO + +# If one adds a struct or class to a group and this option is enabled, then also +# any nested class or struct is added to the same group. By default this option +# is disabled and one has to add nested compounds explicitly via \ingroup. +# The default value is: NO. + +GROUP_NESTED_COMPOUNDS = NO + +# Set the SUBGROUPING tag to YES to allow class member groups of the same type +# (for instance a group of public functions) to be put as a subgroup of that +# type (e.g. under the Public Functions section). Set it to NO to prevent +# subgrouping. Alternatively, this can be done per class using the +# \nosubgrouping command. +# The default value is: YES. + +SUBGROUPING = YES + +# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions +# are shown inside the group in which they are included (e.g. using \ingroup) +# instead of on a separate page (for HTML and Man pages) or section (for LaTeX +# and RTF). +# +# Note that this feature does not work in combination with +# SEPARATE_MEMBER_PAGES. +# The default value is: NO. + +INLINE_GROUPED_CLASSES = NO + +# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions +# with only public data fields or simple typedef fields will be shown inline in +# the documentation of the scope in which they are defined (i.e. file, +# namespace, or group documentation), provided this scope is documented. If set +# to NO, structs, classes, and unions are shown on a separate page (for HTML and +# Man pages) or section (for LaTeX and RTF). +# The default value is: NO. + +INLINE_SIMPLE_STRUCTS = NO + +# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or +# enum is documented as struct, union, or enum with the name of the typedef. So +# typedef struct TypeS {} TypeT, will appear in the documentation as a struct +# with name TypeT. When disabled the typedef will appear as a member of a file, +# namespace, or class. And the struct will be named TypeS. This can typically be +# useful for C code in case the coding convention dictates that all compound +# types are typedef'ed and only the typedef is referenced, never the tag name. +# The default value is: NO. + +TYPEDEF_HIDES_STRUCT = NO + +# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This +# cache is used to resolve symbols given their name and scope. Since this can be +# an expensive process and often the same symbol appears multiple times in the +# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small +# doxygen will become slower. If the cache is too large, memory is wasted. The +# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range +# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536 +# symbols. At the end of a run doxygen will report the cache usage and suggest +# the optimal cache size from a speed point of view. +# Minimum value: 0, maximum value: 9, default value: 0. + +LOOKUP_CACHE_SIZE = 0 + +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- + +# If the EXTRACT_ALL tag is set to YES, doxygen will assume all entities in +# documentation are documented, even if no documentation was available. Private +# class members and static file members will be hidden unless the +# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES. +# Note: This will also disable the warnings about undocumented members that are +# normally produced when WARNINGS is set to YES. +# The default value is: NO. + +EXTRACT_ALL = NO + +# If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will +# be included in the documentation. +# The default value is: NO. + +EXTRACT_PRIVATE = NO + +# If the EXTRACT_PRIV_VIRTUAL tag is set to YES, documented private virtual +# methods of a class will be included in the documentation. +# The default value is: NO. + +EXTRACT_PRIV_VIRTUAL = NO + +# If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal +# scope will be included in the documentation. +# The default value is: NO. + +EXTRACT_PACKAGE = NO + +# If the EXTRACT_STATIC tag is set to YES, all static members of a file will be +# included in the documentation. +# The default value is: NO. + +EXTRACT_STATIC = NO + +# If the EXTRACT_LOCAL_CLASSES tag is set to YES, classes (and structs) defined +# locally in source files will be included in the documentation. If set to NO, +# only classes defined in header files are included. Does not have any effect +# for Java sources. +# The default value is: YES. + +EXTRACT_LOCAL_CLASSES = YES + +# This flag is only useful for Objective-C code. If set to YES, local methods, +# which are defined in the implementation section but not in the interface are +# included in the documentation. If set to NO, only methods in the interface are +# included. +# The default value is: NO. + +EXTRACT_LOCAL_METHODS = NO + +# If this flag is set to YES, the members of anonymous namespaces will be +# extracted and appear in the documentation as a namespace called +# 'anonymous_namespace{file}', where file will be replaced with the base name of +# the file that contains the anonymous namespace. By default anonymous namespace +# are hidden. +# The default value is: NO. + +EXTRACT_ANON_NSPACES = NO + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all +# undocumented members inside documented classes or files. If set to NO these +# members will be included in the various overviews, but no documentation +# section is generated. This option has no effect if EXTRACT_ALL is enabled. +# The default value is: NO. + +HIDE_UNDOC_MEMBERS = NO + +# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. If set +# to NO, these classes will be included in the various overviews. This option +# has no effect if EXTRACT_ALL is enabled. +# The default value is: NO. + +HIDE_UNDOC_CLASSES = NO + +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend +# declarations. If set to NO, these declarations will be included in the +# documentation. +# The default value is: NO. + +HIDE_FRIEND_COMPOUNDS = NO + +# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any +# documentation blocks found inside the body of a function. If set to NO, these +# blocks will be appended to the function's detailed documentation block. +# The default value is: NO. + +HIDE_IN_BODY_DOCS = NO + +# The INTERNAL_DOCS tag determines if documentation that is typed after a +# \internal command is included. If the tag is set to NO then the documentation +# will be excluded. Set it to YES to include the internal documentation. +# The default value is: NO. + +INTERNAL_DOCS = NO + +# If the CASE_SENSE_NAMES tag is set to NO then doxygen will only generate file +# names in lower-case letters. If set to YES, upper-case letters are also +# allowed. This is useful if you have classes or files whose names only differ +# in case and if your file system supports case sensitive file names. Windows +# (including Cygwin) ands Mac users are advised to set this option to NO. +# The default value is: system dependent. + +CASE_SENSE_NAMES = YES + +# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with +# their full class and namespace scopes in the documentation. If set to YES, the +# scope will be hidden. +# The default value is: NO. + +HIDE_SCOPE_NAMES = NO + +# If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then doxygen will +# append additional text to a page's title, such as Class Reference. If set to +# YES the compound reference will be hidden. +# The default value is: NO. + +HIDE_COMPOUND_REFERENCE= NO + +# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of +# the files that are included by a file in the documentation of that file. +# The default value is: YES. + +SHOW_INCLUDE_FILES = YES + +# If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each +# grouped member an include statement to the documentation, telling the reader +# which file to include in order to use the member. +# The default value is: NO. + +SHOW_GROUPED_MEMB_INC = NO + +# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include +# files with double quotes in the documentation rather than with sharp brackets. +# The default value is: NO. + +FORCE_LOCAL_INCLUDES = NO + +# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the +# documentation for inline members. +# The default value is: YES. + +INLINE_INFO = YES + +# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the +# (detailed) documentation of file and class members alphabetically by member +# name. If set to NO, the members will appear in declaration order. +# The default value is: YES. + +SORT_MEMBER_DOCS = YES + +# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief +# descriptions of file, namespace and class members alphabetically by member +# name. If set to NO, the members will appear in declaration order. Note that +# this will also influence the order of the classes in the class list. +# The default value is: NO. + +SORT_BRIEF_DOCS = NO + +# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the +# (brief and detailed) documentation of class members so that constructors and +# destructors are listed first. If set to NO the constructors will appear in the +# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS. +# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief +# member documentation. +# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting +# detailed member documentation. +# The default value is: NO. + +SORT_MEMBERS_CTORS_1ST = NO + +# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy +# of group names into alphabetical order. If set to NO the group names will +# appear in their defined order. +# The default value is: NO. + +SORT_GROUP_NAMES = NO + +# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by +# fully-qualified names, including namespaces. If set to NO, the class list will +# be sorted only by class name, not including the namespace part. +# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. +# Note: This option applies only to the class list, not to the alphabetical +# list. +# The default value is: NO. + +SORT_BY_SCOPE_NAME = NO + +# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper +# type resolution of all parameters of a function it will reject a match between +# the prototype and the implementation of a member function even if there is +# only one candidate or it is obvious which candidate to choose by doing a +# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still +# accept a match between prototype and implementation in such cases. +# The default value is: NO. + +STRICT_PROTO_MATCHING = NO + +# The GENERATE_TODOLIST tag can be used to enable (YES) or disable (NO) the todo +# list. This list is created by putting \todo commands in the documentation. +# The default value is: YES. + +GENERATE_TODOLIST = YES + +# The GENERATE_TESTLIST tag can be used to enable (YES) or disable (NO) the test +# list. This list is created by putting \test commands in the documentation. +# The default value is: YES. + +GENERATE_TESTLIST = YES + +# The GENERATE_BUGLIST tag can be used to enable (YES) or disable (NO) the bug +# list. This list is created by putting \bug commands in the documentation. +# The default value is: YES. + +GENERATE_BUGLIST = YES + +# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or disable (NO) +# the deprecated list. This list is created by putting \deprecated commands in +# the documentation. +# The default value is: YES. + +GENERATE_DEPRECATEDLIST= YES + +# The ENABLED_SECTIONS tag can be used to enable conditional documentation +# sections, marked by \if ... \endif and \cond +# ... \endcond blocks. + +ENABLED_SECTIONS = + +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the +# initial value of a variable or macro / define can have for it to appear in the +# documentation. If the initializer consists of more lines than specified here +# it will be hidden. Use a value of 0 to hide initializers completely. The +# appearance of the value of individual variables and macros / defines can be +# controlled using \showinitializer or \hideinitializer command in the +# documentation regardless of this setting. +# Minimum value: 0, maximum value: 10000, default value: 30. + +MAX_INITIALIZER_LINES = 30 + +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at +# the bottom of the documentation of classes and structs. If set to YES, the +# list will mention the files that were used to generate the documentation. +# The default value is: YES. + +SHOW_USED_FILES = YES + +# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This +# will remove the Files entry from the Quick Index and from the Folder Tree View +# (if specified). +# The default value is: YES. + +SHOW_FILES = YES + +# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces +# page. This will remove the Namespaces entry from the Quick Index and from the +# Folder Tree View (if specified). +# The default value is: YES. + +SHOW_NAMESPACES = YES + +# The FILE_VERSION_FILTER tag can be used to specify a program or script that +# doxygen should invoke to get the current version for each file (typically from +# the version control system). Doxygen will invoke the program by executing (via +# popen()) the command command input-file, where command is the value of the +# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided +# by doxygen. Whatever the program writes to standard output is used as the file +# version. For an example see the documentation. + +FILE_VERSION_FILTER = + +# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed +# by doxygen. The layout file controls the global structure of the generated +# output files in an output format independent way. To create the layout file +# that represents doxygen's defaults, run doxygen with the -l option. You can +# optionally specify a file name after the option, if omitted DoxygenLayout.xml +# will be used as the name of the layout file. +# +# Note that if you run doxygen from a directory containing a file called +# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE +# tag is left empty. + +LAYOUT_FILE = + +# The CITE_BIB_FILES tag can be used to specify one or more bib files containing +# the reference definitions. This must be a list of .bib files. The .bib +# extension is automatically appended if omitted. This requires the bibtex tool +# to be installed. See also https://en.wikipedia.org/wiki/BibTeX for more info. +# For LaTeX the style of the bibliography can be controlled using +# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the +# search path. See also \cite for info how to create references. + +CITE_BIB_FILES = + +#--------------------------------------------------------------------------- +# Configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +# The QUIET tag can be used to turn on/off the messages that are generated to +# standard output by doxygen. If QUIET is set to YES this implies that the +# messages are off. +# The default value is: NO. + +QUIET = NO + +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated to standard error (stderr) by doxygen. If WARNINGS is set to YES +# this implies that the warnings are on. +# +# Tip: Turn warnings on while writing the documentation. +# The default value is: YES. + +WARNINGS = YES + +# If the WARN_IF_UNDOCUMENTED tag is set to YES then doxygen will generate +# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag +# will automatically be disabled. +# The default value is: YES. + +WARN_IF_UNDOCUMENTED = NO + +# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for +# potential errors in the documentation, such as not documenting some parameters +# in a documented function, or documenting parameters that don't exist or using +# markup commands wrongly. +# The default value is: YES. + +WARN_IF_DOC_ERROR = YES + +# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that +# are documented, but have no documentation for their parameters or return +# value. If set to NO, doxygen will only warn about wrong or incomplete +# parameter documentation, but not about the absence of documentation. If +# EXTRACT_ALL is set to YES then this flag will automatically be disabled. +# The default value is: NO. + +WARN_NO_PARAMDOC = NO + +# If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when +# a warning is encountered. +# The default value is: NO. + +WARN_AS_ERROR = YES + +# The WARN_FORMAT tag determines the format of the warning messages that doxygen +# can produce. The string should contain the $file, $line, and $text tags, which +# will be replaced by the file and line number from which the warning originated +# and the warning text. Optionally the format may contain $version, which will +# be replaced by the version of the file (if it could be obtained via +# FILE_VERSION_FILTER) +# The default value is: $file:$line: $text. + +WARN_FORMAT = "$file:$line: $text" + +# The WARN_LOGFILE tag can be used to specify a file to which warning and error +# messages should be written. If left blank the output is written to standard +# error (stderr). + +WARN_LOGFILE = + +#--------------------------------------------------------------------------- +# Configuration options related to the input files +#--------------------------------------------------------------------------- + +# The INPUT tag is used to specify the files and/or directories that contain +# documented source files. You may enter file names like myfile.cpp or +# directories like /usr/src/myproject. Separate the files or directories with +# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING +# Note: If this tag is empty the current directory is searched. + +INPUT = ../Networking ../Tools/octetStream.h + +# This tag can be used to specify the character encoding of the source files +# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses +# libiconv (or the iconv built into libc) for the transcoding. See the libiconv +# documentation (see: https://www.gnu.org/software/libiconv/) for the list of +# possible encodings. +# The default value is: UTF-8. + +INPUT_ENCODING = UTF-8 + +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and +# *.h) to filter out the source-files in the directories. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# read by doxygen. +# +# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp, +# *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, +# *.hh, *.hxx, *.hpp, *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc, +# *.m, *.markdown, *.md, *.mm, *.dox (to be provided as doxygen C comment), +# *.doc (to be provided as doxygen C comment), *.txt (to be provided as doxygen +# C comment), *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, *.f, *.for, *.tcl, *.vhd, +# *.vhdl, *.ucf, *.qsf and *.ice. + +FILE_PATTERNS = *.c \ + *.cc \ + *.cxx \ + *.cpp \ + *.c++ \ + *.java \ + *.ii \ + *.ixx \ + *.ipp \ + *.i++ \ + *.inl \ + *.idl \ + *.ddl \ + *.odl \ + *.h \ + *.hh \ + *.hxx \ + *.hpp \ + *.h++ \ + *.cs \ + *.d \ + *.php \ + *.php4 \ + *.php5 \ + *.phtml \ + *.inc \ + *.m \ + *.markdown \ + *.md \ + *.mm \ + *.dox \ + *.doc \ + *.txt \ + *.py \ + *.pyw \ + *.f90 \ + *.f95 \ + *.f03 \ + *.f08 \ + *.f \ + *.for \ + *.tcl \ + *.vhd \ + *.vhdl \ + *.ucf \ + *.qsf \ + *.ice + +# The RECURSIVE tag can be used to specify whether or not subdirectories should +# be searched for input files as well. +# The default value is: NO. + +RECURSIVE = NO + +# The EXCLUDE tag can be used to specify files and/or directories that should be +# excluded from the INPUT source files. This way you can easily exclude a +# subdirectory from a directory tree whose root is specified with the INPUT tag. +# +# Note that relative paths are relative to the directory from which doxygen is +# run. + +EXCLUDE = + +# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or +# directories that are symbolic links (a Unix file system feature) are excluded +# from the input. +# The default value is: NO. + +EXCLUDE_SYMLINKS = NO + +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude +# certain files from those directories. +# +# Note that the wildcards are matched against the file with absolute path, so to +# exclude all test directories for example use the pattern */test/* + +EXCLUDE_PATTERNS = + +# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names +# (namespaces, classes, functions, etc.) that should be excluded from the +# output. The symbol name can be a fully qualified name, a word, or if the +# wildcard * is used, a substring. Examples: ANamespace, AClass, +# AClass::ANamespace, ANamespace::*Test +# +# Note that the wildcards are matched against the file with absolute path, so to +# exclude all test directories use the pattern */test/* + +EXCLUDE_SYMBOLS = + +# The EXAMPLE_PATH tag can be used to specify one or more files or directories +# that contain example code fragments that are included (see the \include +# command). + +EXAMPLE_PATH = + +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and +# *.h) to filter out the source-files in the directories. If left blank all +# files are included. + +EXAMPLE_PATTERNS = * + +# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be +# searched for input files to be used with the \include or \dontinclude commands +# irrespective of the value of the RECURSIVE tag. +# The default value is: NO. + +EXAMPLE_RECURSIVE = NO + +# The IMAGE_PATH tag can be used to specify one or more files or directories +# that contain images that are to be included in the documentation (see the +# \image command). + +IMAGE_PATH = + +# The INPUT_FILTER tag can be used to specify a program that doxygen should +# invoke to filter for each input file. Doxygen will invoke the filter program +# by executing (via popen()) the command: +# +# +# +# where is the value of the INPUT_FILTER tag, and is the +# name of an input file. Doxygen will then use the output that the filter +# program writes to standard output. If FILTER_PATTERNS is specified, this tag +# will be ignored. +# +# Note that the filter must not add or remove lines; it is applied before the +# code is scanned, but not when the output code is generated. If lines are added +# or removed, the anchors will not be placed correctly. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# properly processed by doxygen. + +INPUT_FILTER = + +# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern +# basis. Doxygen will compare the file name with each pattern and apply the +# filter if there is a match. The filters are a list of the form: pattern=filter +# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how +# filters are used. If the FILTER_PATTERNS tag is empty or if none of the +# patterns match the file name, INPUT_FILTER is applied. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# properly processed by doxygen. + +FILTER_PATTERNS = + +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using +# INPUT_FILTER) will also be used to filter the input files that are used for +# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES). +# The default value is: NO. + +FILTER_SOURCE_FILES = NO + +# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file +# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and +# it is also possible to disable source filtering for a specific pattern using +# *.ext= (so without naming a filter). +# This tag requires that the tag FILTER_SOURCE_FILES is set to YES. + +FILTER_SOURCE_PATTERNS = + +# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that +# is part of the input, its contents will be placed on the main page +# (index.html). This can be useful if you have a project on for instance GitHub +# and want to reuse the introduction page also for the doxygen output. + +USE_MDFILE_AS_MAINPAGE = + +#--------------------------------------------------------------------------- +# Configuration options related to source browsing +#--------------------------------------------------------------------------- + +# If the SOURCE_BROWSER tag is set to YES then a list of source files will be +# generated. Documented entities will be cross-referenced with these sources. +# +# Note: To get rid of all source code in the generated output, make sure that +# also VERBATIM_HEADERS is set to NO. +# The default value is: NO. + +SOURCE_BROWSER = NO + +# Setting the INLINE_SOURCES tag to YES will include the body of functions, +# classes and enums directly into the documentation. +# The default value is: NO. + +INLINE_SOURCES = NO + +# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any +# special comment blocks from generated source code fragments. Normal C, C++ and +# Fortran comments will always remain visible. +# The default value is: YES. + +STRIP_CODE_COMMENTS = YES + +# If the REFERENCED_BY_RELATION tag is set to YES then for each documented +# entity all documented functions referencing it will be listed. +# The default value is: NO. + +REFERENCED_BY_RELATION = NO + +# If the REFERENCES_RELATION tag is set to YES then for each documented function +# all documented entities called/used by that function will be listed. +# The default value is: NO. + +REFERENCES_RELATION = NO + +# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set +# to YES then the hyperlinks from functions in REFERENCES_RELATION and +# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will +# link to the documentation. +# The default value is: YES. + +REFERENCES_LINK_SOURCE = YES + +# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the +# source code will show a tooltip with additional information such as prototype, +# brief description and links to the definition and documentation. Since this +# will make the HTML file larger and loading of large files a bit slower, you +# can opt to disable this feature. +# The default value is: YES. +# This tag requires that the tag SOURCE_BROWSER is set to YES. + +SOURCE_TOOLTIPS = YES + +# If the USE_HTAGS tag is set to YES then the references to source code will +# point to the HTML generated by the htags(1) tool instead of doxygen built-in +# source browser. The htags tool is part of GNU's global source tagging system +# (see https://www.gnu.org/software/global/global.html). You will need version +# 4.8.6 or higher. +# +# To use it do the following: +# - Install the latest version of global +# - Enable SOURCE_BROWSER and USE_HTAGS in the configuration file +# - Make sure the INPUT points to the root of the source tree +# - Run doxygen as normal +# +# Doxygen will invoke htags (and that will in turn invoke gtags), so these +# tools must be available from the command line (i.e. in the search path). +# +# The result: instead of the source browser generated by doxygen, the links to +# source code will now point to the output of htags. +# The default value is: NO. +# This tag requires that the tag SOURCE_BROWSER is set to YES. + +USE_HTAGS = NO + +# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a +# verbatim copy of the header file for each class for which an include is +# specified. Set to NO to disable this. +# See also: Section \class. +# The default value is: YES. + +VERBATIM_HEADERS = YES + +# If the CLANG_ASSISTED_PARSING tag is set to YES then doxygen will use the +# clang parser (see: http://clang.llvm.org/) for more accurate parsing at the +# cost of reduced performance. This can be particularly helpful with template +# rich C++ code for which doxygen's built-in parser lacks the necessary type +# information. +# Note: The availability of this option depends on whether or not doxygen was +# generated with the -Duse_libclang=ON option for CMake. +# The default value is: NO. + +CLANG_ASSISTED_PARSING = NO + +# If clang assisted parsing is enabled you can provide the compiler with command +# line options that you would normally use when invoking the compiler. Note that +# the include paths will already be set by doxygen for the files and directories +# specified with INPUT and INCLUDE_PATH. +# This tag requires that the tag CLANG_ASSISTED_PARSING is set to YES. + +CLANG_OPTIONS = + +# If clang assisted parsing is enabled you can provide the clang parser with the +# path to the compilation database (see: +# http://clang.llvm.org/docs/HowToSetupToolingForLLVM.html) used when the files +# were built. This is equivalent to specifying the "-p" option to a clang tool, +# such as clang-check. These options will then be passed to the parser. +# Note: The availability of this option depends on whether or not doxygen was +# generated with the -Duse_libclang=ON option for CMake. + +CLANG_DATABASE_PATH = + +#--------------------------------------------------------------------------- +# Configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- + +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all +# compounds will be generated. Enable this if the project contains a lot of +# classes, structs, unions or interfaces. +# The default value is: YES. + +ALPHABETICAL_INDEX = YES + +# The COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns in +# which the alphabetical index list will be split. +# Minimum value: 1, maximum value: 20, default value: 5. +# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. + +COLS_IN_ALPHA_INDEX = 5 + +# In case all classes in a project start with a common prefix, all classes will +# be put under the same header in the alphabetical index. The IGNORE_PREFIX tag +# can be used to specify a prefix (or a list of prefixes) that should be ignored +# while generating the index headers. +# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. + +IGNORE_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the HTML output +#--------------------------------------------------------------------------- + +# If the GENERATE_HTML tag is set to YES, doxygen will generate HTML output +# The default value is: YES. + +GENERATE_HTML = NO + +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a +# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of +# it. +# The default directory is: html. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_OUTPUT = html + +# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each +# generated HTML page (for example: .htm, .php, .asp). +# The default value is: .html. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FILE_EXTENSION = .html + +# The HTML_HEADER tag can be used to specify a user-defined HTML header file for +# each generated HTML page. If the tag is left blank doxygen will generate a +# standard header. +# +# To get valid HTML the header file that includes any scripts and style sheets +# that doxygen needs, which is dependent on the configuration options used (e.g. +# the setting GENERATE_TREEVIEW). It is highly recommended to start with a +# default header using +# doxygen -w html new_header.html new_footer.html new_stylesheet.css +# YourConfigFile +# and then modify the file new_header.html. See also section "Doxygen usage" +# for information on how to generate the default header that doxygen normally +# uses. +# Note: The header is subject to change so you typically have to regenerate the +# default header when upgrading to a newer version of doxygen. For a description +# of the possible markers and block names see the documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_HEADER = + +# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each +# generated HTML page. If the tag is left blank doxygen will generate a standard +# footer. See HTML_HEADER for more information on how to generate a default +# footer and what special commands can be used inside the footer. See also +# section "Doxygen usage" for information on how to generate the default footer +# that doxygen normally uses. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FOOTER = + +# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style +# sheet that is used by each HTML page. It can be used to fine-tune the look of +# the HTML output. If left blank doxygen will generate a default style sheet. +# See also section "Doxygen usage" for information on how to generate the style +# sheet that doxygen normally uses. +# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as +# it is more robust and this tag (HTML_STYLESHEET) will in the future become +# obsolete. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_STYLESHEET = + +# The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined +# cascading style sheets that are included after the standard style sheets +# created by doxygen. Using this option one can overrule certain style aspects. +# This is preferred over using HTML_STYLESHEET since it does not replace the +# standard style sheet and is therefore more robust against future updates. +# Doxygen will copy the style sheet files to the output directory. +# Note: The order of the extra style sheet files is of importance (e.g. the last +# style sheet in the list overrules the setting of the previous ones in the +# list). For an example see the documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_STYLESHEET = + +# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or +# other source files which should be copied to the HTML output directory. Note +# that these files will be copied to the base HTML output directory. Use the +# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these +# files. In the HTML_STYLESHEET file, use the file name only. Also note that the +# files will be copied as-is; there are no commands or markers available. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_FILES = + +# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen +# will adjust the colors in the style sheet and background images according to +# this color. Hue is specified as an angle on a colorwheel, see +# https://en.wikipedia.org/wiki/Hue for more information. For instance the value +# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300 +# purple, and 360 is red again. +# Minimum value: 0, maximum value: 359, default value: 220. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_HUE = 220 + +# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors +# in the HTML output. For a value of 0 the output will use grayscales only. A +# value of 255 will produce the most vivid colors. +# Minimum value: 0, maximum value: 255, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_SAT = 100 + +# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the +# luminance component of the colors in the HTML output. Values below 100 +# gradually make the output lighter, whereas values above 100 make the output +# darker. The value divided by 100 is the actual gamma applied, so 80 represents +# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not +# change the gamma. +# Minimum value: 40, maximum value: 240, default value: 80. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_GAMMA = 80 + +# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML +# page will contain the date and time when the page was generated. Setting this +# to YES can help to show when doxygen was last run and thus if the +# documentation is up to date. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_TIMESTAMP = NO + +# If the HTML_DYNAMIC_MENUS tag is set to YES then the generated HTML +# documentation will contain a main index with vertical navigation menus that +# are dynamically created via JavaScript. If disabled, the navigation index will +# consists of multiple levels of tabs that are statically embedded in every HTML +# page. Disable this option to support browsers that do not have JavaScript, +# like the Qt help browser. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_DYNAMIC_MENUS = YES + +# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML +# documentation will contain sections that can be hidden and shown after the +# page has loaded. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_DYNAMIC_SECTIONS = NO + +# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries +# shown in the various tree structured indices initially; the user can expand +# and collapse entries dynamically later on. Doxygen will expand the tree to +# such a level that at most the specified number of entries are visible (unless +# a fully collapsed tree already exceeds this amount). So setting the number of +# entries 1 will produce a full collapsed tree by default. 0 is a special value +# representing an infinite number of entries and will result in a full expanded +# tree by default. +# Minimum value: 0, maximum value: 9999, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_INDEX_NUM_ENTRIES = 100 + +# If the GENERATE_DOCSET tag is set to YES, additional index files will be +# generated that can be used as input for Apple's Xcode 3 integrated development +# environment (see: https://developer.apple.com/xcode/), introduced with OSX +# 10.5 (Leopard). To create a documentation set, doxygen will generate a +# Makefile in the HTML output directory. Running make will produce the docset in +# that directory and running make install will install the docset in +# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at +# startup. See https://developer.apple.com/library/archive/featuredarticles/Doxy +# genXcode/_index.html for more information. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_DOCSET = NO + +# This tag determines the name of the docset feed. A documentation feed provides +# an umbrella under which multiple documentation sets from a single provider +# (such as a company or product suite) can be grouped. +# The default value is: Doxygen generated docs. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_FEEDNAME = "Doxygen generated docs" + +# This tag specifies a string that should uniquely identify the documentation +# set bundle. This should be a reverse domain-name style string, e.g. +# com.mycompany.MyDocSet. Doxygen will append .docset to the name. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_BUNDLE_ID = org.doxygen.Project + +# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify +# the documentation publisher. This should be a reverse domain-name style +# string, e.g. com.mycompany.MyDocSet.documentation. +# The default value is: org.doxygen.Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_PUBLISHER_ID = org.doxygen.Publisher + +# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher. +# The default value is: Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_PUBLISHER_NAME = Publisher + +# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three +# additional HTML index files: index.hhp, index.hhc, and index.hhk. The +# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop +# (see: https://www.microsoft.com/en-us/download/details.aspx?id=21138) on +# Windows. +# +# The HTML Help Workshop contains a compiler that can convert all HTML output +# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML +# files are now used as the Windows 98 help format, and will replace the old +# Windows help format (.hlp) on all Windows platforms in the future. Compressed +# HTML files also contain an index, a table of contents, and you can search for +# words in the documentation. The HTML workshop also contains a viewer for +# compressed HTML files. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_HTMLHELP = NO + +# The CHM_FILE tag can be used to specify the file name of the resulting .chm +# file. You can add a path in front of the file if the result should not be +# written to the html output directory. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +CHM_FILE = + +# The HHC_LOCATION tag can be used to specify the location (absolute path +# including file name) of the HTML help compiler (hhc.exe). If non-empty, +# doxygen will try to run the HTML help compiler on the generated index.hhp. +# The file has to be specified with full path. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +HHC_LOCATION = + +# The GENERATE_CHI flag controls if a separate .chi index file is generated +# (YES) or that it should be included in the master .chm file (NO). +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +GENERATE_CHI = NO + +# The CHM_INDEX_ENCODING is used to encode HtmlHelp index (hhk), content (hhc) +# and project file content. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +CHM_INDEX_ENCODING = + +# The BINARY_TOC flag controls whether a binary table of contents is generated +# (YES) or a normal table of contents (NO) in the .chm file. Furthermore it +# enables the Previous and Next buttons. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +BINARY_TOC = NO + +# The TOC_EXPAND flag can be set to YES to add extra items for group members to +# the table of contents of the HTML help documentation and to the tree view. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +TOC_EXPAND = NO + +# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and +# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that +# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help +# (.qch) of the generated HTML documentation. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_QHP = NO + +# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify +# the file name of the resulting .qch file. The path specified is relative to +# the HTML output folder. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QCH_FILE = + +# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help +# Project output. For more information please see Qt Help Project / Namespace +# (see: https://doc.qt.io/archives/qt-4.8/qthelpproject.html#namespace). +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_NAMESPACE = org.doxygen.Project + +# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt +# Help Project output. For more information please see Qt Help Project / Virtual +# Folders (see: https://doc.qt.io/archives/qt-4.8/qthelpproject.html#virtual- +# folders). +# The default value is: doc. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_VIRTUAL_FOLDER = doc + +# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom +# filter to add. For more information please see Qt Help Project / Custom +# Filters (see: https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom- +# filters). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_CUST_FILTER_NAME = + +# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the +# custom filter to add. For more information please see Qt Help Project / Custom +# Filters (see: https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom- +# filters). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_CUST_FILTER_ATTRS = + +# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this +# project's filter section matches. Qt Help Project / Filter Attributes (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#filter-attributes). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_SECT_FILTER_ATTRS = + +# The QHG_LOCATION tag can be used to specify the location of Qt's +# qhelpgenerator. If non-empty doxygen will try to run qhelpgenerator on the +# generated .qhp file. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHG_LOCATION = + +# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be +# generated, together with the HTML files, they form an Eclipse help plugin. To +# install this plugin and make it available under the help contents menu in +# Eclipse, the contents of the directory containing the HTML and XML files needs +# to be copied into the plugins directory of eclipse. The name of the directory +# within the plugins directory should be the same as the ECLIPSE_DOC_ID value. +# After copying Eclipse needs to be restarted before the help appears. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_ECLIPSEHELP = NO + +# A unique identifier for the Eclipse help plugin. When installing the plugin +# the directory name containing the HTML and XML files should also have this +# name. Each documentation set should have its own identifier. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES. + +ECLIPSE_DOC_ID = org.doxygen.Project + +# If you want full control over the layout of the generated HTML pages it might +# be necessary to disable the index and replace it with your own. The +# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top +# of each HTML page. A value of NO enables the index and the value YES disables +# it. Since the tabs in the index contain the same information as the navigation +# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +DISABLE_INDEX = NO + +# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index +# structure should be generated to display hierarchical information. If the tag +# value is set to YES, a side panel will be generated containing a tree-like +# index structure (just like the one that is generated for HTML Help). For this +# to work a browser that supports JavaScript, DHTML, CSS and frames is required +# (i.e. any modern browser). Windows users are probably better off using the +# HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can +# further fine-tune the look of the index. As an example, the default style +# sheet generated by doxygen has an example that shows how to put an image at +# the root of the tree instead of the PROJECT_NAME. Since the tree basically has +# the same information as the tab index, you could consider setting +# DISABLE_INDEX to YES when enabling this option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_TREEVIEW = NO + +# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that +# doxygen will group on one line in the generated HTML documentation. +# +# Note that a value of 0 will completely suppress the enum values from appearing +# in the overview section. +# Minimum value: 0, maximum value: 20, default value: 4. +# This tag requires that the tag GENERATE_HTML is set to YES. + +ENUM_VALUES_PER_LINE = 4 + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used +# to set the initial width (in pixels) of the frame in which the tree is shown. +# Minimum value: 0, maximum value: 1500, default value: 250. +# This tag requires that the tag GENERATE_HTML is set to YES. + +TREEVIEW_WIDTH = 250 + +# If the EXT_LINKS_IN_WINDOW option is set to YES, doxygen will open links to +# external symbols imported via tag files in a separate window. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +EXT_LINKS_IN_WINDOW = NO + +# Use this tag to change the font size of LaTeX formulas included as images in +# the HTML documentation. When you change the font size after a successful +# doxygen run you need to manually remove any form_*.png images from the HTML +# output directory to force them to be regenerated. +# Minimum value: 8, maximum value: 50, default value: 10. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FORMULA_FONTSIZE = 10 + +# Use the FORMULA_TRANSPARENT tag to determine whether or not the images +# generated for formulas are transparent PNGs. Transparent PNGs are not +# supported properly for IE 6.0, but are supported on all modern browsers. +# +# Note that when changing this option you need to delete any form_*.png files in +# the HTML output directory before the changes have effect. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FORMULA_TRANSPARENT = YES + +# The FORMULA_MACROFILE can contain LaTeX \newcommand and \renewcommand commands +# to create new LaTeX commands to be used in formulas as building blocks. See +# the section "Including formulas" for details. + +FORMULA_MACROFILE = + +# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see +# https://www.mathjax.org) which uses client side JavaScript for the rendering +# instead of using pre-rendered bitmaps. Use this if you do not have LaTeX +# installed or if you want to formulas look prettier in the HTML output. When +# enabled you may also need to install MathJax separately and configure the path +# to it using the MATHJAX_RELPATH option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +USE_MATHJAX = NO + +# When MathJax is enabled you can set the default output format to be used for +# the MathJax output. See the MathJax site (see: +# http://docs.mathjax.org/en/latest/output.html) for more details. +# Possible values are: HTML-CSS (which is slower, but has the best +# compatibility), NativeMML (i.e. MathML) and SVG. +# The default value is: HTML-CSS. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_FORMAT = HTML-CSS + +# When MathJax is enabled you need to specify the location relative to the HTML +# output directory using the MATHJAX_RELPATH option. The destination directory +# should contain the MathJax.js script. For instance, if the mathjax directory +# is located at the same level as the HTML output directory, then +# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax +# Content Delivery Network so you can quickly see the result without installing +# MathJax. However, it is strongly recommended to install a local copy of +# MathJax from https://www.mathjax.org before deployment. +# The default value is: https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_RELPATH = https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/ + +# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax +# extension names that should be enabled during MathJax rendering. For example +# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_EXTENSIONS = + +# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces +# of code that will be used on startup of the MathJax code. See the MathJax site +# (see: http://docs.mathjax.org/en/latest/output.html) for more details. For an +# example see the documentation. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_CODEFILE = + +# When the SEARCHENGINE tag is enabled doxygen will generate a search box for +# the HTML output. The underlying search engine uses javascript and DHTML and +# should work on any modern browser. Note that when using HTML help +# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET) +# there is already a search function so this one should typically be disabled. +# For large projects the javascript based search engine can be slow, then +# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to +# search using the keyboard; to jump to the search box use + S +# (what the is depends on the OS and browser, but it is typically +# , /