foxmath 0.6.1__tar.gz → 0.6.2__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.6.1/src/foxmath.egg-info → foxmath-0.6.2}/PKG-INFO +4 -2
- {foxmath-0.6.1 → foxmath-0.6.2}/README.md +3 -1
- {foxmath-0.6.1 → foxmath-0.6.2}/pyproject.toml +1 -1
- {foxmath-0.6.1 → foxmath-0.6.2/src/foxmath.egg-info}/PKG-INFO +4 -2
- {foxmath-0.6.1 → foxmath-0.6.2}/tests/test_cli.py +58 -0
- {foxmath-0.6.1 → foxmath-0.6.2}/tests/test_foxmath.py +103 -3
- {foxmath-0.6.1 → foxmath-0.6.2}/LICENSE +0 -0
- {foxmath-0.6.1 → foxmath-0.6.2}/setup.cfg +0 -0
- {foxmath-0.6.1 → foxmath-0.6.2}/src/foxmath/__init__.py +0 -0
- {foxmath-0.6.1 → foxmath-0.6.2}/src/foxmath/foxmath.py +0 -0
- {foxmath-0.6.1 → foxmath-0.6.2}/src/foxmath.egg-info/SOURCES.txt +0 -0
- {foxmath-0.6.1 → foxmath-0.6.2}/src/foxmath.egg-info/dependency_links.txt +0 -0
- {foxmath-0.6.1 → foxmath-0.6.2}/src/foxmath.egg-info/entry_points.txt +0 -0
- {foxmath-0.6.1 → foxmath-0.6.2}/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.6.
|
|
3
|
+
Version: 0.6.2
|
|
4
4
|
Summary: Minimalist CLI for math & cryptography exploration 🦊
|
|
5
5
|
Author-email: Abhrankan Chakrabarti <abhrankan@gmail.com>
|
|
6
6
|
License: MIT
|
|
@@ -21,6 +21,8 @@ Dynamic: license-file
|
|
|
21
21
|
|
|
22
22
|
**Minimalist CLI for math & cryptography exploration.**
|
|
23
23
|
|
|
24
|
+
**More about me / other projects:** [abhrankan.netlify.app](https://abhrankan.netlify.app)
|
|
25
|
+
|
|
24
26
|
One binary. Clean output. Zero footguns. Educational by design.
|
|
25
27
|
|
|
26
28
|
Built with curiosity — for students, tinkerers, and crypto/math enthusiasts.
|
|
@@ -58,7 +60,7 @@ Check which version you have installed:
|
|
|
58
60
|
foxmath --version
|
|
59
61
|
```
|
|
60
62
|
```
|
|
61
|
-
foxmath 0.6.
|
|
63
|
+
foxmath 0.6.2
|
|
62
64
|
```
|
|
63
65
|
|
|
64
66
|
## Usage Examples
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
**Minimalist CLI for math & cryptography exploration.**
|
|
4
4
|
|
|
5
|
+
**More about me / other projects:** [abhrankan.netlify.app](https://abhrankan.netlify.app)
|
|
6
|
+
|
|
5
7
|
One binary. Clean output. Zero footguns. Educational by design.
|
|
6
8
|
|
|
7
9
|
Built with curiosity — for students, tinkerers, and crypto/math enthusiasts.
|
|
@@ -39,7 +41,7 @@ Check which version you have installed:
|
|
|
39
41
|
foxmath --version
|
|
40
42
|
```
|
|
41
43
|
```
|
|
42
|
-
foxmath 0.6.
|
|
44
|
+
foxmath 0.6.2
|
|
43
45
|
```
|
|
44
46
|
|
|
45
47
|
## Usage Examples
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: foxmath
|
|
3
|
-
Version: 0.6.
|
|
3
|
+
Version: 0.6.2
|
|
4
4
|
Summary: Minimalist CLI for math & cryptography exploration 🦊
|
|
5
5
|
Author-email: Abhrankan Chakrabarti <abhrankan@gmail.com>
|
|
6
6
|
License: MIT
|
|
@@ -21,6 +21,8 @@ Dynamic: license-file
|
|
|
21
21
|
|
|
22
22
|
**Minimalist CLI for math & cryptography exploration.**
|
|
23
23
|
|
|
24
|
+
**More about me / other projects:** [abhrankan.netlify.app](https://abhrankan.netlify.app)
|
|
25
|
+
|
|
24
26
|
One binary. Clean output. Zero footguns. Educational by design.
|
|
25
27
|
|
|
26
28
|
Built with curiosity — for students, tinkerers, and crypto/math enthusiasts.
|
|
@@ -58,7 +60,7 @@ Check which version you have installed:
|
|
|
58
60
|
foxmath --version
|
|
59
61
|
```
|
|
60
62
|
```
|
|
61
|
-
foxmath 0.6.
|
|
63
|
+
foxmath 0.6.2
|
|
62
64
|
```
|
|
63
65
|
|
|
64
66
|
## Usage Examples
|
|
@@ -98,6 +98,12 @@ class TestSymlinkInvocation(unittest.TestCase):
|
|
|
98
98
|
self.assertEqual(code, 0)
|
|
99
99
|
self.assertIn("Answer:", out)
|
|
100
100
|
|
|
101
|
+
def test_ecmul_symlink(self):
|
|
102
|
+
script = self._make_symlink_script("foxmath-ecmul")
|
|
103
|
+
code, out, err = run("--curve", "toy193", "--k", "7", script=script)
|
|
104
|
+
self.assertEqual(code, 0)
|
|
105
|
+
self.assertIn("Result point: (66, 24)", out)
|
|
106
|
+
|
|
101
107
|
def test_symlink_with_json(self):
|
|
102
108
|
script = self._make_symlink_script("foxmath-legendre")
|
|
103
109
|
code, out, err = run("2", "7", "--json", script=script)
|
|
@@ -140,6 +146,58 @@ class TestNormalUsage(unittest.TestCase):
|
|
|
140
146
|
self.assertEqual(code, 0)
|
|
141
147
|
self.assertIn("Result point: (14, 2)", out)
|
|
142
148
|
|
|
149
|
+
def test_ecmul_readme_example_toy193(self):
|
|
150
|
+
# Exact usage/output shown in the README.
|
|
151
|
+
code, out, err = run("ecmul", "--curve", "toy193", "--k", "7")
|
|
152
|
+
self.assertEqual(code, 0)
|
|
153
|
+
self.assertIn("Result point: (66, 24)", out)
|
|
154
|
+
self.assertIn("on curve", out)
|
|
155
|
+
|
|
156
|
+
def test_ecmul_readme_example_identity(self):
|
|
157
|
+
code, out, err = run("ecmul", "--curve", "toy193", "--k", "193")
|
|
158
|
+
self.assertEqual(code, 0)
|
|
159
|
+
self.assertIn("point at infinity", out)
|
|
160
|
+
|
|
161
|
+
def test_ecmul_secp256k1_matches_known_doubling(self):
|
|
162
|
+
code, out, err = run("ecmul", "--curve", "secp256k1", "--k", "2")
|
|
163
|
+
self.assertEqual(code, 0)
|
|
164
|
+
self.assertIn("89565891926547004231252920425935692360644145829622209833684329913297188986597", out)
|
|
165
|
+
|
|
166
|
+
def test_ecmul_json_output(self):
|
|
167
|
+
code, out, err = run("ecmul", "--curve", "toy193", "--k", "7", "--json")
|
|
168
|
+
self.assertEqual(code, 0)
|
|
169
|
+
import json
|
|
170
|
+
parsed = json.loads(out)
|
|
171
|
+
self.assertEqual(parsed["result"], [66, 24])
|
|
172
|
+
self.assertTrue(parsed["result_on_curve"])
|
|
173
|
+
|
|
174
|
+
def test_ecmul_json_output_infinity(self):
|
|
175
|
+
code, out, err = run("ecmul", "--curve", "toy193", "--k", "193", "--json")
|
|
176
|
+
self.assertEqual(code, 0)
|
|
177
|
+
import json
|
|
178
|
+
parsed = json.loads(out)
|
|
179
|
+
self.assertEqual(parsed["result"], "infinity")
|
|
180
|
+
|
|
181
|
+
def test_ecmul_manual_mode_no_curve(self):
|
|
182
|
+
code, out, err = run("ecmul", "--p", "97", "--x", "3", "--y", "6",
|
|
183
|
+
"--a", "2", "--k", "3")
|
|
184
|
+
self.assertEqual(code, 0)
|
|
185
|
+
self.assertIn("Result point:", out)
|
|
186
|
+
|
|
187
|
+
def test_ecmul_a_override(self):
|
|
188
|
+
code, out, err = run("ecmul", "--curve", "toy97", "--a", "2", "--k", "3")
|
|
189
|
+
self.assertEqual(code, 0)
|
|
190
|
+
|
|
191
|
+
def test_ecmul_missing_curve_and_incomplete_manual_args_errors(self):
|
|
192
|
+
code, out, err = run("ecmul", "--k", "5")
|
|
193
|
+
self.assertEqual(code, 1)
|
|
194
|
+
self.assertIn("Error", err)
|
|
195
|
+
|
|
196
|
+
def test_ecmul_negative_k_errors(self):
|
|
197
|
+
code, out, err = run("ecmul", "--curve", "toy193", "--k", "-5")
|
|
198
|
+
self.assertEqual(code, 1)
|
|
199
|
+
self.assertIn("Error", err)
|
|
200
|
+
|
|
143
201
|
def test_crt(self):
|
|
144
202
|
code, out, err = run("crt", "--r", "2", "3", "2", "--m", "3", "5", "7")
|
|
145
203
|
self.assertEqual(code, 0)
|
|
@@ -13,7 +13,7 @@ from decimal import Decimal
|
|
|
13
13
|
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "src")))
|
|
14
14
|
|
|
15
15
|
from foxmath.foxmath import (
|
|
16
|
-
legendre_symbol, euler_hermann_pi_approx, ec_point_add, crt_solve,
|
|
16
|
+
legendre_symbol, euler_hermann_pi_approx, ec_point_add, ec_mul, crt_solve,
|
|
17
17
|
continued_fraction, cf_convergents, CURVES, is_on_curve,
|
|
18
18
|
generate_problem,
|
|
19
19
|
)
|
|
@@ -154,6 +154,103 @@ class TestContinuedFractions(unittest.TestCase):
|
|
|
154
154
|
self.assertEqual(convs[-1], (expected.numerator, expected.denominator))
|
|
155
155
|
|
|
156
156
|
|
|
157
|
+
class TestECMul(unittest.TestCase):
|
|
158
|
+
"""
|
|
159
|
+
ec_mul (scalar multiplication / double-and-add) had no test coverage
|
|
160
|
+
at all despite being a documented headline feature -- these tests
|
|
161
|
+
were added after finding and closing that gap, not in response to
|
|
162
|
+
any bug (cross-validation below found none).
|
|
163
|
+
"""
|
|
164
|
+
|
|
165
|
+
def _naive_mul(self, k, x, y, a, p):
|
|
166
|
+
# Independent reference: k repeated additions via the
|
|
167
|
+
# user-facing ec_point_add, rather than ec_mul's own
|
|
168
|
+
# double-and-add logic.
|
|
169
|
+
if k == 0:
|
|
170
|
+
return None
|
|
171
|
+
result = None
|
|
172
|
+
point = (x, y)
|
|
173
|
+
for _ in range(k):
|
|
174
|
+
if result is None:
|
|
175
|
+
result = point
|
|
176
|
+
else:
|
|
177
|
+
try:
|
|
178
|
+
result = ec_point_add(result[0], result[1], point[0], point[1], a, p)
|
|
179
|
+
except ValueError:
|
|
180
|
+
result = None # hit the point at infinity
|
|
181
|
+
return result
|
|
182
|
+
|
|
183
|
+
def test_matches_naive_repeated_addition_toy97(self):
|
|
184
|
+
c = CURVES["toy97"]
|
|
185
|
+
for k in [1, 2, 3, 5, 7, 10, 13, 20]:
|
|
186
|
+
fast = ec_mul(k, c["gx"], c["gy"], c["a"], c["p"])
|
|
187
|
+
naive = self._naive_mul(k, c["gx"], c["gy"], c["a"], c["p"])
|
|
188
|
+
self.assertEqual(fast, naive, f"mismatch at k={k}")
|
|
189
|
+
|
|
190
|
+
def test_matches_naive_repeated_addition_toy193(self):
|
|
191
|
+
c = CURVES["toy193"]
|
|
192
|
+
for k in [1, 2, 3, 5, 7, 10, 13, 20]:
|
|
193
|
+
fast = ec_mul(k, c["gx"], c["gy"], c["a"], c["p"])
|
|
194
|
+
naive = self._naive_mul(k, c["gx"], c["gy"], c["a"], c["p"])
|
|
195
|
+
self.assertEqual(fast, naive, f"mismatch at k={k}")
|
|
196
|
+
|
|
197
|
+
def test_secp256k1_matches_independent_reference_including_large_k(self):
|
|
198
|
+
# Reference points cross-checked independently against the
|
|
199
|
+
# `ecdsa` library's SECP256k1.generator * k (same verification
|
|
200
|
+
# approach the pre-existing doubling test above uses). Values
|
|
201
|
+
# are baked in rather than imported at test time so this suite
|
|
202
|
+
# doesn't pick up a runtime dependency on `ecdsa` -- foxmath
|
|
203
|
+
# itself stays pure stdlib (dependencies = [] in pyproject.toml).
|
|
204
|
+
c = CURVES["secp256k1"]
|
|
205
|
+
reference = {
|
|
206
|
+
1: (55066263022277343669578718895168534326250603453777594175500187360389116729240,
|
|
207
|
+
32670510020758816978083085130507043184471273380659243275938904335757337482424),
|
|
208
|
+
2: (89565891926547004231252920425935692360644145829622209833684329913297188986597,
|
|
209
|
+
12158399299693830322967808612713398636155367887041628176798871954788371653930),
|
|
210
|
+
3: (112711660439710606056748659173929673102114977341539408544630613555209775888121,
|
|
211
|
+
25583027980570883691656905877401976406448868254816295069919888960541586679410),
|
|
212
|
+
7: (41948375291644419605210209193538855353224492619856392092318293986323063962044,
|
|
213
|
+
48361766907851246668144012348516735800090617714386977531302791340517493990618),
|
|
214
|
+
100: (107303582290733097924842193972465022053148211775194373671539518313500194639752,
|
|
215
|
+
103795966108782717446806684023742168462365449272639790795591544606836007446638),
|
|
216
|
+
12345: (108607064596551879580190606910245687803607295064141551927605737287325610911759,
|
|
217
|
+
6661302038839728943522144359728938428925407345457796456954441906546235843221),
|
|
218
|
+
# A scalar well beyond anything repeated addition could feasibly verify.
|
|
219
|
+
2**64 + 17: (5136902638900309784563324385263118307222550852398366895311091889317425008288,
|
|
220
|
+
19220983661714808463696268177333916812952649794945216772534518833957341771155),
|
|
221
|
+
}
|
|
222
|
+
for k, expected in reference.items():
|
|
223
|
+
fast = ec_mul(k, c["gx"], c["gy"], c["a"], c["p"])
|
|
224
|
+
self.assertEqual(fast, expected, f"mismatch at k={k}")
|
|
225
|
+
|
|
226
|
+
def test_toy193_has_prime_order_full_cycle(self):
|
|
227
|
+
# README's documented claim: #E = 193 (prime), so every non-zero
|
|
228
|
+
# multiple up to 192 should land on a distinct on-curve point,
|
|
229
|
+
# and k=193 (the order) should land on infinity.
|
|
230
|
+
c = CURVES["toy193"]
|
|
231
|
+
seen = set()
|
|
232
|
+
for k in range(1, 193):
|
|
233
|
+
pt = ec_mul(k, c["gx"], c["gy"], c["a"], c["p"])
|
|
234
|
+
self.assertIsNotNone(pt, f"unexpected infinity at k={k}")
|
|
235
|
+
self.assertTrue(is_on_curve(pt[0], pt[1], c["a"], c["b"], c["p"]))
|
|
236
|
+
self.assertNotIn(pt, seen, f"duplicate point at k={k} -- order is not 193")
|
|
237
|
+
seen.add(pt)
|
|
238
|
+
self.assertEqual(len(seen), 192)
|
|
239
|
+
|
|
240
|
+
def test_k_zero_is_infinity(self):
|
|
241
|
+
c = CURVES["toy193"]
|
|
242
|
+
self.assertIsNone(ec_mul(0, c["gx"], c["gy"], c["a"], c["p"]))
|
|
243
|
+
|
|
244
|
+
def test_k_equal_to_order_is_infinity(self):
|
|
245
|
+
c = CURVES["toy193"]
|
|
246
|
+
self.assertIsNone(ec_mul(193, c["gx"], c["gy"], c["a"], c["p"]))
|
|
247
|
+
|
|
248
|
+
def test_negative_k_raises(self):
|
|
249
|
+
c = CURVES["toy193"]
|
|
250
|
+
with self.assertRaises(ValueError):
|
|
251
|
+
ec_mul(-5, c["gx"], c["gy"], c["a"], c["p"])
|
|
252
|
+
|
|
253
|
+
|
|
157
254
|
class TestNamedCurves(unittest.TestCase):
|
|
158
255
|
def test_secp256k1_generator_is_on_curve(self):
|
|
159
256
|
c = CURVES["secp256k1"]
|
|
@@ -193,10 +290,13 @@ class TestChallengeGenerators(unittest.TestCase):
|
|
|
193
290
|
self.assertEqual(prob["topic"], "crt")
|
|
194
291
|
self.assertIsInstance(prob["answer"], int)
|
|
195
292
|
|
|
196
|
-
def
|
|
293
|
+
def test_mixed_topic_produces_all_kinds(self):
|
|
294
|
+
# mixed draws from every registered generator -- legendre, crt,
|
|
295
|
+
# and ecdlp (this assertion was stale: it predated ecdlp being
|
|
296
|
+
# added to _TOPIC_GENERATORS, and only checked for {legendre, crt}).
|
|
197
297
|
rng = random.Random(1)
|
|
198
298
|
topics = {generate_problem("mixed", rng)["topic"] for _ in range(30)}
|
|
199
|
-
self.assertEqual(topics, {"legendre", "crt"})
|
|
299
|
+
self.assertEqual(topics, {"legendre", "crt", "ecdlp"})
|
|
200
300
|
|
|
201
301
|
def test_reproducible_with_same_seed(self):
|
|
202
302
|
p1 = generate_problem("legendre", random.Random(99))
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|