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