Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemath
GitHub Repository: sagemath/sagesmc
Path: blob/master/src/sage/crypto/lwe.py
8817 views
1
# -*- coding: utf-8 -*-
2
"""
3
(Ring-)LWE oracle generators
4
5
The Learning with Errors problem (LWE) is solving linear systems of equations
6
where the right hand side has been disturbed 'slightly' where 'slightly' is made
7
precise by a noise distribution - typically a discrete Gaussian
8
distribution. See [Reg09]_ for details.
9
10
The Ring Learning with Errors problem (LWE) is solving a set of univariate
11
polynomial equations - typically in a cyclotomic field - where the right hand
12
side was disturbed 'slightly'. See [LPR10]_ for details.
13
14
This module implements generators of LWE samples where parameters are chosen
15
following proposals in the cryptographic literature.
16
17
EXAMPLES:
18
19
We get 30 samples from an LWE oracle parameterised by security parameter
20
``n=20`` and where the modulus and the standard deviation of the noise are
21
chosen as in [Reg09]_::
22
23
sage: from sage.crypto.lwe import samples
24
sage: samples(30, 20, 'Regev')
25
[((360, 264, 123, 368, 398, 392, 41, 84, 25, 389, 311, 68, 322, 41, 161, 372, 222, 153, 243, 381), 126),
26
...
27
((138, 198, 204, 235, 339, 168, 269, 276, 392, 243, 86, 18, 378, 20, 369, 141, 108, 151, 336, 141), 102)]
28
29
30
We may also pass classes to the samples function, which is useful for users
31
implementing their own oracles::
32
33
sage: from sage.crypto.lwe import samples, LindnerPeikert
34
sage: samples(30, 20, LindnerPeikert)
35
[((350, 835, 2023, 1785, 1958, 1818, 1130, 1285, 1331, 284, 2048, 441, 1581, 1406, 1185, 1724, 1397, 258, 994, 1056), 1902),
36
...
37
((1918, 1823, 1598, 18, 588, 1093, 744, 1934, 689, 1327, 1632, 1867, 228, 378, 798, 511, 274, 1001, 1709, 154), 184)]
38
39
40
Finally, :func:`samples` also accepts instances of classes::
41
42
sage: from sage.crypto.lwe import LindnerPeikert
43
sage: lwe = LindnerPeikert(20)
44
sage: samples(30, 20, lwe)
45
[((1817, 1322, 818, 1232, 354, 639, 1770, 754, 1366, 1731, 649, 162, 483, 1741, 1942, 1232, 1424, 1034, 50, 448), 1316),
46
...
47
((2021, 829, 572, 1698, 1025, 170, 598, 1193, 1268, 607, 1502, 1984, 1655, 206, 958, 334, 1213, 1413, 827, 1423), 546)]
48
49
Note that Ring-LWE samples are returned as vectors::
50
51
sage: from sage.crypto.lwe import DiscreteGaussianPolynomialSamplerRejection, RingLWE
52
sage: D = DiscreteGaussianPolynomialSamplerRejection(euler_phi(16), 5)
53
sage: ringlwe = RingLWE(16, 257, D, secret_dist='uniform')
54
sage: samples(30, euler_phi(16), ringlwe)
55
[((158, 49, 174, 179, 109, 92, 234, 41), (200, 159, 131, 197, 241, 172, 1, 107)),
56
...
57
((80, 227, 249, 205, 149, 92, 46, 68), (69, 256, 29, 219, 218, 34, 182, 178))]
58
59
One technical issue when working with these generators is that by default they
60
return vectors and scalars over/in rings modulo some `q`. These are represented
61
as elements in `(0,q-1)` by Sage. However, it usually is more natural to think
62
of these entries as integers in `(-q//2,q//2)`. To allow for this, this module
63
provides the option to balance the representation. In this case vectors and
64
scalars over/in the integers are returned::
65
66
sage: from sage.crypto.lwe import samples
67
sage: samples(30, 20, 'Regev', balanced=True)
68
[((-38, 59, -33, -80, 165, -55, -46, -49, -113, 135, -32, 185, -80, -184, 127, 153, 162, -31, 115, 178), 14),
69
...
70
((-165, -187, -87, 188, 160, -118, -7, 107, -77, -107, -109, 77, 63, -66, -55, -75, -12, 90, 58, -185), 6)]
71
72
AUTHORS:
73
74
- Martin Albrecht
75
- Robert Fitzpatrick
76
- Daniel Cabracas
77
- Florian Göpfert
78
- Michael Schneider
79
80
REFERENCES:
81
82
.. [Reg09] Oded Regev. On Lattices, Learning with Errors, Random Linear Codes,
83
and Cryptography. in Journal of the ACM 56(6). ACM 2009,
84
http://dx.doi.org/10.1145/1060590.1060603
85
86
.. [LP11] Richard Lindner and Chris Peikert. Better key sizes (and attacks) for
87
LWE-based encryption. in Proceeding of the 11th international conference on
88
Topics in cryptology: CT-RSA 2011. Springer 2011,
89
http://dx.doi.org/10.1007/978-3-642-19074-2_21
90
91
.. [LPR10] Vadim Lyubashevsky, Chris Peikert, and Oded Regev. On Ideal Lattices
92
and Learning with Errors over Rings. in Advances in Cryptology – EUROCRYPT
93
2010. Springer 2010. http://dx.doi.org/10.1007/978-3-642-13190-5_1
94
95
.. [CGW13] Daniel Cabarcas, Florian Göpfert, and Patrick Weiden. Provably Secure
96
LWE-Encryption with Uniform Secret. Cryptology ePrint Archive, Report
97
2013/164. 2013. 2013/164. http://eprint.iacr.org/2013/164
98
"""
99
100
from sage.calculus.var import var
101
from sage.functions.log import exp, log
102
from sage.functions.other import sqrt, floor, ceil
103
from sage.misc.functional import cyclotomic_polynomial
104
from sage.misc.randstate import set_random_seed
105
from sage.misc.prandom import randint
106
from sage.misc.misc import get_verbose
107
from sage.modules.free_module import FreeModule
108
from sage.modules.free_module_element import random_vector, vector
109
from sage.numerical.optimize import find_root
110
from sage.rings.all import ZZ, RealField, IntegerModRing, RR
111
from sage.rings.arith import next_prime, euler_phi
112
from sage.structure.element import parent
113
from sage.structure.sage_object import SageObject
114
from sage.symbolic.constants import pi
115
116
class DiscreteGaussianSamplerRejection(SageObject):
117
"""
118
Discrete Gaussian sampler using rejection sampling.
119
120
EXAMPLE::
121
122
sage: from sage.crypto.lwe import DiscreteGaussianSamplerRejection
123
sage: DiscreteGaussianSamplerRejection(3.0)()
124
-1
125
sage: gs = DiscreteGaussianSamplerRejection(3.0, precision=100, tailcut=1.0)
126
sage: all(gs() <= 3.0 for _ in xrange(1000))
127
True
128
129
.. automethod:: __init__
130
.. automethod:: __call__
131
"""
132
def __init__(self, stddev, precision=53, tailcut=4):
133
"""
134
Construct a new discrete Gaussian sampler.
135
136
INPUT:
137
138
- ``stddev`` - standard deviation
139
- ``precision`` - precision used for internal computations (default: ``53``)
140
- ``tailcut`` - cut the tail at ``tailcut`` standard deviations (default: ``4``)
141
142
EXAMPLE::
143
144
sage: from sage.crypto.lwe import DiscreteGaussianSamplerRejection
145
sage: gs = DiscreteGaussianSamplerRejection(3.0)
146
sage: sqrt(variance([gs() for _ in xrange(1000)])).n()
147
2.965...
148
"""
149
self.stddev = stddev
150
self.precision = precision
151
self.tailcut = tailcut
152
self.max_precs = 2**precision
153
self.upper_bound = ZZ(round(tailcut*stddev))
154
155
def __call__(self):
156
"""
157
Return a new sample.
158
159
EXAMPLE::
160
161
sage: from sage.crypto.lwe import DiscreteGaussianSamplerRejection
162
sage: sampler = DiscreteGaussianSamplerRejection(12.0)
163
sage: sampler()
164
-5
165
"""
166
x = 0
167
y = 0
168
z = 0
169
while y >= z :
170
x = randint(0, self.upper_bound-1)
171
y = randint(0, self.max_precs-1)
172
z = self.rho[x]
173
return (2*randint(0,1)-1)*x
174
175
def _repr_(self):
176
"""
177
EXAMPLE::
178
179
sage: from sage.crypto.lwe import DiscreteGaussianSamplerRejection
180
sage: DiscreteGaussianSamplerRejection(3.0)
181
DiscreteGaussianSamplerRejection(3.000000, 53, 4)
182
"""
183
return "DiscreteGaussianSamplerRejection(%f, %d, %d)"%(self.stddev, self.precision, self.tailcut)
184
185
186
def __getattr__(self, name):
187
"""
188
EXAMPLE::
189
190
sage: from sage.crypto.lwe import DiscreteGaussianSamplerRejection
191
sage: DiscreteGaussianSamplerRejection(3.0).foo
192
Traceback (most recent call last):
193
...
194
AttributeError: 'DiscreteGaussianSamplerRejection' object has no attribute 'foo'
195
"""
196
if name == "rho":
197
# we delay the creation of rho until we actually need it
198
R = RealField(self.precision)
199
self.rho = [round(self.max_precs * exp((-(R(x) / R(self.stddev))**2)/R(2))) for x in range(0,self.upper_bound)]
200
self.rho[0] = self.rho[0] / 2
201
return self.rho
202
else:
203
raise AttributeError("'%s' object has no attribute '%s'"%(self.__class__.__name__, name))
204
205
# By default we use rejection sampling
206
DiscreteGaussianSampler = DiscreteGaussianSamplerRejection
207
208
class DiscreteGaussianPolynomialSamplerRejection(SageObject):
209
"""
210
Discrete Gaussian sampler for polynomials.
211
212
EXAMPLE::
213
214
sage: from sage.crypto.lwe import DiscreteGaussianPolynomialSamplerRejection
215
sage: DiscreteGaussianPolynomialSamplerRejection(8, 3.0)()
216
x^7 - x^6 - 2*x^4 + 2*x^3 - x^2 + x - 1
217
sage: gs = DiscreteGaussianPolynomialSamplerRejection(8, 3.0, precision=100, tailcut=1.0)
218
sage: [gs() for _ in xrange(3)]
219
[-x^7 + x^6 + 2*x^5 + 2*x^4 - x^3 - x^2 - 1,
220
x^7 - 2*x^6 + 2*x^5 + x^4 - x^3 + 2*x^2 - x + 2,
221
x^5 + 2*x^3 + 2*x + 1]
222
223
.. automethod:: __init__
224
.. automethod:: __call__
225
"""
226
def __init__(self, n, stddev, precision=53, tailcut=4, D=DiscreteGaussianSampler):
227
"""
228
Construct a sampler for univariate polynomials of degree ``n-1``
229
where coefficients are drawn independently with standard deviation
230
``stddev`` using ``D``.
231
232
INPUT:
233
234
- ``n`` - number of coefficients to be sampled
235
- ``stddev`` - standard deviation
236
- ``precision`` - precision used for internal computations (default: ``53``)
237
- ``tailcut`` - cut the tail at ``tailcut`` standard deviations
238
(default: ``4``)
239
- ``D`` - a discrete Gaussian sampler (default:
240
:class:`DiscreteGaussianSampler`)
241
242
EXAMPLE::
243
244
sage: from sage.crypto.lwe import DiscreteGaussianPolynomialSamplerRejection
245
sage: DiscreteGaussianPolynomialSamplerRejection(8, 3.0)()
246
x^7 - x^6 - 2*x^4 + 2*x^3 - x^2 + x - 1
247
sage: gs = DiscreteGaussianPolynomialSamplerRejection(8, 3.0, precision=100, tailcut=1.0)
248
sage: [gs() for _ in xrange(3)]
249
[-x^7 + x^6 + 2*x^5 + 2*x^4 - x^3 - x^2 - 1,
250
x^7 - 2*x^6 + 2*x^5 + x^4 - x^3 + 2*x^2 - x + 2,
251
x^5 + 2*x^3 + 2*x + 1]
252
"""
253
self.stddev = stddev
254
self.precision = precision
255
self.tailcut = tailcut
256
self.D = D(stddev, precision, tailcut)
257
self.n = ZZ(n)
258
self.P = ZZ['x']
259
260
def __call__(self):
261
"""
262
Return a new sample.
263
264
EXAMPLE::
265
266
sage: from sage.crypto.lwe import DiscreteGaussianPolynomialSamplerRejection
267
sage: sampler = DiscreteGaussianPolynomialSamplerRejection(8, 12.0)
268
sage: sampler()
269
x^7 - 9*x^5 + 2*x^4 + 8*x^3 - 5*x^2 + 7*x - 5
270
"""
271
coeff = [self.D() for _ in range(self.n)]
272
f = self.P(coeff)
273
return f
274
275
def _repr_(self):
276
"""
277
EXAMPLE::
278
279
sage: from sage.crypto.lwe import DiscreteGaussianPolynomialSamplerRejection
280
sage: DiscreteGaussianPolynomialSamplerRejection(8, 3.0)
281
DiscreteGaussianPolynomialSamplerRejection(8, 3.000000, 53, 4)
282
"""
283
return "DiscreteGaussianPolynomialSamplerRejection(%d, %f, %d, %d)"%(self.n, self.stddev, self.precision, self.tailcut)
284
285
286
# By default we use rejection sampling
287
DiscreteGaussianPolynomialSampler = DiscreteGaussianPolynomialSamplerRejection
288
289
class UniformSampler(SageObject):
290
"""
291
Uniform sampling in a range of integers.
292
293
EXAMPLE::
294
295
sage: from sage.crypto.lwe import UniformSampler
296
sage: sampler = UniformSampler(-2, 2); sampler
297
UniformSampler(-2, 2)
298
sage: sampler()
299
-2
300
301
.. automethod:: __init__
302
.. automethod:: __call__
303
"""
304
def __init__(self, lower_bound, upper_bound):
305
"""
306
Construct a uniform sampler with bounds ``lower_bound`` and
307
``upper_bound`` (both endpoints inclusive).
308
309
INPUT:
310
311
- ``lower_bound`` - integer
312
- ``upper_bound`` - integer
313
314
EXAMPLE::
315
316
sage: from sage.crypto.lwe import UniformSampler
317
sage: UniformSampler(-2, 2)
318
UniformSampler(-2, 2)
319
"""
320
if lower_bound > upper_bound:
321
raise TypeError("lower bound must be <= upper bound.")
322
self.lower_bound = ZZ(lower_bound)
323
self.upper_bound = ZZ(upper_bound)
324
325
def __call__(self):
326
"""
327
Return a new sample.
328
329
EXAMPLE::
330
331
sage: from sage.crypto.lwe import UniformSampler
332
sage: sampler = UniformSampler(-12, 12)
333
sage: sampler()
334
-10
335
"""
336
return randint(self.lower_bound, self.upper_bound)
337
338
def _repr_(self):
339
"""
340
EXAMPLE::
341
342
sage: from sage.crypto.lwe import UniformSampler
343
sage: UniformSampler(-2, 2)
344
UniformSampler(-2, 2)
345
"""
346
return "UniformSampler(%d, %d)"%(self.lower_bound, self.upper_bound)
347
348
349
class UniformPolynomialSampler(SageObject):
350
"""
351
Uniform sampler for polynomials.
352
353
EXAMPLE::
354
355
sage: from sage.crypto.lwe import UniformPolynomialSampler
356
sage: UniformPolynomialSampler(8, -2, 2)()
357
-2*x^7 + x^6 - 2*x^5 - x^3 - 2*x^2 - 2
358
359
.. automethod:: __init__
360
.. automethod:: __call__
361
"""
362
def __init__(self, n, lower_bound, upper_bound):
363
"""
364
Construct a sampler for univariate polynomials of degree ``n-1`` where
365
coefficients are drawn uniformly at random between ``lower_bound`` and
366
``upper_bound`` (both endpoints inclusive).
367
368
INPUT:
369
370
- ``n`` - number of coefficients to be sampled
371
- ``lower_bound`` - integer
372
- ``upper_bound`` - integer
373
374
EXAMPLE::
375
376
sage: from sage.crypto.lwe import UniformPolynomialSampler
377
sage: UniformPolynomialSampler(10, -10, 10)
378
UniformPolynomialSampler(10, -10, 10)
379
"""
380
self.n = ZZ(n)
381
self.P = ZZ['x']
382
if lower_bound > upper_bound:
383
raise TypeError("lower bound must be <= upper bound.")
384
self.lower_bound = ZZ(lower_bound)
385
self.upper_bound = ZZ(upper_bound)
386
self.D = UniformSampler(self.lower_bound, self.upper_bound)
387
388
def __call__(self):
389
"""
390
Return a new sample.
391
392
EXAMPLE::
393
394
sage: from sage.crypto.lwe import UniformPolynomialSampler
395
sage: sampler = UniformPolynomialSampler(8, -12, 12)
396
sage: sampler()
397
-10*x^7 + 5*x^6 - 8*x^5 + x^4 - 4*x^3 - 11*x^2 - 10
398
"""
399
coeff = [self.D() for _ in range(self.n)]
400
f = self.P(coeff)
401
return f
402
403
def _repr_(self):
404
"""
405
EXAMPLE::
406
407
sage: from sage.crypto.lwe import UniformPolynomialSampler
408
sage: UniformPolynomialSampler(8, -3, 3)
409
UniformPolynomialSampler(8, -3, 3)
410
"""
411
return "UniformPolynomialSampler(%d, %d, %d)"%(self.n, self.lower_bound, self.upper_bound)
412
413
414
class LWE(SageObject):
415
"""
416
Learning with Errors (LWE) oracle.
417
418
.. automethod:: __init__
419
.. automethod:: __call__
420
"""
421
def __init__(self, n, q, D, secret_dist='uniform', m=None):
422
"""
423
Construct an LWE oracle in dimension ``n`` over a ring of order
424
``q`` with noise distribution ``D``.
425
426
INPUT:
427
428
- ``n`` - dimension (integer > 0)
429
- ``q`` - modulus typically > n (integer > 0)
430
- ``D`` - an error distribution such as an instance of
431
:class:`DiscreteGaussianSamplerRejection` or :class:`UniformSampler`
432
- ``secret_dist`` - distribution of the secret (default: 'uniform'); one of
433
434
- "uniform" - secret follows the uniform distribution in `\Zmod{q}`
435
- "noise" - secret follows the noise distribution
436
- ``(lb,ub)`` - the secret is chosen uniformly from ``[lb,...,ub]`` including both endpoints
437
438
- ``m`` - number of allowed samples or ``None`` if no such limit exists
439
(default: ``None``)
440
441
EXAMPLE:
442
443
First, we construct a noise distribution with standard deviation 3.0::
444
445
sage: from sage.crypto.lwe import DiscreteGaussianSampler
446
sage: D = DiscreteGaussianSampler(3.0)
447
448
Next, we construct our oracle::
449
450
sage: from sage.crypto.lwe import LWE
451
sage: lwe = LWE(n=20, q=next_prime(400), D=D); lwe
452
LWE(20, 401, DiscreteGaussianSamplerRejection(3.000000, 53, 4), 'uniform', None)
453
454
and sample 1000 samples::
455
456
sage: L = [lwe() for _ in range(1000)]
457
458
To test the oracle, we use the internal secret to evaluate the samples
459
in the secret::
460
461
sage: S = [ZZ(a.dot_product(lwe._LWE__s) - c) for (a,c) in L]
462
463
However, while Sage represents finite field elements between 0 and q-1
464
we rely on a balanced representation of those elements here. Hence, we
465
fix the representation and recover the correct standard deviation of the
466
noise::
467
468
sage: sqrt(variance([e if e <= 200 else e-401 for e in S]).n())
469
3.0...
470
471
If ``m`` is not ``None`` the number of available samples is restricted::
472
473
sage: from sage.crypto.lwe import LWE
474
sage: lwe = LWE(n=20, q=next_prime(400), D=D, m=30)
475
sage: _ = [lwe() for _ in range(30)]
476
sage: lwe() # 31
477
Traceback (most recent call last):
478
...
479
IndexError: Number of available samples exhausted.
480
"""
481
self.n = ZZ(n)
482
self.m = m
483
self.__i = 0
484
self.K = IntegerModRing(q)
485
self.FM = FreeModule(self.K, n)
486
self.D = D
487
488
self.secret_dist = secret_dist
489
if secret_dist == 'uniform':
490
self.__s = random_vector(self.K, self.n)
491
elif secret_dist == 'noise':
492
self.__s = vector(self.K, self.n, [self.D() for _ in range(n)])
493
else:
494
try:
495
lb, ub = map(ZZ,secret_dist)
496
self.__s = vector(self.K, self.n, [randint(lb,ub) for _ in range(n)])
497
except (IndexError, TypeError):
498
raise TypeError("Parameter secret_dist=%s not understood."%(secret_dist))
499
500
def _repr_(self):
501
"""
502
EXAMPLE::
503
504
sage: from sage.crypto.lwe import DiscreteGaussianSampler, LWE
505
sage: D = DiscreteGaussianSampler(3.0)
506
sage: lwe = LWE(n=20, q=next_prime(400), D=D); lwe
507
LWE(20, 401, DiscreteGaussianSamplerRejection(3.000000, 53, 4), 'uniform', None)
508
509
sage: lwe = LWE(n=20, q=next_prime(400), D=D, secret_dist=(-3, 3)); lwe
510
LWE(20, 401, DiscreteGaussianSamplerRejection(3.000000, 53, 4), (-3, 3), None)
511
"""
512
if type(self.secret_dist) == str:
513
return "LWE(%d, %d, %s, '%s', %s)"%(self.n,self.K.order(),self.D,self.secret_dist, self.m)
514
else:
515
return "LWE(%d, %d, %s, %s, %s)"%(self.n,self.K.order(),self.D,self.secret_dist, self.m)
516
517
518
def __call__(self):
519
"""
520
EXAMPLE::
521
522
sage: from sage.crypto.lwe import DiscreteGaussianSampler, LWE
523
sage: LWE(10, 401, DiscreteGaussianSampler(3))()
524
((309, 347, 198, 194, 336, 360, 264, 123, 368, 398), 198)
525
"""
526
if self.m is not None:
527
if self.__i >= self.m:
528
raise IndexError("Number of available samples exhausted.")
529
self.__i+=1
530
a = self.FM.random_element()
531
return a, a.dot_product(self.__s) + self.K(self.D())
532
533
534
class Regev(LWE):
535
"""
536
LWE oracle with parameters as in [Reg09]_.
537
538
.. automethod:: __init__
539
"""
540
def __init__(self, n, secret_dist='uniform', m=None):
541
"""
542
Construct LWE instance parameterised by security parameter ``n`` where
543
the modulus ``q`` and the ``stddev`` of the noise are chosen as in
544
[Reg09]_.
545
546
INPUT:
547
548
- ``n`` - security parameter (integer > 0)
549
- ``secret_dist`` - distribution of the secret. See documentation of :class:`LWE`
550
for details (default='uniform')
551
- ``m`` - number of allowed samples or ``None`` if no such limit exists
552
(default: ``None``)
553
554
EXAMPLES::
555
556
sage: from sage.crypto.lwe import Regev
557
sage: Regev(n=20)
558
LWE(20, 401, DiscreteGaussianSamplerRejection(1.915069, 401, 4), 'uniform', None)
559
"""
560
q = ZZ(next_prime(n**2))
561
s = RR(1/(RR(n).sqrt() * log(n, 2)**2) * q)
562
D = DiscreteGaussianSampler(s/sqrt(2*pi.n()), q)
563
LWE.__init__(self, n=n, q=q, D=D, secret_dist=secret_dist, m=m)
564
565
class LindnerPeikert(LWE):
566
"""
567
LWE oracle with parameters as in [LP11]_.
568
569
.. automethod:: __init__
570
"""
571
def __init__(self, n, delta=0.01, m=None):
572
"""
573
Construct LWE instance parameterised by security parameter ``n`` where
574
the modulus ``q`` and the ``stddev`` of the noise is chosen as in
575
[LP11]_.
576
577
INPUT:
578
579
- ``n`` - security parameter (integer > 0)
580
- ``delta`` - error probability per symbol (default: 0.01)
581
- ``m`` - number of allowed samples or ``None`` in which case ``m=2*n +
582
128`` as in [LP11]_ (default: ``None``)
583
584
EXAMPLES::
585
586
sage: from sage.crypto.lwe import LindnerPeikert
587
sage: LindnerPeikert(n=20)
588
LWE(20, 2053, DiscreteGaussianSamplerRejection(3.600954, 53, 4), 'noise', 168)
589
"""
590
if m is None:
591
m = 2*n + 128
592
# Find c>=1 such that c*exp((1-c**2)/2))**(2*n) == 2**-40
593
# (c*exp((1-c**2)/2))**(2*n) == 2**-40
594
# log((c*exp((1-c**2)/2))**(2*n)) == -40*log(2)
595
# (2*n)*log(c*exp((1-c**2)/2)) == -40*log(2)
596
# 2*n*(log(c)+log(exp((1-c**2)/2))) == -40*log(2)
597
# 2*n*(log(c)+(1-c**2)/2) == -40*log(2)
598
# 2*n*log(c)+n*(1-c**2) == -40*log(2)
599
# 2*n*log(c)+n*(1-c**2) + 40*log(2) == 0
600
c = var('c')
601
c = find_root(2*n*log(c)+n*(1-c**2) + 40*log(2) == 0, 1, 10)
602
# Upper bound on s**2/t
603
s_t_bound = (sqrt(2) * pi / c / sqrt(2*n*log(2/delta))).n()
604
# Interpretation of "choose q just large enough to allow for a Gaussian parameter s>=8" in [LP11]_
605
q = next_prime(floor(2**round(log(256 / s_t_bound, 2))))
606
# Gaussian parameter as defined in [LP11]_
607
s = sqrt(s_t_bound*floor(q/4))
608
# Transform s into stddev
609
stddev = s/sqrt(2*pi.n())
610
D = DiscreteGaussianSampler(stddev)
611
LWE.__init__(self, n=n, q=q, D=D, secret_dist='noise', m=m)
612
613
614
class UniformNoiseLWE(LWE):
615
"""
616
LWE oracle with uniform secret with parameters as in [CGW13]_.
617
618
.. automethod:: __init__
619
"""
620
def __init__(self, n, instance='key', m=None):
621
"""
622
Construct LWE instance parameterised by security parameter ``n`` where
623
all other parameters are chosen as in [CGW13]_.
624
625
INPUT:
626
627
- ``n`` - security parameter (integer >= 89)
628
- ``instance`` - one of
629
630
- "key" - the LWE-instance that hides the secret key is generated
631
- "encrypt" - the LWE-instance that hides the message is generated
632
(default: ``key``)
633
634
- ``m`` - number of allowed samples or ``None`` in which case ``m`` is
635
chosen as in [CGW13_]. (default: ``None``)
636
637
EXAMPLES::
638
639
sage: from sage.crypto.lwe import UniformNoiseLWE
640
sage: UniformNoiseLWE(89)
641
LWE(89, 154262477, UniformSampler(0, 351), 'noise', 131)
642
643
sage: UniformNoiseLWE(89, instance='encrypt')
644
LWE(131, 154262477, UniformSampler(0, 497), 'noise', 181)
645
"""
646
647
if n<89:
648
raise TypeError("Parameter too small")
649
650
n2 = n
651
C = 4/sqrt(2*pi)
652
kk = floor((n2-2*log(n2, 2)**2)/5)
653
n1 = floor((3*n2-5*kk)/2)
654
ke = floor((n1-2*log(n1, 2)**2)/5)
655
l = floor((3*n1-5*ke)/2)-n2
656
sk = ceil((C*(n1+n2))**(3/2))
657
se = ceil((C*(n1+n2+l))**(3/2))
658
q = next_prime(max(ceil((4*sk)**((n1+n2)/n1)), ceil((4*se)**((n1+n2+l)/(n2+l))), ceil(4*(n1+n2)*se*sk+4*se+1)))
659
660
if kk<=0:
661
raise TypeError("Parameter too small")
662
663
if instance == 'key':
664
D = UniformSampler(0, sk-1)
665
if m is None:
666
m = n1
667
LWE.__init__(self, n=n2, q=q, D=D, secret_dist='noise', m=m)
668
elif instance == 'encrypt':
669
D = UniformSampler(0, se-1)
670
if m is None:
671
m = n2+l
672
LWE.__init__(self, n=n1, q=q, D=D, secret_dist='noise', m=m)
673
else:
674
raise TypeError("Parameter instance=%s not understood."%(instance))
675
676
class RingLWE(SageObject):
677
"""
678
Ring Learning with Errors oracle.
679
680
.. automethod:: __init__
681
.. automethod:: __call__
682
"""
683
def __init__(self, N, q, D, poly=None, secret_dist='uniform', m=None):
684
"""
685
Construct a Ring-LWE oracle in dimension ``n=phi(N)`` over a ring of order
686
``q`` with noise distribution ``D``.
687
688
INPUT:
689
690
- ``N`` - index of cyclotomic polynomial (integer > 0, must be power of 2)
691
- ``q`` - modulus typically > N (integer > 0)
692
- ``D`` - an error distribution such as an instance of
693
:class:`DiscreteGaussianPolynomialSamplerRejection` or :class:`UniformSampler`
694
- ``poly`` - a polynomial of degree ``phi(N)``. If ``None`` the
695
cyclotomic polynomial used (default: ``None``).
696
- ``secret_dist`` - distribution of the secret. See documentation of
697
:class:`LWE` for details (default='uniform')
698
- ``m`` - number of allowed samples or ``None`` if no such limit exists
699
(default: ``None``)
700
701
EXAMPLE::
702
703
sage: from sage.crypto.lwe import DiscreteGaussianPolynomialSampler, RingLWE
704
sage: D = DiscreteGaussianPolynomialSampler(n=euler_phi(20), stddev=3.0)
705
sage: RingLWE(N=20, q=next_prime(800), D=D);
706
RingLWE(20, 809, DiscreteGaussianPolynomialSamplerRejection(8, 3.000000, 53, 4), x^8 - x^6 + x^4 - x^2 + 1, 'uniform', None)
707
"""
708
self.N = ZZ(N)
709
self.n = euler_phi(N)
710
self.m = m
711
self.__i = 0
712
self.K = IntegerModRing(q)
713
714
if self.n != D.n:
715
raise ValueError("Noise distribution has dimensions %d != %d"%(D.n, self.n))
716
717
self.D = D
718
self.q = q
719
if poly is not None:
720
self.poly = poly
721
else:
722
self.poly = cyclotomic_polynomial(self.N, 'x')
723
724
self.R_q = self.K['x'].quotient(self.poly, 'x')
725
726
self.secret_dist = secret_dist
727
if secret_dist == 'uniform':
728
self.__s = self.R_q.random_element() # uniform sampling of secret
729
elif secret_dist == 'noise':
730
self.__s = self.D()
731
else:
732
raise TypeError("Parameter secret_dist=%s not understood."%(secret_dist))
733
734
def _repr_(self):
735
"""
736
EXAMPLE::
737
738
sage: from sage.crypto.lwe import DiscreteGaussianPolynomialSampler, RingLWE
739
sage: D = DiscreteGaussianPolynomialSampler(n=8, stddev=3.0)
740
sage: RingLWE(N=16, q=next_prime(400), D=D);
741
RingLWE(16, 401, DiscreteGaussianPolynomialSamplerRejection(8, 3.000000, 53, 4), x^8 + 1, 'uniform', None)
742
"""
743
if type(self.secret_dist) == str:
744
return "RingLWE(%d, %d, %s, %s, '%s', %s)"%(self.N, self.K.order(), self.D, self.poly, self.secret_dist, self.m)
745
else:
746
return "RingLWE(%d, %d, %s, %s, %s, %s)"%(self.N, self.K.order(), self.D, self.poly, self.secret_dist, self.m)
747
748
749
def __call__(self):
750
"""
751
EXAMPLE::
752
753
sage: from sage.crypto.lwe import DiscreteGaussianPolynomialSampler, RingLWE
754
sage: N = 16
755
sage: n = euler_phi(N)
756
sage: D = DiscreteGaussianPolynomialSampler(n, 5)
757
sage: ringlwe = RingLWE(N, 257, D, secret_dist='uniform')
758
sage: ringlwe()
759
((228, 149, 226, 198, 38, 222, 222, 127), (177, 138, 68, 134, 74, 162, 203, 243))
760
"""
761
if self.m is not None:
762
if self.__i >= self.m:
763
raise IndexError("Number of available samples exhausted.")
764
self.__i+=1
765
a = self.R_q.random_element()
766
return vector(a), vector(a * (self.__s) + self.D())
767
768
class RingLindnerPeikert(RingLWE):
769
"""
770
Ring-LWE oracle with parameters as in [LP11]_.
771
772
.. automethod:: __init__
773
"""
774
def __init__(self, N, delta=0.01, m=None):
775
"""
776
Construct a Ring-LWE oracle in dimension ``n=phi(N)`` where
777
the modulus ``q`` and the ``stddev`` of the noise is chosen as in
778
[LP11]_.
779
780
INPUT:
781
782
- ``N`` - index of cyclotomic polynomial (integer > 0, must be power of 2)
783
- ``delta`` - error probability per symbol (default: 0.01)
784
- ``m`` - number of allowed samples or ``None`` in which case ``3*n`` is
785
used (default: ``None``)
786
787
EXAMPLES::
788
789
sage: from sage.crypto.lwe import RingLindnerPeikert
790
sage: RingLindnerPeikert(N=16)
791
RingLWE(16, 1031, DiscreteGaussianPolynomialSamplerRejection(8, 2.803372, 53, 4), x^8 + 1, 'noise', 24)
792
"""
793
n = euler_phi(N)
794
if m is None:
795
m = 3*n
796
# Find c>=1 such that c*exp((1-c**2)/2))**(2*n) == 2**-40
797
# i.e c>=1 such that 2*n*log(c)+n*(1-c**2) + 40*log(2) == 0
798
c = var('c')
799
c = find_root(2*n*log(c)+n*(1-c**2) + 40*log(2) == 0, 1, 10)
800
# Upper bound on s**2/t
801
s_t_bound = (sqrt(2) * pi / c / sqrt(2*n*log(2/delta))).n()
802
# Interpretation of "choose q just large enough to allow for a Gaussian parameter s>=8" in [LP11]_
803
q = next_prime(floor(2**round(log(256 / s_t_bound, 2))))
804
# Gaussian parameter as defined in [LP11]_
805
s = sqrt(s_t_bound*floor(q/4))
806
# Transform s into stddev
807
stddev = s/sqrt(2*pi.n())
808
D = DiscreteGaussianPolynomialSampler(n, stddev)
809
RingLWE.__init__(self, N=N, q=q, D=D, poly=None, secret_dist='noise', m=m)
810
811
class RingLWEConverter(SageObject):
812
"""
813
Wrapper callable to convert Ring-LWE oracles into LWE oracles by
814
disregarding the additional structure.
815
816
.. automethod:: __init__
817
.. automethod:: __call__
818
"""
819
def __init__(self, ringlwe):
820
"""
821
INPUT:
822
823
- ``ringlwe`` - an instance of a :class:`RingLWE`
824
825
EXAMPLE::
826
827
sage: from sage.crypto.lwe import DiscreteGaussianPolynomialSampler, RingLWE, RingLWEConverter
828
sage: D = DiscreteGaussianPolynomialSampler(euler_phi(16), 5)
829
sage: lwe = RingLWEConverter(RingLWE(16, 257, D, secret_dist='uniform'))
830
sage: set_random_seed(1337)
831
sage: lwe()
832
((130, 32, 216, 3, 125, 58, 197, 171), 182)
833
"""
834
self.ringlwe = ringlwe
835
self._i = 0
836
self._ac = None
837
self.n = self.ringlwe.n
838
839
def __call__(self):
840
"""
841
EXAMPLE::
842
843
sage: from sage.crypto.lwe import DiscreteGaussianPolynomialSampler, RingLWE, RingLWEConverter
844
sage: D = DiscreteGaussianPolynomialSampler(euler_phi(16), 5)
845
sage: lwe = RingLWEConverter(RingLWE(16, 257, D, secret_dist='uniform'))
846
sage: set_random_seed(1337)
847
sage: lwe()
848
((130, 32, 216, 3, 125, 58, 197, 171), 182)
849
"""
850
R_q = self.ringlwe.R_q
851
852
if (self._i % self.n) == 0:
853
self._ac = self.ringlwe()
854
a, c = self._ac
855
x = R_q.gen()
856
r = vector((x**(self._i % self.n) * R_q(a.list())).list()), c[self._i % self.n]
857
self._i += 1
858
return r
859
860
def _repr_(self):
861
"""
862
EXAMPLE::
863
864
sage: from sage.crypto.lwe import DiscreteGaussianPolynomialSampler, RingLWE, RingLWEConverter
865
sage: D = DiscreteGaussianPolynomialSampler(euler_phi(20), 5)
866
sage: rlwe = RingLWE(20, 257, D)
867
sage: lwe = RingLWEConverter(rlwe)
868
sage: lwe
869
RingLWEConverter(RingLWE(20, 257, DiscreteGaussianPolynomialSamplerRejection(8, 5.000000, 53, 4), x^8 - x^6 + x^4 - x^2 + 1, 'uniform', None))
870
871
"""
872
return "RingLWEConverter(%s)"%str(self.ringlwe)
873
874
def samples(m, n, lwe, seed=None, balanced=False, **kwds):
875
"""
876
Return ``m`` LWE samples.
877
878
INPUT:
879
880
- ``m`` - the number of samples (integer > 0)
881
- ``n`` - the security parameter (integer > 0)
882
- ``lwe`` - either
883
884
- a subclass of :class:`LWE` such as :class:`Regev` or :class:`LindnerPeikert`
885
- an instance of :class:`LWE` or any subclass
886
- the name of any such class (e.g., "Regev", "LindnerPeikert")
887
888
- ``seed`` - seed to be used for generation or ``None`` if no specific seed
889
shall be set (default: ``None``)
890
- ``balanced`` - use function :func:`balance_sample` to return balanced
891
representations of finite field elements (default: ``False``)
892
- ``**kwds`` - passed through to LWE constructor
893
894
EXAMPLE::
895
896
sage: from sage.crypto.lwe import samples, Regev
897
sage: samples(2, 20, Regev, seed=1337)
898
[((199, 388, 337, 53, 200, 284, 336, 215, 75, 14, 274, 234, 97, 255, 246, 153, 268, 218, 396, 351), 18),
899
((286, 42, 175, 155, 190, 275, 114, 280, 45, 218, 304, 386, 98, 235, 77, 0, 65, 20, 163, 14), 334)]
900
901
sage: from sage.crypto.lwe import samples, Regev
902
sage: samples(2, 20, Regev, balanced=True, seed=1337)
903
[((199, -13, -64, 53, 200, -117, -65, -186, 75, 14, -127, -167, 97, -146, -155, 153, -133, -183, -5, -50), 18),
904
((-115, 42, 175, 155, 190, -126, 114, -121, 45, -183, -97, -15, 98, -166, 77, 0, 65, 20, 163, 14), -67)]
905
906
sage: from sage.crypto.lwe import samples
907
sage: samples(2, 20, 'LindnerPeikert')
908
[((1302, 718, 1397, 147, 278, 979, 1185, 133, 902, 1180, 1264, 734, 2029, 314, 428, 18, 707, 2021, 1153, 173), 1127),
909
((2015, 1278, 455, 429, 1391, 186, 149, 1199, 220, 1629, 843, 719, 1744, 1568, 674, 1462, 1549, 972, 248, 1066), 1422)]
910
911
"""
912
if seed is not None:
913
set_random_seed(seed)
914
915
if isinstance(lwe, str):
916
lwe = eval(lwe)
917
918
if type(lwe) == type:
919
lwe = lwe(n, m=m, **kwds)
920
else:
921
lwe = lwe
922
if lwe.n != n:
923
raise ValueError("Passed LWE instance has n=%d, but n=%d was passed to this function."%(lwe.n, n))
924
925
if balanced is False:
926
f = lambda (a,c): (a,c)
927
else:
928
f = balance_sample
929
return [f(lwe()) for _ in xrange(m)]
930
931
def balance_sample(s, q=None):
932
r"""
933
Given ``(a,c) = s`` return a tuple ``(a',c')`` where ``a'`` is an integer
934
vector with entries between -q//2 and q//2 and ``c`` is also within these
935
bounds.
936
937
If ``q`` is given ``(a,c) = s`` may live in the integers. If ``q`` is not
938
given, then ``(a,c)`` are assumed to live in `\Zmod{q}`.
939
940
INPUT:
941
942
- ``s`` - sample of the form (a,c) where a is a vector and c is a scalar
943
- ``q`` - modulus (default: ``None``)
944
945
EXAMPLE::
946
947
sage: from sage.crypto.lwe import balance_sample, samples, Regev
948
sage: map(balance_sample, samples(10, 5, Regev))
949
[((-9, -4, -4, 4, -4), 6), ((-3, -10, 8, -3, -1), -10), ((-6, -12, -3, -2, -6), -6),
950
...
951
((-1, -8, -11, 13, 4), -6), ((10, 11, -3, -13, 0), 6), ((6, -1, 2, -11, 14), 2)]
952
953
954
sage: from sage.crypto.lwe import balance_sample, DiscreteGaussianPolynomialSampler, RingLWE, samples
955
sage: D = DiscreteGaussianPolynomialSampler(8, 5)
956
sage: rlwe = RingLWE(20, 257, D)
957
sage: map(balance_sample, samples(10, 8, rlwe))
958
[((5, -55, -31, -90, 6, 100, -46, -107), (6, -64, -40, 117, 27, 54, -98, -56)),
959
((109, -106, 28, 77, -14, -109, 115, 34), (82, 17, -89, 62, 1, -77, 128, 64)),
960
...
961
((-32, 51, -110, -106, 35, -82, 14, -113), (126, -120, 126, 119, 101, 3, -122, -75))]
962
963
.. note::
964
965
This function is useful to convert between Sage's standard
966
representation of elements in `\Zmod{q}` as integers between 0 and q-1
967
and the usual representation of such elements in lattice cryptography as
968
integers between -q//2 and q//2.
969
"""
970
a, c = s
971
972
try:
973
c[0]
974
scalar = False
975
except TypeError:
976
c = vector(c.parent(),[c])
977
scalar = True
978
979
if q is None:
980
q = parent(c[0]).order()
981
a = a.change_ring(ZZ)
982
c = c.change_ring(ZZ)
983
else:
984
K = IntegerModRing(q)
985
a = a.change_ring(K).change_ring(ZZ)
986
c = c.change_ring(K).change_ring(ZZ)
987
988
q2 = q//2
989
990
if scalar:
991
return vector(ZZ, len(a), [e if e <= q2 else e-q for e in a]), c[0] if c[0] <= q2 else c[0]-q
992
else:
993
return vector(ZZ, len(a), [e if e <= q2 else e-q for e in a]), vector(ZZ, len(c), [e if e <= q2 else e-q for e in c])
994
995