transcrypto 1.0.2__py3-none-any.whl → 1.1.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
transcrypto/modmath.py ADDED
@@ -0,0 +1,535 @@
1
+ #!/usr/bin/env python3
2
+ #
3
+ # Copyright 2025 Daniel Balparda (balparda@github.com) - Apache-2.0 license
4
+ #
5
+ """Balparda's TransCrypto modular math library."""
6
+
7
+ from __future__ import annotations
8
+
9
+ import math
10
+ # import pdb
11
+ from typing import Generator, Reversible
12
+
13
+ from . import base
14
+
15
+ __author__ = 'balparda@github.com'
16
+ __version__: str = base.__version__ # version comes from base!
17
+ __version_tuple__: tuple[int, ...] = base.__version_tuple__
18
+
19
+
20
+ _FIRST_60_PRIMES: set[int] = {
21
+ 2, 3, 5, 7, 11, 13, 17, 19, 23, 29,
22
+ 31, 37, 41, 43, 47, 53, 59, 61, 67, 71,
23
+ 73, 79, 83, 89, 97, 101, 103, 107, 109, 113,
24
+ 127, 131, 137, 139, 149, 151, 157, 163, 167, 173,
25
+ 179, 181, 191, 193, 197, 199, 211, 223, 227, 229,
26
+ 233, 239, 241, 251, 257, 263, 269, 271, 277, 281,
27
+ }
28
+ _FIRST_60_PRIMES_SORTED: list[int] = sorted(_FIRST_60_PRIMES)
29
+ _COMPOSITE_60: int = math.prod(_FIRST_60_PRIMES_SORTED)
30
+ _PRIME_60: int = _FIRST_60_PRIMES_SORTED[-1]
31
+ assert len(_FIRST_60_PRIMES) == 60 and _PRIME_60 == 281, f'should never happen: {_PRIME_60=}'
32
+ _FIRST_49_MERSENNE: set[int] = { # <https://oeis.org/A000043>
33
+ 2, 3, 5, 7, 13, 17, 19, 31, 61, 89,
34
+ 107, 127, 521, 607, 1279, 2203, 2281, 3217, 4253, 4423,
35
+ 9689, 9941, 11213, 19937, 21701, 23209, 44497, 86243, 110503, 132049,
36
+ 216091, 756839, 859433, 1257787, 1398269, 2976221, 3021377, 6972593, 13466917, 20996011,
37
+ 24036583, 25964951, 30402457, 32582657, 37156667, 42643801, 43112609, 57885161, 74207281,
38
+ }
39
+ _FIRST_49_MERSENNE_SORTED: list[int] = sorted(_FIRST_49_MERSENNE)
40
+ assert len(_FIRST_49_MERSENNE) == 49 and _FIRST_49_MERSENNE_SORTED[-1] == 74207281, f'should never happen: {_FIRST_49_MERSENNE_SORTED[-1]}'
41
+
42
+ _MAX_PRIMALITY_SAFETY = 100 # this is an absurd number, just to have a max
43
+
44
+
45
+ class ModularDivideError(base.Error):
46
+ """Divide-by-zero-like exception (TransCrypto)."""
47
+
48
+
49
+ def ModInv(x: int, m: int, /) -> int:
50
+ """Modular inverse of `x` mod `m`: a `y` such that (x * y) % m == 1 if GCD(x, m) == 1.
51
+
52
+ Args:
53
+ x (int): integer to invert
54
+ m (int): modulus, m ≥ 2
55
+
56
+ Returns:
57
+ positive integer `y` such that (x * y) % m == 1
58
+ this only exists if GCD(x, m) == 1, so to guarantee an inverse `m` must be prime
59
+
60
+ Raises:
61
+ InputError: invalid modulus or x
62
+ ModularDivideError: divide-by-zero, i.e., GCD(x, m) != 1 or x == 0
63
+ """
64
+ # test inputs
65
+ if m < 2:
66
+ raise base.InputError(f'invalid modulus: {m=}')
67
+ # easy special cases: 0 and 1
68
+ reduced_x: int = x % m
69
+ if not reduced_x: # "division by 0"
70
+ raise ModularDivideError(f'null inverse {x=} mod {m=}')
71
+ if reduced_x == 1: # trivial degenerate case
72
+ return 1
73
+ # compute actual extended GCD and see if we will have an inverse
74
+ gcd, y, w = base.ExtendedGCD(reduced_x, m)
75
+ if gcd != 1:
76
+ raise ModularDivideError(f'invalid inverse {x=} mod {m=} with {gcd=}')
77
+ assert y and w and y >= -m, f'should never happen: {x=} mod {m=} -> {w=} ; {y=}'
78
+ return y if y >= 0 else (y + m)
79
+
80
+
81
+ def ModDiv(x: int, y: int, m: int, /) -> int:
82
+ """Modular division of `x`/`y` mod `m`, if GCD(y, m) == 1.
83
+
84
+ Args:
85
+ x (int): integer
86
+ y (int): integer
87
+ m (int): modulus, m ≥ 2
88
+
89
+ Returns:
90
+ positive integer `z` such that (z * y) % m == x
91
+ this only exists if GCD(y, m) == 1, so to guarantee an inverse `m` must be prime
92
+
93
+ Raises:
94
+ InputError: invalid modulus or x or y
95
+ ModularDivideError: divide-by-zero, i.e., GCD(y, m) != 1 or y == 0
96
+ """
97
+ # test inputs
98
+ if m < 2:
99
+ raise base.InputError(f'invalid modulus: {m=}')
100
+ if not y: # "division by 0"
101
+ raise ModularDivideError(f'divide by zero {x=} / {y=} mod {m=}')
102
+ # do the math
103
+ if not x:
104
+ return 0
105
+ return ((x % m) * ModInv(y % m, m)) % m
106
+
107
+
108
+ def CRTPair(a1: int, m1: int, a2: int, m2: int) -> int:
109
+ """Chinese Remainder Theorem Pair: given co-prime `m1`/`m2`, solve a1 = x % m1 and a2 = x % m2.
110
+
111
+ <https://en.wikipedia.org/wiki/Chinese_remainder_theorem>
112
+
113
+ Finds the unique integer x in [0, m1 * m2) satisfying
114
+
115
+ x ≡ a1 (mod m1)
116
+ x ≡ a2 (mod m2)
117
+
118
+ The solution is guaranteed to exist and be unique because the moduli are assumed to
119
+ be positive, ≥ 2, and pairwise co-prime, gcd(m1, m2) == 1.
120
+
121
+ Args:
122
+ a1 (int): residue for the first congruence
123
+ m1 (int): modulus 1, m ≥ 2 and co-prime with m2, i.e. gcd(m1, m2) == 1
124
+ a2 (int): residue for the second congruence
125
+ m2 (int): modulus 2, m ≥ 2 and co-prime with m1, i.e. gcd(m1, m2) == 1
126
+
127
+ Returns:
128
+ the least non-negative solution `x` such that a1 = x % m1 and a2 = x % m2 and 0 ≤ x < m1 * m2
129
+
130
+ Raises:
131
+ InputError: invalid inputs
132
+ ModularDivideError: moduli are not co-prime, i.e. gcd(m1, m2) != 1
133
+ """
134
+ # test inputs
135
+ if m1 < 2 or m2 < 2 or m1 == m2:
136
+ raise base.InputError(f'invalid moduli: {m1=} / {m2=}')
137
+ # compute
138
+ a1 %= m1
139
+ a2 %= m2
140
+ try:
141
+ n1: int = ModInv(m1, m2)
142
+ n2: int = ModInv(m2, m1)
143
+ except ModularDivideError as err:
144
+ raise ModularDivideError(f'moduli not co-prime: {m1=} / {m2=}') from err
145
+ return (a1 * m2 * n2 + a2 * m1 * n1) % (m1 * m2)
146
+
147
+
148
+ def ModExp(x: int, y: int, m: int, /) -> int:
149
+ """Modular exponential: returns (x ** y) % m efficiently (can handle huge values).
150
+
151
+ 0 ** 0 mod m = 1 (by convention)
152
+
153
+ Args:
154
+ x (int): integer
155
+ y (int): integer, y ≥ 0
156
+ m (int): modulus, m ≥ 2
157
+
158
+ Returns:
159
+ (x ** y) mod m
160
+
161
+ Raises:
162
+ InputError: invalid inputs
163
+ """
164
+ # test inputs
165
+ if m < 2:
166
+ raise base.InputError(f'invalid modulus: {m=}')
167
+ if y < 0:
168
+ raise base.InputError(f'negative exponent: {y=}')
169
+ # trivial cases
170
+ x %= m
171
+ if not y or x == 1:
172
+ return 1 % m
173
+ if not x:
174
+ return 0 # 0**0==1 was already taken care of by previous condition
175
+ if y == 1:
176
+ return x
177
+ # now both x > 1 and y > 1
178
+ z: int = 1
179
+ while y:
180
+ y, odd = divmod(y, 2)
181
+ if odd:
182
+ z = (z * x) % m
183
+ x = (x * x) % m
184
+ return z
185
+
186
+
187
+ def ModPolynomial(x: int, polynomial: Reversible[int], m: int, /) -> int:
188
+ """Evaluates `polynomial` (coefficients iterable) at `x` modulus `m`.
189
+
190
+ Evaluate a polynomial at `x` under a modulus `m` using Horner's rule. Horner rewrites:
191
+ a_0 + a_1 x + a_2 x^2 + … + a_n x^n
192
+ = (…((a_n x + a_{n-1}) x + a_{n-2}) … ) x + a_0
193
+ This uses exactly n multiplies and n adds, and lets us take `% m` at each
194
+ step so intermediate numbers never explode.
195
+
196
+ Args:
197
+ x (int): The evaluation point
198
+ polynomial (Reversible[int]): Iterable of coefficients a_0, a_1, …, a_n
199
+ (constant term first); it must be reversible because Horner's rule consumes
200
+ coefficients from highest degree downwards
201
+ m (int): modulus, m ≥ 2; if you expect multiplicative inverses elsewhere it should be prime
202
+
203
+ Returns:
204
+ f(x) mod m
205
+
206
+ Raises:
207
+ InputError: invalid inputs
208
+ """
209
+ # test inputs
210
+ if not polynomial:
211
+ raise base.InputError(f'no polynomial: {polynomial=}')
212
+ if m < 2:
213
+ raise base.InputError(f'invalid modulus: {m=}')
214
+ # loop over polynomial coefficients
215
+ total: int = 0
216
+ x %= m # takes care of negative numbers and also x >= m
217
+ for coefficient in reversed(polynomial):
218
+ total = (total * x + coefficient) % m
219
+ return total
220
+
221
+
222
+ def ModLagrangeInterpolate(x: int, points: dict[int, int], m: int, /) -> int:
223
+ """Find the f(x) solution for the given `x` and {x: y} `points` modulus prime `m`.
224
+
225
+ Given `points` will define a polynomial of up to len(points) order.
226
+ Evaluate (interpolate) the unique polynomial of degree ≤ (n-1) that passes
227
+ through the given points (x_i, y_i), and return f(x) mod a prime `m`.
228
+
229
+ Lagrange interpolation writes the polynomial as:
230
+ f(X) = Σ_{i=0}^{n-1} y_i * L_i(X)
231
+ where
232
+ L_i(X) = Π_{j≠i} (X - x_j) / (x_i - x_j)
233
+ are the Lagrange basis polynomials. Each L_i(x_i) = 1 and L_i(x_j)=0 for j≠i,
234
+ so f matches every supplied point.
235
+
236
+ In modular arithmetic we replace division by multiplication with modular
237
+ inverses. Because `m` is prime (or at least co-prime with every denominator),
238
+ every (x_i - x_j) has an inverse `mod m`.
239
+
240
+ Args:
241
+ x (int): The x-value at which to evaluate the interpolated polynomial
242
+ points (dict[int, int]): A mapping {x_i: y_i}, with at least 2 points/entries;
243
+ dict keeps x_i distinct, as they should be; also, `x` cannot be a key to `points`
244
+ m (int): prime modulus, m ≥ 2; we need modular inverses, so gcd(denominator, m) must be 1
245
+
246
+ Returns:
247
+ y-value solution for f(x) mod m given `points` mapping
248
+
249
+ Raises:
250
+ InputError: invalid inputs
251
+ """
252
+ # test inputs
253
+ if m < 2:
254
+ raise base.InputError(f'invalid modulus: {m=}')
255
+ x %= m # takes care of negative numbers and also x >= m
256
+ reduced_points: dict[int, int] = {k % m: v % m for k, v in points.items()}
257
+ if len(points) < 2 or len(reduced_points) != len(points) or x in reduced_points:
258
+ raise base.InputError(f'invalid points or duplicate x/x_i found: {x=} / {points=}')
259
+ # compute everything term-by-term
260
+ result: int = 0
261
+ for xi, yi in reduced_points.items():
262
+ # build numerator and denominator of L_i(x)
263
+ num: int = 1 # Π (x - x_j)
264
+ den: int = 1 # Π (xi - x_j)
265
+ for xj in reduced_points:
266
+ if xj == xi:
267
+ continue
268
+ num = (num * (x - xj)) % m
269
+ den = (den * (xi - xj)) % m
270
+ # add to the result: (y_i * L_i(x)) = (y_i * num / den)
271
+ result = (result + ModDiv(yi * num, den, m)) % m
272
+ # done
273
+ return result
274
+
275
+
276
+ def FermatIsPrime(n: int, /, *, safety: int = 10, witnesses: set[int] | None = None) -> bool:
277
+ """Primality test of `n` by Fermat's algo (n > 0). DO NOT RELY!
278
+
279
+ Will execute Fermat's algo for non-trivial `n` (n > 3 and odd).
280
+ <https://en.wikipedia.org/wiki/Fermat_primality_test>
281
+
282
+ This is for didactical uses only, as it is reasonably easy for this algo to fail
283
+ on simple cases. For example, 8911 will fail for many sets of 10 random witnesses.
284
+ (See <https://en.wikipedia.org/wiki/Carmichael_number> to understand better.)
285
+ Miller-Rabin below (MillerRabinIsPrime) has been tuned to be VERY reliable by default.
286
+
287
+ Args:
288
+ n (int): Number to test primality
289
+ safety (int, optional): Maximum witnesses to use (only if witnesses is not given)
290
+ witnesses (set[int], optional): If given will use exactly these witnesses, in order
291
+
292
+ Returns:
293
+ False if certainly not prime ; True if (probabilistically) prime
294
+
295
+ Raises:
296
+ InputError: invalid inputs
297
+ """
298
+ # test inputs and test for trivial cases: 1, 2, 3, divisible by 2
299
+ if n < 1:
300
+ raise base.InputError(f'invalid number: {n=}')
301
+ if n in (2, 3):
302
+ return True
303
+ if n == 1 or not n % 2:
304
+ return False
305
+ # n is odd and >= 5 so now we generate witnesses (if needed)
306
+ # degenerate case is: n==5, max_safety==2 => randint(2, 3) => {2, 3}
307
+ if not witnesses:
308
+ max_safety: int = min(n // 2, _MAX_PRIMALITY_SAFETY)
309
+ if safety < 1:
310
+ raise base.InputError(f'out of bounds safety: 1 <= {safety=} <= {max_safety}')
311
+ safety = max_safety if safety > max_safety else safety
312
+ witnesses = set()
313
+ while len(witnesses) < safety:
314
+ witnesses.add(base.RandInt(2, n - 2))
315
+ # we have our witnesses: do the actual Fermat algo
316
+ for w in sorted(witnesses):
317
+ if not 2 <= w <= (n - 2):
318
+ raise base.InputError(f'out of bounds witness: 2 ≤ {w=} ≤ {n - 2}')
319
+ if ModExp(w, n - 1, n) != 1:
320
+ # number is proved to be composite
321
+ return False
322
+ # we declare the number PROBABLY a prime to the limits of this test
323
+ return True
324
+
325
+
326
+ def _MillerRabinWitnesses(n: int, /) -> set[int]: # pylint: disable=too-many-return-statements
327
+ """Generates a reasonable set of Miller-Rabin witnesses for testing primality of `n`.
328
+
329
+ For n < 3317044064679887385961981 it is precise. That is more than 2**81. See:
330
+ <https://en.wikipedia.org/wiki/Miller%E2%80%93Rabin_primality_test#Testing_against_small_sets_of_bases>
331
+
332
+ For n >= 3317044064679887385961981 it is probabilistic, but computes an number of witnesses
333
+ that should make the test fail less than once in 2**80 tries (once in 10^25). For all intent and
334
+ purposes it "never" fails.
335
+
336
+ Args:
337
+ n (int): number, n ≥ 5
338
+
339
+ Returns:
340
+ {witness1, witness2, ...} for either "certainty" of primality or error chance < 10**25
341
+
342
+ Raises:
343
+ InputError: invalid inputs
344
+ """
345
+ # test inputs
346
+ if n < 5:
347
+ raise base.InputError(f'invalid number: {n=}')
348
+ # for some "smaller" values there is research that shows these sets are always enough
349
+ if n < 2047:
350
+ return {2} # "safety" 1, but 100% coverage
351
+ if n < 9080191:
352
+ return {31, 73} # "safety" 2, but 100% coverage
353
+ if n < 4759123141:
354
+ return {2, 7, 61} # "safety" 3, but 100% coverage
355
+ if n < 2152302898747:
356
+ return set(_FIRST_60_PRIMES_SORTED[:5]) # "safety" 5, but 100% coverage
357
+ if n < 341550071728321:
358
+ return set(_FIRST_60_PRIMES_SORTED[:7]) # "safety" 7, but 100% coverage
359
+ if n < 18446744073709551616: # 2 ** 64
360
+ return set(_FIRST_60_PRIMES_SORTED[:12]) # "safety" 12, but 100% coverage
361
+ if n < 3317044064679887385961981: # > 2 ** 81
362
+ return set(_FIRST_60_PRIMES_SORTED[:13]) # "safety" 13, but 100% coverage
363
+ # here n should be greater than 2 ** 81, so safety should be 34 or less
364
+ n_bits: int = n.bit_length()
365
+ assert n_bits >= 82, f'should never happen: {n=} -> {n_bits=}'
366
+ safety: int = int(math.ceil(0.375 + 1.59 / (0.000590 * n_bits))) if n_bits <= 1700 else 2
367
+ assert 1 < safety <= 34, f'should never happen: {n=} -> {n_bits=} ; {safety=}'
368
+ return set(_FIRST_60_PRIMES_SORTED[:safety])
369
+
370
+
371
+ def _MillerRabinSR(n: int, /) -> tuple[int, int]:
372
+ """Generates (s, r) where (2 ** s) * r == (n - 1) hold true, for odd n > 5.
373
+
374
+ It should be always true that: s ≥ 1 and r ≥ 1 and r is odd.
375
+
376
+ Args:
377
+ n (int): odd number, n ≥ 5
378
+
379
+ Returns:
380
+ (s, r) so that (2 ** s) * r == (n - 1)
381
+
382
+ Raises:
383
+ InputError: invalid inputs
384
+ """
385
+ # test inputs
386
+ if n < 5 or not n % 2:
387
+ raise base.InputError(f'invalid odd number: {n=}')
388
+ # divide by 2 until we can't anymore
389
+ s: int = 1
390
+ r: int = (n - 1) // 2
391
+ while not r % 2:
392
+ s += 1
393
+ r //= 2
394
+ # make sure everything checks out and return
395
+ assert 1 <= r <= n and r % 2, f'should never happen: {n=} -> {r=}'
396
+ return (s, r)
397
+
398
+
399
+ def MillerRabinIsPrime(n: int, /, *, witnesses: set[int] | None = None) -> bool:
400
+ """Primality test of `n` by Miller-Rabin's algo (n > 0).
401
+
402
+ Will execute Miller-Rabin's algo for non-trivial `n` (n > 3 and odd).
403
+ <https://en.wikipedia.org/wiki/Miller%E2%80%93Rabin_primality_test>
404
+
405
+ Args:
406
+ n (int): Number to test primality, n ≥ 1
407
+ witnesses (set[int], optional): If given will use exactly these witnesses, in order
408
+
409
+ Returns:
410
+ False if certainly not prime ; True if (probabilistically) prime
411
+
412
+ Raises:
413
+ InputError: invalid inputs
414
+ """
415
+ # test inputs and test for trivial cases: 1, 2, 3, divisible by 2
416
+ if n < 1:
417
+ raise base.InputError(f'invalid number: {n=}')
418
+ if n in (2, 3):
419
+ return True
420
+ if n == 1 or not n % 2:
421
+ return False
422
+ # n is odd and >= 5; find s and r so that (2 ** s) * r == (n - 1)
423
+ s, r = _MillerRabinSR(n)
424
+ # do the Miller-Rabin algo
425
+ n_limits: tuple[int, int] = (1, n - 1)
426
+ y: int
427
+ for w in sorted(witnesses if witnesses else _MillerRabinWitnesses(n)):
428
+ if not 2 <= w <= (n - 2):
429
+ raise base.InputError(f'out of bounds witness: 2 ≤ {w=} ≤ {n - 2}')
430
+ x: int = ModExp(w, r, n)
431
+ if x not in n_limits:
432
+ for _ in range(s): # s >= 1 so will execute at least once
433
+ y = (x * x) % n
434
+ if y == 1 and x not in n_limits:
435
+ return False # number is proved to be composite
436
+ x = y
437
+ if x != 1:
438
+ return False # number is proved to be composite
439
+ # we declare the number PROBABLY a prime to the limits of this test
440
+ return True
441
+
442
+
443
+ def IsPrime(n: int, /) -> bool:
444
+ """Primality test of `n` (n > 0).
445
+
446
+ Args:
447
+ n (int): Number to test primality, n ≥ 1
448
+
449
+ Returns:
450
+ False if certainly not prime ; True if (probabilistically) prime
451
+
452
+ Raises:
453
+ InputError: invalid inputs
454
+ """
455
+ # is number divisible by (one of the) first 60 primes? test should eliminate 80%+ of candidates
456
+ if n > _PRIME_60 and base.GCD(n, _COMPOSITE_60) != 1:
457
+ return False
458
+ # do the (more expensive) Miller-Rabin primality test
459
+ return MillerRabinIsPrime(n)
460
+
461
+
462
+ def PrimeGenerator(start: int, /) -> Generator[int, None, None]:
463
+ """Generates all primes from `start` until loop is broken. Tuned for huge numbers.
464
+
465
+ Args:
466
+ start (int): number at which to start generating primes, start ≥ 0
467
+
468
+ Yields:
469
+ prime numbers (int)
470
+
471
+ Raises:
472
+ InputError: invalid inputs
473
+ """
474
+ # test inputs and make sure we start at an odd number
475
+ if start < 0:
476
+ raise base.InputError(f'negative number: {start=}')
477
+ # handle start of sequence manually if needed... because we have here the only EVEN prime...
478
+ if start <= 2:
479
+ yield 2
480
+ start = 3
481
+ # we now focus on odd numbers only and loop forever
482
+ n: int = (start if start % 2 else start + 1) - 2 # n >= 1 always
483
+ while True:
484
+ n += 2 # next odd number
485
+ if IsPrime(n):
486
+ yield n # found a prime
487
+
488
+
489
+ def NBitRandomPrime(n_bits: int, /) -> int:
490
+ """Generates a random prime with (guaranteed) `n_bits` size (i.e., first bit == 1).
491
+
492
+ The fact that the first bit will be 1 means the entropy is ~ (n_bits-1) and
493
+ because of this we only allow for a byte or more prime bits generated. This drawback
494
+ is negligible for the large primes a crypto library will work with, in practice.
495
+
496
+ Args:
497
+ n_bits (int): Number of guaranteed bits in prime representation, n ≥ 8
498
+
499
+ Returns:
500
+ random prime with `n_bits` bits
501
+
502
+ Raises:
503
+ InputError: invalid inputs
504
+ """
505
+ # test inputs
506
+ if n_bits < 8:
507
+ raise base.InputError(f'invalid n: {n_bits=}')
508
+ # get a random number with guaranteed bit size
509
+ prime: int = 0
510
+ while prime.bit_length() != n_bits:
511
+ prime = next(PrimeGenerator(base.RandBits(n_bits)))
512
+ return prime
513
+
514
+
515
+ def MersennePrimesGenerator(start: int, /) -> Generator[tuple[int, int, int], None, None]:
516
+ """Generates all Mersenne prime (2 ** n - 1) exponents from 2**start until loop is broken.
517
+
518
+ <https://en.wikipedia.org/wiki/List_of_Mersenne_primes_and_perfect_numbers>
519
+
520
+ Args:
521
+ start (int): exponent at which to start generating primes, start ≥ 0
522
+
523
+ Yields:
524
+ (exponent, mersenne_prime, perfect_number), given some exponent `n` that will be exactly:
525
+ (n, 2 ** n - 1, (2 ** (n - 1)) * (2 ** n - 1))
526
+
527
+ Raises:
528
+ InputError: invalid inputs
529
+ """
530
+ # we now loop forever over prime exponents
531
+ # "The exponents p corresponding to Mersenne primes must themselves be prime."
532
+ for n in PrimeGenerator(start if start >= 1 else 1):
533
+ mersenne: int = 2 ** n - 1
534
+ if IsPrime(mersenne):
535
+ yield (n, mersenne, (2 ** (n - 1)) * mersenne) # found: also yield perfect number