diff --git a/scripts.py b/scripts.py index 1314e3559..67bee5151 100644 --- a/scripts.py +++ b/scripts.py @@ -2,17 +2,15 @@ import matplotlib.pyplot as plt from sage.stats.distributions.discrete_gaussian_lattice import DiscreteGaussianDistributionIntegerSampler from concrete_params import concrete_LWE_params, concrete_RLWE_params import numpy as np -from pytablewriter import MarkdownTableWriter +#from pytablewriter import MarkdownTableWriter from hybrid_decoding import parameter_search from random import uniform -from mpl_toolkits import mplot3d # easier to just load the estimator -load("estimator.py") +import estimator.estimator as est # define the four cost models used for Concrete (2 classical, 2 quantum) # note that classical and quantum are the two models used in the "HE Std" - def classical(beta, d, B): return ZZ(2) ** RR(0.292 * beta + 16.4 + log(8 * d, 2)) @@ -28,10 +26,9 @@ def classical_conservative(beta, d, B): def quantum_conservative(beta, d, B): return ZZ(2) ** RR(0.265 * beta) +# we add an enumeration model for completeness +cost_models = [classical, quantum, classical_conservative, quantum_conservative, est.BKZ.enum] -cost_models = [classical, quantum, classical_conservative, quantum_conservative, BKZ.enum] - -# functions to automate parameter selection def get_security_level(estimate, decimal_places = 2): """ Function to get the security level from an LWE Estimator output, @@ -115,6 +112,7 @@ def get_all_security_levels(params): return RESULTS + def get_hybrid_security_levels(params): """ A function which gets the security levels of a collection of TFHE parameters, using the four cost models: classical, quantum, classical_conservative, and @@ -207,7 +205,7 @@ def inequality(x, y): return -1 -def automated_param_select_n(sd, n=None, q=2 ** 32, reduction_cost_model=BKZ.sieve, secret_distribution=(0, 1), +def automated_param_select_n(sd, n=None, q=2 ** 32, reduction_cost_model=est.BKZ.sieve, secret_distribution=(0, 1), target_security=128): """ A function used to generate the smallest value of n which allows for target_security bits of security, for the input values of (sd,q) @@ -233,7 +231,6 @@ def automated_param_select_n(sd, n=None, q=2 ** 32, reduction_cost_model=BKZ.sie # initial estimate, to determine if we are above or below the target security level - print("estimating for n = {}, q, sd".format(n)) try: estimate = estimate_lwe(n, alpha, q, secret_distribution=secret_distribution, reduction_cost_model=reduction_cost_model, m=oo, @@ -243,18 +240,12 @@ def automated_param_select_n(sd, n=None, q=2 ** 32, reduction_cost_model=BKZ.sie estimate = estimate_lwe(n, alpha, q, secret_distribution=secret_distribution, reduction_cost_model=reduction_cost_model, m=oo, skip={"bkw", "dec", "arora-gb", "mitm", "dual"}) - print("the estimate is {}".format(estimate)) security_level = get_security_level(estimate) - print("the security level is: {}".format(security_level)) z = inequality(security_level, target_security) - print("the result of Z is{}".format(z)) while z * security_level < z * target_security and n > 80: n += z * 8 - print("N = {}".format(n)) - print("SECURITY LEVEL = {}".format(security_level)) alpha = sqrt(2 * pi) * sd / RR(q) - print("estimating for n = {}, q, sd".format(n)) try: estimate = estimate_lwe(n, alpha, q, secret_distribution=secret_distribution, reduction_cost_model=reduction_cost_model, m=oo, skip = {"bkw","dec","arora-gb","mitm"}) @@ -264,7 +255,7 @@ def automated_param_select_n(sd, n=None, q=2 ** 32, reduction_cost_model=BKZ.sie skip={"bkw", "dec", "arora-gb", "mitm", "dual"}) security_level = get_security_level(estimate) - if (-1 * sd > log(q, 2)): + if (-1 * sd > 0): print("target security level is unatainable") break @@ -272,8 +263,6 @@ def automated_param_select_n(sd, n=None, q=2 ** 32, reduction_cost_model=BKZ.sie if security_level < target_security: n -= z * 8 alpha = sqrt(2 * pi) * sd / RR(q) - print("N = {}".format(n)) - print("SECURITY LEVEL = {}".format(security_level)) try: estimate = estimate_lwe(n, alpha, q, secret_distribution=secret_distribution, reduction_cost_model=reduction_cost_model, m=oo, skip = {"bkw","dec","arora-gb","mitm"}) @@ -296,8 +285,7 @@ def automated_param_select_n(sd, n=None, q=2 ** 32, reduction_cost_model=BKZ.sie return n - -def automated_param_select_sd(n, sd=None, q=2**32, reduction_cost_model=BKZ.sieve, secret_distribution=(0, 1), +def automated_param_select_sd(n, sd=None, q=2**32, reduction_cost_model=est.BKZ.sieve, secret_distribution=(0, 1), target_security=128): """ A function used to generate the smallest value of sd which allows for target_security bits of security, for the input values of (n,q) @@ -325,7 +313,6 @@ def automated_param_select_sd(n, sd=None, q=2**32, reduction_cost_model=BKZ.siev alpha = sqrt(2 * pi) * sd_ / RR(q) # initial estimate, to determine if we are above or below the target security level - print("estimating for n, q, sd = {}".format(log(sd_,2))) try: estimate = estimate_lwe(n, alpha, q, secret_distribution=secret_distribution, reduction_cost_model=reduction_cost_model, m=oo, @@ -338,10 +325,10 @@ def automated_param_select_sd(n, sd=None, q=2**32, reduction_cost_model=BKZ.siev z = inequality(security_level, target_security) while z * security_level < z * target_security and sd > -log(q,2): - sd += z * 1 + + sd += z * (0.5) sd_ = (2 ** sd) * q alpha = sqrt(2 * pi) * sd_ / RR(q) - print("estimating for n, q, sd = {}".format(log(sd_,2))) try: estimate = estimate_lwe(n, alpha, q, secret_distribution=secret_distribution, reduction_cost_model=reduction_cost_model, m=oo, skip = {"bkw","dec","arora-gb","mitm"}) @@ -351,13 +338,14 @@ def automated_param_select_sd(n, sd=None, q=2**32, reduction_cost_model=BKZ.siev skip={"bkw", "dec", "arora-gb", "mitm", "dual"}) security_level = get_security_level(estimate) - if (-1 * sd > log(q, 2)): + ## THIS IS WHERE THE PROBLEM IS, CORRECT THIS CONDITION? + if (sd > log(q, 2)): print("target security level is unatainable") - break + return None # final estimate (we went too far in the above loop) if security_level < target_security: - sd -= z * 1 + sd -= z * (0.5) sd_ = (2 ** sd) * q alpha = sqrt(2 * pi) * sd_ / RR(q) try: @@ -378,7 +366,7 @@ def automated_param_select_sd(n, sd=None, q=2**32, reduction_cost_model=BKZ.siev return sd -def generate_parameter_matrix(n_range, sd=None, q=2**32, reduction_cost_model=BKZ.sieve, +def generate_parameter_matrix(n_range, sd=None, q=2**32, reduction_cost_model=est.BKZ.sieve, secret_distribution=(0, 1), target_security=128): """ :param n_range: a tuple (n_min, n_max) giving the values of n for which to generate parameters @@ -399,12 +387,16 @@ def generate_parameter_matrix(n_range, sd=None, q=2**32, reduction_cost_model=BK RESULTS = [] - # grab min and max value/s of n - (n_min, n_max) = n_range + # grab min and max value/s of n, with a granularity (if given as input) + try: + (n_min, n_max, gran) = n_range + except: + (n_min, n_max) = n_range + gran = 1 sd_ = sd - for n in range(n_min, n_max + 1): + for n in range(n_min, n_max + 1, gran): sd = automated_param_select_sd(n, sd=sd_, q=q, reduction_cost_model=reduction_cost_model, secret_distribution=secret_distribution, target_security=target_security) sd_ = sd @@ -413,10 +405,10 @@ def generate_parameter_matrix(n_range, sd=None, q=2**32, reduction_cost_model=BK return RESULTS -def generate_parameter_matrix_sd(sd_range, n=None, q=2**32, reduction_cost_model=BKZ.sieve, +def generate_parameter_matrix_sd(sd_range, n=None, q=2**32, reduction_cost_model=est.BKZ.sieve, secret_distribution=(0, 1), target_security=128): """ - :param n_range: a tuple (n_min, n_max) giving the values of n for which to generate parameters + :param sd_range: a tuple (sd_min, sd_max) giving the values of sd for which to generate parameters :param sd: the standard deviation of the LWE error :param q: the LWE modulus (q = 2**32, 2**64 in TFHE) :param reduction_cost_model: the BKZ cost model considered, BKZ.sieve is default @@ -476,71 +468,6 @@ def generate_parameter_step(results, label = None, torus_sd = True): return plt -def test_rounded_gaussian(sigma, number_samples, q = None): - """ - TODO: actually use a _rounded_ gaussian to match Concrete - - A function which simulates sampling from a Discrete Gaussian distribution - :param sigma: the standard deviation - :param number_samples: the number of samples to draw - - returns: a list of (value, count) pairs (essentially a histogram) - - EXAMPLE: - - sage: X = test_rounded_gaussian(2/3, 100000) - sage: X - [(-3, 2), (-2, 714), (-1, 19495), (0, 59658), (1, 19452), (2, 678), (3, 1)] - """ - - D = DiscreteGaussianDistributionIntegerSampler(sigma) - samples = [] - - for i in range(number_samples): - if q: - samples.append(D() % q) - else: - samples.append(D()) - # now create a histogram - hist = [] - for val in set(samples): - hist.append((val, samples.count(val))) - - # sort (values) - hist.sort(key=lambda x:x[0]) - return hist - - -def test_uniform(number_samples, q): - """ - TODO: actually use a _rounded_ gaussian to match Concrete - - A function which simulates sampling from a Discrete Gaussian distribution - :param sigma: the standard deviation - :param number_samples: the number of samples to draw - - returns: a list of (value, count) pairs (essentially a histogram) - - EXAMPLE: - - sage: X = test_rounded_gaussian(2/3, 100000) - sage: X - [(-3, 2), (-2, 714), (-1, 19495), (0, 59658), (1, 19452), (2, 678), (3, 1)] - """ - - samples = [] - - for i in range(number_samples): - samples.append(round(uniform(0, q))) - # now create a histogram - hist = [] - for val in set(samples): - hist.append((val, samples.count(val))) - - # sort (values) - hist.sort(key=lambda x: x[0]) - return hist - # dual bug example # n = 256; q = 2**32; sd = 2**(-4); reduction_cost_model = BKZ.sieve # _ = estimate_lwe(n, alpha, q, reduction_cost_model) @@ -550,7 +477,7 @@ def test_params(n, q, sd, secret_distribution): sd = sd * q alpha = RR(sqrt(2*pi) * sd / q) - est = estimate_lwe(n, alpha, q, secret_distribution = secret_distribution, reduction_cost_model = BKZ.sieve, skip = ("arora-gb", "bkw", "mitm", "dec")) + est = estimate_lwe(n, alpha, q, secret_distribution = secret_distribution, reduction_cost_model = est.BKZ.sieve, skip = ("arora-gb", "bkw", "mitm", "dec")) return est @@ -564,10 +491,10 @@ def generate_iso_lines(N = [256, 2048], SD = [0, 32], q = 2**32): sd = 2**sd alpha = sqrt(2*pi) * sd / q try: - est = estimate_lwe(n, alpha, q, secret_distribution = (0,1), reduction_cost_model = BKZ.sieve, skip = ("bkw", "mitm", "arora-gb", "dec")) + est = estimate_lwe(n, alpha, q, secret_distribution = (0,1), reduction_cost_model = est.BKZ.sieve, skip = ("bkw", "mitm", "arora-gb", "dec")) est = get_security_level(est, 2) except: - est = estimate_lwe(n, alpha, q, secret_distribution = (0,1), reduction_cost_model = BKZ.sieve, skip = ("bkw", "mitm", "arora-gb", "dual", "dec")) + est = estimate_lwe(n, alpha, q, secret_distribution = (0,1), reduction_cost_model = est.BKZ.sieve, skip = ("bkw", "mitm", "arora-gb", "dual", "dec")) est = get_security_level(est, 2) RESULTS.append((n, sd, est)) @@ -578,17 +505,14 @@ def plot_iso_lines(results): x1 = [] x2 = [] - x3 = [] for z in results: x1.append(z[0]) # use log(q) # use -ve values to match Pascal's diagram - x2.append(-1 * log(z[1],2)) - x3.append(z[3]) + x2.append(z[2]) - plt.scatter(x1, x2, c = x3) - plt.colorbar() + plt.plot(x1, x2) return plt @@ -604,7 +528,6 @@ def test_multiple_sd(n, q, secret_distribution, reduction_cost_model, split = 33 es = estimate_lwe(n=512, alpha=alpha, q=q, secret_distribution=(0, 1), reduction_cost_model = reduction_cost_model, skip=("bkw", "mitm", "dec", "arora-gb"), m = m) except: - print("except") es = estimate_lwe(n=512, alpha=alpha, q=q, secret_distribution=(0, 1), reduction_cost_model = reduction_cost_model, skip=("bkw", "mitm", "dec", "arora-gb", "dual"), m = m) est.append(get_security_level(es,2)) @@ -612,133 +535,183 @@ def test_multiple_sd(n, q, secret_distribution, reduction_cost_model, split = 33 return est, Y -def output_secret_distribution(m): - """ - generate the correct secret_distirbution for the given input - :param m: the number of elements in the secret distribution +## parameter curves + +def get_parameter_curves_data_sd(sec_levels, sd_range, q): + + Results = [] + for sec in sec_levels: + try: + result_sec = generate_parameter_matrix_sd(n = None, sd_range=sd_range, q=q, reduction_cost_model=est.BKZ.sieve, + secret_distribution=(0,1), target_security=sec) + Results.append(result_sec) + except: + pass + + return Results + + +def get_parameter_curves_data_n(sec_levels, n_range, q): + + Results = [] + for sec in sec_levels: + try: + result_sec = generate_parameter_matrix(n_range, sd = None, q=q, reduction_cost_model=est.BKZ.sieve, + secret_distribution=(0,1), target_security=sec) + Results.append(result_sec) + except: + pass + + return Results + + +def interpolate_result(result, log_q): + + import numpy as np + # linear function interpolation + x = [] + y = [] + + # 1. filter out any points which reccomend sd = -log(q) + 2 + new_result= [] + for res in result: + if res[2] >= - log_q + 2: + new_result.append(res) + + result = new_result + for res in result: + x.append(res[0]) + y.append(res[2]) + + + (a,b) = np.polyfit(x, y, 1) + + return (a,b) + + +def plot_interpolants(interpolants, n_range, log_q, degree = 1): + for x in interpolants: + if degree == 1: + vals = [x[0] * n + x[1] for n in range(n_range[0],n_range[1])] + elif degree == 2: + vals = [x[0] * n**2 + x[1]*n + x[2] for n in range(n_range[0],n_range[1])] + # any values which fall outside of the range and edited to give at least two bits of noise. + + vvals = [] + for v in vals: + if v < -log_q + 2: + vvals.append(-log_q + 2) + else: + vvals.append(v) + + plt.plot(range(n_range[0], n_range[0] + len(vvals)), vvals) + + return 0 + + +## currently running +# sage: n_range = (256, 2048, 16) +# sage: sec_levels = [80 + 16*k for k in range(0,12)] +# sage: results = get_parameter_curves_data_n(sec_levels, n_range, q = 2**64) + +def verify_results(results, security_level, secret_distribution = (0,1), reduction_cost_model = est.BKZ.sieve): + """ A function which verifies that a set of results match a given security level + :param results : a set of tuples of the form (n, q, sd) + :param security_level: the target security level for these params """ - # the code doesn't work for m < 2 - assert m >= 2 + estimates = [] - if m % 2 ==1: - # m is odd - b = (m - 1)/2 - secret_distribution = (-b, b) - else: - # m is even - b = m / 2 - 1 - secret_distribution = (-b, b + 1) + # 1. Grab the parameters + for (n, q, sd) in results: + if sd is not None: + sd = 2**sd + alpha = sqrt(2*pi) * sd - return secret_distribution - -def get_marcs_curves(n_range, q, m_max): - - # the final result will be a list of m_max elements, each containing - # a parameter matrix - RESULTS = [] - - for m in range(2, m_max + 1): - secret_distribution = output_secret_distribution(m) - result_m = generate_parameter_matrix(n_range, sd=None, q=q, reduction_cost_model=BKZ.sieve, - secret_distribution=secret_distribution, target_security=128) - RESULTS.append(result_m) - return RESULTS - - -def get_marcs_curves_n(sd_range, q, m_max): - - # the final result will be a list of m_max elements, each containing - # a parameter matrix - RESULTS = [] - - for m in range(2, m_max + 1): - secret_distribution = output_secret_distribution(m) - result_m = generate_parameter_matrix_sd(n = None, sd_range=sd_range, q=q, reduction_cost_model=BKZ.sieve, - secret_distribution=secret_distribution, target_security=128) - RESULTS.append(result_m) - return RESULTS - - -def tabulate_results(results): - """ Put the results from get_marcs_curves into a LaTeX table - """ - - new_results = [] - num_results = len(results[0]) - num_entries = len(results) - - key = [] - key.append("n") - key.append("q") - for i in range(num_entries): - key.append("m = {}".format(i + 2)) - - new_results.append(key) - - - - - for j in range(num_results): - result_j = [] - result_j.append(results[0][j][0]) - result_j.append(int(log(results[0][0][1],2))) - - for i in range(num_entries): - result_j.append(int(results[i][j][2])) - - new_results.append(result_j) - - return new_results - -def tabulate_results_sd(results): - - new_results = [] - num_results = len(results[0]) - num_entries = len(results) - - key = [] - key.append("sd") - key.append("q") - - for i in range(num_entries): - key.append("m = {}".format(i + 2)) - - new_results.append(key) - - for j in range(num_results): - result_j = [] - result_j.append(results[0][j][2]) - result_j.append(int(log(results[0][0][1],2))) - - for i in range(num_entries): + # 2. Test that these parameters satisfy the given security level try: - result_j.append(int(results[i][j][0])) + estimate = estimate_lwe(n, alpha, q, secret_distribution=secret_distribution, + reduction_cost_model=reduction_cost_model, m=oo, skip = {"bkw","dec","arora-gb","mitm"}) except: - result_j.append(str(results[i][j][0])) + estimate = estimate_lwe(n, alpha, q, secret_distribution=secret_distribution, + reduction_cost_model=reduction_cost_model, m=oo, + skip={"bkw", "dec", "arora-gb", "mitm", "dual"}) + + estimates.append(estimate) - new_results.append(result_j) + return estimates - return new_results -# code to cross-check the security levels for marc/pascal results -# sage: with open("results_32_128.txt", "rb") as fp: # Unpickling -# ....: ... X = pickle.load(fp) -# res = [] -# sage: for i in range(len(X)): -# ....: x = X[i] -# ....: m = i + 2 -# ....: secret_distribution = output_secret_distribution(m) -# ....: for (n, q, sd) in x: -# ....: if n is not None: -# ....: sd = 2**(sd) -# ....: alpha = sqrt(2*pi) * sd -# ....: print((n, q, sd)) -# ....: try: -# ....: _ = estimate_lwe(n, alpha, q, secret_distribution = secret_distribution, reduction_cost_model = BKZ.sieve, skip = ("arora-gb", "mitm", "bkw", "dec")) -# ....: except: -# ....: _ = estimate_lwe(n, alpha, q, secret_distribution = secret_distribution, reduction_cost_model = BKZ.sieve, skip = ("arora-gb", "mitm", "bkw", "dec", "dual")) -# ....: else: -# ....: print("None") -# ....: res.append(get_security_level(_)) -# ....:print(min(res)) \ No newline at end of file +def verify_interpolants(interpolant, n_range, log_q, secret_distribution = (0,1), reduction_cost_model = est.BKZ.sieve): + + estimates = [] + q = 2**log_q + (a, b) = interpolant + + for n in range(n_range[0], n_range[1]): + print(n) + # we take the max here to ensure that the "cut-off" for the minimal error is met. + sd = max(a * n + b, -log_q + 2) + sd = 2 ** sd + alpha = sqrt(2*pi) * sd + + try: + estimate = estimate_lwe(n, alpha, q, secret_distribution=secret_distribution, + reduction_cost_model=reduction_cost_model, m=oo, skip = {"bkw","dec","arora-gb","mitm"}) + except: + estimate = estimate_lwe(n, alpha, q, secret_distribution=secret_distribution, + reduction_cost_model=reduction_cost_model, m=oo, + skip={"bkw", "dec", "arora-gb", "mitm", "dual"}) + estimates.append(get_security_level(estimate)) + + return estimates + +def get_zama_curves(): + + # hardcode the parameters for now + n_range = [128, 2048, 32] + sec_levels = [80 + 16*k for k in range(0,12)] + results = get_parameter_curves_data_n(sec_levels, n_range, q = 2**64) + + return results + + +def test_curves(): + + # a small hardcoded example for testing purposes + + n_range = [256, 1024, 128] + sec_levels = [80, 128, 256] + results = get_parameter_curves_data_n(sec_levels, n_range, q = 2**64) + + return results + + +## we start with 80/128/192/256-bits of security + +## lambda = 80 +## z = verify_interpolants(interps[0], (128,2048), 64) +## i = 0 +## min(z[i:]) = 80.36 +## so the model is sd(n) = max(-0.04047677865612648 * n + 1.1433465085639063, log_q - 2), n >= 128 + + +## lambda = 128 +## z = verify_interpolants(interps[3], (128,2048), 64) +## i = 83 +## min(z[i:]) = 128.02 +## so the model is sd(n) = max(-0.026374888765705498 * n + 2.012143923330495, log_q - 2), n >= 211 ( = 128 + 83) + + +## lambda = 192 +## z = verify_interpolants(interps[7], (128,2048), 64) +## i = 304 +## min(z[i:]) = 192.19 +## so the model is sd(n) = max(-0.018504919354426233 * n + 2.6634073426215843, log_q - 2), n >= 432 ( = 128 + 212) + + +## lambda = 256 +## z = verify_interpolants(interps[-1], (128,2048), 64) +## i = 653 +## min(z[i:]) = 256.25 +## so the model is sd(n) = max(-0.014327640360322604 * n + 2.899270827311091), log_q - 2), n >= 781 ( = 128 + 653)