foxmath 0.2.0__tar.gz → 0.4.0__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.2.0/src/foxmath.egg-info → foxmath-0.4.0}/PKG-INFO +26 -4
- {foxmath-0.2.0 → foxmath-0.4.0}/README.md +25 -3
- {foxmath-0.2.0 → foxmath-0.4.0}/pyproject.toml +2 -1
- {foxmath-0.2.0 → foxmath-0.4.0}/src/foxmath/foxmath.py +113 -8
- {foxmath-0.2.0 → foxmath-0.4.0/src/foxmath.egg-info}/PKG-INFO +26 -4
- {foxmath-0.2.0 → foxmath-0.4.0}/src/foxmath.egg-info/entry_points.txt +1 -0
- {foxmath-0.2.0 → foxmath-0.4.0}/tests/test_cli.py +48 -0
- {foxmath-0.2.0 → foxmath-0.4.0}/tests/test_foxmath.py +61 -1
- {foxmath-0.2.0 → foxmath-0.4.0}/LICENSE +0 -0
- {foxmath-0.2.0 → foxmath-0.4.0}/setup.cfg +0 -0
- {foxmath-0.2.0 → foxmath-0.4.0}/src/foxmath/__init__.py +0 -0
- {foxmath-0.2.0 → foxmath-0.4.0}/src/foxmath.egg-info/SOURCES.txt +0 -0
- {foxmath-0.2.0 → foxmath-0.4.0}/src/foxmath.egg-info/dependency_links.txt +0 -0
- {foxmath-0.2.0 → foxmath-0.4.0}/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.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: Minimalist CLI for math & cryptography exploration 🦊
|
|
5
5
|
Author-email: Abhrankan Chakrabarti <abhrankan@gmail.com>
|
|
6
6
|
License: MIT
|
|
@@ -28,11 +28,13 @@ Built with curiosity — for students, tinkerers, and crypto/math enthusiasts.
|
|
|
28
28
|
## Features
|
|
29
29
|
- Legendre symbol
|
|
30
30
|
- Euler/Hermann Machin-like π approximation
|
|
31
|
-
- Elliptic curve point addition (over finite fields)
|
|
31
|
+
- Elliptic curve point addition (over finite fields), including
|
|
32
|
+
named curves (secp256k1) with on-curve result validation
|
|
32
33
|
- Chinese Remainder Theorem solver (handles non-coprime moduli)
|
|
33
|
-
-
|
|
34
|
+
- Continued fraction expansion and convergents
|
|
35
|
+
- Symlink-friendly (foxmath-legendre, foxmath-pi, foxmath-ecadd, foxmath-crt, foxmath-cf)
|
|
34
36
|
- JSON output mode
|
|
35
|
-
- Coming soon:
|
|
37
|
+
- Coming soon: challenge mode
|
|
36
38
|
|
|
37
39
|
## Installation
|
|
38
40
|
|
|
@@ -71,6 +73,15 @@ foxmath-ecadd --x1 1 --y1 2 --x2 3 --y2 4 --p 17
|
|
|
71
73
|
Result point: (14, 2)
|
|
72
74
|
```
|
|
73
75
|
|
|
76
|
+
```bash
|
|
77
|
+
# Named curve (secp256k1) — computes 2G by default
|
|
78
|
+
foxmath ecadd --curve secp256k1
|
|
79
|
+
```
|
|
80
|
+
```
|
|
81
|
+
Result point: (89565891926547004231252920425935692360644145829622209833684329913297188986597, 12158399299693830322967808612713398636155367887041628176798871954788371653930)
|
|
82
|
+
✓ on curve
|
|
83
|
+
```
|
|
84
|
+
|
|
74
85
|
```bash
|
|
75
86
|
# Chinese Remainder Theorem
|
|
76
87
|
foxmath crt --r 2 3 2 --m 3 5 7
|
|
@@ -79,6 +90,17 @@ foxmath crt --r 2 3 2 --m 3 5 7
|
|
|
79
90
|
x ≡ 23 (mod 105)
|
|
80
91
|
```
|
|
81
92
|
|
|
93
|
+
```bash
|
|
94
|
+
# Continued fraction expansion + convergents
|
|
95
|
+
foxmath cf --num 355 --den 113
|
|
96
|
+
```
|
|
97
|
+
```
|
|
98
|
+
[3; 7, 16]
|
|
99
|
+
3/1
|
|
100
|
+
22/7
|
|
101
|
+
355/113
|
|
102
|
+
```
|
|
103
|
+
|
|
82
104
|
```bash
|
|
83
105
|
# JSON output
|
|
84
106
|
foxmath pi --terms 100 --json
|
|
@@ -9,11 +9,13 @@ Built with curiosity — for students, tinkerers, and crypto/math enthusiasts.
|
|
|
9
9
|
## Features
|
|
10
10
|
- Legendre symbol
|
|
11
11
|
- Euler/Hermann Machin-like π approximation
|
|
12
|
-
- Elliptic curve point addition (over finite fields)
|
|
12
|
+
- Elliptic curve point addition (over finite fields), including
|
|
13
|
+
named curves (secp256k1) with on-curve result validation
|
|
13
14
|
- Chinese Remainder Theorem solver (handles non-coprime moduli)
|
|
14
|
-
-
|
|
15
|
+
- Continued fraction expansion and convergents
|
|
16
|
+
- Symlink-friendly (foxmath-legendre, foxmath-pi, foxmath-ecadd, foxmath-crt, foxmath-cf)
|
|
15
17
|
- JSON output mode
|
|
16
|
-
- Coming soon:
|
|
18
|
+
- Coming soon: challenge mode
|
|
17
19
|
|
|
18
20
|
## Installation
|
|
19
21
|
|
|
@@ -52,6 +54,15 @@ foxmath-ecadd --x1 1 --y1 2 --x2 3 --y2 4 --p 17
|
|
|
52
54
|
Result point: (14, 2)
|
|
53
55
|
```
|
|
54
56
|
|
|
57
|
+
```bash
|
|
58
|
+
# Named curve (secp256k1) — computes 2G by default
|
|
59
|
+
foxmath ecadd --curve secp256k1
|
|
60
|
+
```
|
|
61
|
+
```
|
|
62
|
+
Result point: (89565891926547004231252920425935692360644145829622209833684329913297188986597, 12158399299693830322967808612713398636155367887041628176798871954788371653930)
|
|
63
|
+
✓ on curve
|
|
64
|
+
```
|
|
65
|
+
|
|
55
66
|
```bash
|
|
56
67
|
# Chinese Remainder Theorem
|
|
57
68
|
foxmath crt --r 2 3 2 --m 3 5 7
|
|
@@ -60,6 +71,17 @@ foxmath crt --r 2 3 2 --m 3 5 7
|
|
|
60
71
|
x ≡ 23 (mod 105)
|
|
61
72
|
```
|
|
62
73
|
|
|
74
|
+
```bash
|
|
75
|
+
# Continued fraction expansion + convergents
|
|
76
|
+
foxmath cf --num 355 --den 113
|
|
77
|
+
```
|
|
78
|
+
```
|
|
79
|
+
[3; 7, 16]
|
|
80
|
+
3/1
|
|
81
|
+
22/7
|
|
82
|
+
355/113
|
|
83
|
+
```
|
|
84
|
+
|
|
63
85
|
```bash
|
|
64
86
|
# JSON output
|
|
65
87
|
foxmath pi --terms 100 --json
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "foxmath"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.4.0"
|
|
8
8
|
description = "Minimalist CLI for math & cryptography exploration 🦊"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = {text = "MIT"}
|
|
@@ -32,3 +32,4 @@ foxmath-legendre = "foxmath.foxmath:main"
|
|
|
32
32
|
foxmath-pi = "foxmath.foxmath:main"
|
|
33
33
|
foxmath-ecadd = "foxmath.foxmath:main"
|
|
34
34
|
foxmath-crt = "foxmath.foxmath:main"
|
|
35
|
+
foxmath-cf = "foxmath.foxmath:main"
|
|
@@ -54,6 +54,28 @@ def ec_point_add(x1, y1, x2, y2, a, p):
|
|
|
54
54
|
y3 = (lam * (x1 - x3) - y1) % p
|
|
55
55
|
return x3, y3
|
|
56
56
|
|
|
57
|
+
# Named curve presets: a, b, p, and the standard generator point G (if defined).
|
|
58
|
+
CURVES = {
|
|
59
|
+
"secp256k1": {
|
|
60
|
+
"a": 0,
|
|
61
|
+
"b": 7,
|
|
62
|
+
"p": 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F,
|
|
63
|
+
"gx": 0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798,
|
|
64
|
+
"gy": 0x483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8,
|
|
65
|
+
},
|
|
66
|
+
"toy97": { # small curve for quick hand-checkable examples: y^2 = x^3 + 2x + 3 (mod 97)
|
|
67
|
+
"a": 2,
|
|
68
|
+
"b": 3,
|
|
69
|
+
"p": 97,
|
|
70
|
+
"gx": 3,
|
|
71
|
+
"gy": 6,
|
|
72
|
+
},
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
def is_on_curve(x: int, y: int, a: int, b: int, p: int) -> bool:
|
|
76
|
+
"""Check y^2 = x^3 + a*x + b (mod p)."""
|
|
77
|
+
return (y * y - (x ** 3 + a * x + b)) % p == 0
|
|
78
|
+
|
|
57
79
|
def _extended_gcd(a: int, b: int):
|
|
58
80
|
"""Returns (g, x, y) such that a*x + b*y == g == gcd(a, b)."""
|
|
59
81
|
old_r, r = a, b
|
|
@@ -96,6 +118,38 @@ def crt_solve(remainders, moduli):
|
|
|
96
118
|
r, m = _combine_crt(r, m, ri % mi, mi)
|
|
97
119
|
return r, m
|
|
98
120
|
|
|
121
|
+
def continued_fraction(num: int, den: int, max_terms: int = 100):
|
|
122
|
+
"""
|
|
123
|
+
Continued fraction expansion [a0; a1, a2, ...] of num/den via the
|
|
124
|
+
Euclidean algorithm. Terminates naturally for any rational input.
|
|
125
|
+
"""
|
|
126
|
+
if den == 0:
|
|
127
|
+
raise ValueError("denominator cannot be zero")
|
|
128
|
+
cf = []
|
|
129
|
+
n, d = num, den
|
|
130
|
+
while d != 0 and len(cf) < max_terms:
|
|
131
|
+
a = n // d
|
|
132
|
+
cf.append(a)
|
|
133
|
+
n, d = d, n - a * d
|
|
134
|
+
return cf
|
|
135
|
+
|
|
136
|
+
def cf_convergents(cf):
|
|
137
|
+
"""
|
|
138
|
+
Convergents (p_n, q_n) of a continued fraction, via the standard
|
|
139
|
+
recurrence h_n = a_n*h_(n-1) + h_(n-2), same for k_n, with
|
|
140
|
+
h_(-2)=0, h_(-1)=1, k_(-2)=1, k_(-1)=0.
|
|
141
|
+
"""
|
|
142
|
+
convergents = []
|
|
143
|
+
h2, h1 = 0, 1
|
|
144
|
+
k2, k1 = 1, 0
|
|
145
|
+
for a in cf:
|
|
146
|
+
h = a * h1 + h2
|
|
147
|
+
k = a * k1 + k2
|
|
148
|
+
convergents.append((h, k))
|
|
149
|
+
h2, h1 = h1, h
|
|
150
|
+
k2, k1 = k1, k
|
|
151
|
+
return convergents
|
|
152
|
+
|
|
99
153
|
# ------------------- CLI -------------------
|
|
100
154
|
|
|
101
155
|
def get_command():
|
|
@@ -139,18 +193,26 @@ def main():
|
|
|
139
193
|
|
|
140
194
|
# Elliptic curve add
|
|
141
195
|
ec = subparsers.add_parser("ecadd", help="Elliptic curve point addition")
|
|
142
|
-
ec.add_argument("--
|
|
143
|
-
|
|
144
|
-
ec.add_argument("--
|
|
145
|
-
ec.add_argument("--
|
|
146
|
-
ec.add_argument("--
|
|
147
|
-
ec.add_argument("--
|
|
196
|
+
ec.add_argument("--curve", choices=sorted(CURVES.keys()),
|
|
197
|
+
help="Named curve (provides a, b, p, and generator G)")
|
|
198
|
+
ec.add_argument("--x1", type=int, help="Defaults to G's x if --curve is given")
|
|
199
|
+
ec.add_argument("--y1", type=int, help="Defaults to G's y if --curve is given")
|
|
200
|
+
ec.add_argument("--x2", type=int, help="Defaults to (x1,y1) if omitted (i.e. doubling)")
|
|
201
|
+
ec.add_argument("--y2", type=int, help="Defaults to (x1,y1) if omitted (i.e. doubling)")
|
|
202
|
+
ec.add_argument("--a", type=int, help="Curve parameter a (overrides --curve if given)")
|
|
203
|
+
ec.add_argument("--p", type=int, help="Prime modulus (overrides --curve if given)")
|
|
148
204
|
|
|
149
205
|
# Chinese Remainder Theorem
|
|
150
206
|
crt = subparsers.add_parser("crt", help="Solve a system of congruences x = r (mod m)")
|
|
151
207
|
crt.add_argument("--r", type=int, nargs="+", required=True, help="Remainders, e.g. --r 2 3 2")
|
|
152
208
|
crt.add_argument("--m", type=int, nargs="+", required=True, help="Moduli, e.g. --m 3 5 7")
|
|
153
209
|
|
|
210
|
+
# Continued fractions
|
|
211
|
+
cf_cmd = subparsers.add_parser("cf", help="Continued fraction expansion and convergents")
|
|
212
|
+
cf_cmd.add_argument("--num", type=int, required=True, help="Numerator")
|
|
213
|
+
cf_cmd.add_argument("--den", type=int, required=True, help="Denominator")
|
|
214
|
+
cf_cmd.add_argument("--terms", type=int, default=100, help="Max terms to expand")
|
|
215
|
+
|
|
154
216
|
args = parser.parse_args(argv)
|
|
155
217
|
|
|
156
218
|
result = {"tool": "foxmath"}
|
|
@@ -169,10 +231,41 @@ def main():
|
|
|
169
231
|
print(f"π ≈ {approx} ({args.terms} terms)")
|
|
170
232
|
|
|
171
233
|
elif cmd == "ecadd" or args.command == "ecadd":
|
|
172
|
-
|
|
173
|
-
|
|
234
|
+
a, b = args.a, None
|
|
235
|
+
p = args.p
|
|
236
|
+
x1, y1 = args.x1, args.y1
|
|
237
|
+
|
|
238
|
+
if args.curve:
|
|
239
|
+
preset = CURVES[args.curve]
|
|
240
|
+
a = preset["a"] if a is None else a
|
|
241
|
+
p = preset["p"] if p is None else p
|
|
242
|
+
b = preset["b"]
|
|
243
|
+
if x1 is None:
|
|
244
|
+
x1, y1 = preset["gx"], preset["gy"]
|
|
245
|
+
elif a is None:
|
|
246
|
+
a = -3 # historical default for manual (non-curve) mode
|
|
247
|
+
|
|
248
|
+
if a is None or p is None or x1 is None or y1 is None:
|
|
249
|
+
raise ValueError(
|
|
250
|
+
"Provide --curve NAME, or at least --p --x1 --y1 manually "
|
|
251
|
+
"(--a defaults to -3 if omitted)."
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
x2 = x1 if args.x2 is None else args.x2
|
|
255
|
+
y2 = y1 if args.y2 is None else args.y2
|
|
256
|
+
|
|
257
|
+
x3, y3 = ec_point_add(x1, y1, x2, y2, a, p)
|
|
258
|
+
result.update({
|
|
259
|
+
"command": "ecadd", "curve": args.curve,
|
|
260
|
+
"p1": [x1, y1], "p2": [x2, y2], "result": (x3, y3),
|
|
261
|
+
})
|
|
262
|
+
if b is not None:
|
|
263
|
+
result["result_on_curve"] = is_on_curve(x3, y3, a, b, p)
|
|
174
264
|
if not json_output:
|
|
175
265
|
print(f"Result point: ({x3}, {y3})")
|
|
266
|
+
if b is not None:
|
|
267
|
+
status = "✓ on curve" if result["result_on_curve"] else "✗ NOT on curve"
|
|
268
|
+
print(f" {status}")
|
|
176
269
|
|
|
177
270
|
elif cmd == "crt" or args.command == "crt":
|
|
178
271
|
x, m = crt_solve(args.r, args.m)
|
|
@@ -180,6 +273,18 @@ def main():
|
|
|
180
273
|
if not json_output:
|
|
181
274
|
print(f"x ≡ {x} (mod {m})")
|
|
182
275
|
|
|
276
|
+
elif cmd == "cf" or args.command == "cf":
|
|
277
|
+
cf = continued_fraction(args.num, args.den, args.terms)
|
|
278
|
+
convs = cf_convergents(cf)
|
|
279
|
+
result.update({
|
|
280
|
+
"command": "cf", "num": args.num, "den": args.den,
|
|
281
|
+
"cf": cf, "convergents": convs,
|
|
282
|
+
})
|
|
283
|
+
if not json_output:
|
|
284
|
+
print(f"[{cf[0]}; {', '.join(str(a) for a in cf[1:])}]")
|
|
285
|
+
for p, q in convs:
|
|
286
|
+
print(f" {p}/{q}")
|
|
287
|
+
|
|
183
288
|
except Exception as e:
|
|
184
289
|
print(f"Error: {e}", file=sys.stderr)
|
|
185
290
|
sys.exit(1)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: foxmath
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: Minimalist CLI for math & cryptography exploration 🦊
|
|
5
5
|
Author-email: Abhrankan Chakrabarti <abhrankan@gmail.com>
|
|
6
6
|
License: MIT
|
|
@@ -28,11 +28,13 @@ Built with curiosity — for students, tinkerers, and crypto/math enthusiasts.
|
|
|
28
28
|
## Features
|
|
29
29
|
- Legendre symbol
|
|
30
30
|
- Euler/Hermann Machin-like π approximation
|
|
31
|
-
- Elliptic curve point addition (over finite fields)
|
|
31
|
+
- Elliptic curve point addition (over finite fields), including
|
|
32
|
+
named curves (secp256k1) with on-curve result validation
|
|
32
33
|
- Chinese Remainder Theorem solver (handles non-coprime moduli)
|
|
33
|
-
-
|
|
34
|
+
- Continued fraction expansion and convergents
|
|
35
|
+
- Symlink-friendly (foxmath-legendre, foxmath-pi, foxmath-ecadd, foxmath-crt, foxmath-cf)
|
|
34
36
|
- JSON output mode
|
|
35
|
-
- Coming soon:
|
|
37
|
+
- Coming soon: challenge mode
|
|
36
38
|
|
|
37
39
|
## Installation
|
|
38
40
|
|
|
@@ -71,6 +73,15 @@ foxmath-ecadd --x1 1 --y1 2 --x2 3 --y2 4 --p 17
|
|
|
71
73
|
Result point: (14, 2)
|
|
72
74
|
```
|
|
73
75
|
|
|
76
|
+
```bash
|
|
77
|
+
# Named curve (secp256k1) — computes 2G by default
|
|
78
|
+
foxmath ecadd --curve secp256k1
|
|
79
|
+
```
|
|
80
|
+
```
|
|
81
|
+
Result point: (89565891926547004231252920425935692360644145829622209833684329913297188986597, 12158399299693830322967808612713398636155367887041628176798871954788371653930)
|
|
82
|
+
✓ on curve
|
|
83
|
+
```
|
|
84
|
+
|
|
74
85
|
```bash
|
|
75
86
|
# Chinese Remainder Theorem
|
|
76
87
|
foxmath crt --r 2 3 2 --m 3 5 7
|
|
@@ -79,6 +90,17 @@ foxmath crt --r 2 3 2 --m 3 5 7
|
|
|
79
90
|
x ≡ 23 (mod 105)
|
|
80
91
|
```
|
|
81
92
|
|
|
93
|
+
```bash
|
|
94
|
+
# Continued fraction expansion + convergents
|
|
95
|
+
foxmath cf --num 355 --den 113
|
|
96
|
+
```
|
|
97
|
+
```
|
|
98
|
+
[3; 7, 16]
|
|
99
|
+
3/1
|
|
100
|
+
22/7
|
|
101
|
+
355/113
|
|
102
|
+
```
|
|
103
|
+
|
|
82
104
|
```bash
|
|
83
105
|
# JSON output
|
|
84
106
|
foxmath pi --terms 100 --json
|
|
@@ -85,6 +85,12 @@ class TestSymlinkInvocation(unittest.TestCase):
|
|
|
85
85
|
self.assertEqual(code, 0)
|
|
86
86
|
self.assertIn("x ≡ 23 (mod 105)", out)
|
|
87
87
|
|
|
88
|
+
def test_cf_symlink(self):
|
|
89
|
+
script = self._make_symlink_script("foxmath-cf")
|
|
90
|
+
code, out, err = run("--num", "355", "--den", "113", script=script)
|
|
91
|
+
self.assertEqual(code, 0)
|
|
92
|
+
self.assertIn("[3; 7, 16]", out)
|
|
93
|
+
|
|
88
94
|
def test_symlink_with_json(self):
|
|
89
95
|
script = self._make_symlink_script("foxmath-legendre")
|
|
90
96
|
code, out, err = run("2", "7", "--json", script=script)
|
|
@@ -104,6 +110,29 @@ class TestNormalUsage(unittest.TestCase):
|
|
|
104
110
|
self.assertEqual(code, 1)
|
|
105
111
|
self.assertIn("point at infinity", err)
|
|
106
112
|
|
|
113
|
+
def test_ecadd_curve_secp256k1_defaults_to_doubling_generator(self):
|
|
114
|
+
code, out, err = run("ecadd", "--curve", "secp256k1")
|
|
115
|
+
self.assertEqual(code, 0)
|
|
116
|
+
self.assertIn("89565891926547004231252920425935692360644145829622209833684329913297188986597", out)
|
|
117
|
+
self.assertIn("on curve", out)
|
|
118
|
+
|
|
119
|
+
def test_ecadd_curve_manual_a_override(self):
|
|
120
|
+
# --a explicitly provided should override the curve preset's a.
|
|
121
|
+
code, out, err = run("ecadd", "--curve", "toy97", "--a", "2")
|
|
122
|
+
self.assertEqual(code, 0)
|
|
123
|
+
|
|
124
|
+
def test_ecadd_no_args_at_all_errors_clearly(self):
|
|
125
|
+
code, out, err = run("ecadd")
|
|
126
|
+
self.assertEqual(code, 1)
|
|
127
|
+
self.assertIn("Error", err)
|
|
128
|
+
|
|
129
|
+
def test_ecadd_backward_compatible_manual_call(self):
|
|
130
|
+
# Original documented usage: --a omitted, defaults to -3.
|
|
131
|
+
code, out, err = run("ecadd", "--x1", "1", "--y1", "2",
|
|
132
|
+
"--x2", "3", "--y2", "4", "--p", "17")
|
|
133
|
+
self.assertEqual(code, 0)
|
|
134
|
+
self.assertIn("Result point: (14, 2)", out)
|
|
135
|
+
|
|
107
136
|
def test_crt(self):
|
|
108
137
|
code, out, err = run("crt", "--r", "2", "3", "2", "--m", "3", "5", "7")
|
|
109
138
|
self.assertEqual(code, 0)
|
|
@@ -122,6 +151,25 @@ class TestNormalUsage(unittest.TestCase):
|
|
|
122
151
|
self.assertEqual(code, 1)
|
|
123
152
|
self.assertIn("inconsistent", err)
|
|
124
153
|
|
|
154
|
+
def test_cf(self):
|
|
155
|
+
code, out, err = run("cf", "--num", "355", "--den", "113")
|
|
156
|
+
self.assertEqual(code, 0)
|
|
157
|
+
self.assertIn("[3; 7, 16]", out)
|
|
158
|
+
self.assertIn("355/113", out)
|
|
159
|
+
|
|
160
|
+
def test_cf_json_is_pure_json(self):
|
|
161
|
+
code, out, err = run("cf", "--num", "355", "--den", "113", "--json")
|
|
162
|
+
self.assertEqual(code, 0)
|
|
163
|
+
import json
|
|
164
|
+
parsed = json.loads(out)
|
|
165
|
+
self.assertEqual(parsed["cf"], [3, 7, 16])
|
|
166
|
+
self.assertEqual(parsed["convergents"][-1], [355, 113])
|
|
167
|
+
|
|
168
|
+
def test_cf_zero_denominator_errors(self):
|
|
169
|
+
code, out, err = run("cf", "--num", "5", "--den", "0")
|
|
170
|
+
self.assertEqual(code, 1)
|
|
171
|
+
self.assertIn("Error", err)
|
|
172
|
+
|
|
125
173
|
|
|
126
174
|
if __name__ == "__main__":
|
|
127
175
|
unittest.main()
|
|
@@ -12,7 +12,10 @@ from decimal import Decimal
|
|
|
12
12
|
|
|
13
13
|
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "src")))
|
|
14
14
|
|
|
15
|
-
from foxmath.foxmath import
|
|
15
|
+
from foxmath.foxmath import (
|
|
16
|
+
legendre_symbol, euler_hermann_pi_approx, ec_point_add, crt_solve,
|
|
17
|
+
continued_fraction, cf_convergents, CURVES, is_on_curve,
|
|
18
|
+
)
|
|
16
19
|
|
|
17
20
|
# Reference pi digits (mpmath, 250 dps of working precision), "3" + fractional digits.
|
|
18
21
|
PI_REFERENCE = Decimal(
|
|
@@ -115,5 +118,62 @@ class TestCRT(unittest.TestCase):
|
|
|
115
118
|
self.assertEqual(x % 7, 3)
|
|
116
119
|
|
|
117
120
|
|
|
121
|
+
class TestContinuedFractions(unittest.TestCase):
|
|
122
|
+
def test_classic_pi_approximation(self):
|
|
123
|
+
cf = continued_fraction(355, 113)
|
|
124
|
+
self.assertEqual(cf, [3, 7, 16])
|
|
125
|
+
convs = cf_convergents(cf)
|
|
126
|
+
self.assertEqual(convs, [(3, 1), (22, 7), (355, 113)])
|
|
127
|
+
|
|
128
|
+
def test_convergents_reduce_to_final_fraction(self):
|
|
129
|
+
cf = continued_fraction(19, 7)
|
|
130
|
+
convs = cf_convergents(cf)
|
|
131
|
+
self.assertEqual(convs[-1], (19, 7))
|
|
132
|
+
|
|
133
|
+
def test_negative_numerator(self):
|
|
134
|
+
cf = continued_fraction(-19, 7)
|
|
135
|
+
convs = cf_convergents(cf)
|
|
136
|
+
self.assertEqual(convs[-1], (-19, 7))
|
|
137
|
+
|
|
138
|
+
def test_zero_denominator_raises(self):
|
|
139
|
+
with self.assertRaises(ValueError):
|
|
140
|
+
continued_fraction(5, 0)
|
|
141
|
+
|
|
142
|
+
def test_randomized_against_fractions_module(self):
|
|
143
|
+
import random
|
|
144
|
+
from fractions import Fraction
|
|
145
|
+
random.seed(42)
|
|
146
|
+
for _ in range(200):
|
|
147
|
+
n = random.randint(-10000, 10000)
|
|
148
|
+
d = random.randint(1, 10000)
|
|
149
|
+
cf = continued_fraction(n, d)
|
|
150
|
+
convs = cf_convergents(cf)
|
|
151
|
+
expected = Fraction(n, d)
|
|
152
|
+
self.assertEqual(convs[-1], (expected.numerator, expected.denominator))
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
class TestNamedCurves(unittest.TestCase):
|
|
156
|
+
def test_secp256k1_generator_is_on_curve(self):
|
|
157
|
+
c = CURVES["secp256k1"]
|
|
158
|
+
self.assertTrue(is_on_curve(c["gx"], c["gy"], c["a"], c["b"], c["p"]))
|
|
159
|
+
|
|
160
|
+
def test_secp256k1_doubling_matches_known_value(self):
|
|
161
|
+
# Cross-checked independently against the `ecdsa` library's
|
|
162
|
+
# SECP256k1.generator * 2.
|
|
163
|
+
c = CURVES["secp256k1"]
|
|
164
|
+
x2, y2 = ec_point_add(c["gx"], c["gy"], c["gx"], c["gy"], c["a"], c["p"])
|
|
165
|
+
self.assertEqual(
|
|
166
|
+
x2, 89565891926547004231252920425935692360644145829622209833684329913297188986597
|
|
167
|
+
)
|
|
168
|
+
self.assertEqual(
|
|
169
|
+
y2, 12158399299693830322967808612713398636155367887041628176798871954788371653930
|
|
170
|
+
)
|
|
171
|
+
self.assertTrue(is_on_curve(x2, y2, c["a"], c["b"], c["p"]))
|
|
172
|
+
|
|
173
|
+
def test_toy97_generator_is_on_curve(self):
|
|
174
|
+
c = CURVES["toy97"]
|
|
175
|
+
self.assertTrue(is_on_curve(c["gx"], c["gy"], c["a"], c["b"], c["p"]))
|
|
176
|
+
|
|
177
|
+
|
|
118
178
|
if __name__ == "__main__":
|
|
119
179
|
unittest.main()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|