foxmath 0.7.0__tar.gz → 0.7.1__tar.gz
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.
- {foxmath-0.7.0/src/foxmath.egg-info → foxmath-0.7.1}/PKG-INFO +2 -2
- {foxmath-0.7.0 → foxmath-0.7.1}/README.md +1 -1
- {foxmath-0.7.0 → foxmath-0.7.1}/pyproject.toml +1 -1
- {foxmath-0.7.0 → foxmath-0.7.1}/src/foxmath/foxmath.py +53 -7
- {foxmath-0.7.0 → foxmath-0.7.1/src/foxmath.egg-info}/PKG-INFO +2 -2
- {foxmath-0.7.0 → foxmath-0.7.1}/tests/test_cli.py +10 -0
- {foxmath-0.7.0 → foxmath-0.7.1}/tests/test_foxmath.py +33 -1
- {foxmath-0.7.0 → foxmath-0.7.1}/LICENSE +0 -0
- {foxmath-0.7.0 → foxmath-0.7.1}/setup.cfg +0 -0
- {foxmath-0.7.0 → foxmath-0.7.1}/src/foxmath/__init__.py +0 -0
- {foxmath-0.7.0 → foxmath-0.7.1}/src/foxmath.egg-info/SOURCES.txt +0 -0
- {foxmath-0.7.0 → foxmath-0.7.1}/src/foxmath.egg-info/dependency_links.txt +0 -0
- {foxmath-0.7.0 → foxmath-0.7.1}/src/foxmath.egg-info/entry_points.txt +0 -0
- {foxmath-0.7.0 → foxmath-0.7.1}/src/foxmath.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: foxmath
|
|
3
|
-
Version: 0.7.
|
|
3
|
+
Version: 0.7.1
|
|
4
4
|
Summary: Minimalist CLI for math & cryptography exploration 🦊
|
|
5
5
|
Author-email: Abhrankan Chakrabarti <abhrankan@gmail.com>
|
|
6
6
|
License: MIT
|
|
@@ -61,7 +61,7 @@ Check which version you have installed:
|
|
|
61
61
|
foxmath --version
|
|
62
62
|
```
|
|
63
63
|
```
|
|
64
|
-
foxmath 0.7.
|
|
64
|
+
foxmath 0.7.1
|
|
65
65
|
```
|
|
66
66
|
|
|
67
67
|
## Usage Examples
|
|
@@ -22,20 +22,66 @@ def _package_version() -> str:
|
|
|
22
22
|
|
|
23
23
|
# ------------------- Math Functions -------------------
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
25
|
+
# First 13 primes. As Miller-Rabin bases they make the test *deterministic*
|
|
26
|
+
# for every n below 3,317,044,064,679,887,385,961,981 (~3.3e24); 12 bases
|
|
27
|
+
# would not be enough (399165290221 * 798330580441, ~3.2e23, fools them).
|
|
28
|
+
_PRIME_TEST_BASES = (2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41)
|
|
29
|
+
|
|
30
|
+
def _is_probable_prime(n: int) -> bool:
|
|
31
|
+
"""Miller-Rabin primality test.
|
|
32
|
+
|
|
33
|
+
Exact for n < ~3.3e24. Above that it is a strong probable-prime test:
|
|
34
|
+
ordinary composites essentially never pass, but a composite built
|
|
35
|
+
specifically to fool these 13 bases could.
|
|
36
|
+
"""
|
|
37
|
+
if n < 2:
|
|
38
|
+
return False
|
|
39
|
+
for sp in _PRIME_TEST_BASES:
|
|
40
|
+
if n == sp:
|
|
41
|
+
return True
|
|
42
|
+
if n % sp == 0:
|
|
43
|
+
return False
|
|
44
|
+
d, r = n - 1, 0
|
|
45
|
+
while d % 2 == 0:
|
|
46
|
+
d //= 2
|
|
47
|
+
r += 1
|
|
48
|
+
for a in _PRIME_TEST_BASES:
|
|
49
|
+
x = pow(a, d, n)
|
|
50
|
+
if x == 1 or x == n - 1:
|
|
51
|
+
continue
|
|
52
|
+
for _ in range(r - 1):
|
|
53
|
+
x = x * x % n
|
|
54
|
+
if x == n - 1:
|
|
55
|
+
break
|
|
56
|
+
else:
|
|
57
|
+
return False
|
|
58
|
+
return True
|
|
59
|
+
|
|
60
|
+
def _require_odd_prime(p: int) -> None:
|
|
61
|
+
"""Enforce the contract the Legendre symbol and Tonelli-Shanks rely on.
|
|
62
|
+
|
|
63
|
+
Euler's criterion is only meaningful for a prime modulus. On a composite
|
|
64
|
+
it returns garbage, and Tonelli-Shanks' search for a non-residue can loop
|
|
65
|
+
forever (mod 9 or mod 561, for example, there is none to find).
|
|
66
|
+
"""
|
|
67
|
+
if p < 2 or p % 2 == 0 or not _is_probable_prime(p):
|
|
28
68
|
raise ValueError("p must be an odd prime")
|
|
69
|
+
|
|
70
|
+
def _legendre_unchecked(a: int, p: int) -> int:
|
|
29
71
|
res = pow(a, (p - 1) // 2, p)
|
|
30
72
|
return 1 if res == 1 else (-1 if res == p - 1 else 0)
|
|
31
73
|
|
|
74
|
+
def legendre_symbol(a: int, p: int) -> int:
|
|
75
|
+
"""Compute Legendre symbol (a/p). p must be an odd prime."""
|
|
76
|
+
_require_odd_prime(p)
|
|
77
|
+
return _legendre_unchecked(a, p)
|
|
78
|
+
|
|
32
79
|
def sqrt_mod(a: int, p: int) -> tuple:
|
|
33
80
|
"""Return the square root(s) of a modulo an odd prime p."""
|
|
34
|
-
|
|
35
|
-
raise ValueError("p must be an odd prime")
|
|
81
|
+
_require_odd_prime(p)
|
|
36
82
|
|
|
37
83
|
a %= p
|
|
38
|
-
symbol =
|
|
84
|
+
symbol = _legendre_unchecked(a, p)
|
|
39
85
|
if symbol == 0:
|
|
40
86
|
return (0,)
|
|
41
87
|
if symbol == -1:
|
|
@@ -52,7 +98,7 @@ def sqrt_mod(a: int, p: int) -> tuple:
|
|
|
52
98
|
s += 1
|
|
53
99
|
|
|
54
100
|
non_residue = 2
|
|
55
|
-
while
|
|
101
|
+
while _legendre_unchecked(non_residue, p) != -1:
|
|
56
102
|
non_residue += 1
|
|
57
103
|
|
|
58
104
|
c = pow(non_residue, q, p)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: foxmath
|
|
3
|
-
Version: 0.7.
|
|
3
|
+
Version: 0.7.1
|
|
4
4
|
Summary: Minimalist CLI for math & cryptography exploration 🦊
|
|
5
5
|
Author-email: Abhrankan Chakrabarti <abhrankan@gmail.com>
|
|
6
6
|
License: MIT
|
|
@@ -61,7 +61,7 @@ Check which version you have installed:
|
|
|
61
61
|
foxmath --version
|
|
62
62
|
```
|
|
63
63
|
```
|
|
64
|
-
foxmath 0.7.
|
|
64
|
+
foxmath 0.7.1
|
|
65
65
|
```
|
|
66
66
|
|
|
67
67
|
## Usage Examples
|
|
@@ -301,6 +301,16 @@ class TestNormalUsage(unittest.TestCase):
|
|
|
301
301
|
self.assertEqual(code, 0)
|
|
302
302
|
self.assertIn("sqrt(0) = 0 (mod 17)", out)
|
|
303
303
|
|
|
304
|
+
def test_legendre_composite_p_errors(self):
|
|
305
|
+
code, out, err = run("legendre", "2", "9")
|
|
306
|
+
self.assertEqual(code, 1)
|
|
307
|
+
self.assertIn("p must be an odd prime", err)
|
|
308
|
+
|
|
309
|
+
def test_sqrtmod_composite_p_errors(self):
|
|
310
|
+
code, out, err = run("sqrtmod", "2", "9")
|
|
311
|
+
self.assertEqual(code, 1)
|
|
312
|
+
self.assertIn("p must be an odd prime", err)
|
|
313
|
+
|
|
304
314
|
|
|
305
315
|
if __name__ == "__main__":
|
|
306
316
|
unittest.main()
|
|
@@ -15,7 +15,7 @@ sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..",
|
|
|
15
15
|
from foxmath.foxmath import (
|
|
16
16
|
legendre_symbol, sqrt_mod, euler_hermann_pi_approx, ec_point_add, ec_mul, crt_solve,
|
|
17
17
|
continued_fraction, cf_convergents, CURVES, is_on_curve,
|
|
18
|
-
generate_problem,
|
|
18
|
+
generate_problem, _is_probable_prime,
|
|
19
19
|
)
|
|
20
20
|
import random
|
|
21
21
|
|
|
@@ -51,6 +51,12 @@ class TestLegendreSymbol(unittest.TestCase):
|
|
|
51
51
|
with self.assertRaises(ValueError):
|
|
52
52
|
legendre_symbol(3, 1)
|
|
53
53
|
|
|
54
|
+
def test_rejects_composite_odd_p(self):
|
|
55
|
+
for comp in (9, 15, 21, 25, 27, 33, 35, 561):
|
|
56
|
+
with self.subTest(p=comp):
|
|
57
|
+
with self.assertRaises(ValueError):
|
|
58
|
+
legendre_symbol(2, comp)
|
|
59
|
+
|
|
54
60
|
|
|
55
61
|
class TestSqrtMod(unittest.TestCase):
|
|
56
62
|
def test_tonelli_shanks_returns_both_roots(self):
|
|
@@ -93,6 +99,32 @@ class TestSqrtMod(unittest.TestCase):
|
|
|
93
99
|
self.assertEqual(roots_97, (14, 83))
|
|
94
100
|
self.assertTrue(all((r * r - 2) % 97 == 0 for r in roots_97))
|
|
95
101
|
|
|
102
|
+
def test_rejects_composite_odd_p(self):
|
|
103
|
+
for comp in (9, 15, 25, 561):
|
|
104
|
+
with self.subTest(p=comp):
|
|
105
|
+
with self.assertRaises(ValueError):
|
|
106
|
+
sqrt_mod(4, comp)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
class TestPrimality(unittest.TestCase):
|
|
110
|
+
def test_small_primes(self):
|
|
111
|
+
for p in (2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 97, 193):
|
|
112
|
+
with self.subTest(p=p):
|
|
113
|
+
self.assertTrue(_is_probable_prime(p))
|
|
114
|
+
|
|
115
|
+
def test_small_composites(self):
|
|
116
|
+
for c in (0, 1, 4, 6, 8, 9, 10, 12, 14, 15, 16, 18, 20, 21, 25, 27):
|
|
117
|
+
with self.subTest(c=c):
|
|
118
|
+
self.assertFalse(_is_probable_prime(c))
|
|
119
|
+
|
|
120
|
+
def test_carmichael_numbers_rejected(self):
|
|
121
|
+
for cn in (561, 1105, 1729):
|
|
122
|
+
with self.subTest(cn=cn):
|
|
123
|
+
self.assertFalse(_is_probable_prime(cn))
|
|
124
|
+
|
|
125
|
+
def test_mersenne_prime(self):
|
|
126
|
+
self.assertTrue(_is_probable_prime(2147483647))
|
|
127
|
+
|
|
96
128
|
|
|
97
129
|
class TestEulerHermannPiApprox(unittest.TestCase):
|
|
98
130
|
def test_converges_to_pi(self):
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|