transcrypto 1.5.1__py3-none-any.whl → 1.7.0__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/__init__.py +7 -0
- transcrypto/aes.py +150 -44
- transcrypto/base.py +640 -411
- transcrypto/constants.py +20070 -1906
- transcrypto/dsa.py +132 -99
- transcrypto/elgamal.py +116 -84
- transcrypto/modmath.py +88 -78
- transcrypto/profiler.py +228 -180
- transcrypto/rsa.py +126 -90
- transcrypto/sss.py +122 -70
- transcrypto/transcrypto.py +2362 -1412
- {transcrypto-1.5.1.dist-info → transcrypto-1.7.0.dist-info}/METADATA +78 -58
- transcrypto-1.7.0.dist-info/RECORD +17 -0
- {transcrypto-1.5.1.dist-info → transcrypto-1.7.0.dist-info}/WHEEL +1 -2
- transcrypto-1.7.0.dist-info/entry_points.txt +4 -0
- transcrypto/safetrans.py +0 -1231
- transcrypto-1.5.1.dist-info/RECORD +0 -18
- transcrypto-1.5.1.dist-info/top_level.txt +0 -1
- {transcrypto-1.5.1.dist-info → transcrypto-1.7.0.dist-info}/licenses/LICENSE +0 -0
transcrypto/modmath.py
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
#
|
|
3
|
-
# Copyright 2025 Daniel Balparda (balparda@github.com) - Apache-2.0 license
|
|
4
|
-
#
|
|
1
|
+
# SPDX-FileCopyrightText: Copyright 2026 Daniel Balparda <balparda@github.com>
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
5
3
|
"""Balparda's TransCrypto modular math library."""
|
|
6
4
|
|
|
7
5
|
from __future__ import annotations
|
|
@@ -10,18 +8,12 @@ import concurrent.futures
|
|
|
10
8
|
import math
|
|
11
9
|
import multiprocessing
|
|
12
10
|
import os
|
|
13
|
-
|
|
14
|
-
from typing import Generator, Reversible
|
|
11
|
+
from collections import abc
|
|
15
12
|
|
|
16
|
-
import gmpy2
|
|
13
|
+
import gmpy2
|
|
17
14
|
|
|
18
15
|
from . import base, constants
|
|
19
16
|
|
|
20
|
-
__author__ = 'balparda@github.com'
|
|
21
|
-
__version__: str = base.__version__ # version comes from base!
|
|
22
|
-
__version_tuple__: tuple[int, ...] = base.__version_tuple__
|
|
23
|
-
|
|
24
|
-
|
|
25
17
|
_MAX_PRIMALITY_SAFETY = 100 # this is an absurd number, just to have a max
|
|
26
18
|
|
|
27
19
|
|
|
@@ -43,9 +35,10 @@ def ModInv(x: int, m: int, /) -> int:
|
|
|
43
35
|
Raises:
|
|
44
36
|
InputError: invalid modulus or x
|
|
45
37
|
ModularDivideError: divide-by-zero, i.e., GCD(x, m) != 1 or x == 0
|
|
38
|
+
|
|
46
39
|
"""
|
|
47
40
|
# test inputs
|
|
48
|
-
if m < 2:
|
|
41
|
+
if m < 2: # noqa: PLR2004
|
|
49
42
|
raise base.InputError(f'invalid modulus: {m=}')
|
|
50
43
|
# easy special cases: 0 and 1
|
|
51
44
|
reduced_x: int = x % m
|
|
@@ -57,7 +50,7 @@ def ModInv(x: int, m: int, /) -> int:
|
|
|
57
50
|
gcd, y, w = base.ExtendedGCD(reduced_x, m)
|
|
58
51
|
if gcd != 1:
|
|
59
52
|
raise ModularDivideError(f'invalid inverse {x=} mod {m=} with {gcd=}')
|
|
60
|
-
assert y and w and y >= -m, f'should never happen: {x=} mod {m=} -> {w=} ; {y=}'
|
|
53
|
+
assert y and w and y >= -m, f'should never happen: {x=} mod {m=} -> {w=} ; {y=}' # noqa: PT018, S101
|
|
61
54
|
return y if y >= 0 else (y + m)
|
|
62
55
|
|
|
63
56
|
|
|
@@ -76,9 +69,10 @@ def ModDiv(x: int, y: int, m: int, /) -> int:
|
|
|
76
69
|
Raises:
|
|
77
70
|
InputError: invalid modulus or x or y
|
|
78
71
|
ModularDivideError: divide-by-zero, i.e., GCD(y, m) != 1 or y == 0
|
|
72
|
+
|
|
79
73
|
"""
|
|
80
74
|
# test inputs
|
|
81
|
-
if m < 2:
|
|
75
|
+
if m < 2: # noqa: PLR2004
|
|
82
76
|
raise base.InputError(f'invalid modulus: {m=}')
|
|
83
77
|
if not y: # "division by 0"
|
|
84
78
|
raise ModularDivideError(f'divide by zero {x=} / {y=} mod {m=}')
|
|
@@ -113,9 +107,10 @@ def CRTPair(a1: int, m1: int, a2: int, m2: int) -> int:
|
|
|
113
107
|
Raises:
|
|
114
108
|
InputError: invalid inputs
|
|
115
109
|
ModularDivideError: moduli are not co-prime, i.e. gcd(m1, m2) != 1
|
|
110
|
+
|
|
116
111
|
"""
|
|
117
112
|
# test inputs
|
|
118
|
-
if m1 < 2 or m2 < 2 or m1 == m2:
|
|
113
|
+
if m1 < 2 or m2 < 2 or m1 == m2: # noqa: PLR2004
|
|
119
114
|
raise base.InputError(f'invalid moduli: {m1=} / {m2=}')
|
|
120
115
|
# compute
|
|
121
116
|
a1 %= m1
|
|
@@ -143,9 +138,10 @@ def ModExp(x: int, y: int, m: int, /) -> int:
|
|
|
143
138
|
|
|
144
139
|
Raises:
|
|
145
140
|
InputError: invalid inputs
|
|
141
|
+
|
|
146
142
|
"""
|
|
147
143
|
# test inputs
|
|
148
|
-
if m < 2:
|
|
144
|
+
if m < 2: # noqa: PLR2004
|
|
149
145
|
raise base.InputError(f'invalid modulus: {m=}')
|
|
150
146
|
if y < 0:
|
|
151
147
|
raise base.InputError(f'negative exponent: {y=}')
|
|
@@ -168,8 +164,8 @@ def ModExp(x: int, y: int, m: int, /) -> int:
|
|
|
168
164
|
return z
|
|
169
165
|
|
|
170
166
|
|
|
171
|
-
def ModPolynomial(x: int, polynomial: Reversible[int], m: int, /) -> int:
|
|
172
|
-
"""
|
|
167
|
+
def ModPolynomial(x: int, polynomial: abc.Reversible[int], m: int, /) -> int:
|
|
168
|
+
"""Evaluate `polynomial` (coefficients iterable) at `x` modulus `m`.
|
|
173
169
|
|
|
174
170
|
Evaluate a polynomial at `x` under a modulus `m` using Horner's rule. Horner rewrites:
|
|
175
171
|
a_0 + a_1 x + a_2 x^2 + … + a_n x^n
|
|
@@ -189,11 +185,12 @@ def ModPolynomial(x: int, polynomial: Reversible[int], m: int, /) -> int:
|
|
|
189
185
|
|
|
190
186
|
Raises:
|
|
191
187
|
InputError: invalid inputs
|
|
188
|
+
|
|
192
189
|
"""
|
|
193
190
|
# test inputs
|
|
194
191
|
if not polynomial:
|
|
195
192
|
raise base.InputError(f'no polynomial: {polynomial=}')
|
|
196
|
-
if m < 2:
|
|
193
|
+
if m < 2: # noqa: PLR2004
|
|
197
194
|
raise base.InputError(f'invalid modulus: {m=}')
|
|
198
195
|
# loop over polynomial coefficients
|
|
199
196
|
total: int = 0
|
|
@@ -232,13 +229,14 @@ def ModLagrangeInterpolate(x: int, points: dict[int, int], m: int, /) -> int:
|
|
|
232
229
|
|
|
233
230
|
Raises:
|
|
234
231
|
InputError: invalid inputs
|
|
232
|
+
|
|
235
233
|
"""
|
|
236
234
|
# test inputs
|
|
237
|
-
if m < 2:
|
|
235
|
+
if m < 2: # noqa: PLR2004
|
|
238
236
|
raise base.InputError(f'invalid modulus: {m=}')
|
|
239
237
|
x %= m # takes care of negative numbers and also x >= m
|
|
240
238
|
reduced_points: dict[int, int] = {k % m: v % m for k, v in points.items()}
|
|
241
|
-
if len(points) < 2 or len(reduced_points) != len(points) or x in reduced_points:
|
|
239
|
+
if len(points) < 2 or len(reduced_points) != len(points) or x in reduced_points: # noqa: PLR2004
|
|
242
240
|
raise base.InputError(f'invalid points or duplicate x/x_i found: {x=} / {points=}')
|
|
243
241
|
# compute everything term-by-term
|
|
244
242
|
result: int = 0
|
|
@@ -258,7 +256,7 @@ def ModLagrangeInterpolate(x: int, points: dict[int, int], m: int, /) -> int:
|
|
|
258
256
|
|
|
259
257
|
|
|
260
258
|
def FermatIsPrime(n: int, /, *, safety: int = 10, witnesses: set[int] | None = None) -> bool:
|
|
261
|
-
"""Primality test of `n` by Fermat's algo (n > 0)
|
|
259
|
+
"""Primality test of `n` by Fermat's algo (n > 0) (UNRELIABLE!! -> use IsPrime()).
|
|
262
260
|
|
|
263
261
|
Will execute Fermat's algo for non-trivial `n` (n > 3 and odd).
|
|
264
262
|
<https://en.wikipedia.org/wiki/Fermat_primality_test>
|
|
@@ -278,11 +276,12 @@ def FermatIsPrime(n: int, /, *, safety: int = 10, witnesses: set[int] | None = N
|
|
|
278
276
|
|
|
279
277
|
Raises:
|
|
280
278
|
InputError: invalid inputs
|
|
279
|
+
|
|
281
280
|
"""
|
|
282
281
|
# test inputs and test for trivial cases: 1, 2, 3, divisible by 2
|
|
283
282
|
if n < 1:
|
|
284
283
|
raise base.InputError(f'invalid number: {n=}')
|
|
285
|
-
if n in
|
|
284
|
+
if n in {2, 3}:
|
|
286
285
|
return True
|
|
287
286
|
if n == 1 or not n % 2:
|
|
288
287
|
return False
|
|
@@ -292,23 +291,23 @@ def FermatIsPrime(n: int, /, *, safety: int = 10, witnesses: set[int] | None = N
|
|
|
292
291
|
max_safety: int = min(n // 2, _MAX_PRIMALITY_SAFETY)
|
|
293
292
|
if safety < 1:
|
|
294
293
|
raise base.InputError(f'out of bounds safety: 1 <= {safety=} <= {max_safety}')
|
|
295
|
-
safety =
|
|
294
|
+
safety = min(safety, max_safety)
|
|
296
295
|
witnesses = set()
|
|
297
296
|
while len(witnesses) < safety:
|
|
298
297
|
witnesses.add(base.RandInt(2, n - 2))
|
|
299
298
|
# we have our witnesses: do the actual Fermat algo
|
|
300
299
|
for w in sorted(witnesses):
|
|
301
|
-
if not 2 <= w <= (n - 2):
|
|
300
|
+
if not 2 <= w <= (n - 2): # noqa: PLR2004
|
|
302
301
|
raise base.InputError(f'out of bounds witness: 2 ≤ {w=} ≤ {n - 2}')
|
|
303
|
-
if gmpy2.powmod(w, n - 1, n) != 1:
|
|
302
|
+
if gmpy2.powmod(w, n - 1, n) != 1:
|
|
304
303
|
# number is proved to be composite
|
|
305
304
|
return False
|
|
306
305
|
# we declare the number PROBABLY a prime to the limits of this test
|
|
307
306
|
return True
|
|
308
307
|
|
|
309
308
|
|
|
310
|
-
def _MillerRabinWitnesses(n: int, /) -> set[int]: #
|
|
311
|
-
"""
|
|
309
|
+
def _MillerRabinWitnesses(n: int, /) -> set[int]: # noqa: PLR0911
|
|
310
|
+
"""Generate a reasonable set of Miller-Rabin witnesses for testing primality of `n`.
|
|
312
311
|
|
|
313
312
|
For n < 3317044064679887385961981 it is precise. That is more than 2**81. See:
|
|
314
313
|
<https://en.wikipedia.org/wiki/Miller%E2%80%93Rabin_primality_test#Testing_against_small_sets_of_bases>
|
|
@@ -325,35 +324,36 @@ def _MillerRabinWitnesses(n: int, /) -> set[int]: # pylint: disable=too-many-re
|
|
|
325
324
|
|
|
326
325
|
Raises:
|
|
327
326
|
InputError: invalid inputs
|
|
327
|
+
|
|
328
328
|
"""
|
|
329
329
|
# test inputs
|
|
330
|
-
if n < 5:
|
|
330
|
+
if n < 5: # noqa: PLR2004
|
|
331
331
|
raise base.InputError(f'invalid number: {n=}')
|
|
332
332
|
# for some "smaller" values there is research that shows these sets are always enough
|
|
333
|
-
if n < 2047:
|
|
334
|
-
return {2}
|
|
335
|
-
if n < 9080191:
|
|
336
|
-
return {31, 73}
|
|
337
|
-
if n < 4759123141:
|
|
338
|
-
return {2, 7, 61}
|
|
339
|
-
if n < 2152302898747:
|
|
340
|
-
return set(constants.FIRST_5K_PRIMES_SORTED[:5])
|
|
341
|
-
if n < 341550071728321:
|
|
342
|
-
return set(constants.FIRST_5K_PRIMES_SORTED[:7])
|
|
343
|
-
if n < 18446744073709551616:
|
|
333
|
+
if n < 2047: # noqa: PLR2004
|
|
334
|
+
return {2} # "safety" 1, but 100% coverage
|
|
335
|
+
if n < 9080191: # noqa: PLR2004
|
|
336
|
+
return {31, 73} # "safety" 2, but 100% coverage
|
|
337
|
+
if n < 4759123141: # noqa: PLR2004
|
|
338
|
+
return {2, 7, 61} # "safety" 3, but 100% coverage
|
|
339
|
+
if n < 2152302898747: # noqa: PLR2004
|
|
340
|
+
return set(constants.FIRST_5K_PRIMES_SORTED[:5]) # "safety" 5, but 100% coverage
|
|
341
|
+
if n < 341550071728321: # noqa: PLR2004
|
|
342
|
+
return set(constants.FIRST_5K_PRIMES_SORTED[:7]) # "safety" 7, but 100% coverage
|
|
343
|
+
if n < 18446744073709551616: # 2 ** 64 # noqa: PLR2004
|
|
344
344
|
return set(constants.FIRST_5K_PRIMES_SORTED[:12]) # "safety" 12, but 100% coverage
|
|
345
|
-
if n < 3317044064679887385961981:
|
|
345
|
+
if n < 3317044064679887385961981: # > 2 ** 81 # noqa: PLR2004
|
|
346
346
|
return set(constants.FIRST_5K_PRIMES_SORTED[:13]) # "safety" 13, but 100% coverage
|
|
347
347
|
# here n should be greater than 2 ** 81, so safety should be 34 or less
|
|
348
348
|
n_bits: int = n.bit_length()
|
|
349
|
-
assert n_bits >= 82, f'should never happen: {n=} -> {n_bits=}'
|
|
350
|
-
safety: int =
|
|
351
|
-
assert 1 < safety <= 34, f'should never happen: {n=} -> {n_bits=} ; {safety=}'
|
|
349
|
+
assert n_bits >= 82, f'should never happen: {n=} -> {n_bits=}' # noqa: PLR2004, S101
|
|
350
|
+
safety: int = max(2, math.ceil(0.375 + 1.59 / (0.000590 * n_bits))) if n_bits <= 1700 else 2 # noqa: PLR2004
|
|
351
|
+
assert 1 < safety <= 34, f'should never happen: {n=} -> {n_bits=} ; {safety=}' # noqa: PLR2004, S101
|
|
352
352
|
return set(constants.FIRST_5K_PRIMES_SORTED[:safety])
|
|
353
353
|
|
|
354
354
|
|
|
355
355
|
def _MillerRabinSR(n: int, /) -> tuple[int, int]:
|
|
356
|
-
"""
|
|
356
|
+
"""Generate (s, r) where (2 ** s) * r == (n - 1) hold true, for odd n > 5.
|
|
357
357
|
|
|
358
358
|
It should be always true that: s ≥ 1 and r ≥ 1 and r is odd.
|
|
359
359
|
|
|
@@ -365,9 +365,10 @@ def _MillerRabinSR(n: int, /) -> tuple[int, int]:
|
|
|
365
365
|
|
|
366
366
|
Raises:
|
|
367
367
|
InputError: invalid inputs
|
|
368
|
+
|
|
368
369
|
"""
|
|
369
370
|
# test inputs
|
|
370
|
-
if n < 5 or not n % 2:
|
|
371
|
+
if n < 5 or not n % 2: # noqa: PLR2004
|
|
371
372
|
raise base.InputError(f'invalid odd number: {n=}')
|
|
372
373
|
# divide by 2 until we can't anymore
|
|
373
374
|
s: int = 1
|
|
@@ -376,7 +377,7 @@ def _MillerRabinSR(n: int, /) -> tuple[int, int]:
|
|
|
376
377
|
s += 1
|
|
377
378
|
r //= 2
|
|
378
379
|
# make sure everything checks out and return
|
|
379
|
-
assert 1 <= r <= n and r % 2, f'should never happen: {n=} -> {r=}'
|
|
380
|
+
assert 1 <= r <= n and r % 2, f'should never happen: {n=} -> {r=}' # noqa: PT018, S101
|
|
380
381
|
return (s, r)
|
|
381
382
|
|
|
382
383
|
|
|
@@ -395,11 +396,12 @@ def MillerRabinIsPrime(n: int, /, *, witnesses: set[int] | None = None) -> bool:
|
|
|
395
396
|
|
|
396
397
|
Raises:
|
|
397
398
|
InputError: invalid inputs
|
|
399
|
+
|
|
398
400
|
"""
|
|
399
401
|
# test inputs and test for trivial cases: 1, 2, 3, divisible by 2
|
|
400
402
|
if n < 1:
|
|
401
403
|
raise base.InputError(f'invalid number: {n=}')
|
|
402
|
-
if n in
|
|
404
|
+
if n in {2, 3}:
|
|
403
405
|
return True
|
|
404
406
|
if n == 1 or not n % 2:
|
|
405
407
|
return False
|
|
@@ -408,10 +410,10 @@ def MillerRabinIsPrime(n: int, /, *, witnesses: set[int] | None = None) -> bool:
|
|
|
408
410
|
# do the Miller-Rabin algo
|
|
409
411
|
n_limits: tuple[int, int] = (1, n - 1)
|
|
410
412
|
y: int
|
|
411
|
-
for w in sorted(witnesses
|
|
412
|
-
if not 2 <= w <= (n - 2):
|
|
413
|
+
for w in sorted(witnesses or _MillerRabinWitnesses(n)):
|
|
414
|
+
if not 2 <= w <= (n - 2): # noqa: PLR2004
|
|
413
415
|
raise base.InputError(f'out of bounds witness: 2 ≤ {w=} ≤ {n - 2}')
|
|
414
|
-
x: int = int(gmpy2.powmod(w, r, n))
|
|
416
|
+
x: int = int(gmpy2.powmod(w, r, n))
|
|
415
417
|
if x not in n_limits:
|
|
416
418
|
for _ in range(s): # s >= 1 so will execute at least once
|
|
417
419
|
y = (x * x) % n
|
|
@@ -419,7 +421,7 @@ def MillerRabinIsPrime(n: int, /, *, witnesses: set[int] | None = None) -> bool:
|
|
|
419
421
|
return False # number is proved to be composite
|
|
420
422
|
x = y
|
|
421
423
|
if x != 1:
|
|
422
|
-
return False
|
|
424
|
+
return False # number is proved to be composite
|
|
423
425
|
# we declare the number PROBABLY a prime to the limits of this test
|
|
424
426
|
return True
|
|
425
427
|
|
|
@@ -433,8 +435,6 @@ def IsPrime(n: int, /) -> bool:
|
|
|
433
435
|
Returns:
|
|
434
436
|
False if certainly not prime ; True if (probabilistically) prime
|
|
435
437
|
|
|
436
|
-
Raises:
|
|
437
|
-
InputError: invalid inputs
|
|
438
438
|
"""
|
|
439
439
|
# is number divisible by (one of the) first 20000 primes? test should eliminate 90%+ of candidates
|
|
440
440
|
if n in constants.FIRST_20K_PRIMES:
|
|
@@ -446,8 +446,8 @@ def IsPrime(n: int, /) -> bool:
|
|
|
446
446
|
return MillerRabinIsPrime(n)
|
|
447
447
|
|
|
448
448
|
|
|
449
|
-
def PrimeGenerator(start: int, /) -> Generator[int
|
|
450
|
-
"""
|
|
449
|
+
def PrimeGenerator(start: int, /) -> abc.Generator[int]:
|
|
450
|
+
"""Generate all primes from `start` until loop is broken. Tuned for huge numbers.
|
|
451
451
|
|
|
452
452
|
Args:
|
|
453
453
|
start (int): number at which to start generating primes, start ≥ 0
|
|
@@ -457,12 +457,13 @@ def PrimeGenerator(start: int, /) -> Generator[int, None, None]:
|
|
|
457
457
|
|
|
458
458
|
Raises:
|
|
459
459
|
InputError: invalid inputs
|
|
460
|
+
|
|
460
461
|
"""
|
|
461
462
|
# test inputs and make sure we start at an odd number
|
|
462
463
|
if start < 0:
|
|
463
464
|
raise base.InputError(f'negative number: {start=}')
|
|
464
465
|
# handle start of sequence manually if needed... because we have here the only EVEN prime...
|
|
465
|
-
if start <= 2:
|
|
466
|
+
if start <= 2: # noqa: PLR2004
|
|
466
467
|
yield 2
|
|
467
468
|
start = 3
|
|
468
469
|
# we now focus on odd numbers only and loop forever
|
|
@@ -474,7 +475,7 @@ def PrimeGenerator(start: int, /) -> Generator[int, None, None]:
|
|
|
474
475
|
|
|
475
476
|
|
|
476
477
|
def NBitRandomPrimes(n_bits: int, /, *, serial: bool = True, n_primes: int = 1) -> set[int]:
|
|
477
|
-
"""
|
|
478
|
+
"""Generate a random prime with (guaranteed) `n_bits` size (i.e., first bit == 1).
|
|
478
479
|
|
|
479
480
|
The fact that the first bit will be 1 means the entropy is ~ (n_bits-1) and
|
|
480
481
|
because of this we only allow for a byte or more prime bits generated. This drawback
|
|
@@ -508,16 +509,18 @@ def NBitRandomPrimes(n_bits: int, /, *, serial: bool = True, n_primes: int = 1)
|
|
|
508
509
|
|
|
509
510
|
Raises:
|
|
510
511
|
InputError: invalid inputs
|
|
512
|
+
Error: prime search failed
|
|
513
|
+
|
|
511
514
|
"""
|
|
512
515
|
# test inputs
|
|
513
|
-
if n_bits < 8:
|
|
516
|
+
if n_bits < 8: # noqa: PLR2004
|
|
514
517
|
raise base.InputError(f'invalid n: {n_bits=}')
|
|
515
|
-
n_primes =
|
|
518
|
+
n_primes = max(n_primes, 1)
|
|
516
519
|
# get number of CPUs and decide if we do parallel or not
|
|
517
520
|
n_workers: int = min(4, os.cpu_count() or 1)
|
|
518
521
|
pr_set: set[int] = set()
|
|
519
522
|
pr: int | None = None
|
|
520
|
-
if serial or n_workers <= 1 or n_bits < 200:
|
|
523
|
+
if serial or n_workers <= 1 or n_bits < 200: # noqa: PLR2004
|
|
521
524
|
# do one worker
|
|
522
525
|
while len(pr_set) < n_primes:
|
|
523
526
|
while pr is None or pr.bit_length() != n_bits:
|
|
@@ -529,10 +532,12 @@ def NBitRandomPrimes(n_bits: int, /, *, serial: bool = True, n_primes: int = 1)
|
|
|
529
532
|
multiprocessing.set_start_method('fork', force=True)
|
|
530
533
|
with concurrent.futures.ProcessPoolExecutor(max_workers=n_workers) as pool:
|
|
531
534
|
workers: set[concurrent.futures.Future[int | None]] = {
|
|
532
|
-
|
|
535
|
+
pool.submit(_PrimeSearchShard, n_bits) for _ in range(n_workers)
|
|
536
|
+
}
|
|
533
537
|
while workers:
|
|
534
538
|
done: set[concurrent.futures.Future[int | None]] = concurrent.futures.wait(
|
|
535
|
-
|
|
539
|
+
workers, return_when=concurrent.futures.FIRST_COMPLETED
|
|
540
|
+
)[0]
|
|
536
541
|
for worker in done:
|
|
537
542
|
workers.remove(worker)
|
|
538
543
|
pr = worker.result()
|
|
@@ -548,15 +553,16 @@ def NBitRandomPrimes(n_bits: int, /, *, serial: bool = True, n_primes: int = 1)
|
|
|
548
553
|
|
|
549
554
|
|
|
550
555
|
def _PrimeSearchShard(n_bits: int) -> int | None:
|
|
551
|
-
"""Search for a `n_bits` random prime, starting from a random point, for ~
|
|
556
|
+
"""Search for a `n_bits` random prime, starting from a random point, for ~6x expected prime gap.
|
|
552
557
|
|
|
553
558
|
Args:
|
|
554
559
|
n_bits (int): Number of guaranteed bits in prime representation
|
|
555
560
|
|
|
556
561
|
Returns:
|
|
557
562
|
int | None: either the prime int or None if no prime found in this shard
|
|
563
|
+
|
|
558
564
|
"""
|
|
559
|
-
shard_len: int = max(2000, 6 * int(0.693 * n_bits)) # ~
|
|
565
|
+
shard_len: int = max(2000, 6 * int(0.693 * n_bits)) # ~6x expected prime gap ~2^k (≈ 0.693*k)
|
|
560
566
|
pr: int = base.RandBits(n_bits) | 1 # random position; make ODD
|
|
561
567
|
count: int = 0
|
|
562
568
|
while count < shard_len and pr.bit_length() == n_bits:
|
|
@@ -567,18 +573,24 @@ def _PrimeSearchShard(n_bits: int) -> int | None:
|
|
|
567
573
|
return None
|
|
568
574
|
|
|
569
575
|
|
|
570
|
-
def FirstNPrimesSorted(n: int) ->
|
|
571
|
-
"""
|
|
572
|
-
|
|
576
|
+
def FirstNPrimesSorted(n: int) -> abc.Generator[int]:
|
|
577
|
+
"""Return list of `n` first primes in a sorted list.
|
|
578
|
+
|
|
579
|
+
Args:
|
|
580
|
+
n (int): number of primes to return
|
|
581
|
+
|
|
582
|
+
Yields:
|
|
583
|
+
Generator[int]: primes
|
|
584
|
+
|
|
585
|
+
"""
|
|
573
586
|
for i, pr in enumerate(PrimeGenerator(0)):
|
|
574
587
|
if i >= n:
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
return primes
|
|
588
|
+
return
|
|
589
|
+
yield pr
|
|
578
590
|
|
|
579
591
|
|
|
580
|
-
def MersennePrimesGenerator(start: int, /) -> Generator[tuple[int, int, int]
|
|
581
|
-
"""
|
|
592
|
+
def MersennePrimesGenerator(start: int, /) -> abc.Generator[tuple[int, int, int]]:
|
|
593
|
+
"""Generate all Mersenne prime (2 ** n - 1) exponents from start until loop is broken.
|
|
582
594
|
|
|
583
595
|
<https://en.wikipedia.org/wiki/List_of_Mersenne_primes_and_perfect_numbers>
|
|
584
596
|
|
|
@@ -589,12 +601,10 @@ def MersennePrimesGenerator(start: int, /) -> Generator[tuple[int, int, int], No
|
|
|
589
601
|
(exponent, mersenne_prime, perfect_number), given some exponent `n` that will be exactly:
|
|
590
602
|
(n, 2 ** n - 1, (2 ** (n - 1)) * (2 ** n - 1))
|
|
591
603
|
|
|
592
|
-
Raises:
|
|
593
|
-
InputError: invalid inputs
|
|
594
604
|
"""
|
|
595
605
|
# we now loop forever over prime exponents
|
|
596
606
|
# "The exponents p corresponding to Mersenne primes must themselves be prime."
|
|
597
|
-
for n in PrimeGenerator(start
|
|
598
|
-
mersenne: int = 2
|
|
607
|
+
for n in PrimeGenerator(max(start, 1)):
|
|
608
|
+
mersenne: int = 2**n - 1
|
|
599
609
|
if IsPrime(mersenne):
|
|
600
610
|
yield (n, mersenne, (2 ** (n - 1)) * mersenne) # found: also yield perfect number
|