Skip to content

Commit 9f2a641

Browse files
committed
Protocols with function-dependent preprocessing.
1 parent 96aac1e commit 9f2a641

File tree

278 files changed

+7823
-1027
lines changed

Some content is hidden

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

278 files changed

+7823
-1027
lines changed
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
---
2+
name: Unexpected benchmarking result
3+
about: Some aspects are unexpectedly slow or fast
4+
title: ''
5+
labels: ''
6+
assignees: ''
7+
8+
---
9+
10+
**Describe the result**
11+
Why is the result unexpected?
12+
13+
**To reproduce**
14+
High-level code as well as compilation and execution command lines
15+
16+
**Outputs**
17+
Virtual machine outputs using `--verbose`
18+
19+
**MP-SPDZ version**
20+
Version or commit you are using
21+
22+
**Additional context**
23+
Add any other context about the problem here.

BMR/RealGarbleWire.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ template<class T> class RealProgramParty;
1212
template<class T> class RealGarbleWire;
1313

1414
template<class T>
15-
class GarbleInputter
15+
class GarbleInputter : public InputterBase
1616
{
1717
public:
1818
RealProgramParty<T>& party;

BMR/Register.h

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,11 @@ class Register {
194194
void set_trace();
195195
};
196196

197+
inline ostream& operator<<(ostream&, const Register&)
198+
{
199+
throw runtime_error("BMR secret output not implemented");
200+
}
201+
197202

198203
// this is to fake a "cout" that does nothing
199204
class BlackHole
@@ -221,18 +226,18 @@ class Phase
221226

222227
static const bool actual_inputs = true;
223228

229+
static const bool garbled = true;
230+
224231
template <class T>
225-
static void store_clear_in_dynamic(T& mem, const vector<GC::ClearWriteAccess>& accesses)
226-
{ (void)mem; (void)accesses; }
232+
static void store_clear_in_dynamic(T&, const vector<GC::ClearWriteAccess>&)
233+
{}
227234

228235
template<class T>
229-
static void store(NoMemory& dest,
230-
const vector<GC::WriteAccess<T> >& accesses)
231-
{ (void)dest; (void)accesses; throw runtime_error("dynamic memory not implemented"); }
236+
static void store(NoMemory&, const vector<GC::WriteAccess<T> >&)
237+
{ throw no_dynamic_memory(); }
232238
template<class T>
233-
static void load(vector<GC::ReadAccess<T> >& accesses,
234-
const NoMemory& source)
235-
{ (void)accesses; (void)source; throw runtime_error("dynamic memory not implemented"); }
239+
static void load(vector<GC::ReadAccess<T> >&, const NoMemory&)
240+
{ throw no_dynamic_memory(); }
236241

237242
template <class T>
238243
static void andrs(T& processor, const vector<int>& args) { processor.andrs(args); }
@@ -265,7 +270,16 @@ class Phase
265270
void output() {}
266271
};
267272

268-
class NoOpInputter
273+
class InputterBase
274+
{
275+
public:
276+
static bool is_me(int player, int my_num)
277+
{
278+
return player == my_num;
279+
}
280+
};
281+
282+
class NoOpInputter : public InputterBase
269283
{
270284
public:
271285
PointerVector<char> inputs;
@@ -337,7 +351,7 @@ class PRFRegister : public ProgramRegister
337351
class ProgramParty;
338352
class EvalRegister;
339353

340-
class EvalInputter
354+
class EvalInputter : public InputterBase
341355
{
342356
class Tuple
343357
{

CHANGELOG.md

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

3+
## 0.4.1 (May 30, 2025)
4+
5+
- Add protocols with function-dependent preprocessing (https://eprint.iacr.org/2025/919)
6+
- Parallelize shuffling (@vincent-ehrmanntraut)
7+
- More efficient probabilistic truncation in Rep3
8+
- More efficient binary to arithmetic conversion for one bit in Rep3
9+
- Backend optimizations benefitting the most efficient protocols like Rep3
10+
- Allow regint registers as argument in exported functions
11+
- More efficient dot product for GF(2^n)
12+
- File persistance for GF(2^n)
13+
- Output of binary secrets
14+
- SHA256
15+
- Improved navigation by providing links to relevant papers (`./compile.py --papers`) and outputting which code is executed (`./<protocol>-party.x --code-locations`)
16+
- Fixed security bug: remove MAC key in case of failure
17+
318
## 0.4.0 (November 21, 2024)
419

520
- Functionality to call high-level code from C++

CONFIG

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ endif
102102

103103
ifeq ($(OS), Linux)
104104
LDLIBS += -lrt
105+
LDLIBS += -z noexecstack
105106
endif
106107

107108
BOOST = -lboost_thread $(MY_BOOST)

Compiler/GC/instructions.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ class ClearBitsAF(base.RegisterArgFormat):
7676
PRINTREGPLAINB = 0x222,
7777
PRINTFLOATPLAINB = 0x223,
7878
CONDPRINTSTRB = 0x224,
79+
PRINTREGPLAINSB = 0x225,
7980
CONVCBIT = 0x230,
8081
CONVCBITVEC = 0x231,
8182
)
@@ -551,6 +552,11 @@ def __init__(self, *args, **kwargs):
551552
super(split_class, self).__init__(*args, **kwargs)
552553
assert (len(args) - 2) % args[0] == 0
553554

555+
def add_usage(self, req_node):
556+
req_node.increment(('modp', '%d-way split' % self.args[0]),
557+
self.get_size())
558+
req_node.increment(('modp', '%d-way split round' % self.args[0]), 1)
559+
554560
class movsb(BinaryVectorInstruction):
555561
""" Copy secret bit register.
556562
@@ -694,6 +700,14 @@ class print_reg_plainb(NonVectorInstruction, base.IOInstruction):
694700
code = opcodes['PRINTREGPLAINB']
695701
arg_format = ['cb']
696702

703+
class print_reg_plainsb(NonVectorInstruction, base.IOInstruction):
704+
""" Output secret bit register.
705+
706+
:param: source (sbit)
707+
"""
708+
code = opcodes['PRINTREGPLAINSB']
709+
arg_format = ['sb']
710+
697711
class print_reg_signed(base.IOInstruction):
698712
""" Signed output of clear bit register.
699713

Compiler/GC/types.py

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,10 @@ def new(cls, value=None, n=None):
136136
if util.is_constant(value):
137137
n = value.bit_length()
138138
return cls.get_type(n)(value)
139+
@classmethod
140+
def same_type(cls, size=None):
141+
assert size is None or size == math.ceil(self.n / self.unit)
142+
return cls()
139143
def __init__(self, value=None, n=None, size=None):
140144
assert n == self.n or n is None
141145
if size != 1 and size is not None:
@@ -163,7 +167,7 @@ def load_int(self, value):
163167
def load_other(self, other):
164168
if isinstance(other, cint):
165169
assert(self.n == other.size)
166-
self.conv_regint_by_bit(self.n, self, other.to_regint(1))
170+
self.conv_regint_by_bit(self.n, self, other.to_regint(1, sync=False))
167171
elif isinstance(other, int):
168172
self.set_length(self.n or util.int_len(other))
169173
self.load_int(other)
@@ -284,8 +288,7 @@ def copy_from_part(self, source, base, size):
284288
self.bit_compose(source.bit_decompose()[base:base + size]))
285289
def vector_size(self):
286290
return self.n
287-
@staticmethod
288-
def size_for_mem():
291+
def size_for_mem(self):
289292
return 1
290293

291294
class cbits(bits):
@@ -576,7 +579,7 @@ def __mul__(self, other):
576579
__rmul__ = __mul__
577580
def _and(self, other):
578581
res = self.new(n=self.n)
579-
if not isinstance(other, sbits):
582+
if not isinstance(other, sbits) and Program.prog.use_mulm:
580583
other = cbits.get_type(self.n).conv(other)
581584
inst.andm(self.n, res, self, other)
582585
return res
@@ -687,6 +690,8 @@ def bit_adder(*args, **kwargs):
687690
@staticmethod
688691
def ripple_carry_adder(*args, **kwargs):
689692
return sbitint.ripple_carry_adder(*args, **kwargs)
693+
def output(self):
694+
inst.print_reg_plainsb(self)
690695

691696
class sbitvec(_vec, _bit, _binary):
692697
""" Vector of registers of secret bits, effectively a matrix of secret bits.
@@ -789,6 +794,8 @@ def __init__(self, other=None, size=None):
789794
instructions_base.check_vector_size(size)
790795
if other is not None:
791796
if util.is_constant(other):
797+
if util.int_len(other) > n:
798+
raise CompilerError('constant outside domain')
792799
t = sbits.get_type(size or 1)
793800
self.v = [t(((other >> i) & 1) * ((1 << t.n) - 1))
794801
for i in range(n)]
@@ -870,13 +877,14 @@ def from_matrix(cls, matrix):
870877
# any number of rows, limited number of columns
871878
return cls.combine(cls(row) for row in matrix)
872879
@classmethod
873-
def from_hex(cls, string):
880+
def from_hex(cls, string, reverse=True):
874881
""" Create from hexadecimal string (little-endian). """
875882
assert len(string) % 2 == 0
876883
v = []
884+
trans = reversed if reverse else list
877885
for i in range(0, len(string), 2):
878886
v += [sbit(int(x))
879-
for x in reversed(bin(int(string[i:i + 2], 16))[2:].zfill(8))]
887+
for x in trans(bin(int(string[i:i + 2], 16))[2:].zfill(8))]
880888
return cls.from_vec(v)
881889
def __init__(self, elements=None, length=None, input_length=None):
882890
if length:

Compiler/allocator.py

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -499,14 +499,15 @@ def handle_mem_access(addr, reg_type, last_access_this_kind,
499499
def mem_access(n, instr, last_access_this_kind, last_access_other_kind):
500500
addr = instr.args[1]
501501
reg_type = instr.args[0].reg_type
502+
budget = block.parent.program.budget
502503
if isinstance(addr, int):
503-
for i in range(min(instr.get_size(), 100)):
504+
for i in range(min(instr.get_size(), budget)):
504505
addr_i = addr + i
505506
handle_mem_access(addr_i, reg_type, last_access_this_kind,
506507
last_access_other_kind)
507508
if block.warn_about_mem and \
508509
not block.parent.warned_about_mem and \
509-
(instr.get_size() > 100) and not instr._protect:
510+
(instr.get_size() > budget) and not instr._protect:
510511
print('WARNING: Order of memory instructions ' \
511512
'not preserved due to long vector, errors possible')
512513
block.parent.warned_about_mem = True
@@ -593,7 +594,7 @@ def keep_text_order(inst, n):
593594
keep_text_order(instr, n)
594595
elif isinstance(instr, RawInputInstruction):
595596
keep_merged_order(instr, n, RawInputInstruction)
596-
elif isinstance(instr, matmulsm):
597+
elif isinstance(instr, matmulsm_class):
597598
if options.preserve_mem_order:
598599
strict_mem_access(n, last_mem_read, last_mem_write)
599600
else:
@@ -735,7 +736,7 @@ def merge_nodes(self, i, j):
735736
G.get_attr(i, 'merges').append(j)
736737
G.remove_node(j)
737738

738-
def eliminate_dead_code(self):
739+
def eliminate_dead_code(self, only_ldint=False):
739740
instructions = self.instructions
740741
G = self.G
741742
merge_nodes = self.open_nodes
@@ -745,6 +746,8 @@ def eliminate_dead_code(self):
745746
for i,inst in zip(range(len(instructions) - 1, -1, -1), reversed(instructions)):
746747
if inst is None:
747748
continue
749+
if only_ldint and not isinstance(inst, ldint_class):
750+
continue
748751
can_eliminate_defs = True
749752
for reg in inst.get_def():
750753
for dup in reg.duplicates:
@@ -800,7 +803,9 @@ def add_offset(self, res, new_base, new_offset, multiplier):
800803
self.rev_offset_cache[new_base.i, new_offset, multiplier] = res
801804

802805
def run(self, instructions, program):
806+
changed = defaultdict(int)
803807
for i, inst in enumerate(instructions):
808+
pre = inst
804809
if isinstance(inst, ldint_class):
805810
self.cache[inst.args[0]] = inst.args[1]
806811
elif isinstance(inst, incint):
@@ -882,8 +887,12 @@ def f(reg, cached, reverse):
882887
cond = self.cache[inst.args[0]]
883888
if not cond:
884889
instructions[i] = None
890+
if pre != instructions[i]:
891+
changed[type(inst).__name__] += 1
885892
pre = len(instructions)
886893
instructions[:] = list(filter(lambda x: x is not None, instructions))
887894
post = len(instructions)
895+
if changed and program.options.verbose:
896+
print('regint optimizer changed:', dict(changed))
888897
if pre != post and program.options.verbose:
889898
print('regint optimizer removed %d instructions' % (pre - post))

Compiler/circuit.py

Lines changed: 69 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
import math
1212

1313
from Compiler.GC.types import *
14-
from Compiler.library import function_block, get_tape
14+
from Compiler.library import *
1515
from Compiler import util
1616
import itertools
1717
import struct
@@ -63,8 +63,11 @@ def __call__(self, *inputs):
6363
def run(self, *inputs):
6464
n = inputs[0][0].n, get_tape()
6565
if n not in self.functions:
66-
self.functions[n] = function_block(
67-
lambda *args: self.compile(*args))
66+
if get_program().force_cisc_tape:
67+
f = function_call_tape
68+
else:
69+
f = function_block
70+
self.functions[n] = f(lambda *args: self.compile(*args))
6871
self.functions[n].name = '%s(%d)' % (self.name, inputs[0][0].n)
6972
flat_res = self.functions[n](*itertools.chain(*inputs))
7073
res = []
@@ -234,6 +237,69 @@ def insert_block(local_S, local_P):
234237
S = unflatten(Keccak_f(flatten(S)))
235238
return sbitvec.from_vec(Z[:256])
236239

240+
sha256_circuit = None
241+
242+
def sha256(x):
243+
"""
244+
This function implements SHA2-256::
245+
246+
from circuit import sha256
247+
a = sbitvec.from_vec([])
248+
b = sbitvec.from_hex('d3', reverse=False)
249+
c = sbitvec.from_hex(
250+
'3ebfb06db8c38d5ba037f1363e118550aad94606e26835a01af05078533cc25f2f39573c04b632f62f68c294ab31f2a3e2a1a0d8c2be51',
251+
reverse=False)
252+
d = sbitvec.from_hex(
253+
'5a86b737eaea8ee976a0a24da63e7ed7eefad18a101c1211e2b3650c5187c2a8a650547208251f6d4237e661c7bf4c77f335390394c37fa1a9f9be836ac28509',
254+
reverse=False)
255+
256+
for x in a, b, c:
257+
sha256(x).reveal().print_reg()
258+
259+
This should output the hashes of the above inputs, namely
260+
the `test vectors
261+
<https://csrc.nist.gov/Projects/Cryptographic-Algorithm-Validation-Program/Secure-Hashing#shavs>`_
262+
of SHA2-256 for 0, 8, 440, 512 bits::
263+
264+
Reg[0] = 0xe3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 #
265+
Reg[0] = 0x28969cdfa74a12c82f3bad960b0b000aca2ac329deea5c2328ebc6f2ba9802c1 #
266+
Reg[0] = 0x6595a2ef537a69ba8583dfbf7f5bec0ab1f93ce4c8ee1916eff44a93af5749c4 #
267+
Reg[0] = 0x42e61e174fbb3897d6dd6cef3dd2802fe67b331953b06114a65c772859dfc1aa #
268+
269+
"""
270+
global sha256_circuit
271+
if not sha256_circuit:
272+
sha256_circuit = Circuit('sha256')
273+
274+
L = len(x.v)
275+
padding = [1]
276+
while (len(padding) + L) % 512 != (512 - 64):
277+
padding.append(0)
278+
padding += [(L >> (63 - i)) & 1 for i in range(64)]
279+
280+
padded = x.v + [sbit(b) for b in padding]
281+
282+
h = [
283+
0x6a, 0x09, 0xe6, 0x67,
284+
0xbb, 0x67, 0xae, 0x85,
285+
0x3c, 0x6e, 0xf3, 0x72,
286+
0xa5, 0x4f, 0xf5, 0x3a,
287+
0x51, 0x0e, 0x52, 0x7f,
288+
0x9b, 0x05, 0x68, 0x8c,
289+
0x1f, 0x83, 0xd9, 0xab,
290+
0x5b, 0xe0, 0xcd, 0x19
291+
]
292+
293+
state = list(reversed(
294+
[sbit((h[i // 8] >> (7 - i % 8)) & 1) for i in range(256)]))
295+
296+
for i in range(0, len(padded), 512):
297+
chunk = list(reversed(padded[i:i + 512]))
298+
assert len(chunk) == 512
299+
state = sha256_circuit(chunk + state).v
300+
301+
return sbitvec.from_vec(state)
302+
237303
class ieee_float:
238304
"""
239305
This gives access IEEE754 floating-point operations using Bristol

0 commit comments

Comments
 (0)