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 gamma_h_subgroups(self): r""" Return the subgroups of the form `\Gamma_H(N)` contained in self, where `N` is the level of self. EXAMPLES:: sage: G = Gamma0(11) sage: G.gamma_h_subgroups() [Congruence Subgroup Gamma0(11), Congruence Subgroup Gamma_H(11) with H generated by [4], Congruence Subgroup Gamma_H(11) with H generated by [10], Congruence Subgroup Gamma1(11)] sage: G = Gamma0(12) sage: G.gamma_h_subgroups() [Congruence Subgroup Gamma0(12), Congruence Subgroup Gamma_H(12) with H generated by [7], Congruence Subgroup Gamma_H(12) with H generated by [11], Congruence Subgroup Gamma_H(12) with H generated by [5], Congruence Subgroup Gamma1(12)] """ from all import GammaH N = self.level() R = IntegerModRing(N) return [GammaH(N, H) for H in R.multiplicative_subgroups()]
def gamma_h_subgroups(self): r""" Return the subgroups of the form `\Gamma_H(N)` contained in self, where `N` is the level of self. EXAMPLES:: sage: G = Gamma0(11) sage: G.gamma_h_subgroups() [Congruence Subgroup Gamma0(11), Congruence Subgroup Gamma_H(11) with H generated by [4], Congruence Subgroup Gamma_H(11) with H generated by [10], Congruence Subgroup Gamma1(11)] sage: G = Gamma0(12) sage: G.gamma_h_subgroups() [Congruence Subgroup Gamma0(12), Congruence Subgroup Gamma_H(12) with H generated by [7], Congruence Subgroup Gamma_H(12) with H generated by [11], Congruence Subgroup Gamma_H(12) with H generated by [5], Congruence Subgroup Gamma1(12)] """ from .all import GammaH N = self.level() R = IntegerModRing(N) return [GammaH(N, H) for H in R.multiplicative_subgroups()]
def teichmuller_type(self): r""" Return the Teichmuller type of this weight-character `\kappa`, which is the unique `t \in \ZZ/(p-1)\ZZ` such that `\kappa(\mu) = \mu^t` for \mu a `(p-1)`-st root of 1. For `p = 2` this doesn't make sense, but we still want the Teichmuller type to correspond to the index of the component of weight space in which `\kappa` lies, so we return 1 if `\kappa` is odd and 0 otherwise. EXAMPLES:: sage: pAdicWeightSpace(11)(2, DirichletGroup(11,QQ).0).teichmuller_type() 7 sage: pAdicWeightSpace(29)(13, DirichletGroup(29, Qp(29)).0).teichmuller_type() 14 sage: pAdicWeightSpace(2)(3, DirichletGroup(4,QQ).0).teichmuller_type() 0 """ # Special case p == 2 if self._p == 2: if self.is_even(): return IntegerModRing(2)(0) else: return IntegerModRing(2)(1) m = IntegerModRing(self._p).multiplicative_generator() x = [ y for y in IntegerModRing(self._chi.modulus()) if y == m and y**(self._p - 1) == 1 ] if len(x) != 1: raise ArithmeticError x = x[0] f = IntegerModRing(self._p)(self._chi(x)).log(m) return IntegerModRing(self._p - 1)(self._k + f)
def _generators_for_H(self): """ Return generators for the subgroup H of the units mod self.level() that defines self. EXAMPLES:: sage: Gamma0(15)._generators_for_H() [11, 7] """ if self.level() in [1, 2]: return [] return [ZZ(x) for x in IntegerModRing(self.level()).unit_gens()]
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 brown_invariant(self): r""" Return the Brown invariant of this torsion quadratic form. Let `(D,q)` be a torsion quadratic module with values in `\QQ / 2 \ZZ`. The Brown invariant `Br(D,q) \in \Zmod{8}` is defined by the equation .. MATH:: \exp \left( \frac{2 \pi i }{8} Br(q)\right) = \frac{1}{\sqrt{D}} \sum_{x \in D} \exp(i \pi q(x)). The Brown invariant is additive with respect to direct sums of torsion quadratic modules. OUTPUT: - an element of `\Zmod{8}` EXAMPLES:: sage: L = IntegralLattice("D4") sage: D = L.discriminant_group() sage: D.brown_invariant() 4 We require the quadratic form to be defined modulo `2 \ZZ`:: sage: from sage.modules.torsion_quadratic_module import TorsionQuadraticModule sage: V = FreeQuadraticModule(ZZ,3,matrix.identity(3)) sage: T = TorsionQuadraticModule((1/10)*V, V) sage: T.brown_invariant() Traceback (most recent call last): ... ValueError: the torsion quadratic form must have values in QQ / 2 ZZ """ if self._modulus_qf != 2: raise ValueError("the torsion quadratic form must have values in " "QQ / 2 ZZ") from sage.quadratic_forms.genera.normal_form import collect_small_blocks brown = IntegerModRing(8).zero() for p in self.annihilator().gen().prime_divisors(): q = self.primary_part(p).normal_form() q = q.gram_matrix_quadratic() L = collect_small_blocks(q) for qi in L: brown += _brown_indecomposable(qi, p) return brown
def CO_delta(r, p, N, eps): r""" This is used as an intermediate value in computations related to the paper of Cohen-Oesterle. INPUT: - ``r`` - positive integer - ``p`` - a prime - ``N`` - positive integer - ``eps`` - character OUTPUT: element of the base ring of the character EXAMPLES:: sage: G.<eps> = DirichletGroup(7) sage: sage.modular.dims.CO_delta(1,5,7,eps^3) 2 """ if not is_prime(p): raise ValueError, "p must be prime" K = eps.base_ring() if p % 4 == 3: return K(0) if p == 2: if r == 1: return K(1) return K(0) # interesting case: p=1(mod 4). # omega is a primitive 4th root of unity mod p. omega = (IntegerModRing(p).unit_gens()[0])**((p - 1) // 4) # this n is within a p-power root of a "local" 4th root of 1 modulo p. n = Mod(int(omega.crt(Mod(1, N // (p**r)))), N) n = n**(p**(r - 1)) # this is correct now t = eps(n) if t == K(1): return K(2) if t == K(-1): return K(-2) return K(0)
def CO_nu(r, p, N, eps): r""" This is used as an intermediate value in computations related to the paper of Cohen-Oesterle. INPUT: - ``r`` - positive integer - ``p`` - a prime - ``N`` - positive integer - ``eps`` - character OUTPUT: element of the base ring of the character EXAMPLES:: sage: G.<eps> = DirichletGroup(7) sage: G.<eps> = DirichletGroup(7) sage: sage.modular.dims.CO_nu(1,7,7,eps) -1 """ K = eps.base_ring() if p % 3 == 2: return K(0) if p == 3: if r == 1: return K(1) return K(0) # interesting case: p=1(mod 3) # omega is a cube root of 1 mod p. omega = (IntegerModRing(p).unit_gens()[0])**((p - 1) // 3) n = Mod(omega.crt(Mod(1, N // (p**r))), N) # within a p-power root of a "local" cube root of 1 mod p. n = n**(p**(r - 1)) # this is right now t = eps(n) if t == K(1): return K(2) return K(-1)
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 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): """ 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())
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())
def balance_sample(s, q=None): r""" Given ``(a,c) = s`` return a tuple ``(a',c')`` where ``a'`` is an integer vector with entries between -q//2 and q//2 and ``c`` is also within these bounds. If ``q`` is given ``(a,c) = s`` may live in the integers. If ``q`` is not given, then ``(a,c)`` are assumed to live in `\Zmod{q}`. INPUT: - ``s`` - sample of the form (a,c) where a is a vector and c is a scalar - ``q`` - modulus (default: ``None``) EXAMPLES:: sage: from sage.crypto.lwe import balance_sample, samples, Regev sage: [balance_sample(s) for s in samples(10, 5, Regev)] [((-9, -4, -4, 4, -4), 4), ((-8, 11, 12, -11, -11), -7), ... ((-11, 12, 0, -6, -3), 7), ((-7, 14, 8, 11, -8), -12)] sage: from sage.crypto.lwe import balance_sample, DiscreteGaussianDistributionPolynomialSampler, RingLWE, samples sage: D = DiscreteGaussianDistributionPolynomialSampler(ZZ['x'], 8, 5) sage: rlwe = RingLWE(20, 257, D) sage: [balance_sample(s) for s in samples(10, 8, rlwe)] [((-64, 107, -91, -24, 120, 54, 38, -35), (-84, 121, 28, -99, 91, 54, -60, 11)), ... ((-40, -117, 35, -69, -11, 10, 122, 48), (-80, -2, 119, -91, 27, 66, 121, -1))] .. note:: This function is useful to convert between Sage's standard representation of elements in `\Zmod{q}` as integers between 0 and q-1 and the usual representation of such elements in lattice cryptography as integers between -q//2 and q//2. """ a, c = s try: c[0] scalar = False except TypeError: c = vector(c.parent(), [c]) scalar = True if q is None: q = parent(c[0]).order() a = a.change_ring(ZZ) c = c.change_ring(ZZ) else: K = IntegerModRing(q) a = a.change_ring(K).change_ring(ZZ) c = c.change_ring(K).change_ring(ZZ) q2 = q // 2 if scalar: 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 else: 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])
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())
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 __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))
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())
def lfun_character(chi): """ Create the L-function of a primitive Dirichlet character. If the given character is not primitive, it is replaced by its associated primitive character. OUTPUT: one :pari:`lfun` object EXAMPLES:: sage: from sage.lfunctions.pari import lfun_character, LFunction sage: chi = DirichletGroup(6).gen().primitive_character() sage: L = LFunction(lfun_character(chi)) sage: L(3) 1.20205690315959 TESTS: A non-primitive one:: sage: L = LFunction(lfun_character(DirichletGroup(6).gen())) sage: L(4) 1.08232323371114 With complex arguments:: sage: from sage.lfunctions.pari import lfun_character, LFunction sage: chi = DirichletGroup(6, CC).gen().primitive_character() sage: L = LFunction(lfun_character(chi)) sage: L(3) 1.20205690315959 """ if not chi.is_primitive(): chi = chi.primitive_character() conductor = chi.conductor() G = pari.znstar(conductor, 1) pari_orders = [pari(o) for o in G[2][1]] pari_gens = IntegerModRing(conductor).unit_gens(algorithm="pari") # should coincide with G[2][2] values_on_gens = (chi(x) for x in pari_gens) # now compute the input for pari (list of exponents) P = chi.parent() if is_ComplexField(P.base_ring()): zeta = P.zeta() zeta_argument = zeta.argument() v = [int(x.argument() / zeta_argument) for x in values_on_gens] else: dlog = P._zeta_dlog v = [dlog[x] for x in values_on_gens] m = P.zeta_order() v = [(vi * oi) // m for vi, oi in zip(v, pari_orders)] return pari.lfuncreate([G, v])
def balance_sample(s, q=None): r""" Given ``(a,c) = s`` return a tuple ``(a',c')`` where ``a'`` is an integer vector with entries between -q//2 and q//2 and ``c`` is also within these bounds. If ``q`` is given ``(a,c) = s`` may live in the integers. If ``q`` is not given, then ``(a,c)`` are assumed to live in `\Zmod{q}`. INPUT: - ``s`` - sample of the form (a,c) where a is a vector and c is a scalar - ``q`` - modulus (default: ``None``) EXAMPLE:: sage: from sage.crypto.lwe import balance_sample, samples, Regev sage: map(balance_sample, samples(10, 5, Regev)) [((-9, -4, -4, 4, -4), 6), ((-3, -10, 8, -3, -1), -10), ((-6, -12, -3, -2, -6), -6), ... ((-1, -8, -11, 13, 4), -6), ((10, 11, -3, -13, 0), 6), ((6, -1, 2, -11, 14), 2)] sage: from sage.crypto.lwe import balance_sample, DiscreteGaussianPolynomialSampler, RingLWE, samples sage: D = DiscreteGaussianPolynomialSampler(8, 5) sage: rlwe = RingLWE(20, 257, D) sage: map(balance_sample, samples(10, 8, rlwe)) [((5, -55, -31, -90, 6, 100, -46, -107), (6, -64, -40, 117, 27, 54, -98, -56)), ((109, -106, 28, 77, -14, -109, 115, 34), (82, 17, -89, 62, 1, -77, 128, 64)), ... ((-32, 51, -110, -106, 35, -82, 14, -113), (126, -120, 126, 119, 101, 3, -122, -75))] .. note:: This function is useful to convert between Sage's standard representation of elements in `\Zmod{q}` as integers between 0 and q-1 and the usual representation of such elements in lattice cryptography as integers between -q//2 and q//2. """ a, c = s try: c[0] scalar = False except TypeError: c = vector(c.parent(), [c]) scalar = True if q is None: q = parent(c[0]).order() a = a.change_ring(ZZ) c = c.change_ring(ZZ) else: K = IntegerModRing(q) a = a.change_ring(K).change_ring(ZZ) c = c.change_ring(K).change_ring(ZZ) q2 = q // 2 if scalar: 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 else: 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])
def balance_sample(s, q=None): r""" Given ``(a,c) = s`` return a tuple ``(a',c')`` where ``a'`` is an integer vector with entries between -q//2 and q//2 and ``c`` is also within these bounds. If ``q`` is given ``(a,c) = s`` may live in the integers. If ``q`` is not given, then ``(a,c)`` are assumed to live in `\Zmod{q}`. INPUT: - ``s`` - sample of the form (a,c) where a is a vector and c is a scalar - ``q`` - modulus (default: ``None``) EXAMPLES:: sage: from sage.crypto.lwe import balance_sample, samples, Regev sage: for s in samples(10, 5, Regev): ....: b = balance_sample(s) ....: assert all(-29//2 <= c <= 29//2 for c in b[0]) ....: assert -29//2 <= b[1] <= 29//2 ....: assert all(s[0][j] == b[0][j] % 29 for j in range(5)) ....: assert s[1] == b[1] % 29 sage: from sage.crypto.lwe import balance_sample, DiscreteGaussianDistributionPolynomialSampler, RingLWE, samples sage: D = DiscreteGaussianDistributionPolynomialSampler(ZZ['x'], 8, 5) sage: rlwe = RingLWE(20, 257, D) sage: for s in samples(10, 8, rlwe): ....: b = balance_sample(s) ....: assert all(-257//2 <= c <= 257//2 for bi in b for c in bi) ....: assert all(s[i][j] == b[i][j] % 257 for i in range(2) for j in range(8)) .. note:: This function is useful to convert between Sage's standard representation of elements in `\Zmod{q}` as integers between 0 and q-1 and the usual representation of such elements in lattice cryptography as integers between -q//2 and q//2. """ a, c = s try: c[0] scalar = False except TypeError: c = vector(c.parent(), [c]) scalar = True if q is None: q = parent(c[0]).order() a = a.change_ring(ZZ) c = c.change_ring(ZZ) else: K = IntegerModRing(q) a = a.change_ring(K).change_ring(ZZ) c = c.change_ring(K).change_ring(ZZ) q2 = q // 2 if scalar: 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 else: 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])