mirror of
https://github.com/zama-ai/concrete.git
synced 2026-02-15 15:15:06 -05:00
539 lines
18 KiB
Python
539 lines
18 KiB
Python
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 hybrid_decoding import parameter_search
|
|
from random import uniform
|
|
from mpl_toolkits import mplot3d
|
|
# easier to just load the estimator
|
|
load("estimator.py")
|
|
|
|
# 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))
|
|
|
|
|
|
def quantum(beta, d, B):
|
|
return ZZ(2) ** RR(0.265 * beta + 16.4 + log(8 * d, 2))
|
|
|
|
|
|
def classical_conservative(beta, d, B):
|
|
return ZZ(2) ** RR(0.292 * beta)
|
|
|
|
|
|
def quantum_conservative(beta, d, B):
|
|
return ZZ(2) ** RR(0.265 * beta)
|
|
|
|
|
|
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,
|
|
i.e. returns only the bit-security level (without the attack params)
|
|
:param estimate: the input estimate
|
|
:param decimal_places: the number of decimal places
|
|
|
|
EXAMPLE:
|
|
sage: x = estimate_lwe(n = 256, q = 2**32, alpha = RR(8/2**32))
|
|
sage: get_security_level(x)
|
|
33.8016789754458
|
|
"""
|
|
|
|
levels = []
|
|
|
|
# use try/except to cover cases where we only consider one or two attacks
|
|
|
|
try:
|
|
levels.append(estimate["usvp"]["rop"])
|
|
|
|
except:
|
|
pass
|
|
|
|
try:
|
|
levels.append(estimate["dec"]["rop"])
|
|
|
|
except:
|
|
pass
|
|
|
|
try:
|
|
levels.append(estimate["dual"]["rop"])
|
|
|
|
except:
|
|
pass
|
|
|
|
# take the minimum attack cost (in bits)
|
|
security_level = round(log(min(levels), 2), decimal_places)
|
|
|
|
return security_level
|
|
|
|
|
|
def get_all_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
|
|
quantum_conservative
|
|
:param params: a dictionary of LWE parameter sets (see concrete_params)
|
|
|
|
EXAMPLE:
|
|
sage: X = get_all_security_levels(concrete_LWE_params)
|
|
sage: X
|
|
[['LWE128_256',
|
|
126.692189756144,
|
|
117.566189756144,
|
|
98.6960000000000,
|
|
89.5700000000000], ...]
|
|
"""
|
|
|
|
RESULTS = []
|
|
|
|
for param in params:
|
|
|
|
results = [param]
|
|
x = params["{}".format(param)]
|
|
n = x["n"] * x["k"]
|
|
q = 2 ** 32
|
|
sd = 2 ** (x["sd"]) * q
|
|
alpha = sqrt(2 * pi) * sd / RR(q)
|
|
secret_distribution = (0, 1)
|
|
# assume access to an infinite number of samples
|
|
m = oo
|
|
|
|
for model in cost_models:
|
|
try:
|
|
model = model[0]
|
|
except:
|
|
model = model
|
|
estimate = parameter_search(mitm = True, reduction_cost_model = est.BKZ.sieve, n = n, q = q, alpha = alpha, m = m, secret_distribution = secret_distribution)
|
|
results.append(get_security_level(estimate))
|
|
|
|
RESULTS.append(results)
|
|
|
|
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
|
|
quantum_conservative
|
|
:param params: a dictionary of LWE parameter sets (see concrete_params)
|
|
|
|
EXAMPLE:
|
|
sage: X = get_all_security_levels(concrete_LWE_params)
|
|
sage: X
|
|
[['LWE128_256',
|
|
126.692189756144,
|
|
117.566189756144,
|
|
98.6960000000000,
|
|
89.5700000000000], ...]
|
|
"""
|
|
|
|
RESULTS = []
|
|
|
|
for param in params:
|
|
|
|
results = [param]
|
|
x = params["{}".format(param)]
|
|
n = x["n"] * x["k"]
|
|
q = 2 ** 32
|
|
sd = 2 ** (x["sd"]) * q
|
|
alpha = sqrt(2 * pi) * sd / RR(q)
|
|
secret_distribution = (0, 1)
|
|
# assume access to an infinite number of papers
|
|
m = oo
|
|
|
|
model = est.BKZ.sieve
|
|
estimate = parameter_search(mitm = True, reduction_cost_model = est.BKZ.sieve, n = n, q = q, alpha = alpha, m = m, secret_distribution = secret_distribution)
|
|
results.append(get_security_level(estimate))
|
|
|
|
RESULTS.append(results)
|
|
|
|
return RESULTS
|
|
|
|
|
|
def latexit(results):
|
|
"""
|
|
A function which takes the output of get_all_security_levels() and
|
|
turns it into a latex table
|
|
:param results: the security levels
|
|
|
|
sage: X = get_all_security_levels(concrete_LWE_params)
|
|
sage: latextit(X)
|
|
\begin{tabular}{llllll}
|
|
LWE128_256 & $126.69$ & $117.57$ & $98.7$ & $89.57$ & $217.55$ \\
|
|
LWE128_512 & $135.77$ & $125.92$ & $106.58$ & $96.73$ & $218.53$ \\
|
|
LWE128_638 & $135.27$ & $125.49$ & $105.7$ & $95.93$ & $216.81$ \\
|
|
[...]
|
|
"""
|
|
|
|
return latex(table(results))
|
|
|
|
|
|
def markdownit(results, headings = ["Parameter Set", "Classical", "Quantum", "Classical (c)", "Quantum (c)", "Enum"]):
|
|
"""
|
|
A function which takes the output of get_all_security_levels() and
|
|
turns it into a markdown table
|
|
:param results: the security levels
|
|
|
|
sage: X = get_all_security_levels(concrete_LWE_params)
|
|
sage: markdownit(X)
|
|
# estimates
|
|
|Parameter Set|Classical|Quantum|Classical (c)|Quantum (c)| Enum |
|
|
|-------------|---------|-------|-------------|-----------|------|
|
|
|LWE128_256 |126.69 |117.57 |98.7 |89.57 |217.55|
|
|
|LWE128_512 |135.77 |125.92 |106.58 |96.73 |218.53|
|
|
|LWE128_638 |135.27 |125.49 |105.7 |95.93 |216.81|
|
|
[...]
|
|
"""
|
|
|
|
writer = MarkdownTableWriter(value_matrix = results, headers = headings, table_name = "estimates")
|
|
writer.write_table()
|
|
|
|
return writer
|
|
|
|
|
|
def inequality(x, y):
|
|
""" A function which compresses the conditions
|
|
x < y and x > y into a single condition via a
|
|
multiplier
|
|
"""
|
|
if x <= y:
|
|
return 1
|
|
|
|
if x > y:
|
|
return -1
|
|
|
|
|
|
def automated_param_select_n(sd, n=None, q=2 ** 32, reduction_cost_model=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)
|
|
:param sd: the standard deviation of the error
|
|
:param n: an initial value of n to use in optimisation, guessed if None
|
|
: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
|
|
:param secret_distribution: the LWE secret distribution
|
|
:param target_security: the target number of bits of security, 128 is default
|
|
|
|
EXAMPLE:
|
|
sage: X = automated_param_select_n(sd = -25, q = 2**32)
|
|
sage: X
|
|
1054
|
|
"""
|
|
|
|
if n is None:
|
|
# pick some random n which gets us close (based on concrete_LWE_params)
|
|
n = sd * (-25) * (target_security/80)
|
|
|
|
sd = 2 ** sd * q
|
|
alpha = sqrt(2 * pi) * sd / RR(q)
|
|
|
|
|
|
# initial estimate, to determine if we are above or below the target security level
|
|
estimate = estimate_lwe(n, alpha, q, secret_distribution=secret_distribution,
|
|
reduction_cost_model=reduction_cost_model, m=oo, skip = {"bkw","dec","arora-gb","mitm"})
|
|
security_level = get_security_level(estimate)
|
|
z = inequality(security_level, target_security)
|
|
|
|
while z * security_level < z * target_security:
|
|
estimate = estimate_lwe(n, alpha, q, secret_distribution=secret_distribution,
|
|
reduction_cost_model=reduction_cost_model, m=oo, skip = {"bkw","dec","arora-gb","mitm"})
|
|
security_level = get_security_level(estimate)
|
|
|
|
n += 1
|
|
|
|
print("the finalised parameters are {}, {}, with a security level of {}".format(n, q, security_level))
|
|
|
|
return ZZ(n)
|
|
|
|
|
|
def automated_param_select_sd(n, sd=None, q=2**32, reduction_cost_model=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)
|
|
:param n: the LWE dimension
|
|
:param sd: an initial value of sd to use in optimisation, guessed if None
|
|
: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
|
|
:param secret_distribution: the LWE secret distribution
|
|
:param target_security: the target number of bits of security, 128 is default
|
|
|
|
EXAMPLE
|
|
sage: X = automated_param_select_sd(n = 1054, q = 2**32)
|
|
sage: X
|
|
-26
|
|
"""
|
|
|
|
if sd is None:
|
|
# pick some random sd which gets us close (based on concrete_LWE_params)
|
|
sd = round(n * 80 / (target_security * (-25)))
|
|
|
|
# make sure sd satisfies q * sd > 1
|
|
sd = max(sd, -(log(q,2) - 2))
|
|
|
|
sd_ = (2 ** sd) * q
|
|
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,
|
|
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"})
|
|
security_level = get_security_level(estimate)
|
|
z = inequality(security_level, target_security)
|
|
|
|
while z * security_level < z * target_security and sd > -log(q,2):
|
|
sd += z * 1
|
|
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"})
|
|
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"})
|
|
security_level = get_security_level(estimate)
|
|
|
|
if (-1 * sd > log(q, 2)):
|
|
print("target security level is unatainable")
|
|
break
|
|
|
|
# final estimate (we went too far in the above loop)
|
|
if security_level < target_security:
|
|
sd -= z * 1
|
|
sd_ = (2 ** sd) * q
|
|
alpha = sqrt(2 * pi) * sd_ / RR(q)
|
|
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"})
|
|
security_level = get_security_level(estimate)
|
|
|
|
print("the finalised parameters are n = {}, log2(sd) = {}, log2(q) = {}, with a security level of {}-bits".format(n,
|
|
sd,
|
|
log(q,
|
|
2),
|
|
security_level))
|
|
|
|
return sd
|
|
|
|
|
|
def generate_parameter_matrix(n_range, sd=None, q=2**32, reduction_cost_model=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: 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
|
|
:param secret_distribution: the LWE secret distribution
|
|
:param target_security: the target number of bits of security, 128 is default
|
|
|
|
TODO: we should probably parallelise this function for speed
|
|
TODO: code seems to fail when the initial estimate is < target_security bits
|
|
|
|
EXAMPLE:
|
|
sage: X = generate_parameter_matrix([788, 790])
|
|
sage: X
|
|
[(788, 4294967296, -20.0), (789, 4294967296, -20.0)]
|
|
"""
|
|
|
|
RESULTS = []
|
|
|
|
# grab min and max value/s of n
|
|
(n_min, n_max) = n_range
|
|
|
|
sd_ = sd
|
|
|
|
for n in range(n_min, n_max):
|
|
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
|
|
RESULTS.append((n, q, sd))
|
|
|
|
return RESULTS
|
|
|
|
|
|
def generate_parameter_step(results, label = None, torus_sd = True):
|
|
"""
|
|
Plot results
|
|
:param results: an output of generate_parameter_matrix
|
|
|
|
returns: a step plot of chosen parameters
|
|
|
|
EXAMPLE:
|
|
X = generate_parameter_matrix([700, 790])
|
|
generate_parameter_step(X)
|
|
plt.show()
|
|
"""
|
|
|
|
N = []
|
|
SD = []
|
|
|
|
for (n, q, sd) in results:
|
|
N.append(n)
|
|
if torus_sd:
|
|
SD.append(sd)
|
|
else:
|
|
SD.append(sd + log(q,2))
|
|
|
|
plt.plot(N, SD, label = label)
|
|
plt.legend(loc = "upper right")
|
|
|
|
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)
|
|
|
|
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"))
|
|
|
|
return est
|
|
|
|
def generate_iso_lines(N = [256, 2048], SD = [0, 32], q = 2**32):
|
|
|
|
RESULTS = []
|
|
|
|
for n in range(N[0], N[1] + 1, 1):
|
|
for sd in range(SD[0], SD[1] + 1, 1):
|
|
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 = 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 = get_security_level(est, 2)
|
|
RESULTS.append((n, sd, est))
|
|
|
|
return RESULTS
|
|
|
|
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])
|
|
|
|
plt.scatter(x1, x2, c = x3)
|
|
plt.colorbar()
|
|
|
|
return plt
|
|
|
|
|
|
def test_multiple_sd(n, q, secret_distribution, reduction_cost_model, split = 33, m = oo):
|
|
est = []
|
|
Y = []
|
|
for sd_ in np.linspace(0,32,split):
|
|
Y.append(sd_)
|
|
sd = (2** (-1 * sd_))* q
|
|
alpha = sqrt(2*pi) * sd / q
|
|
try:
|
|
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))
|
|
|
|
return est, Y
|
|
|
|
|
|
def estimate_lwe_sd(n, sd, q, secret_distribution, reduction_cost_model, skip = ("bkw","mitm","dec","arora-gb"), m = oo):
|
|
|
|
alpha = sqrt(2*pi) * sd/q
|
|
x = estimate_lwe(n = n, alpha = alpha , q = q, m = m, secret_distribution = secret_distribution, reduction_cost_model = reduction_cost_model, skip = skip)
|
|
|
|
return x
|
|
|