From cfe7c2dc3df9b9ab9e89293a173b2459695d19e7 Mon Sep 17 00:00:00 2001 From: slow-connect <louisdeberlin+github@gmail.com> Date: Wed, 5 Feb 2025 12:39:13 +0100 Subject: [PATCH] initial commit --- main.sage | 130 +++++++++++++++++++++++++++++++++++++++++++++ plots.py | 156 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ tools.py | 109 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 395 insertions(+) create mode 100644 main.sage create mode 100644 plots.py create mode 100644 tools.py diff --git a/main.sage b/main.sage new file mode 100644 index 0000000..3df4421 --- /dev/null +++ b/main.sage @@ -0,0 +1,130 @@ +from sage.all import * +from sage.crypto.lwe import samples, LWE + +from tools import * +from plots import * + +def nistlike(n, q): + """ + Given n, q, generate a NIST-like LWE instance + + args: + n (int): dimension + q (int): modulus + + returns: + A (matrix): matrix + t (array): target vector + s (array): secret vector + e (array): error vector + """ + m=1 + DB = CenteredBinomialDistibution(2) + lwe_instance = LWE(n=n, q=q, D=DB, secret_dist='noise', m=m) + s = lwe_instance._LWE__s + s = array([int(x) for x in s]) + s = array([int(x) if x<= q//2 else int(x-q) for x in s], dtype=int) + a = samples(m, n, lwe_instance, balanced=True) + a = a[0][0] + assert all(-q//2 <=x <= q // 2 for x in a) + A = zeros((n, n), dtype=int) + for i in range(n): + for j in range(n): + if j > i: + A[i][j] = int(-a[i-j]) + else: + A[i][j] = int(a[i-j]) + e = zeros(n, dtype=int) + for i in range(n): + e[i] = int(DB()) + t = calculate_t(A, s, e, q) + return A, t, s, e + +def bkz_with_blocksize(instance): + A = instance[0] + t = instance[1] + s = instance[2] + e = instance[3] + q = instance[4] + b = instance[5] + B = SVPreduction(A, t, q) + params = BKZ.Param(b, strategies=BKZ.DEFAULT_STRATEGY, flags=BKZ.VERBOSE|BKZ.AUTO_ABORT) + bkz = BKZ2(GSO.Mat(B, float_type='mpfr')) + try: + bkz(params) + except: + return None + B_np = makenumpy(B) + s_best = B_np[0][:n] + e_best = B_np[0][n:-1] + sc = score(t, s_best, e_best, A, s, p) + if sc == 0: + return True + else: return False + +def time_bkz(task_function, *args, **kwargs): + start_time = time.time() + result = task_function(*args) + end_time = time.time() + execution_time = end_time - start_time + return result, execution_time + + +# Run LLL +tries = 128 +pp = [3, 17, 71, 277, 401, 521, 1031, 3329] +nn = range(8, 81, 8) +for p in pp: + x = zeros(len(nn)) + k = 0 + for n in nn: + for _ in range(tries): + A, t, s, e = nistlike(n, p) + B = SVPreduction(A.copy(), t, p) + L = LLL.reduction(B) + B_np = makenumpy(L) + s_best = B_np[0][:n] + e_best = B_np[0][n:2*n] + sc = score(t, s_best, e_best, A, s, p) + if sc == 0: + x[k] += 1 + x[k] = x[k] / tries + output = open('lll_results.txt', 'a') + output.write(f'{p} {n} {x[k-1]}\n') + output.close() + k += 1 + +plot_lll() + + + +# Run BKZ +tries = 256 +pp = [71, 401, 3329] +nn = range(8, 81, 8) +blocks = [8, 16, 24, 32, 40, 48, 56] + +for p in pp: + for n in nn: + inst = [] + for _ in range(tries): + A, t, s, e = nistlike(n, p) + i = (A, t, s, e, p) + inst.append(i) + for b in blocks: + b_inst = [(i[0].copy(), i[1].copy(), i[2].copy(), i[3].copy(), i[4].copy(), b) for i in inst] + with multiprocessing.Pool() as pool: + result = pool.starmap(time_bkz, [(bkz_with_blocksize, i) for i in b_inst]) + res = [r for r in result if r[0] != None] + if len(res) == 0: + # fpylll infinite babei loop error for all instances + tt = nan + prob = 0 + else: + tt = sum(r[1] for r in res)/len(res) + prob = sum(r[0] for r in res)/len(res) + output = open('bkz_results.txt', 'a') + output.write(f"{n} {p} {b} {prob} {tt} {len(res)}\n") + output.close() + +plot_bkz() diff --git a/plots.py b/plots.py new file mode 100644 index 0000000..d47a23f --- /dev/null +++ b/plots.py @@ -0,0 +1,156 @@ +import matplotlib.pyplot as plt +from numpy import log, exp, linspace, inf +from scipy.optimize import curve_fit +import scienceplots +plt.style.use(["science", "grid"]) + +def sigmoid(x, right, curve): + return 1 - 1/(1 + exp(right-curve*x)) + +col = ['#1f77b4', '#ff7f0e', '#2ca02c', '#d62728', '#9467bd', '#8c564b', '#e377c2', '#7f7f7f', '#bcbd22'] +marker = ["v", "p", "h", "8", "s", 'P', 'D', 'X', "*"] + +def plot_lll(filepath='results_lll.txt'): + d = open(filepath, 'r') + dt = d.readlines() + n = [] + q = [] + x = [] + for l in dt: + ll = l.split() + q.append(int(ll[0])) + n.append(int(ll[1])) + x.append(float(ll[2])) + k=0 + x_val = linspace(1, 80, 1001) + unique_q = list(set(q)) + unique_q.sort() + s, q_s = [], [] + plt.figure(figsize=(4,4)) + for qs in unique_q: + indicies = [i for i in range(len(q)) if q[i] == qs] + nn = [n[i] for i in indicies] + xx = [x[i] for i in indicies] + assert len(nn) == len(xx) + popt, pcov = curve_fit(sigmoid, nn,xx, bounds=[(-20, 0), (120, inf)]) + q_s.append(qs) + s.append(popt[0]/popt[1]) + if qs in [71, 401, 3329]: + plt.plot(nn, xx, marker=marker[k%3], linestyle='', markersize=10, + color=col[k%3], + markerfacecolor=col[k%3], + markerfacecoloralt=col[k%3], + markeredgecolor=col[k%3], fillstyle='none', label=f"q={qs}") + + yy = sigmoid(x_val, popt[0], popt[1]) + plt.plot(x_val, yy, '--', color=col[k%3]) + k += 1 + plt.xlabel(r'Key length $n$', fontsize=12) + plt.ylabel(r'Probability $p$', fontsize=12) + legend = plt.legend(fancybox=False, edgecolor="black", loc=3) + legend.get_frame().set_linewidth(0.5) + + plt.figure() + plt.plot(q_s[1:], s[1:], marker=marker[1], linestyle='', markersize=10, color=col[0], fillstyle='none', + markerfacecolor=col[0], + markerfacecoloralt=col[0], + markeredgecolor=col[0]) + plt.xlabel(r'Modulus $q$', fontsize=12) + plt.ylabel(r'Key length $n$', fontsize=12) + plt.show() + + +def plot_bkz(filename='bkz_results.txt'): + d = open(filename, 'r') + nn, pp, bb, score, time, sample = [], [], [], [], [], [] + l = d.readline() + while l != '': + l = l.replace('\n', '') + strg = l.split(' ') + nn.append(int(strg[0])) + pp.append(int(strg[1])) + bb.append(int(strg[2])) + score.append(float(strg[3])) + if strg[4] == 'nan': + time.append(-1.0) + sample.append(-1) + else: + time.append(float(strg[4])) + sample.append(int(strg[5])) + l = d.readline() + assert len(nn) == len(pp) == len(bb) == len(score) == len(time) == len(sample) + n = list(set(nn)) + n.sort() + p = set(pp) + b = list(set(bb)) + b.sort() + results = dict() + for block in b: + results[block] = dict() + for module in p: + results[block][module] = dict() + for keylength in n: + results[block][module][keylength] = dict() + idx = [i for i in range(len(nn)) if nn[i] == keylength and pp[i] == module and bb[i] == block] + if len(idx) == 1: + results[block][module][keylength]['score'] = score[idx[0]] + results[block][module][keylength]['time'] = time[idx[0]] + results[block][module][keylength]['samplesize'] = sample[idx[0]] + elif len(idx) > 1: + z = len(idx) + samples = [sample[i] for i in idx] + times = [time[i] for i in idx] + scores = [score[i] for i in idx] + weighted_times = sum([times[i]*samples[i] for i in range(z)])/sum(samples) + weighted_scores = sum([scores[i]*samples[i] for i in range(z)])/sum(samples) + try: + results[block][module][keylength]['score'] = weighted_scores + results[block][module][keylength]['time'] = weighted_times + results[block][module][keylength]['samplesize'] = sum(samples) + except: + print(block, module, keylength) + block_plot = list(set(bb)) + block_plot.sort() + primes = list(set(pp)) + primes.sort() + k = 0 + x_val = linspace(1, 80, 1001) + for prime in primes: + plt.figure(figsize=(4,4)) + for block in block_plot: + nnn = [] + scores = [] + for keylength in n: + nnn.append(keylength) + scores.append(results[block][prime][keylength]['score']) + plt.plot(nnn, scores, marker=marker[k], linestyle='-', markersize=5, + color=col[k], + markerfacecolor=col[k], + markerfacecoloralt=col[k], + markeredgecolor=col[k], fillstyle='none', label=fr"$\beta={block}$") + k += 1 + k %= 9 + legend = plt.legend(fancybox=False, edgecolor="black", loc=3) + legend.get_frame().set_linewidth(0.5) + plt.xlabel(r'Key length $n$', fontsize=12) + plt.ylabel(r'Probability $p$', fontsize=12) + plt.title(f'Prime: {prime}') + plt.figure(figsize=(6,4)) + k = 0 + for keylength in n: + if keylength > 40: + b = [] + times = [] + for blocks in block_plot: + b.append(blocks) + times.append(results[blocks][401][keylength]['time']) + plt.plot(b, times, marker=marker[k], linestyle='-', markersize=5, color=col[k], fillstyle='none', label=fr"$n$ = {keylength}") + + k += 1 + legend = plt.legend(fancybox=False, edgecolor="black", bbox_to_anchor=(1.05, 1.0), loc='upper left') + legend.get_frame().set_linewidth(0.5) + plt.tight_layout() + plt.xlabel(r'Block size $\beta$', fontsize=12) + plt.ylabel(r'Time $t\: [s]$', fontsize=12) + plt.yscale('log') + plt.show() diff --git a/tools.py b/tools.py new file mode 100644 index 0000000..c2aa883 --- /dev/null +++ b/tools.py @@ -0,0 +1,109 @@ +from fpylll import LLL, BKZ, IntegerMatrix, GSO +from fpylll.algorithms.bkz2 import BKZReduction as BKZ2 +from numpy import zeros, array, matrix, dot, arange, nan +from numpy.linalg import norm +import random +import multiprocessing +import time + +class CenteredBinomialDistibution: + + def __init__(self, n): + self.n = n + def __call__(self): + x, y = 0, 0 + for k in range(self.n): + x += random.randint(0, 1) + y += random.randint(0, 1) + return x - y + +def SVPreduction(A, t, q): + """ + Given A, t, q, return the SVP-reduced basis using an embedding technique + B = [I_n | -A^T | 0 + 0 | qI_n | 0 + 0 | t | 1] + + args: + A (matrix): matrix + t (array): target vector + q (int): modulus + + returns: + B (matrix): SVP-reduced basis + """ + d, n = A.shape + B = IntegerMatrix(n+d+1,n+d+1) + for i in range(n): + for j in range(n, n+d): + B[i,j] = -A[j - n][i] + + for i in range(n): + B[i,i] = 1 + + for i in range(n,n+d): + B[i,i] = q + + B[-1,-1] = 1 + + for i in range(n, n+d): + B[-1,i] = t[i-n] + + return B + +def calculate_t(A:matrix, s:array, e:array, q:int) -> array: + """ + Given A, s, e, q, calculate t = (A*s + e) mod q + + args: + A (matrix): matrix + s (array): secret vector + e (array): error vector + q (int): modulus + returns: + array t + """ + t = array([(dot(A[j], s) + e[j]) % q for j in range(len(A))]) + for i in range(len(t)): + t[i] = t[i] if t[i] <= q//2 else t[i]-q + return t + +def makenumpy(B): + """ + Convert fpylll integer matrix to numpy array + + args: + B (fpylll integer matrix): matrix + + returns: + A (numpy array): matrix + """ + A = zeros((B.nrows, B.ncols), dtype=int) + for i in range(B.nrows): + for j in range(B.ncols): + A[i][j] = int(B[i,j]) + return A + +def score(t, ss, ee, A, s, p): + """ + Given t, s, e, A, p, calculate the score + Calculate t' = (A*s + e) mod p + Returns ||t-t'||_1 + + args: + t (array): target vector + ss (array): secret vector + ee (array): error vector + A (matrix): matrix + p (int): modulus + + returns: + score (int): score + """ + tt = calculate_t(A, ss, ee, p) + for k in range(len(t)): + if t[k] >= p//2: + t[k] = t[k] - p + if tt[k] >= p//2: + tt[k] = tt[k] - p + return int(norm(t-tt, ord=1)) -- GitLab