diff --git a/lwe_challenge.py b/lwe_challenge.py index df35169..ea29e34 100755 --- a/lwe_challenge.py +++ b/lwe_challenge.py @@ -14,6 +14,7 @@ from fpylll import BKZ as fplll_bkz from fpylll.algorithms.bkz2 import BKZReduction +from fpylll.tools.bkz_simulator import simulate from fpylll.tools.quality import basis_quality from fpylll.util import gaussian_heuristic, set_threads @@ -93,6 +94,7 @@ def lwe_kernel(arg0, params=None, seed=None): # flow of the lwe solver svp_bkz_time_factor = params.pop("lwe/svp_bkz_time_factor") goal_margin = params.pop("lwe/goal_margin") + favour_memory_factor = params.pop("lwe/favour_memory_factor") # generation of lwe instance and Kannan's embedding alpha = params.pop("lwe/alpha") @@ -108,8 +110,8 @@ def lwe_kernel(arg0, params=None, seed=None): set_threads(threads) A, c, q = load_lwe_challenge(n=n, alpha=alpha) - print( "-------------------------" ) - print( "Primal attack, LWE challenge n=%d, alpha=%.4f" % (n, alpha) ) + print("-------------------------") + print("Primal attack, LWE challenge n=%d, alpha=%.4f" % (n, alpha)) if m is None: try: @@ -125,7 +127,7 @@ def lwe_kernel(arg0, params=None, seed=None): (b, s, _) = min_cost_param except TypeError: raise TypeError("No winning parameters.") - print( "Chose %d samples. Predict solution at bkz-%d + svp-%d" % (m, b, s)) + print("Chose %d samples. Predict solution at bkz-%d + svp-%d" % (m, b, s)) print("") target_norm = goal_margin * (alpha*q)**2 * m + 1 @@ -133,13 +135,12 @@ def lwe_kernel(arg0, params=None, seed=None): if blocksizes is not None: blocksizes = list(range(10, 40)) + eval("range(%s)" % re.sub(":", ",", blocksizes)) # noqa else: -# blocksizes = list(range(10, 50)) + list(range(90, b-15, 5)) + [b-15] + list(range(b - 13, b + 25, 2)) - blocksizes = list(range(10, 50)) + list(range(70, b-15, 10)) + [b-15] + list(range(b - 13, b + 25, 2)) + blocksizes = list(range(10, 50)) + list(range(70, b-15, 10)) + [b-15] + list(range(b - 13, b + 25, 2)) # noqa B = primal_lattice_basis(A, c, q, m=m) g6k = Siever(B, params) - print( "GSO precision: ", g6k.M.float_type ) + print("GSO precision: ", g6k.M.float_type) if dont_trace: tracer = dummy_tracer @@ -149,17 +150,19 @@ def lwe_kernel(arg0, params=None, seed=None): d = g6k.full_n g6k.lll(0, g6k.full_n) slope = basis_quality(g6k.M)["/"] - print( "Intial Slope = %.5f\n" % slope ) + print("Intial Slope = %.5f\n" % slope) T0 = time.time() T0_BKZ = time.time() + T_blocksizes = {} for blocksize in blocksizes: for tt in range(tours): # BKZ tours - + T0_this_blocksize = time.time() if blocksize < fpylll_crossover: if verbose: - print( "Starting a fpylll BKZ-%d tour. " % (blocksize), end='') + print("Starting a fpylll BKZ-%d tour. " % + (blocksize), end='') sys.stdout.flush() bkz = BKZReduction(g6k.M) par = fplll_bkz.Param(blocksize, @@ -169,7 +172,7 @@ def lwe_kernel(arg0, params=None, seed=None): else: if verbose: - print( "Starting a pnjBKZ-%d tour. " % (blocksize) ) + print("Starting a pnjBKZ-%d tour. " % (blocksize)) pump_n_jump_bkz_tour(g6k, tracer, blocksize, jump=jump, verbose=verbose, @@ -178,36 +181,94 @@ def lwe_kernel(arg0, params=None, seed=None): goal_r0=target_norm, pump_params=pump_params) + # total time spent on BKZ thus far T_BKZ = time.time() - T0_BKZ + # time for one tour of this blocksize + T_blocksizes[blocksize] = time.time() - T0_this_blocksize + if verbose: slope = basis_quality(g6k.M)["/"] fmt = "slope: %.5f, walltime: %.3f sec" - print( fmt % (slope, time.time() - T0) ) + print(fmt % (slope, time.time() - T0)) g6k.lll(0, g6k.full_n) if g6k.M.get_r(0, 0) <= target_norm: break - # overdoing n_max would allocate too much memory, so we are careful - svp_Tmax = svp_bkz_time_factor * T_BKZ expo = 0.292 - # solving for maximal d s.t. 2^{expo * d} / param.threads <= svp_Tmax - # cannot figure out the additive 58, will leave for now - n_max = int(58+6 + (1./expo) * log(svp_Tmax * params.threads)/log(2.)) - - rr = [g6k.M.get_r(i, i) for i in range(d)] - for n_expected in range(2, d-2): - x = (target_norm/goal_margin) * n_expected/(1.*d) - if 4./3 * gaussian_heuristic(rr[d-n_expected:]) > x: - break - - print( "Without otf, would expect solution at pump-%d. n_max=%d in the given time." % (n_expected, n_max) )# noqa - if n_expected >= n_max - 1: + svp_Tmax = svp_bkz_time_factor * T_BKZ + + def attainable_SVP_dim(T_svp): + # solve max d s.t. 2^{expo * d} / param.threads <= svp_Tmax + return int(58+6 + (1./expo) * + log(T_svp * params.threads)/log(2.)) + + def expected_successful_SVP_dim(rr=None): + if rr is None: + rr = [g6k.M.get_r(i, i) for i in range(d)] + for n_expected in range(2, d): + x = (target_norm/goal_margin) * n_expected/(1.*d) + if 4./3 * gaussian_heuristic(rr[d-n_expected:]) > x: + break + return n_expected + + # given current state of basis + current_n_max = attainable_SVP_dim(svp_Tmax) + current_n_expected = expected_successful_SVP_dim() + + print("Without otf, would expect solution at pump-%d. n_max=%d in the given time." % (current_n_expected, current_n_max)) # noqa + if current_n_expected >= current_n_max - 1: + # stronger BKZ required + continue + + # perhaps we still prefer stronger BKZ, e.g. to reduce memory + # currently look at the next three potential tours + ind = blocksizes.index(blocksize) + remaining_blocksizes = blocksizes[ind+1:ind+4] + + # expected extra BKZ times, and future basis profiles + extra_T_BKZs = [] + future_rrs = [] + + # data on blocksize, basis profile, individual blocksize times + previous_blocksize = blocksize + rr = list(g6k.M.r()) + extra_T_BKZ = 0 + temp_T_blocksizes = T_blocksizes + + for rblock in remaining_blocksizes: + # simulate basis profile after next tour, update basis profile + bkz_params = fplll_bkz.Param(block_size=rblock, max_loops=1) + rr, _ = simulate(rr, bkz_params) + future_rrs += [rr] + + # time for BKZ-rblock tour as a function of the previous tour + T_rblock_BKZ = temp_T_blocksizes[previous_blocksize] * 2**(expo * (rblock - previous_blocksize)) # noqa + temp_T_blocksizes[rblock] = T_rblock_BKZ + + # total extra BKZ time with rblock tour + extra_T_BKZ += T_rblock_BKZ + extra_T_BKZs += [extra_T_BKZ] + previous_blocksize = rblock + + more_BKZ = False + + for (rr, extra_T_BKZ) in zip(future_rrs, extra_T_BKZs): + potential_n_expected = expected_successful_SVP_dim(rr=rr) + svp_dim_dec = potential_n_expected - current_n_expected + if svp_dim_dec >= 0: + continue + time_SVP_current = 2**(expo * (current_n_expected - 64)) + time_SVP_current /= params.threads + if extra_T_BKZ < favour_memory_factor * (1-2**(expo*svp_dim_dec)) * time_SVP_current: # noqa + more_BKZ = True + + if more_BKZ: continue - n_max += 1 + current_n_max += 1 # Larger SVP @@ -215,16 +276,16 @@ def lwe_kernel(arg0, params=None, seed=None): while gaussian_heuristic([g6k.M.get_r(i, i) for i in range(llb, d)]) < target_norm * (d - llb)/(1.*d): # noqa llb -= 1 - f = d-llb-n_max + f = d-llb-current_n_max if verbose: - print( "Starting svp pump_{%d, %d, %d}, n_max = %d, Tmax= %.2f sec" % (llb, d-llb, f, n_max, svp_Tmax) ) # noqa + print( "Starting svp pump_{%d, %d, %d}, n_max = %d, Tmax= %.2f sec" % (llb, d-llb, f, current_n_max, svp_Tmax) ) # noqa pump(g6k, tracer, llb, d-llb, f, verbose=verbose, goal_r0=target_norm * (d - llb)/(1.*d)) if verbose: slope = basis_quality(g6k.M)["/"] fmt = "\n slope: %.5f, walltime: %.3f sec" - print( fmt % (slope, time.time() - T0) ) + print(fmt % (slope, time.time() - T0)) print g6k.lll(0, g6k.full_n) @@ -233,8 +294,8 @@ def lwe_kernel(arg0, params=None, seed=None): break if g6k.M.get_r(0, 0) <= target_norm: - print( "Finished! TT=%.2f sec" % (time.time() - T0) ) - print( g6k.M.B[0] ) + print("Finished! TT=%.2f sec" % (time.time() - T0)) + print(g6k.M.B[0]) alpha_ = int(alpha*1000) filename = 'lwechallenge/%03d-%03d-solution.txt' % (n, alpha_) fn = open(filename, "w") @@ -257,6 +318,7 @@ def lwe(): lwe__m=None, lwe__goal_margin=1.5, lwe__svp_bkz_time_factor=1, + lwe__favour_memory_factor=1, bkz__blocksizes=None, bkz__tours=1, bkz__jump=1,