From 98251fc101c24e01dc2debd96a111f4202061508 Mon Sep 17 00:00:00 2001 From: Marcel Keller Date: Tue, 6 May 2025 12:40:09 +1000 Subject: [PATCH] Random fixed-point number generation in binary circuits. --- Compiler/GC/types.py | 10 +++++ Compiler/types.py | 88 ++++++++++++++++++++++---------------------- 2 files changed, 54 insertions(+), 44 deletions(-) diff --git a/Compiler/GC/types.py b/Compiler/GC/types.py index 1aae47fd..36c5bd91 100644 --- a/Compiler/GC/types.py +++ b/Compiler/GC/types.py @@ -841,6 +841,16 @@ class sbitvec(_vec, _bit, _binary): return self.from_vec(x.zero_if_not(condition) for x in self.v) def __str__(self): return 'sbitvec(%d)' % n + @classmethod + def get_random_int(cls, n_bits): + assert instructions_base.get_global_vector_size() == 1 + return cls.from_vec( + [sbit.get_random_bit() for i in range(n_bits)] + \ + [0] * (n - n_bits)) + @staticmethod + def get_random_bit(): + assert instructions_base.get_global_vector_size() == 1 + return sbit.get_random_bit() sbitvecn.basic_type = sbitvecn sbitvecn.reg_type = 'sb' return sbitvecn diff --git a/Compiler/types.py b/Compiler/types.py index fb5130f2..97bd6c9a 100644 --- a/Compiler/types.py +++ b/Compiler/types.py @@ -4853,6 +4853,50 @@ class _fix(_single): assert self.f == other.f self.v.update(other.v) + @vectorized_classmethod + def get_random(cls, lower, upper, symmetric=True, public_randomness=False): + """ Uniform secret random number around centre of bounds. + Actual range can be smaller but never larger. + + :param lower: float + :param upper: float + :param symmetric: symmetric distribution at higher cost + :param public_randomness: use public randomness (avoids preprocessing) + :param size: vector size (int, default 1) + """ + if public_randomness: + get_random_int = regint.get_random + get_random_bit = lambda: regint.get_random(1) + else: + get_random_int = cls.int_type.get_random_int + get_random_bit = cls.int_type.get_random_bit + f = cls.f + k = cls.k + log_range = int(math.log(upper - lower, 2)) + n_bits = log_range + cls.f + gen_range = (2 ** (n_bits) - 1) / 2 ** cls.f + diff = upper - lower + factor = diff / gen_range + real = lambda x: cfix.int_rep(x, f, k) * 2 ** -f + real_range = real(real(factor) * gen_range) + average = lower + 0.5 * (upper - lower) + lower = average - 0.5 * real_range + upper = average + 0.5 * real_range + r = cls._new(get_random_int(n_bits)) * factor + lower + if symmetric: + lowest = math.floor(lower * 2 ** cls.f) / 2 ** cls.f + highest = math.ceil(upper * 2 ** cls.f) / 2 ** cls.f + if program.verbose: + print('randomness range [%f,%f], ' + 'fringes half the probability' % \ + (lowest, highest)) + return get_random_bit().if_else(r, -r + 2 * average) + else: + if program.verbose: + print('randomness range [%f,%f], %d bits' % \ + (real(lower), real(lower) + real_range, n_bits)) + return r + class sfix(_fix): """ Secret fixed-point number represented as secret integer, by multiplying with ``2^f`` and then rounding. See :py:class:`sint` @@ -4904,50 +4948,6 @@ class sfix(_fix): def get_raw_input_from(cls, player): return cls._new(cls.int_type.get_raw_input_from(player)) - @vectorized_classmethod - def get_random(cls, lower, upper, symmetric=True, public_randomness=False): - """ Uniform secret random number around centre of bounds. - Actual range can be smaller but never larger. - - :param lower: float - :param upper: float - :param symmetric: symmetric distribution at higher cost - :param public_randomness: use public randomness (avoids preprocessing) - :param size: vector size (int, default 1) - """ - if public_randomness: - get_random_int = regint.get_random - get_random_bit = lambda: regint.get_random(1) - else: - get_random_int = cls.int_type.get_random_int - get_random_bit = cls.int_type.get_random_bit - f = cls.f - k = cls.k - log_range = int(math.log(upper - lower, 2)) - n_bits = log_range + cls.f - gen_range = (2 ** (n_bits) - 1) / 2 ** cls.f - diff = upper - lower - factor = diff / gen_range - real = lambda x: cfix.int_rep(x, f, k) * 2 ** -f - real_range = real(real(factor) * gen_range) - average = lower + 0.5 * (upper - lower) - lower = average - 0.5 * real_range - upper = average + 0.5 * real_range - r = cls._new(get_random_int(n_bits)) * factor + lower - if symmetric: - lowest = math.floor(lower * 2 ** cls.f) / 2 ** cls.f - highest = math.ceil(upper * 2 ** cls.f) / 2 ** cls.f - if program.verbose: - print('randomness range [%f,%f], ' - 'fringes half the probability' % \ - (lowest, highest)) - return get_random_bit().if_else(r, -r + 2 * average) - else: - if program.verbose: - print('randomness range [%f,%f], %d bits' % \ - (real(lower), real(lower) + real_range, n_bits)) - return r - @classmethod def direct_matrix_mul(cls, A, B, n, m, l, reduce=True, indices=None): # pre-multiplication must be identity