def find_opt_distr(sigma, samples, ubits, cost_cl, cost_pq, cost_pp): """Finds an optimal distribution approximating rounded continuous Gaussian. Args: sigma: The standard deviation of the target (rounded) Gaussian. samples: The total number of samples drawn by both parties combined. ubits: The bound on the number of uniform bits required for sampling. cost_cl, cost_pq, cost_pp: Estimated costs of the rounded Gaussian. Returns: Four-tuple consisting of the distribution and the cost triplet. """ cost_cl_opt, d, _ = approximate_dgauss(sigma, samples, cost_cl, None, ubits, quiet=True) sym_d = pdf_product(d, {+1: .5, -1: .5}) dg = dgauss(sigma) _, cost_pq_opt = opt_renyi_bound(-cost_pq * log(2), sym_d, dg, samples) _, cost_pp_opt = opt_renyi_bound(-cost_pp * log(2), sym_d, dg, samples) return [sym_d, cost_cl_opt, -cost_pq_opt / log(2), -cost_pp_opt / log(2)]
def find_binomial_cost(sigma, samples, cost_cl, cost_pq, cost_pp): """Estimates the cost of replacing a rounded Gaussian with a binomial. Args: sigma: The standard deviation of the Gaussian. samples: The total number of samples drawn by Alice and Bob. cost_cl, cost_pq, cost_pp: Estimated costs of the rounded Gaussian. Returns: Four-tuple consisting of the distribution and the cost triplet. """ dg = dgauss(sigma) # The binomial is defined as B(2*z, .5) - z. sb = sym_binomial(2 * sigma**2) _, cost_cl_binomial = opt_renyi_bound(-cost_cl * log(2), sb, dg, samples) _, cost_pq_binomial = opt_renyi_bound(-cost_pq * log(2), sb, dg, samples) _, cost_pp_binomial = opt_renyi_bound(-cost_pp * log(2), sb, dg, samples) return [ sb, -cost_cl_binomial / log(2), -cost_pq_binomial / log(2), -cost_pp_binomial / log(2) ]
def distribution_to_TeX(p): """Formats distribution for use in TeX file. Args: p: A dictionary with entries for 'distr', 'sigma', 'a', 'D' Returns: LaTeX string. """ distr, sigma, a, distr_name = p['distr'], p['sigma'], p['a'], p['D'] n = max(distr.iterkeys()) + 1 # range is [0..n) b = bits_needed_to_sample(distr) ret = "${}$ & {} & {:.2f} & ".format(distr_name, b + 1, sigma**2) for i in sorted(distr.iterkeys()): if i >= 0: p = int(round(distr[i] * 2**b)) if i == 0: p *= 2 ret += r"\!\!{}\!\!&".format(p) for i in xrange(max(distr.iterkeys()), 6): ret += "&" divergence = renyi(distr, nonnegative_half(dgauss(sigma)), a) ret += " {:.1f} & {:.7f}".format(a, divergence) return ret + r" \\"
def distribution_to_tex(p): """Formats distribution for use in TeX file. Args: p: A dictionary with entries for 'distr', 'sigma', 'a', 'name' Returns: LaTeX string. """ distr, sigma, a, distr_name = p['distr'], p['sigma'], p['a'], p['name'] dg = dgauss(sigma) b = bits_needed_to_sample(distr) ret = '{} & {:.3f} &'.format(distr_name, sigma) for i in sorted(distr.iterkeys()): if i >= 0: p = int(round(distr[i] * 2**b)) if i == 0: p *= 2 ret += r'\!\!{}\!\!&'.format(p) for i in range(max(distr.iterkeys()), 12): ret += '&' divergence = renyi(distr, nonnegative_half(dg), a) ret += r' {:.1f} & ${:.2f}\times 10^{{-4}}$'.format(a, divergence * 10**4) return ret + r' \\'
def parameters_to_tex(p): """Formats parameters for use in TeX file. Args: p: A dictionary with entries for 'name', 'n', 'q', 'D', 'B', 'distr' Returns: LaTeX string. """ def print_cost(attack_type): _, _, cost_pc = optimize_attack( 2**q, n, max(nbar, mbar) + n, sigma, dual_cost, attack_type ) # Only compute the dual cost, since it is smaller than the primal cost. cost_pc -= log(nbar + mbar) / log( 2 ) # Take into account the hybrid argument over mbar + nbar instances. _, cost_pc_reduced = opt_renyi_bound(-cost_pc * log(2), sym_distr, dg, samples) return ' & {}'.format(int(-cost_pc_reduced / log(2))) name, n, q, b, distr, sigma, nbar, mbar, keylen = (p['name'], p['n'], p['q'], p['B'], p['distr'], p['sigma'], p['n_bar'], p['m_bar'], p['key_len']) s = name.capitalize() s += r' & \!\! {} \!\!'.format(n) s += r' & \!\! $2^{{{}}}$ \!\!'.format(q) sigma_str = r'{:.3f}'.format(sigma).rstrip('0.') s += r' &\quad ' + sigma_str s += r' &$[{}\dots {}]$'.format(-max(distr), max(distr)) s += r' &\!\!{}\!\!'.format(b) s += r' & ${}\times {}$'.format(nbar, mbar) sym_distr = pdf_product(distr, {+1: .5, -1: .5}) failure_prob = exact_failure_prob_pke(sym_distr, 2**q, n, b, keylen) if failure_prob == 0: s += r' & $0$ ' else: s += r' & $2^{{{:.1f}}}$'.format(log(failure_prob) / log(2)) ct_len = ((mbar * n + mbar * nbar) * q + keylen) // 8 s += r' & {}'.format(ct_len) samples = n * (nbar + mbar) + nbar * mbar dg = dgauss(sigma) s += print_cost(svp_classical) # s += print_cost(svp_quantum) s += print_cost(svp_plausible) s += r' \\' return s
def security_to_TeX(p, nbar, print_sec=True): """Formats security estimates for use in TeX file. Args: p: A dictionary with entries for 'name', 'n', 'q', 'distr', 'sigma' nbar: Number of columns in exchanged matrices. print_sec: If true, output will include security estimates Returns: LaTeX string. """ name, n, qlog, d, sigma = p['name'], p['n'], p['q'], p['distr'], p['sigma'] samples = 2 * n * nbar + nbar**2 q = 2**qlog ret = "" ret += r"\multirow{2}{*}{" + name.capitalize() + "} " for cost in [primal_cost, dual_cost]: m_pc, b_pc, cost_pc = optimize_attack(q, n, samples, sigma, cost, svp_classical, verbose=False) m_pq, b_pq, cost_pq = optimize_attack(q, n, samples, sigma, cost, svp_quantum, verbose=False) m_pp, b_pp, cost_pp = optimize_attack(q, n, samples, sigma, cost, svp_plausible, verbose=False) if cost == primal_cost: ret += "& Primal & " else: ret += "& Dual & " ret += "{} & {} &".format(m_pc, b_pc) if print_sec: sym_d = pdf_product(d, {+1: .5, -1: .5}) dg = dgauss(sigma) _, cost_pc_reduced = opt_renyi_bound(-cost_pc * log(2), sym_d, dg, samples) _, cost_pq_reduced = opt_renyi_bound(-cost_pq * log(2), sym_d, dg, samples) _, cost_pp_reduced = opt_renyi_bound(-cost_pp * log(2), sym_d, dg, samples) ret += "{} & {} & {} & {} & {} & {} \\\\".format( int(cost_pc), int(cost_pq), int(cost_pp), int(-cost_pc_reduced / log(2)), int(-cost_pq_reduced / log(2)), int(-cost_pp_reduced / log(2))) # always round down else: ret += "-- & -- & -- & -- & -- & -- \\\\" ret += "\n" return ret
def main(): d1 = {0: 44 / 128., 1: 61 / 128., 2: 20 / 128., 3: 3. / 128} sym_d1 = pdf_product(d1, {+1: .5, -1: .5}) print "Parameters leading to deliberately large probability of failure (for testing purposes):" print "Distribution = D1,", print_failure_props(sym_d1, 10, 320, 1, 64) print "Recommended parameters:" d3 = { 0: 603 / 2048., 1: 919 / 2048., 2: 406 / 2048., 3: 104 / 2048., 4: 15 / 2048., 5: 1 / 2048. } sym_d3 = pdf_product(d3, {+1: .5, -1: .5}) print "Distribution = D3,", print_failure_props(sym_d3, 15, 752, 4, 256) print "Parameters similar to recommended, distribution is different:" dg175 = dgauss(sqrt(1.75)) print "Distribution = rounded Gaussian with sigma^2 = 1.75,", print_failure_props(dg175, 15, 752, 4, 256)
def approximate_dgauss(sigma, samples, base_security, max_table_len, max_rand_bits, suffix='', quiet=True): """Approximates rounded Gaussian with a binomial and an optimal discrete distribution. Args: sigma: The standard deviation of the target Gaussian distribution. samples: Total number of samples per protocol run. base_security: The baseline security level, in bits (e.g., 150.34). max_table_len: Upper bound on the support of the distribution (can be None). max_rand_bits: Total number of uniformly random bits required for sampling. suffix: Suffix for printed out names. quiet: If quiet, suppress all output. Returns: (security bound, non-negative part of distribution, optimal Renyi order). """ dg = dgauss(sigma) half_dg = nonnegative_half(dg) if not quiet: print(suffix) z = sigma**2 * 2 if fmod(z, 1.) != 0: print('Skipping binomial') else: sb = sym_binomial(2 * int(z)) opt_a_sb, opt_bound_sb = opt_renyi_bound(-base_security * log(2), sb, dg, samples) print( 'Sigma = {:.3f}: Binomial distribution z = {}, security = {:.2f}, a ' '= {:.2f};').format(sigma, z, -opt_bound_sb / log(2), opt_a_sb) max_security = 0 opt_r = None opt_d = {} opt_a = None for a in orders: for random_bits in range( 1, max_rand_bits): # reserve one bit for the sign d = round_distr_opt(half_dg, 2**-random_bits, a, max_table_len, quiet=True) if d is not None: r = renyi(d, half_dg, a) security = -renyi_bound(-base_security * log(2), r * samples, a) if security > max_security: max_security = security opt_a = a opt_d = d opt_r = r if not quiet: if max_security == 0: print('Approximation is infeasible under given constraints') else: print('Security = {:.2f} a = {} Renyi divergence = {}'.format( max_security / log(2), opt_a, opt_r)) return [max_security / log(2), opt_d, opt_a]
def main(): # pyformat: disable parameters = [ { 'name': 'Frodo-640', 'sigma': 2.8, 'n': 640, 'm_bar': 8, 'n_bar': 8, 'q': 15, 'B': 2, 'key_len': 128, 'rand_bits': 16, 'sec_base': 105 }, { 'name': 'Frodo-976', 'sigma': 2.3, 'n': 976, 'm_bar': 8, 'n_bar': 8, 'q': 16, 'B': 3, 'key_len': 192, 'rand_bits': 16, 'sec_base': 151 }, { 'name': 'Frodo-1344', 'sigma': 1.4, 'n': 1344, 'm_bar': 8, 'n_bar': 8, 'q': 16, 'B': 4, 'key_len': 256, 'rand_bits': 16, 'sec_base': 195 }, ] # pyformat: enable for p in parameters: if p['rand_bits'] is not None: samples = (p['n_bar'] + p['m_bar']) * p['n'] + p['n_bar'] * p['m_bar'] _, p['distr'], p['a'] = approximate_dgauss(p['sigma'], samples, p['sec_base'], None, p['rand_bits'], quiet=True) else: gauss_dist = cutoff_tails(dgauss(p['sigma']), 2**-16) p['distr'], p['a'] = gauss_dist, float('inf') print('### DISTRIBUTION TO PYTHON ###') for p in parameters: print('sigma = {:.2f}: {}'.format(p['sigma'], distribution_to_python(p))) print() print('### C Code ###') for p in parameters: print(distribution_to_c(p['distr'])) print() print('### TABLE 1 ###') for p in parameters: print(parameters_to_tex(p)) print() print('### TABLE 2 ###') for p in parameters: print(distribution_to_tex(p)) print() print('### TABLE 4 ###') for p in parameters: print(print_sizes(p, 'Frodo', kem=True)) # print(r'\midrule') # for p in parameters: # print(print_sizes(p, 'Frodo', kem=False)) print() print('### PARAMETERS FOR CRYPTANALYIS ###') for p in parameters: print(security_to_tex(p), end='') if p['key_len'] == 256: print(r'\bottomrule') else: print(r'\midrule')
def approximate_dgauss(sigma, samples, base_security, max_table_len, max_rand_bits, suffix="", quiet=True): """Approximates rounded Gaussian with a binomial and an optimal discrete distribution. Args: sigma: The standard deviation of the target Gaussian distribution. samples: Total number of samples per protocol run. base_security: The baseline security level, in bits (e.g., 150.34). max_table_len: Upper bound on the support of the distribution (can be None). max_rand_bits: Total number of uniformly random bits required for sampling. suffix: Suffix for printed out names. quiet: If quiet, suppress all output. Returns: Optimal rounded distribution (only the non-negative support), its security bound, and the order of Renyi divergence used to derive this bound. """ dg = dgauss(sigma) half_dg = nonnegative_half(dg) if not quiet: print suffix z = sigma**2 * 2 if fmod(z, 1.) != 0: print "Skipping binomial" else: sb = sym_binomial(2 * int(z)) opt_a_sb, opt_bound_sb = opt_renyi_bound(-base_security * log(2), sb, dg, samples) print( "Sigma = {:.3f}: Binomial distribution z = {}, security = {:.2f}, a = " "{:.2f};").format(sigma, z, -opt_bound_sb / log(2), opt_a_sb) # Constrain Renyi orders of interest to the following set for performance # and aesthetic reasons a_values = [ 1.5, 2., 5., 10., 15., 20., 25., 30., 40., 50., 75., 100., 200., 500., float("inf") ] max_security = 0 opt_r = None opt_d = {} opt_a = None for a in a_values: for random_bits in xrange( 1, max_rand_bits): # reserve one bit for the sign d = round_distr_opt(half_dg, 2**-random_bits, a, max_table_len, quiet=True) if d is not None: r = renyi(d, half_dg, a) security = -renyi_bound(-base_security * log(2), log(r) * samples, a) if security > max_security: max_security = security opt_a = a opt_d = d opt_r = r if not quiet: if max_security == 0: print "Approximation is infeasible under given constraints" else: print "Security = {:.2f} a = {} Renyi divergence = {}".format( max_security / log(2), opt_a, opt_r) return [max_security / log(2), opt_d, opt_a]