class RingLWE(SageObject): """ Ring Learning with Errors oracle. .. automethod:: __init__ .. automethod:: __call__ """ def __init__(self, N, q, D, poly=None, secret_dist='uniform', m=None): """ Construct a Ring-LWE oracle in dimension ``n=phi(N)`` over a ring of order ``q`` with noise distribution ``D``. INPUT: - ``N`` - index of cyclotomic polynomial (integer > 0, must be power of 2) - ``q`` - modulus typically > N (integer > 0) - ``D`` - an error distribution such as an instance of :class:`DiscreteGaussianDistributionPolynomialSampler` or :class:`UniformSampler` - ``poly`` - a polynomial of degree ``phi(N)``. If ``None`` the cyclotomic polynomial used (default: ``None``). - ``secret_dist`` - distribution of the secret. See documentation of :class:`LWE` for details (default='uniform') - ``m`` - number of allowed samples or ``None`` if no such limit exists (default: ``None``) EXAMPLES:: sage: from sage.crypto.lwe import RingLWE sage: from sage.stats.distributions.discrete_gaussian_polynomial import DiscreteGaussianDistributionPolynomialSampler sage: D = DiscreteGaussianDistributionPolynomialSampler(ZZ['x'], n=euler_phi(20), sigma=3.0) sage: RingLWE(N=20, q=next_prime(800), D=D) RingLWE(20, 809, Discrete Gaussian sampler for polynomials of degree < 8 with σ=3.000000 in each component, x^8 - x^6 + x^4 - x^2 + 1, 'uniform', None) """ self.N = ZZ(N) self.n = euler_phi(N) self.m = m self.__i = 0 self.K = IntegerModRing(q) if self.n != D.n: raise ValueError("Noise distribution has dimensions %d != %d"%(D.n, self.n)) self.D = D self.q = q if poly is not None: self.poly = poly else: self.poly = cyclotomic_polynomial(self.N, 'x') self.R_q = self.K['x'].quotient(self.poly, 'x') self.secret_dist = secret_dist if secret_dist == 'uniform': self.__s = self.R_q.random_element() # uniform sampling of secret elif secret_dist == 'noise': self.__s = self.D() else: raise TypeError("Parameter secret_dist=%s not understood."%(secret_dist)) def _repr_(self): """ EXAMPLES:: sage: from sage.crypto.lwe import DiscreteGaussianDistributionPolynomialSampler, RingLWE sage: D = DiscreteGaussianDistributionPolynomialSampler(ZZ['x'], n=8, sigma=3.0) sage: RingLWE(N=16, q=next_prime(400), D=D) RingLWE(16, 401, Discrete Gaussian sampler for polynomials of degree < 8 with σ=3.000000 in each component, x^8 + 1, 'uniform', None) """ if isinstance(self.secret_dist, str): return "RingLWE(%d, %d, %s, %s, '%s', %s)"%(self.N, self.K.order(), self.D, self.poly, self.secret_dist, self.m) else: return "RingLWE(%d, %d, %s, %s, %s, %s)"%(self.N, self.K.order(), self.D, self.poly, self.secret_dist, self.m) def __call__(self): """ EXAMPLES:: sage: from sage.crypto.lwe import DiscreteGaussianDistributionPolynomialSampler, RingLWE sage: N = 16 sage: n = euler_phi(N) sage: D = DiscreteGaussianDistributionPolynomialSampler(ZZ['x'], n, 5) sage: ringlwe = RingLWE(N, 257, D, secret_dist='uniform') sage: ringlwe() ((226, 198, 38, 222, 222, 127, 194, 124), (11, 191, 177, 59, 105, 203, 108, 42)) """ if self.m is not None: if self.__i >= self.m: raise IndexError("Number of available samples exhausted.") self.__i+=1 a = self.R_q.random_element() return vector(a), vector(a * (self.__s) + self.D())
class RingLWE(SageObject): """ Ring Learning with Errors oracle. .. automethod:: __init__ .. automethod:: __call__ """ def __init__(self, N, q, D, poly=None, secret_dist='uniform', m=None): """ Construct a Ring-LWE oracle in dimension ``n=phi(N)`` over a ring of order ``q`` with noise distribution ``D``. INPUT: - ``N`` - index of cyclotomic polynomial (integer > 0, must be power of 2) - ``q`` - modulus typically > N (integer > 0) - ``D`` - an error distribution such as an instance of :class:`DiscreteGaussianPolynomialSamplerRejection` or :class:`UniformSampler` - ``poly`` - a polynomial of degree ``phi(N)``. If ``None`` the cyclotomic polynomial used (default: ``None``). - ``secret_dist`` - distribution of the secret. See documentation of :class:`LWE` for details (default='uniform') - ``m`` - number of allowed samples or ``None`` if no such limit exists (default: ``None``) EXAMPLE:: sage: from sage.crypto.lwe import DiscreteGaussianPolynomialSampler, RingLWE sage: D = DiscreteGaussianPolynomialSampler(n=euler_phi(20), stddev=3.0) sage: RingLWE(N=20, q=next_prime(800), D=D); RingLWE(20, 809, DiscreteGaussianPolynomialSamplerRejection(8, 3.000000, 53, 4), x^8 - x^6 + x^4 - x^2 + 1, 'uniform', None) """ self.N = ZZ(N) self.n = euler_phi(N) self.m = m self.__i = 0 self.K = IntegerModRing(q) if self.n != D.n: raise ValueError("Noise distribution has dimensions %d != %d" % (D.n, self.n)) self.D = D self.q = q if poly is not None: self.poly = poly else: self.poly = cyclotomic_polynomial(self.N, 'x') self.R_q = self.K['x'].quotient(self.poly, 'x') self.secret_dist = secret_dist if secret_dist == 'uniform': self.__s = self.R_q.random_element() # uniform sampling of secret elif secret_dist == 'noise': self.__s = self.D() else: raise TypeError("Parameter secret_dist=%s not understood." % (secret_dist)) def _repr_(self): """ EXAMPLE:: sage: from sage.crypto.lwe import DiscreteGaussianPolynomialSampler, RingLWE sage: D = DiscreteGaussianPolynomialSampler(n=8, stddev=3.0) sage: RingLWE(N=16, q=next_prime(400), D=D); RingLWE(16, 401, DiscreteGaussianPolynomialSamplerRejection(8, 3.000000, 53, 4), x^8 + 1, 'uniform', None) """ if type(self.secret_dist) == str: return "RingLWE(%d, %d, %s, %s, '%s', %s)" % (self.N, self.K.order( ), self.D, self.poly, self.secret_dist, self.m) else: return "RingLWE(%d, %d, %s, %s, %s, %s)" % (self.N, self.K.order( ), self.D, self.poly, self.secret_dist, self.m) def __call__(self): """ EXAMPLE:: sage: from sage.crypto.lwe import DiscreteGaussianPolynomialSampler, RingLWE sage: N = 16 sage: n = euler_phi(N) sage: D = DiscreteGaussianPolynomialSampler(n, 5) sage: ringlwe = RingLWE(N, 257, D, secret_dist='uniform') sage: ringlwe() ((228, 149, 226, 198, 38, 222, 222, 127), (177, 138, 68, 134, 74, 162, 203, 243)) """ if self.m is not None: if self.__i >= self.m: raise IndexError("Number of available samples exhausted.") self.__i += 1 a = self.R_q.random_element() return vector(a), vector(a * (self.__s) + self.D())
class LWE(SageObject): """ Learning with Errors (LWE) oracle. .. automethod:: __init__ .. automethod:: __call__ """ def __init__(self, n, q, D, secret_dist='uniform', m=None): r""" Construct an LWE oracle in dimension ``n`` over a ring of order ``q`` with noise distribution ``D``. INPUT: - ``n`` - dimension (integer > 0) - ``q`` - modulus typically > n (integer > 0) - ``D`` - an error distribution such as an instance of :class:`DiscreteGaussianDistributionIntegerSampler` or :class:`UniformSampler` - ``secret_dist`` - distribution of the secret (default: 'uniform'); one of - "uniform" - secret follows the uniform distribution in `\Zmod{q}` - "noise" - secret follows the noise distribution - ``(lb,ub)`` - the secret is chosen uniformly from ``[lb,...,ub]`` including both endpoints - ``m`` - number of allowed samples or ``None`` if no such limit exists (default: ``None``) EXAMPLES: First, we construct a noise distribution with standard deviation 3.0:: sage: from sage.stats.distributions.discrete_gaussian_integer import DiscreteGaussianDistributionIntegerSampler sage: D = DiscreteGaussianDistributionIntegerSampler(3.0) Next, we construct our oracle:: sage: from sage.crypto.lwe import LWE sage: lwe = LWE(n=20, q=next_prime(400), D=D); lwe LWE(20, 401, Discrete Gaussian sampler over the Integers with sigma = 3.000000 and c = 0, 'uniform', None) and sample 1000 samples:: sage: L = [lwe() for _ in range(1000)] To test the oracle, we use the internal secret to evaluate the samples in the secret:: sage: S = [ZZ(a.dot_product(lwe._LWE__s) - c) for (a,c) in L] However, while Sage represents finite field elements between 0 and q-1 we rely on a balanced representation of those elements here. Hence, we fix the representation and recover the correct standard deviation of the noise:: sage: sqrt(variance([e if e <= 200 else e-401 for e in S]).n()) 3.0... If ``m`` is not ``None`` the number of available samples is restricted:: sage: from sage.crypto.lwe import LWE sage: lwe = LWE(n=20, q=next_prime(400), D=D, m=30) sage: _ = [lwe() for _ in range(30)] sage: lwe() # 31 Traceback (most recent call last): ... IndexError: Number of available samples exhausted. """ self.n = ZZ(n) self.m = m self.__i = 0 self.K = IntegerModRing(q) self.FM = FreeModule(self.K, n) self.D = D self.secret_dist = secret_dist if secret_dist == 'uniform': self.__s = random_vector(self.K, self.n) elif secret_dist == 'noise': self.__s = vector(self.K, self.n, [self.D() for _ in range(n)]) else: try: lb, ub = map(ZZ, secret_dist) self.__s = vector(self.K, self.n, [randint(lb,ub) for _ in range(n)]) except (IndexError, TypeError): raise TypeError("Parameter secret_dist=%s not understood."%(secret_dist)) def _repr_(self): """ EXAMPLES:: sage: from sage.stats.distributions.discrete_gaussian_integer import DiscreteGaussianDistributionIntegerSampler sage: from sage.crypto.lwe import LWE sage: D = DiscreteGaussianDistributionIntegerSampler(3.0) sage: lwe = LWE(n=20, q=next_prime(400), D=D); lwe LWE(20, 401, Discrete Gaussian sampler over the Integers with sigma = 3.000000 and c = 0, 'uniform', None) sage: lwe = LWE(n=20, q=next_prime(400), D=D, secret_dist=(-3, 3)); lwe LWE(20, 401, Discrete Gaussian sampler over the Integers with sigma = 3.000000 and c = 0, (-3, 3), None) """ if isinstance(self.secret_dist, str): return "LWE(%d, %d, %s, '%s', %s)"%(self.n,self.K.order(),self.D,self.secret_dist, self.m) else: return "LWE(%d, %d, %s, %s, %s)"%(self.n,self.K.order(),self.D,self.secret_dist, self.m) def __call__(self): """ EXAMPLES:: sage: from sage.crypto.lwe import DiscreteGaussianDistributionIntegerSampler, LWE sage: LWE(10, 401, DiscreteGaussianDistributionIntegerSampler(3))() ((309, 347, 198, 194, 336, 360, 264, 123, 368, 398), 198) """ if self.m is not None: if self.__i >= self.m: raise IndexError("Number of available samples exhausted.") self.__i+=1 a = self.FM.random_element() return a, a.dot_product(self.__s) + self.K(self.D())
class LWE(SageObject): """ Learning with Errors (LWE) oracle. .. automethod:: __init__ .. automethod:: __call__ """ def __init__(self, n, q, D, secret_dist='uniform', m=None): """ Construct an LWE oracle in dimension ``n`` over a ring of order ``q`` with noise distribution ``D``. INPUT: - ``n`` - dimension (integer > 0) - ``q`` - modulus typically > n (integer > 0) - ``D`` - an error distribution such as an instance of :class:`DiscreteGaussianSamplerRejection` or :class:`UniformSampler` - ``secret_dist`` - distribution of the secret (default: 'uniform'); one of - "uniform" - secret follows the uniform distribution in `\Zmod{q}` - "noise" - secret follows the noise distribution - ``(lb,ub)`` - the secret is chosen uniformly from ``[lb,...,ub]`` including both endpoints - ``m`` - number of allowed samples or ``None`` if no such limit exists (default: ``None``) EXAMPLE: First, we construct a noise distribution with standard deviation 3.0:: sage: from sage.crypto.lwe import DiscreteGaussianSampler sage: D = DiscreteGaussianSampler(3.0) Next, we construct our oracle:: sage: from sage.crypto.lwe import LWE sage: lwe = LWE(n=20, q=next_prime(400), D=D); lwe LWE(20, 401, DiscreteGaussianSamplerRejection(3.000000, 53, 4), 'uniform', None) and sample 1000 samples:: sage: L = [lwe() for _ in range(1000)] To test the oracle, we use the internal secret to evaluate the samples in the secret:: sage: S = [ZZ(a.dot_product(lwe._LWE__s) - c) for (a,c) in L] However, while Sage represents finite field elements between 0 and q-1 we rely on a balanced representation of those elements here. Hence, we fix the representation and recover the correct standard deviation of the noise:: sage: sqrt(variance([e if e <= 200 else e-401 for e in S]).n()) 3.0... If ``m`` is not ``None`` the number of available samples is restricted:: sage: from sage.crypto.lwe import LWE sage: lwe = LWE(n=20, q=next_prime(400), D=D, m=30) sage: _ = [lwe() for _ in range(30)] sage: lwe() # 31 Traceback (most recent call last): ... IndexError: Number of available samples exhausted. """ self.n = ZZ(n) self.m = m self.__i = 0 self.K = IntegerModRing(q) self.FM = FreeModule(self.K, n) self.D = D self.secret_dist = secret_dist if secret_dist == 'uniform': self.__s = random_vector(self.K, self.n) elif secret_dist == 'noise': self.__s = vector(self.K, self.n, [self.D() for _ in range(n)]) else: try: lb, ub = map(ZZ, secret_dist) self.__s = vector(self.K, self.n, [randint(lb, ub) for _ in range(n)]) except (IndexError, TypeError): raise TypeError("Parameter secret_dist=%s not understood." % (secret_dist)) def _repr_(self): """ EXAMPLE:: sage: from sage.crypto.lwe import DiscreteGaussianSampler, LWE sage: D = DiscreteGaussianSampler(3.0) sage: lwe = LWE(n=20, q=next_prime(400), D=D); lwe LWE(20, 401, DiscreteGaussianSamplerRejection(3.000000, 53, 4), 'uniform', None) sage: lwe = LWE(n=20, q=next_prime(400), D=D, secret_dist=(-3, 3)); lwe LWE(20, 401, DiscreteGaussianSamplerRejection(3.000000, 53, 4), (-3, 3), None) """ if type(self.secret_dist) == str: return "LWE(%d, %d, %s, '%s', %s)" % ( self.n, self.K.order(), self.D, self.secret_dist, self.m) else: return "LWE(%d, %d, %s, %s, %s)" % (self.n, self.K.order(), self.D, self.secret_dist, self.m) def __call__(self): """ EXAMPLE:: sage: from sage.crypto.lwe import DiscreteGaussianSampler, LWE sage: LWE(10, 401, DiscreteGaussianSampler(3))() ((309, 347, 198, 194, 336, 360, 264, 123, 368, 398), 198) """ if self.m is not None: if self.__i >= self.m: raise IndexError("Number of available samples exhausted.") self.__i += 1 a = self.FM.random_element() return a, a.dot_product(self.__s) + self.K(self.D())