(Ring-)LWE oracle generators¶
The Learning with Errors problem (LWE) is solving linear systems of equations where the right hand side has been disturbed ‘slightly’ where ‘slightly’ is made precise by a noise distribution - typically a discrete Gaussian distribution. See [Reg09] for details.
The Ring Learning with Errors problem (LWE) is solving a set of univariate polynomial equations - typically in a cyclotomic field - where the right hand side was disturbed ‘slightly’. See [LPR2010] for details.
This module implements generators of LWE samples where parameters are chosen following proposals in the cryptographic literature.
EXAMPLES:
We get 30 samples from an LWE oracle parameterised by security parameter
n=20
and where the modulus and the standard deviation of the noise are
chosen as in [Reg09]:
sage: from sage.crypto.lwe import samples
sage: samples(30, 20, 'Regev')
[((360, 264, 123, 368, 398, 392, 41, 84, 25, 389, 311, 68, 322, 41, 161, 372, 222, 153, 243, 381), 122),
...
((155, 22, 357, 312, 87, 298, 182, 163, 296, 181, 219, 135, 164, 308, 248, 320, 64, 166, 214, 104), 152)]
We may also pass classes to the samples function, which is useful for users implementing their own oracles:
sage: from sage.crypto.lwe import samples, LindnerPeikert
sage: samples(30, 20, LindnerPeikert)
[((1275, 168, 1529, 2024, 1874, 1309, 16, 1869, 1114, 1696, 1645, 618, 1372, 1273, 683, 237, 1526, 879, 1305, 1355), 950),
...
((1787, 2033, 1677, 331, 1562, 49, 796, 1002, 627, 98, 91, 711, 1712, 418, 2024, 163, 1773, 184, 1548, 3), 1815)]
Finally, samples()
also accepts instances of classes:
sage: from sage.crypto.lwe import LindnerPeikert
sage: lwe = LindnerPeikert(20)
sage: samples(30, 20, lwe)
[((465, 180, 440, 706, 1367, 106, 1380, 614, 1162, 1354, 1098, 2036, 1974, 1417, 1502, 1431, 863, 1894, 1368, 1771), 618),
...
((1050, 1017, 1314, 1310, 1941, 2041, 484, 104, 1199, 1744, 161, 1905, 679, 1663, 531, 1630, 168, 1559, 1040, 1719), 1006)]
Note that Ring-LWE samples are returned as vectors:
sage: from sage.crypto.lwe import RingLWE
sage: from sage.stats.distributions.discrete_gaussian_polynomial import DiscreteGaussianDistributionPolynomialSampler
sage: D = DiscreteGaussianDistributionPolynomialSampler(ZZ['x'], euler_phi(16), 5)
sage: ringlwe = RingLWE(16, 257, D, secret_dist='uniform')
sage: samples(30, euler_phi(16), ringlwe)
[((232, 79, 223, 85, 26, 68, 60, 72), (72, 158, 117, 166, 140, 103, 142, 223)),
...
((27, 191, 241, 179, 246, 204, 36, 72), (207, 158, 127, 240, 225, 141, 156, 201))]
One technical issue when working with these generators is that by default they return vectors and scalars over/in rings modulo some \(q\). These are represented as elements in \((0,q-1)\) by Sage. However, it usually is more natural to think of these entries as integers in \((-q//2,q//2)\). To allow for this, this module provides the option to balance the representation. In this case vectors and scalars over/in the integers are returned:
sage: from sage.crypto.lwe import samples
sage: samples(30, 20, 'Regev', balanced=True)
[((-46, -84, 21, -72, -47, -162, -40, -31, -9, -131, 74, 183, 62, -83, -135, 164, -33, -109, -127, -124), 96),
...
((-48, 185, 118, 69, 57, 109, 109, 138, -42, -45, -16, 180, 34, 178, 20, -119, -58, -136, -46, 169), -72)]
AUTHORS:
- Martin Albrecht
- Robert Fitzpatrick
- Daniel Cabracas
- Florian Göpfert
- Michael Schneider
REFERENCES:
-
class
sage.crypto.lwe.
LWE
(n, q, D, secret_dist='uniform', m=None)¶ Bases:
sage.structure.sage_object.SageObject
Learning with Errors (LWE) oracle.
-
__init__
(n, q, D, secret_dist='uniform', m=None)¶ Construct an LWE oracle in dimension
n
over a ring of orderq
with noise distributionD
.INPUT:
n
- dimension (integer > 0)q
- modulus typically > n (integer > 0)D
- an error distribution such as an instance ofDiscreteGaussianDistributionIntegerSampler
orUniformSampler
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 orNone
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 notNone
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.
-
__call__
()¶ 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)
-
-
class
sage.crypto.lwe.
LindnerPeikert
(n, delta=0.01, m=None)¶ Bases:
sage.crypto.lwe.LWE
LWE oracle with parameters as in [LP2011].
-
__init__
(n, delta=0.01, m=None)¶ Construct LWE instance parameterised by security parameter
n
where the modulusq
and thestddev
of the noise is chosen as in [LP2011].INPUT:
n
- security parameter (integer > 0)delta
- error probability per symbol (default: 0.01)m
- number of allowed samples orNone
in which casem=2*n + 128
as in [LP2011] (default:None
)
EXAMPLES:
sage: from sage.crypto.lwe import LindnerPeikert sage: LindnerPeikert(n=20) LWE(20, 2053, Discrete Gaussian sampler over the Integers with sigma = 3.600954 and c = 0, 'noise', 168)
-
-
class
sage.crypto.lwe.
Regev
(n, secret_dist='uniform', m=None)¶ Bases:
sage.crypto.lwe.LWE
LWE oracle with parameters as in [Reg09].
-
__init__
(n, secret_dist='uniform', m=None)¶ Construct LWE instance parameterised by security parameter
n
where the modulusq
and thestddev
of the noise are chosen as in [Reg09].INPUT:
n
- security parameter (integer > 0)secret_dist
- distribution of the secret. See documentation ofLWE
for details (default=’uniform’)m
- number of allowed samples orNone
if no such limit exists (default:None
)
EXAMPLES:
sage: from sage.crypto.lwe import Regev sage: Regev(n=20) LWE(20, 401, Discrete Gaussian sampler over the Integers with sigma = 1.915069 and c = 401, 'uniform', None)
-
-
class
sage.crypto.lwe.
RingLWE
(N, q, D, poly=None, secret_dist='uniform', m=None)¶ Bases:
sage.structure.sage_object.SageObject
Ring Learning with Errors oracle.
-
__init__
(N, q, D, poly=None, secret_dist='uniform', m=None)¶ Construct a Ring-LWE oracle in dimension
n=phi(N)
over a ring of orderq
with noise distributionD
.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 ofDiscreteGaussianDistributionPolynomialSampler
orUniformSampler
poly
- a polynomial of degreephi(N)
. IfNone
the cyclotomic polynomial used (default:None
).secret_dist
- distribution of the secret. See documentation ofLWE
for details (default=’uniform’)m
- number of allowed samples orNone
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)
-
__call__
()¶ 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))
-
-
class
sage.crypto.lwe.
RingLWEConverter
(ringlwe)¶ Bases:
sage.structure.sage_object.SageObject
Wrapper callable to convert Ring-LWE oracles into LWE oracles by disregarding the additional structure.
-
__init__
(ringlwe)¶ INPUT:
ringlwe
- an instance of aRingLWE
EXAMPLES:
sage: from sage.crypto.lwe import DiscreteGaussianDistributionPolynomialSampler, RingLWE, RingLWEConverter sage: D = DiscreteGaussianDistributionPolynomialSampler(ZZ['x'], euler_phi(16), 5) sage: lwe = RingLWEConverter(RingLWE(16, 257, D, secret_dist='uniform')) sage: set_random_seed(1337) sage: lwe() ((32, 216, 3, 125, 58, 197, 171, 43), 81)
-
__call__
()¶ EXAMPLES:
sage: from sage.crypto.lwe import DiscreteGaussianDistributionPolynomialSampler, RingLWE, RingLWEConverter sage: D = DiscreteGaussianDistributionPolynomialSampler(ZZ['x'], euler_phi(16), 5) sage: lwe = RingLWEConverter(RingLWE(16, 257, D, secret_dist='uniform')) sage: set_random_seed(1337) sage: lwe() ((32, 216, 3, 125, 58, 197, 171, 43), 81)
-
-
class
sage.crypto.lwe.
RingLindnerPeikert
(N, delta=0.01, m=None)¶ Bases:
sage.crypto.lwe.RingLWE
Ring-LWE oracle with parameters as in [LP2011].
-
__init__
(N, delta=0.01, m=None)¶ Construct a Ring-LWE oracle in dimension
n=phi(N)
where the modulusq
and thestddev
of the noise is chosen as in [LP2011].INPUT:
N
- index of cyclotomic polynomial (integer > 0, must be power of 2)delta
- error probability per symbol (default: 0.01)m
- number of allowed samples orNone
in which case3*n
is used (default:None
)
EXAMPLES:
sage: from sage.crypto.lwe import RingLindnerPeikert sage: RingLindnerPeikert(N=16) RingLWE(16, 1031, Discrete Gaussian sampler for polynomials of degree < 8 with σ=2.803372 in each component, x^8 + 1, 'noise', 24)
-
-
class
sage.crypto.lwe.
UniformNoiseLWE
(n, instance='key', m=None)¶ Bases:
sage.crypto.lwe.LWE
LWE oracle with uniform secret with parameters as in [CGW2013].
-
__init__
(n, instance='key', m=None)¶ Construct LWE instance parameterised by security parameter
n
where all other parameters are chosen as in [CGW2013].INPUT:
n
- security parameter (integer >= 89)instance
- one of- “key” - the LWE-instance that hides the secret key is generated
- “encrypt” - the LWE-instance that hides the message is generated
(default:
key
)
m
- number of allowed samples orNone
in which casem
is chosen as in [CGW2013]. (default:None
)
EXAMPLES:
sage: from sage.crypto.lwe import UniformNoiseLWE sage: UniformNoiseLWE(89) LWE(89, 64311834871, UniformSampler(0, 6577), 'noise', 131) sage: UniformNoiseLWE(89, instance='encrypt') LWE(131, 64311834871, UniformSampler(0, 11109), 'noise', 181)
-
-
class
sage.crypto.lwe.
UniformPolynomialSampler
(P, n, lower_bound, upper_bound)¶ Bases:
sage.structure.sage_object.SageObject
Uniform sampler for polynomials.
EXAMPLES:
sage: from sage.crypto.lwe import UniformPolynomialSampler sage: UniformPolynomialSampler(ZZ['x'], 8, -2, 2)() -2*x^7 + x^6 - 2*x^5 - x^3 - 2*x^2 - 2
-
__init__
(P, n, lower_bound, upper_bound)¶ Construct a sampler for univariate polynomials of degree
n-1
where coefficients are drawn uniformly at random betweenlower_bound
andupper_bound
(both endpoints inclusive).INPUT:
P
- a univariate polynomial ring over the Integersn
- number of coefficients to be sampledlower_bound
- integerupper_bound
- integer
EXAMPLES:
sage: from sage.crypto.lwe import UniformPolynomialSampler sage: UniformPolynomialSampler(ZZ['x'], 10, -10, 10) UniformPolynomialSampler(10, -10, 10)
-
__call__
()¶ Return a new sample.
EXAMPLES:
sage: from sage.crypto.lwe import UniformPolynomialSampler sage: sampler = UniformPolynomialSampler(ZZ['x'], 8, -12, 12) sage: sampler() -10*x^7 + 5*x^6 - 8*x^5 + x^4 - 4*x^3 - 11*x^2 - 10
-
-
class
sage.crypto.lwe.
UniformSampler
(lower_bound, upper_bound)¶ Bases:
sage.structure.sage_object.SageObject
Uniform sampling in a range of integers.
EXAMPLES:
sage: from sage.crypto.lwe import UniformSampler sage: sampler = UniformSampler(-2, 2); sampler UniformSampler(-2, 2) sage: sampler() -2
-
__init__
(lower_bound, upper_bound)¶ Construct a uniform sampler with bounds
lower_bound
andupper_bound
(both endpoints inclusive).INPUT:
lower_bound
- integerupper_bound
- integer
EXAMPLES:
sage: from sage.crypto.lwe import UniformSampler sage: UniformSampler(-2, 2) UniformSampler(-2, 2)
-
__call__
()¶ Return a new sample.
EXAMPLES:
sage: from sage.crypto.lwe import UniformSampler sage: sampler = UniformSampler(-12, 12) sage: sampler() -10
-
-
sage.crypto.lwe.
balance_sample
(s, q=None)¶ Given
(a,c) = s
return a tuple(a',c')
wherea'
is an integer vector with entries between -q//2 and q//2 andc
is also within these bounds.If
q
is given(a,c) = s
may live in the integers. Ifq
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 scalarq
- 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.
-
sage.crypto.lwe.
samples
(m, n, lwe, seed=None, balanced=False, **kwds)¶ Return
m
LWE samples.INPUT:
m
- the number of samples (integer > 0)n
- the security parameter (integer > 0)lwe
- either- a subclass of
LWE
such asRegev
orLindnerPeikert
- an instance of
LWE
or any subclass - the name of any such class (e.g., “Regev”, “LindnerPeikert”)
- a subclass of
seed
- seed to be used for generation orNone
if no specific seed shall be set (default:None
)balanced
- use functionbalance_sample()
to return balanced representations of finite field elements (default:False
)**kwds
- passed through to LWE constructor
EXAMPLES:
sage: from sage.crypto.lwe import samples, Regev sage: samples(2, 20, Regev, seed=1337) [((199, 388, 337, 53, 200, 284, 336, 215, 75, 14, 274, 234, 97, 255, 246, 153, 268, 218, 396, 351), 15), ((365, 227, 333, 165, 76, 328, 288, 206, 286, 42, 175, 155, 190, 275, 114, 280, 45, 218, 304, 386), 143)] sage: from sage.crypto.lwe import samples, Regev sage: samples(2, 20, Regev, balanced=True, seed=1337) [((199, -13, -64, 53, 200, -117, -65, -186, 75, 14, -127, -167, 97, -146, -155, 153, -133, -183, -5, -50), 15), ((-36, -174, -68, 165, 76, -73, -113, -195, -115, 42, 175, 155, 190, -126, 114, -121, 45, -183, -97, -15), 143)] sage: from sage.crypto.lwe import samples sage: samples(2, 20, 'LindnerPeikert') [((506, 1205, 398, 0, 337, 106, 836, 75, 1242, 642, 840, 262, 1823, 1798, 1831, 1658, 1084, 915, 1994, 163), 1447), ((463, 250, 1226, 1906, 330, 933, 1014, 1061, 1322, 2035, 1849, 285, 1993, 1975, 864, 1341, 41, 1955, 1818, 1357), 312)]