Ticket #14478: trac_14478_lwe.patch

File trac_14478_lwe.patch, 37.6 KB (added by malb, 10 years ago)
  • doc/en/reference/cryptography/index.rst

    # HG changeset patch
    # User Martin Albrecht <martinralbrecht@googlemail.com>
    # Date 1366888455 -3600
    # Node ID 66dfe0663ca05b9af9f735c7a0b0533bb0d11e52
    # Parent  1077314f416653b28e199c382667a1f11e444bdd
    #14478 add (Ring-)LWE generators
    
    diff --git a/doc/en/reference/cryptography/index.rst b/doc/en/reference/cryptography/index.rst
    a b  
    2727   sage/crypto/mq/sbox
    2828
    2929   sage/crypto/lattice
     30   sage/crypto/lwe
    3031
    3132.. include:: ../footer.txt
  • new file sage/crypto/lwe.py

    diff --git a/sage/crypto/lwe.py b/sage/crypto/lwe.py
    new file mode 100644
    - +  
     1# -*- coding: utf-8 -*-
     2"""
     3(Ring-)LWE oracle generators
     4
     5The Learning with Errors problem (LWE) is solving linear systems of equations
     6where the right hand side has been disturbed 'slightly' where 'slightly' is made
     7precise by a noise distribution - typically a discrete Gaussian
     8distribution. See [Reg09]_ for details.
     9
     10The Ring Learning with Errors problem (LWE) is solving a set of univariate
     11polynomial equations - typically in a cyclotomic field - where the right hand
     12side was disturbed 'slightly'. See [LPR10]_ for details.
     13
     14This module implements generators of LWE samples where parameters are chosen
     15following proposals in the cryptographic literature.
     16
     17EXAMPLES:
     18
     19We 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
     21chosen 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
     30We may also pass classes to the samples function, which is useful for users
     31implementing 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
     40Finally, :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
     49Note 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
     59One technical issue when working with these generators is that by default they
     60return vectors and scalars over/in rings modulo some `q`. These are represented
     61as elements in `(0,q-1)` by Sage. However, it usually is more natural to think
     62of these entries as integers in `(-q//2,q//2)`. To allow for this, this module
     63provides the option to balance the representation. In this case vectors and
     64scalars 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
     72AUTHORS:
     73
     74- Martin Albrecht
     75- Robert Fitzpatrick
     76- Daniel Cabracas
     77- Florian Göpfert
     78- Michael Schneider
     79
     80REFERENCES:
     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
     100from sage.calculus.var import var
     101from sage.functions.log import exp, log
     102from sage.functions.other import sqrt, floor, ceil
     103from sage.misc.functional import cyclotomic_polynomial
     104from sage.misc.randstate import set_random_seed
     105from sage.misc.prandom import randint
     106from sage.misc.misc import get_verbose
     107from sage.modules.free_module import FreeModule
     108from sage.modules.free_module_element import random_vector, vector
     109from sage.numerical.optimize import find_root
     110from sage.rings.all import ZZ, RealField, IntegerModRing, RR
     111from sage.rings.arith import next_prime, euler_phi
     112from sage.structure.element import parent
     113from sage.structure.sage_object import SageObject
     114from sage.symbolic.constants import pi
     115
     116class 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
     206DiscreteGaussianSampler = DiscreteGaussianSamplerRejection
     207
     208class 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 coeficients 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
     287DiscreteGaussianPolynomialSampler = DiscreteGaussianPolynomialSamplerRejection
     288
     289class 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 <= than 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
     349class 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 coeficients 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 <= than 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
     414class 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; one of
     433
     434          - "uniform" - secret follows the uniform distribution in `\Zmod{q}`
     435          - "noise" - secret follows the noise distrbution
     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
     534class 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 paramter ``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 paramter (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
     565class 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 paramter ``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 paramter (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
     614class UniformNoiseLWE(LWE):
     615    """
     616    LWE oracle with uniform sectet 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 paramter ``n`` where
     623        all other parameters are chosen as in [CGW13]_.
     624
     625        INPUT:
     626
     627        - ``n`` - security paramter (integer >= 80)
     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        - ``m`` - number of allowed samples or ``None`` in which case ``m`` is
     634          chosen as in [CGW13_].  (default: ``None``)
     635
     636        EXAMPLES::
     637
     638            sage: from sage.crypto.lwe import UniformNoiseLWE
     639            sage: UniformNoiseLWE(89)
     640            LWE(89, 154262477, UniformSampler(0, 351), 'noise', 131)
     641
     642            sage: UniformNoiseLWE(89, instance='encrypt')
     643            LWE(131, 154262477, UniformSampler(0, 497), 'noise', 181)
     644        """
     645       
     646        if n<89:
     647            raise TypeError("Parameter too small")
     648           
     649        n2 = n
     650        C  = 4/sqrt(2*pi)
     651        kk = floor((n2-2*log(n2, 2)**2)/5)
     652        n1 = floor((3*n2-5*kk)/2)
     653        ke = floor((n1-2*log(n1, 2)**2)/5)
     654        l  = floor((3*n1-5*ke)/2)-n2
     655        sk = ceil((C*(n1+n2))**(3/2))
     656        se = ceil((C*(n1+n2+l))**(3/2))
     657        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)))
     658           
     659        if kk<=0:
     660            raise TypeError("Parameter too small")
     661         
     662        if instance == 'key':
     663            D  = UniformSampler(0, sk-1)
     664            if m is None:
     665                m = n1
     666            LWE.__init__(self, n=n2, q=q, D=D, secret_dist='noise', m=m)
     667        elif instance == 'encrypt':
     668            D   = UniformSampler(0, se-1)
     669            if m is None:
     670                m = n2+l
     671            LWE.__init__(self, n=n1, q=q, D=D, secret_dist='noise', m=m)
     672        else:
     673            raise TypeError("Parameter instance=%s not understood."%(instance))
     674
     675class RingLWE(SageObject):
     676    """
     677    Ring Learning with Errors oracle.
     678
     679    .. automethod:: __init__
     680    .. automethod:: __call__
     681    """
     682    def __init__(self, N, q, D, poly=None, secret_dist='uniform', m=None):
     683        """
     684        Construct a Ring-LWE oracle in dimension ``n=phi(N)`` over a ring of order
     685        ``q`` with noise distribution ``D``.
     686
     687        INPUT:
     688
     689        - ``N`` - index of cyclotomic polynomial (integer > 0, must be power of 2)
     690        - ``q`` - modulus typically > N (integer > 0)
     691        - ``D`` - an error distribution such as an instance of
     692          :class:`DiscreteGaussianPolynomialSamplerRejection` or :class:`UniformSampler`
     693        - ``poly`` - a polynomial of degree ``phi(N)``. If ``None`` the
     694          cyclotomic polynomial used (default: ``None``).
     695        - ``secret_dist`` - distribution of the secret. See documentation of
     696          :class:`LWE` for details (default='uniform')
     697        - ``m`` - number of allowed samples or ``None`` if no such limit exists
     698          (default: ``None``)
     699
     700        EXAMPLE::
     701
     702            sage: from sage.crypto.lwe import DiscreteGaussianPolynomialSampler, RingLWE
     703            sage: D = DiscreteGaussianPolynomialSampler(n=euler_phi(20), stddev=3.0)
     704            sage: RingLWE(N=20, q=next_prime(800), D=D);
     705            RingLWE(20, 809, DiscreteGaussianPolynomialSamplerRejection(8, 3.000000, 53, 4), x^8 - x^6 + x^4 - x^2 + 1, 'uniform', None)
     706        """
     707        self.N  = ZZ(N)
     708        self.n = euler_phi(N)
     709        self.m =  m
     710        self.__i = 0
     711        self.K  = IntegerModRing(q)
     712
     713        if self.n != D.n:
     714            raise ValueError("Noise distribution has dimensions %d != %d"%(D.n, self.n))
     715
     716        self.D = D
     717        self.q = q
     718        if poly is not None:
     719            self.poly = poly
     720        else:
     721            self.poly = cyclotomic_polynomial(self.N, 'x')
     722
     723        self.R_q = self.K['x'].quotient(self.poly, 'x')
     724
     725        self.secret_dist = secret_dist
     726        if secret_dist == 'uniform':
     727            self.__s = self.R_q.random_element()  # uniform sampling of secret
     728        elif secret_dist == 'noise':
     729            self.__s = self.D()
     730        else:
     731            raise TypeError("Parameter secret_dist=%s not understood."%(secret_dist))
     732
     733    def _repr_(self):
     734        """
     735        EXAMPLE::
     736
     737            sage: from sage.crypto.lwe import DiscreteGaussianPolynomialSampler, RingLWE
     738            sage: D = DiscreteGaussianPolynomialSampler(n=8, stddev=3.0)
     739            sage: RingLWE(N=16, q=next_prime(400), D=D);
     740            RingLWE(16, 401, DiscreteGaussianPolynomialSamplerRejection(8, 3.000000, 53, 4), x^8 + 1, 'uniform', None)
     741        """
     742        if type(self.secret_dist) == str:
     743            return "RingLWE(%d, %d, %s, %s, '%s', %s)"%(self.N, self.K.order(), self.D, self.poly, self.secret_dist, self.m)
     744        else:
     745            return "RingLWE(%d, %d, %s, %s, %s, %s)"%(self.N, self.K.order(), self.D, self.poly, self.secret_dist, self.m)
     746
     747
     748    def __call__(self):
     749        """
     750        EXAMPLE::
     751           
     752            sage: from sage.crypto.lwe import DiscreteGaussianPolynomialSampler, RingLWE
     753            sage: N = 16
     754            sage: n = euler_phi(N)
     755            sage: D = DiscreteGaussianPolynomialSampler(n, 5)
     756            sage: ringlwe = RingLWE(N, 257, D, secret_dist='uniform')
     757            sage: ringlwe()
     758            ((228, 149, 226, 198, 38, 222, 222, 127), (177, 138, 68, 134, 74, 162, 203, 243))
     759        """
     760        if self.m is not None:
     761            if self.__i >= self.m:
     762                raise IndexError("Number of available samples exhausted.")
     763        self.__i+=1
     764        a = self.R_q.random_element()
     765        return vector(a), vector(a * (self.__s) + self.D())
     766
     767class RingLindnerPeikert(RingLWE):
     768    """
     769    Ring-LWE oracle with parameters as in [LP11]_.
     770
     771    .. automethod:: __init__
     772    """
     773    def __init__(self, N, delta=0.01, m=None):
     774        """
     775        Construct a Ring-LWE oracle in dimension ``n=phi(N)`` where
     776        the modulus ``q`` and the ``stddev`` of the noise is chosen as in
     777        [LP11]_.
     778
     779        INPUT:
     780
     781        - ``N`` - index of cyclotomic polynomial (integer > 0, must be power of 2)
     782        - ``delta`` - error probability per symbol (default: 0.01)
     783        - ``m`` - number of allowed samples or ``None`` in which case ``3*n`` is
     784          used (default: ``None``)
     785
     786        EXAMPLES::
     787
     788            sage: from sage.crypto.lwe import RingLindnerPeikert
     789            sage: RingLindnerPeikert(N=16)
     790            RingLWE(16, 1031, DiscreteGaussianPolynomialSamplerRejection(8, 2.803372, 53, 4), x^8 + 1, 'noise', 24)
     791        """
     792        n = euler_phi(N)
     793        if m is None:
     794            m = 3*n
     795        # Find c>=1 such that c*exp((1-c**2)/2))**(2*n) == 2**-40
     796        #  i.e c>=1 such that 2*n*log(c)+n*(1-c**2) + 40*log(2) == 0       
     797        c = var('c')
     798        c = find_root(2*n*log(c)+n*(1-c**2) + 40*log(2) == 0, 1, 10)
     799        # Upper bound on s**2/t
     800        s_t_bound = (sqrt(2) * pi / c / sqrt(2*n*log(2/delta))).n()
     801        # Interpretation of "choose q just large enough to allow for a Gaussian parameter s>=8" in [LP11]_
     802        q = next_prime(floor(2**round(log(256 / s_t_bound, 2))))
     803        # Gaussian parameter as defined in [LP11]_
     804        s = sqrt(s_t_bound*floor(q/4))
     805        # Transform s into stddev
     806        stddev = s/sqrt(2*pi.n())       
     807        D = DiscreteGaussianPolynomialSampler(n, stddev)
     808        RingLWE.__init__(self, N=N, q=q, D=D, poly=None, secret_dist='noise', m=m)
     809
     810class RingLWEConverter(SageObject):
     811    """
     812    Wrapper callable to convert Ring-LWE oracles into LWE oracles by
     813    disregarding the additional structure.
     814
     815    .. automethod:: __init__
     816    .. automethod:: __call__
     817    """
     818    def __init__(self, ringlwe):
     819        """
     820        INPUT:
     821
     822        - ``ringlwe`` - an instance of a :class:`RingLWE`
     823
     824        EXAMPLE::
     825
     826            sage: from sage.crypto.lwe import DiscreteGaussianPolynomialSampler, RingLWE, RingLWEConverter
     827            sage: D = DiscreteGaussianPolynomialSampler(euler_phi(16), 5)
     828            sage: lwe = RingLWEConverter(RingLWE(16, 257, D, secret_dist='uniform'))
     829            sage: set_random_seed(1337)
     830            sage: lwe()
     831            ((130, 32, 216, 3, 125, 58, 197, 171), 182)
     832        """
     833        self.ringlwe = ringlwe
     834        self._i = 0
     835        self._ac = None
     836        self.n = self.ringlwe.n
     837
     838    def __call__(self):
     839        """
     840        EXAMPLE::
     841
     842            sage: from sage.crypto.lwe import DiscreteGaussianPolynomialSampler, RingLWE, RingLWEConverter
     843            sage: D = DiscreteGaussianPolynomialSampler(euler_phi(16), 5)
     844            sage: lwe = RingLWEConverter(RingLWE(16, 257, D, secret_dist='uniform'))
     845            sage: set_random_seed(1337)
     846            sage: lwe()
     847            ((130, 32, 216, 3, 125, 58, 197, 171), 182)
     848        """
     849        R_q = self.ringlwe.R_q
     850
     851        if (self._i % self.n) == 0:
     852            self._ac = self.ringlwe()
     853        a, c = self._ac
     854        x = R_q.gen()
     855        r = vector((x**(self._i % self.n) * R_q(a.list())).list()), c[self._i % self.n]
     856        self._i += 1
     857        return r
     858
     859    def _repr_(self):
     860        """
     861        EXAMPLE::
     862
     863            sage: from sage.crypto.lwe import DiscreteGaussianPolynomialSampler, RingLWE, RingLWEConverter
     864            sage: D = DiscreteGaussianPolynomialSampler(euler_phi(20), 5)
     865            sage: rlwe = RingLWE(20, 257, D)
     866            sage: lwe = RingLWEConverter(rlwe)
     867            sage: lwe
     868            RingLWEConverter(RingLWE(20, 257, DiscreteGaussianPolynomialSamplerRejection(8, 5.000000, 53, 4), x^8 - x^6 + x^4 - x^2 + 1, 'uniform', None))
     869
     870        """
     871        return "RingLWEConverter(%s)"%str(self.ringlwe)
     872
     873def samples(m, n, lwe, seed=None, balanced=False, **kwds):
     874    """
     875    Return ``m`` LWE samples.
     876
     877    INPUT:
     878
     879    - ``m`` - the number of samples (integer > 0)
     880    - ``n`` - the security parameter (integer > 0)
     881    - ``lwe`` - either
     882
     883      - a subclass of :class:`LWE` such as :class:`Regev` or :class:`LindnerPeikert`
     884      - an instance of :class:`LWE` or any subclass
     885      - the name of any such class (e.g., "Regev", "LindnerPeikert")
     886
     887    - ``seed`` - seed to be used for generation or ``None`` if no specific seed
     888      shall be set (default: ``None``)
     889    - ``balanced`` - use function :func:`balance_sample` to return balanced
     890      representations of finite field elements (default: ``False``)
     891    - ``**kwds`` - passed through to LWE constructor
     892
     893    EXAMPLE::
     894
     895        sage: from sage.crypto.lwe import samples, Regev
     896        sage: samples(2, 20, Regev, seed=1337)
     897        [((199, 388, 337, 53, 200, 284, 336, 215, 75, 14, 274, 234, 97, 255, 246, 153, 268, 218, 396, 351), 18),
     898        ((286, 42, 175, 155, 190, 275, 114, 280, 45, 218, 304, 386, 98, 235, 77, 0, 65, 20, 163, 14), 334)]
     899
     900        sage: from sage.crypto.lwe import samples, Regev
     901        sage: samples(2, 20, Regev, balanced=True, seed=1337)
     902        [((199, -13, -64, 53, 200, -117, -65, -186, 75, 14, -127, -167, 97, -146, -155, 153, -133, -183, -5, -50), 18),
     903        ((-115, 42, 175, 155, 190, -126, 114, -121, 45, -183, -97, -15, 98, -166, 77, 0, 65, 20, 163, 14), -67)]
     904
     905        sage: from sage.crypto.lwe import samples
     906        sage: samples(2, 20, 'LindnerPeikert')
     907        [((1302, 718, 1397, 147, 278, 979, 1185, 133, 902, 1180, 1264, 734, 2029, 314, 428, 18, 707, 2021, 1153, 173), 1127),
     908        ((2015, 1278, 455, 429, 1391, 186, 149, 1199, 220, 1629, 843, 719, 1744, 1568, 674, 1462, 1549, 972, 248, 1066), 1422)]
     909
     910    """
     911    if seed is not None:
     912        set_random_seed(seed)
     913
     914    if isinstance(lwe, str):
     915        lwe = eval(lwe)
     916
     917    if type(lwe) == type:
     918        lwe = lwe(n, m=m, **kwds)
     919    else:
     920        lwe = lwe
     921        if lwe.n != n:
     922            raise ValueError("Passed LWE instance has n=%d, but n=%d was passed to this function."%(lwe.n, n))
     923
     924    if balanced is False:
     925        f = lambda (a,c): (a,c)
     926    else:
     927        f = balance_sample
     928    return [f(lwe()) for _ in xrange(m)]
     929
     930def balance_sample(s, q=None):
     931    r"""
     932    Given ``(a,c) = s`` return a tuple ``(a',c')`` where ``a'`` is a integer
     933    vector with entries between -q//2 and q//2 and ``c`` is also within these
     934    bounds.
     935
     936    If ``q`` is given ``(a,c) = s`` may live in the integers. If ``q`` is not
     937    given, then ``(a,c)`` are assumed to live in `\Zmod{q}`.
     938
     939    INPUT:
     940
     941    - ``s`` - sample of the form (a,c) where a is a vector and c is a scalar
     942    - ``q`` - modulus (default: ``None``)
     943
     944    EXAMPLE::
     945
     946        sage: from sage.crypto.lwe import balance_sample, samples, Regev
     947        sage: map(balance_sample, samples(10, 5, Regev))
     948        [((-9, -4, -4, 4, -4), 6), ((-3, -10, 8, -3, -1), -10), ((-6, -12, -3, -2, -6), -6),
     949        ...
     950        ((-1, -8, -11, 13, 4), -6), ((10, 11, -3, -13, 0), 6), ((6, -1, 2, -11, 14), 2)]
     951
     952
     953        sage: from sage.crypto.lwe import balance_sample, DiscreteGaussianPolynomialSampler, RingLWE, samples
     954        sage: D = DiscreteGaussianPolynomialSampler(8, 5)
     955        sage: rlwe = RingLWE(20, 257, D)
     956        sage: map(balance_sample, samples(10, 8, rlwe))
     957        [((5, -55, -31, -90, 6, 100, -46, -107), (6, -64, -40, 117, 27, 54, -98, -56)),
     958         ((109, -106, 28, 77, -14, -109, 115, 34), (82, 17, -89, 62, 1, -77, 128, 64)),
     959         ...
     960         ((-32, 51, -110, -106, 35, -82, 14, -113), (126, -120, 126, 119, 101, 3, -122, -75))]
     961
     962    .. note::
     963
     964        This function is useful to convert between Sage's standard
     965        representation of elements in `\Zmod{q}` as integers between 0 and q-1
     966        and the usual representation of such elements in lattice cryptography as
     967        integers between -q//2 and q//2.
     968    """
     969    a, c = s
     970
     971    try:
     972        c[0]
     973        scalar = False
     974    except TypeError:
     975        c = vector(c.parent(),[c])
     976        scalar = True
     977
     978    if q is None:
     979        q = parent(c[0]).order()
     980        a = a.change_ring(ZZ)
     981        c = c.change_ring(ZZ)
     982    else:
     983        K = IntegerModRing(q)
     984        a = a.change_ring(K).change_ring(ZZ)
     985        c = c.change_ring(K).change_ring(ZZ)
     986
     987    q2 = q//2
     988
     989    if scalar:
     990        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
     991    else:
     992        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])