def test_decoder_selection(mocked_cpu_count, galois_field): # Very small n < 8. Vandermonde should always be picked point = EvalPoint(galois_field, 4, use_omega_powers=True) for cpu_count in [1, 100]: mocked_cpu_count.return_value = cpu_count for batch_size in [1, 1000, 100000]: DecoderSelector.set_optimal_thread_count(batch_size) assert isinstance( DecoderSelector.select(point, batch_size), VandermondeDecoder ) # Small batches (~n). Reasonable number of threads. Pick FFT point = EvalPoint(galois_field, 65, use_omega_powers=True) for cpu_count in [1, 2, 4, 8]: mocked_cpu_count.return_value = cpu_count for batch_size in [1, 16, 32]: DecoderSelector.set_optimal_thread_count(batch_size) assert isinstance(DecoderSelector.select(point, batch_size), FFTDecoder) # Small n. Reasonable number of threads, # Large batch sizes > ~ numthreads * n. Pick Vandermonde point = EvalPoint(galois_field, 65, use_omega_powers=True) for cpu_count in [1, 2, 4, 8]: mocked_cpu_count.return_value = cpu_count for batch_size in [512, 1024, 2048, 4096]: DecoderSelector.set_optimal_thread_count(batch_size) assert isinstance( DecoderSelector.select(point, batch_size), VandermondeDecoder ) # Extremely large n. FFT should ideally be picked at reasonable batch sizes point = EvalPoint(galois_field, 65536, use_omega_powers=True) for cpu_count in [1, 2, 4, 8]: mocked_cpu_count.return_value = cpu_count for batch_size in [512, 1024, 2048, 4096, 8192]: DecoderSelector.set_optimal_thread_count(batch_size) assert isinstance(DecoderSelector.select(point, batch_size), FFTDecoder) # The scenarios above checked extreme cases. Below we check more rigorously based # on the exact approximation formula. Ideally, the cases above should not change # over time but the tests below will as the selection algorithm changes for n in [32, 64, 128, 256]: point = EvalPoint(galois_field, n, use_omega_powers=True) for cpu_count in [2 ** i for i in range(5)]: mocked_cpu_count.return_value = cpu_count for batch_size in [2 ** i for i in range(16)]: DecoderSelector.set_optimal_thread_count(batch_size) if batch_size > 0.5 * n * min(batch_size, AvailableNTLThreads()): assert isinstance( DecoderSelector.select(point, batch_size), VandermondeDecoder ) else: assert isinstance( DecoderSelector.select(point, batch_size), FFTDecoder )
async def test_randousha(test_router, polynomial, galois_field, n, k): t = (n - 1) // 3 sends, receives, _ = test_router(n) shares_per_party = await asyncio.gather(*[ randousha(n, t, k, i, sends[i], receives[i], galois_field) for i in range(n) ]) assert len(shares_per_party) == n assert all( len(random_shares) == (n - 2 * t) * k for random_shares in shares_per_party) random_values = [] eval_point = EvalPoint(galois_field, n, use_omega_powers=False) decoder = DecoderFactory.get(eval_point, Algorithm.VANDERMONDE) for i, shares in enumerate(zip(*shares_per_party)): shares_t, shares_2t = zip(*shares) poly_t = polynomial(decoder.decode(list(range(n)), shares_t)) poly_2t = polynomial(decoder.decode(list(range(n)), shares_2t)) r_t = poly_t(0) r_2t = poly_2t(0) assert len(poly_t.coeffs) == t + 1 assert len(poly_2t.coeffs) == 2 * t + 1 assert r_t == r_2t random_values.append(r_t) assert len(set(random_values)) == (n - 2 * t) * k
def test_encoder_selection(galois_field): # Very small n < 8. Vandermonde should always be picked point = EvalPoint(galois_field, 4, use_omega_powers=True) assert isinstance(EncoderSelector.select(point, 1), VandermondeEncoder) assert isinstance(EncoderSelector.select(point, 100000), VandermondeEncoder) # Intermediate values of n (8 < n < 128 # Bad n (Nearest power of 2 is much higher) Vandermonde should # always be picked point = EvalPoint(galois_field, 65, use_omega_powers=True) assert isinstance(EncoderSelector.select(point, 1), VandermondeEncoder) assert isinstance(EncoderSelector.select(point, 100000), VandermondeEncoder) point = EvalPoint(galois_field, 40, use_omega_powers=True) assert isinstance(EncoderSelector.select(point, 1), VandermondeEncoder) assert isinstance(EncoderSelector.select(point, 100000), VandermondeEncoder) # Good n (Nearest power of 2 is close) FFT should be picked point = EvalPoint(galois_field, 120, use_omega_powers=True) assert isinstance(EncoderSelector.select(point, 1), FFTEncoder) assert isinstance(EncoderSelector.select(point, 100000), FFTEncoder) point = EvalPoint(galois_field, 55, use_omega_powers=True) assert isinstance(EncoderSelector.select(point, 1), FFTEncoder) assert isinstance(EncoderSelector.select(point, 100000), FFTEncoder) # Large n. Always pick FFT point = EvalPoint(galois_field, 255, use_omega_powers=True) assert isinstance(EncoderSelector.select(point, 1), FFTEncoder) assert isinstance(EncoderSelector.select(point, 100000), FFTEncoder) point = EvalPoint(galois_field, 257, use_omega_powers=True) assert isinstance(EncoderSelector.select(point, 1), FFTEncoder) assert isinstance(EncoderSelector.select(point, 100000), FFTEncoder)
async def _get_inputmask(self, idx): # Private reconstruct contract_concise = ConciseContract(self.contract) n = contract_concise.n() poly = polynomials_over(field) eval_point = EvalPoint(field, n, use_omega_powers=False) shares = [] for i in range(n): share = self.req_mask(i, idx) shares.append(share) shares = await asyncio.gather(*shares) shares = [(eval_point(i), share) for i, share in enumerate(shares)] mask = poly.interpolate_at(shares, 0) return mask
def fft_decoding_test_cases(galois_field): point = EvalPoint(galois_field, 4, use_omega_powers=True) omega = point.omega.value p = galois_field.modulus test_cases = [] test_cases.append( [ [1, 3], [(2 * pow(omega, 1, p) + 1) % p, (2 * pow(omega, 3, p) + 1) % p], [1, 2], point, ] ) return test_cases
def robust_decoding_test_cases(galois_field): omega = EvalPoint(galois_field, 4, use_omega_powers=True).omega.value p = galois_field.modulus # Order: Index of parties, Encoded values, Expected decoded values, # Expected erroneous parties, t, point test_cases = [ # Correct array would be [3, 5, 7, 9] [[0, 1, 2, 3], [3, 5, 0, 9], [1, 2], [2], 1, EvalPoint(galois_field, 4)], [ [0, 1, 2, 3], [ (2 * pow(omega, 0, p) + 1) % p, (2 * pow(omega, 1, p) + 1) % p, 0, (2 * pow(omega, 3, p) + 1) % p, ], [1, 2], [2], 1, EvalPoint(galois_field, 4, use_omega_powers=True), ], ] return test_cases
def refine_randoms(n, t, field, random_shares_int): assert 3 * t + 1 <= n # Number of nodes which have contributed values to this batch k = len(random_shares_int) assert k >= n - t and k <= n encoder = EncoderFactory.get(EvalPoint(field, n, use_omega_powers=True)) # Assume these shares to be the coefficients of a random polynomial. The # refined shares are evaluations of this polynomial at powers of omega. output_shares_int = encoder.encode(random_shares_int) # Remove `t` shares since they might have been contributed by corrupt parties. return output_shares_int[:k - t]
def fft_reconstruction_input(galois_field): n = 4 t = 1 fp = galois_field p = fp.modulus point = EvalPoint(fp, n, use_omega_powers=True) omega = point.omega.value # x + 2, 3x + 4 secret_shares = [ (omega**0 + 2, 3 * omega**0 + 4), (omega**1 + 2, 3 * omega**1 + 4), (omega**2 + 2, 3 * omega**2 + 4), (omega**3 + 2, 3 * omega**3 + 4), ] secrets = [2, 4] return n, t, fp, p, omega, secret_shares, secrets
def test_benchmark_gao_robust_decode(benchmark, t, galois_field): n = 3 * t + 1 galois_field = GF(Subgroup.BLS12_381) point = EvalPoint(galois_field, n) dec = GaoRobustDecoder(t, point) parties = [_ for _ in range(n)] poly = polynomials_over(galois_field) truepoly = poly.random(degree=t) faults = [] while len(faults) < t: r = randint(0, n - 1) if r not in faults: faults.append(r) shares_with_faults = [] for i in parties: if i in faults: shares_with_faults.append(int(galois_field.random())) else: shares_with_faults.append(int(truepoly(i + 1))) benchmark(dec.robust_decode, parties, shares_with_faults)
async def randousha(n, t, k, my_id, _send, _recv, field): """ Generates a batch of (n-2t)k secret sharings of random elements """ poly = polynomials_over(field) eval_point = EvalPoint(field, n, use_omega_powers=False) big_t = n - (2 * t) - 1 # This is same as `T` in the HyperMPC paper. encoder = EncoderFactory.get(eval_point) # Pick k random elements def to_int(coeffs): return tuple(map(int, coeffs)) my_randoms = [field.random() for _ in range(k)] # Generate t and 2t shares of the random element. coeffs_t = [to_int(poly.random(t, r).coeffs) for r in my_randoms] coeffs_2t = [to_int(poly.random(2 * t, r).coeffs) for r in my_randoms] unref_t = encoder.encode(coeffs_t) unref_2t = encoder.encode(coeffs_2t) subscribe_recv_task, subscribe = subscribe_recv(_recv) def _get_send_recv(tag): return wrap_send(tag, _send), subscribe(tag) # Start listening for my share of t and 2t shares from all parties. send, recv = _get_send_recv("H1") share_recv_task = asyncio.create_task(_recv_loop(n, recv)) # Send each party their shares. to_send_t = transpose_lists(unref_t) to_send_2t = transpose_lists(unref_2t) for i in range(n): send(i, (to_send_t[i], to_send_2t[i])) # Wait until all shares are received. received_shares = await share_recv_task unrefined_t_shares, unrefined_2t_shares = zip(*received_shares) # Apply the hyper-invertible matrix. # Assume the unrefined shares to be coefficients of a polynomial # and then evaluate that polynomial at powers of omega. ref_t = encoder.encode(transpose_lists(list(unrefined_t_shares))) ref_2t = encoder.encode(transpose_lists(list(unrefined_2t_shares))) # Parties with id in [N-2t+1, N] need to start # listening for shares which they have to check. send, recv = _get_send_recv("H2") to_send_t = transpose_lists(ref_t) to_send_2t = transpose_lists(ref_2t) if my_id > big_t: share_chk_recv_task = asyncio.create_task(_recv_loop(n, recv)) # Send shares of parties with id in [N-2t+1, N] to those parties. for i in range(big_t + 1, n): send(i, (to_send_t[i], to_send_2t[i])) # Parties with id in [N-2t+1, N] need to verify that the shares are in-fact correct. if my_id > big_t: shares_to_check = await share_chk_recv_task shares_t, shares_2t = zip(*shares_to_check) response = HyperInvMessageType.ABORT def get_degree(p): for i in range(len(p))[::-1]: if p[i] != 0: return i return 0 def get_degree_and_secret(shares): decoder = DecoderFactory.get(eval_point) polys = decoder.decode(list(range(n)), transpose_lists(list(shares))) secrets = [p[0] for p in polys] degrees = [get_degree(p) for p in polys] return degrees, secrets degree_t, secret_t = get_degree_and_secret(shares_t) degree_2t, secret_2t = get_degree_and_secret(shares_2t) # Verify that the shares are in-fact `t` and `2t` shared. # Verify that both `t` and `2t` shares of the same value. if (all(deg == t for deg in degree_t) and all(deg == 2 * t for deg in degree_2t) and secret_t == secret_2t): response = HyperInvMessageType.SUCCESS logging.debug( "[%d] Degree check: %s, Secret Check: %s", my_id, all(deg == t for deg in degree_t) and all(deg == 2 * t for deg in degree_2t), secret_t == secret_2t, ) # Start listening for the verification response. send, recv = _get_send_recv("H3") response_recv_task = asyncio.create_task( _recv_loop(n - big_t - 1, recv, big_t + 1)) # Send the verification response. if my_id > big_t: for i in range(n): send(i, response) responses = await response_recv_task subscribe_recv_task.cancel() # If any of [T+1, N] parties say that the shares are inconsistent then abort. if responses.count(HyperInvMessageType.SUCCESS) != n - big_t - 1: raise HoneyBadgerMPCError( "Aborting because the shares were inconsistent.") out_t = flatten_lists([s[:big_t + 1] for s in ref_t]) out_2t = flatten_lists([s[:big_t + 1] for s in ref_2t]) return tuple(zip(out_t, out_2t))
def make_wb_encoder_decoder(n, k, p, point=None): """ n: number of symbols to encode k: number of symbols in the message (k=t+1) where t is the degree of the polynomial """ if not k <= n <= p: raise Exception( "Must have k <= n <= p but instead had (n,k,p) == (%r, %r, %r)" % (n, k, p) ) t = k - 1 # degree of polynomial fp = GF(p) poly = polynomials_over(fp) # the message points correspond to polynomial evaluations # at either f(i) for convenience, or # f( omega^i ) where omega. If omega is an n'th root of unity, # then we can do efficient FFT-based polynomial interpolations. if point is None or type(point) is not EvalPoint: point = EvalPoint(fp, n, use_omega_powers=False) # message is a list of integers at most p def encode(message): if not all(x < p for x in message): raise Exception( "Message is improperly encoded as integers < p. It was:\n%r" % message ) assert len(message) == t + 1 the_poly = poly(message) return [the_poly(point(i)) for i in range(n)] def solve_system(encoded_message, max_e, debug=False): """ input: points in form (x,y) output: coefficients of interpolated polynomial due to Jeremy Kun https://jeremykun.com/2015/09/07/welch-berlekamp/ """ for e in range(max_e, 0, -1): e_num_vars = e + 1 q_num_vars = e + k def row(i, a, b): return ( [b * a ** j for j in range(e_num_vars)] + [-1 * a ** j for j in range(q_num_vars)] + [0] ) # the "extended" part of the linear system system = [row(i, a, b) for (i, (a, b)) in enumerate(encoded_message)] + [ [fp(0)] * (e_num_vars - 1) + [fp(1)] + [fp(0)] * (q_num_vars) + [fp(1)] ] # ensure coefficient of x^e in E(x) is 1 if debug: logging.debug("\ne is %r" % e) logging.debug("\nsystem is:\n\n") for row in system: logging.debug("\t%r" % (row,)) solution = some_solution(system, free_variable_value=1) e_ = poly([solution[j] for j in range(e + 1)]) q_ = poly([solution[j] for j in range(e + 1, len(solution))]) if debug: logging.debug("\nreduced system is:\n\n") for row in system: logging.debug("\t%r" % (row,)) logging.debug("solution is %r" % (solution,)) logging.debug("Q is %r" % (q_,)) logging.debug("E is %r" % (e_,)) p_, remainder = q_.__divmod__(e_) if debug: logging.debug("P(x) = %r" % p_) logging.debug("r(x) = %r" % remainder) if remainder.is_zero(): return q_, e_ raise ValueError("found no divisors!") def decode(encoded_msg, debug=True): assert len(encoded_msg) == n c = sum(m is None for m in encoded_msg) # number of erasures assert 2 * t + 1 + c <= n # e = ceil((n - c - t - 1) / 2) = ((n - c - t) // 2) e = (n - c - t) // 2 if debug: logging.debug(f"n: {n} k: {k} t: {t} c: {c}") logging.debug(f"decoding with e: {e}") logging.debug(f"decoding with c: {c}") enc_m = [(point(i), m) for i, m in enumerate(encoded_msg) if m is not None] if e == 0: # decode with no errors p_ = poly.interpolate(enc_m) return p_.coeffs q_, e_ = solve_system(enc_m, max_e=e, debug=debug) p_, remainder = q_.__divmod__(e_) if not remainder.is_zero(): raise Exception("Q is not divisibly by E!") return p_.coeffs return encode, decode, solve_system
def decoding_test_cases(galois_field): test_cases = [ [[1, 3], [5, 9], [1, 2], EvalPoint(galois_field, 4)], [[1, 3], [[5, 9], [8, 14]], [[1, 2], [2, 3]], EvalPoint(galois_field, 4)], ] return test_cases
def encoding_test_cases(galois_field): test_cases = [ [[1, 2], [3, 5, 7, 9], EvalPoint(galois_field, 4)], [[[1, 2], [2, 3]], [[3, 5, 7, 9], [5, 8, 11, 14]], EvalPoint(galois_field, 4)], ] return test_cases