transcrypto 1.7.0__py3-none-any.whl → 1.8.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 +1 -1
- transcrypto/base.py +58 -339
- transcrypto/cli/__init__.py +3 -0
- transcrypto/cli/aeshash.py +368 -0
- transcrypto/cli/bidsecret.py +334 -0
- transcrypto/cli/clibase.py +303 -0
- transcrypto/cli/intmath.py +427 -0
- transcrypto/cli/publicalgos.py +877 -0
- transcrypto/profiler.py +10 -7
- transcrypto/transcrypto.py +40 -1986
- {transcrypto-1.7.0.dist-info → transcrypto-1.8.0.dist-info}/METADATA +12 -10
- transcrypto-1.8.0.dist-info/RECORD +23 -0
- transcrypto-1.7.0.dist-info/RECORD +0 -17
- {transcrypto-1.7.0.dist-info → transcrypto-1.8.0.dist-info}/WHEEL +0 -0
- {transcrypto-1.7.0.dist-info → transcrypto-1.8.0.dist-info}/entry_points.txt +0 -0
- {transcrypto-1.7.0.dist-info → transcrypto-1.8.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,427 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: Copyright 2026 Daniel Balparda <balparda@github.com>
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
"""Balparda's TransCrypto CLI: Integer mathematics commands."""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import typer
|
|
8
|
+
|
|
9
|
+
from transcrypto import base, modmath, transcrypto
|
|
10
|
+
from transcrypto.cli import clibase
|
|
11
|
+
|
|
12
|
+
# =============================== "PRIME"-like COMMANDS ============================================
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@transcrypto.app.command(
|
|
16
|
+
'isprime',
|
|
17
|
+
help='Primality test with safe defaults, useful for any integer size.',
|
|
18
|
+
epilog=(
|
|
19
|
+
'Example:\n\n\n\n'
|
|
20
|
+
'$ poetry run transcrypto isprime 2305843009213693951\n\n'
|
|
21
|
+
'True\n\n'
|
|
22
|
+
'$ poetry run transcrypto isprime 2305843009213693953\n\n'
|
|
23
|
+
'False'
|
|
24
|
+
),
|
|
25
|
+
)
|
|
26
|
+
@clibase.CLIErrorGuard
|
|
27
|
+
def IsPrimeCLI( # documentation is help/epilog/args # noqa: D103
|
|
28
|
+
*,
|
|
29
|
+
ctx: typer.Context,
|
|
30
|
+
n: str = typer.Argument(..., help='Integer to test, ≥ 1'),
|
|
31
|
+
) -> None:
|
|
32
|
+
config: transcrypto.TransConfig = ctx.obj
|
|
33
|
+
n_i: int = transcrypto.ParseInt(n, min_value=1)
|
|
34
|
+
config.console.print(str(modmath.IsPrime(n_i)))
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@transcrypto.app.command(
|
|
38
|
+
'primegen',
|
|
39
|
+
help='Generate (stream) primes ≥ `start` (prints a limited `count` by default).',
|
|
40
|
+
epilog=('Example:\n\n\n\n$ poetry run transcrypto primegen 100 -c 3\n\n101\n\n103\n\n107'),
|
|
41
|
+
)
|
|
42
|
+
@clibase.CLIErrorGuard
|
|
43
|
+
def PrimeGenCLI( # documentation is help/epilog/args # noqa: D103
|
|
44
|
+
*,
|
|
45
|
+
ctx: typer.Context,
|
|
46
|
+
start: str = typer.Argument(..., help='Starting integer (inclusive), ≥ 0'),
|
|
47
|
+
count: int = typer.Option(1, '-c', '--count', min=1, help='How many to print, ≥ 1'),
|
|
48
|
+
) -> None:
|
|
49
|
+
config: transcrypto.TransConfig = ctx.obj
|
|
50
|
+
start_i: int = transcrypto.ParseInt(start, min_value=0)
|
|
51
|
+
for i, pr in enumerate(modmath.PrimeGenerator(start_i)):
|
|
52
|
+
if i >= count:
|
|
53
|
+
return
|
|
54
|
+
config.console.print(pr)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@transcrypto.app.command(
|
|
58
|
+
'mersenne',
|
|
59
|
+
help=(
|
|
60
|
+
'Generate (stream) Mersenne prime exponents `k`, also outputting `2^k-1` '
|
|
61
|
+
'(the Mersenne prime, `M`) and `M×2^(k-1)` (the associated perfect number), ' # noqa: RUF001
|
|
62
|
+
'starting at `min-k` and stopping once `k` > `max-k`.'
|
|
63
|
+
),
|
|
64
|
+
epilog=(
|
|
65
|
+
'Example:\n\n\n\n'
|
|
66
|
+
'$ poetry run transcrypto mersenne -k 0 -m 15\n\n'
|
|
67
|
+
'k=2 M=3 perfect=6\n\n'
|
|
68
|
+
'k=3 M=7 perfect=28\n\n'
|
|
69
|
+
'k=5 M=31 perfect=496\n\n'
|
|
70
|
+
'k=7 M=127 perfect=8128\n\n'
|
|
71
|
+
'k=13 M=8191 perfect=33550336\n\n'
|
|
72
|
+
'k=17 M=131071 perfect=8589869056'
|
|
73
|
+
),
|
|
74
|
+
)
|
|
75
|
+
@clibase.CLIErrorGuard
|
|
76
|
+
def MersenneCLI( # documentation is help/epilog/args # noqa: D103
|
|
77
|
+
*,
|
|
78
|
+
ctx: typer.Context,
|
|
79
|
+
min_k: int = typer.Option(2, '-k', '--min-k', min=1, help='Starting exponent `k`, ≥ 2'),
|
|
80
|
+
max_k: int = typer.Option(10000, '-m', '--max-k', min=1, help='Stop once `k` > `max-k`, ≥ 2'),
|
|
81
|
+
) -> None:
|
|
82
|
+
config: transcrypto.TransConfig = ctx.obj
|
|
83
|
+
if max_k < min_k:
|
|
84
|
+
raise base.InputError(f'max-k ({max_k}) must be >= min-k ({min_k})')
|
|
85
|
+
for k, m, perfect in modmath.MersennePrimesGenerator(min_k):
|
|
86
|
+
if k > max_k:
|
|
87
|
+
return
|
|
88
|
+
config.console.print(f'k={k} M={m} perfect={perfect}')
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
# ================================== "*GCD" COMMANDS ===============================================
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
@transcrypto.app.command(
|
|
95
|
+
'gcd',
|
|
96
|
+
help='Greatest Common Divisor (GCD) of integers `a` and `b`.',
|
|
97
|
+
epilog=(
|
|
98
|
+
'Example:\n\n\n\n'
|
|
99
|
+
'$ poetry run transcrypto gcd 462 1071\n\n'
|
|
100
|
+
'21\n\n'
|
|
101
|
+
'$ poetry run transcrypto gcd 0 5\n\n'
|
|
102
|
+
'5\n\n'
|
|
103
|
+
'$ poetry run transcrypto gcd 127 13\n\n'
|
|
104
|
+
'1'
|
|
105
|
+
),
|
|
106
|
+
)
|
|
107
|
+
@clibase.CLIErrorGuard
|
|
108
|
+
def GcdCLI( # documentation is help/epilog/args # noqa: D103
|
|
109
|
+
*,
|
|
110
|
+
ctx: typer.Context,
|
|
111
|
+
a: str = typer.Argument(..., help='Integer, ≥ 0'),
|
|
112
|
+
b: str = typer.Argument(..., help="Integer, ≥ 0 (can't be both zero)"),
|
|
113
|
+
) -> None:
|
|
114
|
+
config: transcrypto.TransConfig = ctx.obj
|
|
115
|
+
a_i: int = transcrypto.ParseInt(a, min_value=0)
|
|
116
|
+
b_i: int = transcrypto.ParseInt(b, min_value=0)
|
|
117
|
+
if a_i == 0 and b_i == 0:
|
|
118
|
+
raise base.InputError("`a` and `b` can't both be zero")
|
|
119
|
+
config.console.print(base.GCD(a_i, b_i))
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
@transcrypto.app.command(
|
|
123
|
+
'xgcd',
|
|
124
|
+
help=(
|
|
125
|
+
'Extended Greatest Common Divisor (x-GCD) of integers `a` and `b`, '
|
|
126
|
+
'will return `(g, x, y)` where `a×x+b×y==g`.' # noqa: RUF001
|
|
127
|
+
),
|
|
128
|
+
epilog=(
|
|
129
|
+
'Example:\n\n\n\n'
|
|
130
|
+
'$ poetry run transcrypto xgcd 462 1071\n\n'
|
|
131
|
+
'(21, 7, -3)\n\n'
|
|
132
|
+
'$ poetry run transcrypto xgcd 0 5\n\n'
|
|
133
|
+
'(5, 0, 1)\n\n'
|
|
134
|
+
'$ poetry run transcrypto xgcd 127 13\n\n'
|
|
135
|
+
'(1, 4, -39)'
|
|
136
|
+
),
|
|
137
|
+
)
|
|
138
|
+
@clibase.CLIErrorGuard
|
|
139
|
+
def XgcdCLI( # documentation is help/epilog/args # noqa: D103
|
|
140
|
+
*,
|
|
141
|
+
ctx: typer.Context,
|
|
142
|
+
a: str = typer.Argument(..., help='Integer, ≥ 0'),
|
|
143
|
+
b: str = typer.Argument(..., help="Integer, ≥ 0 (can't be both zero)"),
|
|
144
|
+
) -> None:
|
|
145
|
+
config: transcrypto.TransConfig = ctx.obj
|
|
146
|
+
a_i: int = transcrypto.ParseInt(a, min_value=0)
|
|
147
|
+
b_i: int = transcrypto.ParseInt(b, min_value=0)
|
|
148
|
+
if a_i == 0 and b_i == 0:
|
|
149
|
+
raise base.InputError("`a` and `b` can't both be zero")
|
|
150
|
+
config.console.print(str(base.ExtendedGCD(a_i, b_i)))
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
# ================================= "RANDOM" COMMAND ===============================================
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
random_app = typer.Typer(
|
|
157
|
+
no_args_is_help=True,
|
|
158
|
+
help='Cryptographically secure randomness, from the OS CSPRNG.',
|
|
159
|
+
)
|
|
160
|
+
transcrypto.app.add_typer(random_app, name='random')
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
@random_app.command(
|
|
164
|
+
'bits',
|
|
165
|
+
help='Random integer with exact bit length = `bits` (MSB will be 1).',
|
|
166
|
+
epilog=('Example:\n\n\n\n$ poetry run transcrypto random bits 16\n\n36650'),
|
|
167
|
+
)
|
|
168
|
+
@clibase.CLIErrorGuard
|
|
169
|
+
def RandomBits( # documentation is help/epilog/args # noqa: D103
|
|
170
|
+
*,
|
|
171
|
+
ctx: typer.Context,
|
|
172
|
+
bits: int = typer.Argument(..., min=8, help='Number of bits, ≥ 8'),
|
|
173
|
+
) -> None:
|
|
174
|
+
config: transcrypto.TransConfig = ctx.obj
|
|
175
|
+
config.console.print(base.RandBits(bits))
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
@random_app.command(
|
|
179
|
+
'int',
|
|
180
|
+
help='Uniform random integer in `[min, max]` range, inclusive.',
|
|
181
|
+
epilog=('Example:\n\n\n\n$ poetry run transcrypto random int 1000 2000\n\n1628'),
|
|
182
|
+
)
|
|
183
|
+
@clibase.CLIErrorGuard
|
|
184
|
+
def RandomInt( # documentation is help/epilog/args # noqa: D103
|
|
185
|
+
*,
|
|
186
|
+
ctx: typer.Context,
|
|
187
|
+
min_: str = typer.Argument(..., help='Minimum, ≥ 0'),
|
|
188
|
+
max_: str = typer.Argument(..., help='Maximum, > `min`'),
|
|
189
|
+
) -> None:
|
|
190
|
+
config: transcrypto.TransConfig = ctx.obj
|
|
191
|
+
min_i: int = transcrypto.ParseInt(min_, min_value=0)
|
|
192
|
+
max_i: int = transcrypto.ParseInt(max_, min_value=min_i + 1)
|
|
193
|
+
config.console.print(base.RandInt(min_i, max_i))
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
@random_app.command(
|
|
197
|
+
'bytes',
|
|
198
|
+
help='Generates `n` cryptographically secure random bytes.',
|
|
199
|
+
epilog=(
|
|
200
|
+
'Example:\n\n\n\n'
|
|
201
|
+
'$ poetry run transcrypto random bytes 32\n\n'
|
|
202
|
+
'6c6f1f88cb93c4323285a2224373d6e59c72a9c2b82e20d1c376df4ffbe9507f'
|
|
203
|
+
),
|
|
204
|
+
)
|
|
205
|
+
@clibase.CLIErrorGuard
|
|
206
|
+
def RandomBytes( # documentation is help/epilog/args # noqa: D103
|
|
207
|
+
*,
|
|
208
|
+
ctx: typer.Context,
|
|
209
|
+
n: int = typer.Argument(..., min=1, help='Number of bytes, ≥ 1'),
|
|
210
|
+
) -> None:
|
|
211
|
+
config: transcrypto.TransConfig = ctx.obj
|
|
212
|
+
config.console.print(transcrypto.BytesToText(base.RandBytes(n), config.output_format))
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
@random_app.command(
|
|
216
|
+
'prime',
|
|
217
|
+
help='Generate a random prime with exact bit length = `bits` (MSB will be 1).',
|
|
218
|
+
epilog=('Example:\n\n\n\n$ poetry run transcrypto random prime 32\n\n2365910551'),
|
|
219
|
+
)
|
|
220
|
+
@clibase.CLIErrorGuard
|
|
221
|
+
def RandomPrime( # documentation is help/epilog/args # noqa: D103
|
|
222
|
+
*,
|
|
223
|
+
ctx: typer.Context,
|
|
224
|
+
bits: int = typer.Argument(..., min=11, help='Bit length, ≥ 11'),
|
|
225
|
+
) -> None:
|
|
226
|
+
config: transcrypto.TransConfig = ctx.obj
|
|
227
|
+
config.console.print(modmath.NBitRandomPrimes(bits).pop())
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
# =================================== "MOD" COMMAND ================================================
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
mod_app = typer.Typer(
|
|
234
|
+
no_args_is_help=True,
|
|
235
|
+
help='Modular arithmetic helpers.',
|
|
236
|
+
)
|
|
237
|
+
transcrypto.app.add_typer(mod_app, name='mod')
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
@mod_app.command(
|
|
241
|
+
'inv',
|
|
242
|
+
help=(
|
|
243
|
+
'Modular inverse: find integer 0≤`i`<`m` such that `a×i ≡ 1 (mod m)`. ' # noqa: RUF001
|
|
244
|
+
'Will only work if `gcd(a,m)==1`, else will fail with a message.'
|
|
245
|
+
),
|
|
246
|
+
epilog=(
|
|
247
|
+
'Example:\n\n\n\n'
|
|
248
|
+
'$ poetry run transcrypto mod inv 127 13\n\n'
|
|
249
|
+
'4\n\n'
|
|
250
|
+
'$ poetry run transcrypto mod inv 17 3120\n\n'
|
|
251
|
+
'2753\n\n'
|
|
252
|
+
'$ poetry run transcrypto mod inv 462 1071\n\n'
|
|
253
|
+
'<<INVALID>> no modular inverse exists (ModularDivideError)'
|
|
254
|
+
),
|
|
255
|
+
)
|
|
256
|
+
@clibase.CLIErrorGuard
|
|
257
|
+
def ModInv( # documentation is help/epilog/args # noqa: D103
|
|
258
|
+
*,
|
|
259
|
+
ctx: typer.Context,
|
|
260
|
+
a: str = typer.Argument(..., help='Integer to invert'),
|
|
261
|
+
m: str = typer.Argument(..., help='Modulus `m`, ≥ 2'),
|
|
262
|
+
) -> None:
|
|
263
|
+
config: transcrypto.TransConfig = ctx.obj
|
|
264
|
+
a_i: int = transcrypto.ParseInt(a)
|
|
265
|
+
m_i: int = transcrypto.ParseInt(m, min_value=2)
|
|
266
|
+
try:
|
|
267
|
+
config.console.print(modmath.ModInv(a_i, m_i))
|
|
268
|
+
except modmath.ModularDivideError:
|
|
269
|
+
config.console.print('<<INVALID>> no modular inverse exists (ModularDivideError)')
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
@mod_app.command(
|
|
273
|
+
'div',
|
|
274
|
+
help=(
|
|
275
|
+
'Modular division: find integer 0≤`z`<`m` such that `z×y ≡ x (mod m)`. ' # noqa: RUF001
|
|
276
|
+
'Will only work if `gcd(y,m)==1` and `y!=0`, else will fail with a message.'
|
|
277
|
+
),
|
|
278
|
+
epilog=(
|
|
279
|
+
'Example:\n\n\n\n'
|
|
280
|
+
'$ poetry run transcrypto mod div 6 127 13\n\n'
|
|
281
|
+
'11\n\n'
|
|
282
|
+
'$ poetry run transcrypto mod div 6 0 13\n\n'
|
|
283
|
+
'<<INVALID>> divide-by-zero or not invertible (ModularDivideError)'
|
|
284
|
+
),
|
|
285
|
+
)
|
|
286
|
+
@clibase.CLIErrorGuard
|
|
287
|
+
def ModDiv( # documentation is help/epilog/args # noqa: D103
|
|
288
|
+
*,
|
|
289
|
+
ctx: typer.Context,
|
|
290
|
+
x: str = typer.Argument(..., help='Integer'),
|
|
291
|
+
y: str = typer.Argument(..., help='Integer, cannot be zero'),
|
|
292
|
+
m: str = typer.Argument(..., help='Modulus `m`, ≥ 2'),
|
|
293
|
+
) -> None:
|
|
294
|
+
config: transcrypto.TransConfig = ctx.obj
|
|
295
|
+
x_i: int = transcrypto.ParseInt(x)
|
|
296
|
+
y_i: int = transcrypto.ParseInt(y)
|
|
297
|
+
m_i: int = transcrypto.ParseInt(m, min_value=2)
|
|
298
|
+
try:
|
|
299
|
+
config.console.print(modmath.ModDiv(x_i, y_i, m_i))
|
|
300
|
+
except modmath.ModularDivideError:
|
|
301
|
+
config.console.print('<<INVALID>> divide-by-zero or not invertible (ModularDivideError)')
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
@mod_app.command(
|
|
305
|
+
'exp',
|
|
306
|
+
help='Modular exponentiation: `a^e mod m`. Efficient, can handle huge values.',
|
|
307
|
+
epilog=(
|
|
308
|
+
'Example:\n\n\n\n'
|
|
309
|
+
'$ poetry run transcrypto mod exp 438 234 127\n\n'
|
|
310
|
+
'32\n\n'
|
|
311
|
+
'$ poetry run transcrypto mod exp 438 234 89854\n\n'
|
|
312
|
+
'60622'
|
|
313
|
+
),
|
|
314
|
+
)
|
|
315
|
+
@clibase.CLIErrorGuard
|
|
316
|
+
def ModExp( # documentation is help/epilog/args # noqa: D103
|
|
317
|
+
*,
|
|
318
|
+
ctx: typer.Context,
|
|
319
|
+
a: str = typer.Argument(..., help='Integer value'),
|
|
320
|
+
e: str = typer.Argument(..., help='Integer exponent, ≥ 0'),
|
|
321
|
+
m: str = typer.Argument(..., help='Modulus `m`, ≥ 2'),
|
|
322
|
+
) -> None:
|
|
323
|
+
config: transcrypto.TransConfig = ctx.obj
|
|
324
|
+
a_i: int = transcrypto.ParseInt(a)
|
|
325
|
+
e_i: int = transcrypto.ParseInt(e, min_value=0)
|
|
326
|
+
m_i: int = transcrypto.ParseInt(m, min_value=2)
|
|
327
|
+
config.console.print(modmath.ModExp(a_i, e_i, m_i))
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
@mod_app.command(
|
|
331
|
+
'poly',
|
|
332
|
+
help=(
|
|
333
|
+
'Efficiently evaluate polynomial with `coeff` coefficients at point `x` modulo `m` '
|
|
334
|
+
'(`c₀+c₁×x+c₂×x²+…+cₙ×x^n mod m`).' # noqa: RUF001
|
|
335
|
+
),
|
|
336
|
+
epilog=(
|
|
337
|
+
'Example:\n\n\n\n'
|
|
338
|
+
'$ poetry run transcrypto mod poly 12 17 10 20 30\n\n'
|
|
339
|
+
'14 # (10+20×12+30×12² ≡ 14 (mod 17))\n\n' # noqa: RUF001
|
|
340
|
+
'$ poetry run transcrypto mod poly 10 97 3 0 0 1 1\n\n'
|
|
341
|
+
'42 # (3+1×10³+1×10⁴ ≡ 42 (mod 97))' # noqa: RUF001
|
|
342
|
+
),
|
|
343
|
+
)
|
|
344
|
+
@clibase.CLIErrorGuard
|
|
345
|
+
def ModPoly( # documentation is help/epilog/args # noqa: D103
|
|
346
|
+
*,
|
|
347
|
+
ctx: typer.Context,
|
|
348
|
+
x: str = typer.Argument(..., help='Evaluation point `x`'),
|
|
349
|
+
m: str = typer.Argument(..., help='Modulus `m`, ≥ 2'),
|
|
350
|
+
coeff: list[str] = typer.Argument( # noqa: B008
|
|
351
|
+
...,
|
|
352
|
+
help='Coefficients (constant-term first: `c₀+c₁×x+c₂×x²+…+cₙ×x^n`)', # noqa: RUF001
|
|
353
|
+
),
|
|
354
|
+
) -> None:
|
|
355
|
+
config: transcrypto.TransConfig = ctx.obj
|
|
356
|
+
x_i: int = transcrypto.ParseInt(x)
|
|
357
|
+
m_i: int = transcrypto.ParseInt(m, min_value=2)
|
|
358
|
+
coeff_i: list[int] = [transcrypto.ParseInt(z) for z in coeff]
|
|
359
|
+
config.console.print(modmath.ModPolynomial(x_i, coeff_i, m_i))
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
@mod_app.command(
|
|
363
|
+
'lagrange',
|
|
364
|
+
help=(
|
|
365
|
+
'Lagrange interpolation over modulus `m`: find the `f(x)` solution for the '
|
|
366
|
+
'given `x` and `zₙ:f(zₙ)` points `pt`. The modulus `m` must be a prime.'
|
|
367
|
+
),
|
|
368
|
+
epilog=(
|
|
369
|
+
'Example:\n\n\n\n'
|
|
370
|
+
'$ poetry run transcrypto mod lagrange 5 13 2:4 6:3 7:1\n\n'
|
|
371
|
+
'3 # passes through (2,4), (6,3), (7,1)\n\n'
|
|
372
|
+
'$ poetry run transcrypto mod lagrange 11 97 1:1 2:4 3:9 4:16 5:25\n\n'
|
|
373
|
+
'24 # passes through (1,1), (2,4), (3,9), (4,16), (5,25)'
|
|
374
|
+
),
|
|
375
|
+
)
|
|
376
|
+
@clibase.CLIErrorGuard
|
|
377
|
+
def ModLagrange( # documentation is help/epilog/args # noqa: D103
|
|
378
|
+
*,
|
|
379
|
+
ctx: typer.Context,
|
|
380
|
+
x: str = typer.Argument(..., help='Evaluation point `x`'),
|
|
381
|
+
m: str = typer.Argument(..., help='Modulus `m`, ≥ 2'),
|
|
382
|
+
pt: list[str] = typer.Argument( # noqa: B008
|
|
383
|
+
...,
|
|
384
|
+
help='Points `zₙ:f(zₙ)` as `key:value` pairs (e.g., `2:4 5:3 7:1`)',
|
|
385
|
+
),
|
|
386
|
+
) -> None:
|
|
387
|
+
config: transcrypto.TransConfig = ctx.obj
|
|
388
|
+
x_i: int = transcrypto.ParseInt(x)
|
|
389
|
+
m_i: int = transcrypto.ParseInt(m, min_value=2)
|
|
390
|
+
pts: dict[int, int] = dict(transcrypto.ParseIntPairCLI(kv) for kv in pt)
|
|
391
|
+
config.console.print(modmath.ModLagrangeInterpolate(x_i, pts, m_i))
|
|
392
|
+
|
|
393
|
+
|
|
394
|
+
@mod_app.command(
|
|
395
|
+
'crt',
|
|
396
|
+
help=(
|
|
397
|
+
'Solves Chinese Remainder Theorem (CRT) Pair: finds the unique integer 0≤`x`<`(m1×m2)` ' # noqa: RUF001
|
|
398
|
+
'satisfying both `x ≡ a1 (mod m1)` and `x ≡ a2 (mod m2)`, if `gcd(m1,m2)==1`.'
|
|
399
|
+
),
|
|
400
|
+
epilog=(
|
|
401
|
+
'Example:\n\n\n\n'
|
|
402
|
+
'$ poetry run transcrypto mod crt 6 7 127 13\n\n'
|
|
403
|
+
'62\n\n'
|
|
404
|
+
'$ poetry run transcrypto mod crt 12 56 17 19\n\n'
|
|
405
|
+
'796\n\n'
|
|
406
|
+
'$ poetry run transcrypto mod crt 6 7 462 1071\n\n'
|
|
407
|
+
'<<INVALID>> moduli m1/m2 not co-prime (ModularDivideError)'
|
|
408
|
+
),
|
|
409
|
+
)
|
|
410
|
+
@clibase.CLIErrorGuard
|
|
411
|
+
def ModCRT( # documentation is help/epilog/args # noqa: D103
|
|
412
|
+
*,
|
|
413
|
+
ctx: typer.Context,
|
|
414
|
+
a1: str = typer.Argument(..., help='Integer residue for first congruence'),
|
|
415
|
+
m1: str = typer.Argument(..., help='Modulus `m1`, ≥ 2'),
|
|
416
|
+
a2: str = typer.Argument(..., help='Integer residue for second congruence'),
|
|
417
|
+
m2: str = typer.Argument(..., help='Modulus `m2`, ≥ 2, !=`m1`, and `gcd(m1,m2)==1`'),
|
|
418
|
+
) -> None:
|
|
419
|
+
config: transcrypto.TransConfig = ctx.obj
|
|
420
|
+
a1_i: int = transcrypto.ParseInt(a1)
|
|
421
|
+
m1_i: int = transcrypto.ParseInt(m1, min_value=2)
|
|
422
|
+
a2_i: int = transcrypto.ParseInt(a2)
|
|
423
|
+
m2_i: int = transcrypto.ParseInt(m2, min_value=2)
|
|
424
|
+
try:
|
|
425
|
+
config.console.print(modmath.CRTPair(a1_i, m1_i, a2_i, m2_i))
|
|
426
|
+
except modmath.ModularDivideError:
|
|
427
|
+
config.console.print('<<INVALID>> moduli `m1`/`m2` not co-prime (ModularDivideError)')
|