transcrypto 1.5.1__py3-none-any.whl → 1.7.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 +7 -0
- transcrypto/aes.py +150 -44
- transcrypto/base.py +640 -411
- transcrypto/constants.py +20070 -1906
- transcrypto/dsa.py +132 -99
- transcrypto/elgamal.py +116 -84
- transcrypto/modmath.py +88 -78
- transcrypto/profiler.py +228 -180
- transcrypto/rsa.py +126 -90
- transcrypto/sss.py +122 -70
- transcrypto/transcrypto.py +2362 -1412
- {transcrypto-1.5.1.dist-info → transcrypto-1.7.0.dist-info}/METADATA +78 -58
- transcrypto-1.7.0.dist-info/RECORD +17 -0
- {transcrypto-1.5.1.dist-info → transcrypto-1.7.0.dist-info}/WHEEL +1 -2
- transcrypto-1.7.0.dist-info/entry_points.txt +4 -0
- transcrypto/safetrans.py +0 -1231
- transcrypto-1.5.1.dist-info/RECORD +0 -18
- transcrypto-1.5.1.dist-info/top_level.txt +0 -1
- {transcrypto-1.5.1.dist-info → transcrypto-1.7.0.dist-info}/licenses/LICENSE +0 -0
transcrypto/profiler.py
CHANGED
|
@@ -1,191 +1,239 @@
|
|
|
1
|
-
|
|
2
|
-
#
|
|
3
|
-
# Copyright 2025 Daniel Balparda (balparda@github.com) - Apache-2.0 license
|
|
4
|
-
#
|
|
1
|
+
# SPDX-FileCopyrightText: Copyright 2026 Daniel Balparda <balparda@github.com>
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
5
3
|
"""Balparda's TransCrypto Profiler command line interface.
|
|
6
4
|
|
|
7
|
-
See
|
|
5
|
+
See <profiler.md> for documentation on how to use. Quick examples:
|
|
8
6
|
|
|
9
|
-
|
|
7
|
+
--- Primes / DSA ---
|
|
8
|
+
poetry run profiler -n 10 primes
|
|
9
|
+
poetry run profiler --no-serial -n 20 dsa
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
--- Markdown ---
|
|
12
|
+
poetry run profiler markdown > profiler.md
|
|
13
|
+
|
|
14
|
+
Test this CLI with:
|
|
15
|
+
|
|
16
|
+
poetry run pytest -vvv tests/profiler_test.py
|
|
14
17
|
"""
|
|
15
18
|
|
|
16
19
|
from __future__ import annotations
|
|
17
20
|
|
|
18
|
-
import
|
|
19
|
-
import
|
|
20
|
-
|
|
21
|
-
import
|
|
22
|
-
from
|
|
23
|
-
|
|
24
|
-
from . import base,
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
'
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
21
|
+
import dataclasses
|
|
22
|
+
from collections import abc
|
|
23
|
+
|
|
24
|
+
import typer
|
|
25
|
+
from rich import console as rich_console
|
|
26
|
+
|
|
27
|
+
from . import __version__, base, dsa, modmath
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@dataclasses.dataclass(kw_only=True, slots=True, frozen=True)
|
|
31
|
+
class ProfilerConfig(base.CLIConfig):
|
|
32
|
+
"""CLI global context, storing the configuration."""
|
|
33
|
+
|
|
34
|
+
serial: bool
|
|
35
|
+
repeats: int
|
|
36
|
+
confidence: int
|
|
37
|
+
bits: tuple[int, int, int]
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
# CLI app setup, this is an important object and can be imported elsewhere and called
|
|
41
|
+
app = typer.Typer(
|
|
42
|
+
add_completion=True,
|
|
43
|
+
no_args_is_help=True,
|
|
44
|
+
help='profiler: CLI for TransCrypto Profiler, measure library performance.',
|
|
45
|
+
epilog=(
|
|
46
|
+
'Examples:\n\n\n\n'
|
|
47
|
+
'# --- Primes / DSA ---\n\n'
|
|
48
|
+
'poetry run profiler -n 10 primes\n\n'
|
|
49
|
+
'poetry run profiler --no-serial -n 20 dsa\n\n\n\n'
|
|
50
|
+
'# --- Markdown ---\n\n'
|
|
51
|
+
'poetry run profiler markdown > profiler.md'
|
|
52
|
+
),
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def Run() -> None:
|
|
57
|
+
"""Run the CLI."""
|
|
58
|
+
app()
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
@app.callback(
|
|
62
|
+
invoke_without_command=True, # have only one; this is the "constructor"
|
|
63
|
+
help='Profile TransCrypto library performance.',
|
|
64
|
+
)
|
|
65
|
+
def Main( # documentation is help/epilog/args # noqa: D103
|
|
66
|
+
*,
|
|
67
|
+
ctx: typer.Context, # global context
|
|
68
|
+
version: bool = typer.Option(False, '--version', help='Show version and exit.'),
|
|
69
|
+
verbose: int = typer.Option(
|
|
70
|
+
0,
|
|
71
|
+
'-v',
|
|
72
|
+
'--verbose',
|
|
73
|
+
count=True,
|
|
74
|
+
help='Verbosity (nothing=ERROR, -v=WARNING, -vv=INFO, -vvv=DEBUG).',
|
|
75
|
+
min=0,
|
|
76
|
+
max=3,
|
|
77
|
+
),
|
|
78
|
+
color: bool | None = typer.Option(
|
|
79
|
+
None,
|
|
80
|
+
'--color/--no-color',
|
|
81
|
+
help=(
|
|
82
|
+
'Force enable/disable colored output (respects NO_COLOR env var if not provided). '
|
|
83
|
+
'Defaults to having colors.' # state default because None default means docs don't show it
|
|
84
|
+
),
|
|
85
|
+
),
|
|
86
|
+
serial: bool = typer.Option(
|
|
87
|
+
True,
|
|
88
|
+
'--serial/--no-serial',
|
|
89
|
+
help='Execute operation serially (i.e. do not use threads/multiprocessing).',
|
|
90
|
+
),
|
|
91
|
+
repeats: int = typer.Option(
|
|
92
|
+
15,
|
|
93
|
+
'-n',
|
|
94
|
+
'--number',
|
|
95
|
+
help='Number of experiments (repeats) for every measurement.',
|
|
96
|
+
min=1,
|
|
97
|
+
max=1000,
|
|
98
|
+
),
|
|
99
|
+
confidence: int = typer.Option(
|
|
100
|
+
98,
|
|
101
|
+
'-c',
|
|
102
|
+
'--confidence',
|
|
103
|
+
help=(
|
|
104
|
+
'Confidence level to evaluate measurements at as int percentage points [50,99], '
|
|
105
|
+
'inclusive, representing 50% to 99%'
|
|
106
|
+
),
|
|
107
|
+
min=50,
|
|
108
|
+
max=99,
|
|
109
|
+
),
|
|
110
|
+
bits: str = typer.Option(
|
|
111
|
+
'1000,9000,1000',
|
|
112
|
+
'-b',
|
|
113
|
+
'--bits',
|
|
114
|
+
help=(
|
|
115
|
+
'Bit lengths to investigate as [green]"int,int,int"[/]; behaves like arguments for range(), '
|
|
116
|
+
'i.e., [green]"start,stop,step"[/], eg. [green]"1000,3000,500"[/] will investigate '
|
|
117
|
+
'[yellow]1000,1500,2000,2500[/]'
|
|
118
|
+
),
|
|
119
|
+
),
|
|
120
|
+
) -> None:
|
|
121
|
+
if version:
|
|
122
|
+
typer.echo(__version__)
|
|
123
|
+
raise typer.Exit(0)
|
|
124
|
+
console, verbose, color = base.InitLogging(
|
|
125
|
+
verbose,
|
|
126
|
+
color=color,
|
|
127
|
+
include_process=False, # decide if you want process names in logs
|
|
128
|
+
soft_wrap=False, # decide if you want soft wrapping of long lines
|
|
129
|
+
)
|
|
130
|
+
# create context with the arguments we received
|
|
131
|
+
int_bits: tuple[int, ...] = tuple(int(x, 10) for x in bits.strip().split(','))
|
|
132
|
+
if len(int_bits) != 3: # noqa: PLR2004
|
|
133
|
+
raise typer.BadParameter(
|
|
134
|
+
'-b/--bits should be 3 ints, like: start,stop,step; eg.: 1000,3000,500'
|
|
135
|
+
)
|
|
136
|
+
ctx.obj = ProfilerConfig(
|
|
137
|
+
console=console,
|
|
138
|
+
verbose=verbose,
|
|
139
|
+
color=color,
|
|
140
|
+
serial=serial,
|
|
141
|
+
repeats=repeats,
|
|
142
|
+
confidence=confidence,
|
|
143
|
+
bits=(int_bits[0], int_bits[1], int_bits[2]),
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
@app.command(
|
|
148
|
+
'primes',
|
|
149
|
+
help='Measure regular prime generation.',
|
|
150
|
+
epilog=(
|
|
151
|
+
'Example:\n\n\n\n'
|
|
152
|
+
'$ poetry run profiler -n 30 -b 9000,11000,1000 primes\n\n'
|
|
153
|
+
'Starting [yellow]SERIAL regular primes[/] test\n\n'
|
|
154
|
+
'9000 → 38.88 s ± 14.74 s [24.14 s … 53.63 s]98%CI@30\n\n'
|
|
155
|
+
'10000 → 41.26 s ± 22.82 s [18.44 s … 1.07 min]98%CI@30\n\n'
|
|
156
|
+
'Finished in 40.07 min'
|
|
157
|
+
),
|
|
158
|
+
)
|
|
159
|
+
@base.CLIErrorGuard
|
|
160
|
+
def Primes(*, ctx: typer.Context) -> None: # documentation is help/epilog/args # noqa: D103
|
|
161
|
+
config: ProfilerConfig = ctx.obj # get application global config
|
|
162
|
+
config.console.print(
|
|
163
|
+
f'Starting [yellow]{"SERIAL" if config.serial else "PARALLEL"} regular primes[/] test'
|
|
164
|
+
)
|
|
165
|
+
_PrimeProfiler(
|
|
166
|
+
lambda n: modmath.NBitRandomPrimes(n, serial=config.serial, n_primes=1).pop(),
|
|
167
|
+
config.console,
|
|
168
|
+
config.repeats,
|
|
169
|
+
config.bits,
|
|
170
|
+
config.confidence / 100.0,
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
@app.command(
|
|
175
|
+
'dsa',
|
|
176
|
+
help='Measure DSA prime generation.',
|
|
177
|
+
epilog=(
|
|
178
|
+
'Example:\n\n\n\n'
|
|
179
|
+
'$ poetry run profiler --no-serial -n 2 -b 1000,1500,100 -c 80 dsa\n\n'
|
|
180
|
+
'Starting [yellow]PARALLEL DSA primes[/] test\n\n'
|
|
181
|
+
'1000 → 236.344 ms ± 273.236 ms [*0.00 s … 509.580 ms]80%CI@2\n\n'
|
|
182
|
+
'1100 → 319.308 ms ± 639.775 ms [*0.00 s … 959.083 ms]80%CI@2\n\n'
|
|
183
|
+
'1200 → 523.885 ms ± 879.981 ms [*0.00 s … 1.40 s]80%CI@2\n\n'
|
|
184
|
+
'1300 → 506.285 ms ± 687.153 ms [*0.00 s … 1.19 s]80%CI@2\n\n'
|
|
185
|
+
'1400 → 552.840 ms ± 47.012 ms [505.828 ms … 599.852 ms]80%CI@2\n\n'
|
|
186
|
+
'Finished in 4.12 s'
|
|
187
|
+
),
|
|
188
|
+
)
|
|
189
|
+
@base.CLIErrorGuard
|
|
190
|
+
def DSA(*, ctx: typer.Context) -> None: # documentation is help/epilog/args # noqa: D103
|
|
191
|
+
config: ProfilerConfig = ctx.obj # get application global config
|
|
192
|
+
config.console.print(
|
|
193
|
+
f'Starting [yellow]{"SERIAL" if config.serial else "PARALLEL"} DSA primes[/] test'
|
|
194
|
+
)
|
|
195
|
+
_PrimeProfiler(
|
|
196
|
+
lambda n: dsa.NBitRandomDSAPrimes(n, n // 2, serial=config.serial)[0],
|
|
197
|
+
config.console,
|
|
198
|
+
config.repeats,
|
|
199
|
+
config.bits,
|
|
200
|
+
config.confidence / 100.0,
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
@app.command(
|
|
205
|
+
'markdown',
|
|
206
|
+
help='Emit Markdown docs for the CLI (see README.md section "Creating a New Version").',
|
|
207
|
+
epilog='Example:\n\n\n\n$ poetry run profiler markdown > profiler.md\n\n<<saves CLI doc>>',
|
|
208
|
+
)
|
|
209
|
+
@base.CLIErrorGuard
|
|
210
|
+
def Markdown() -> None: # documentation is help/epilog/args # noqa: D103
|
|
211
|
+
console: rich_console.Console = base.Console()
|
|
212
|
+
console.print(base.GenerateTyperHelpMarkdown(app, prog_name='profiler'))
|
|
106
213
|
|
|
107
214
|
|
|
108
215
|
def _PrimeProfiler(
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
logging.basicConfig(
|
|
134
|
-
level=levels[min(args.verbose, len(levels) - 1)], # type: ignore
|
|
135
|
-
format=getattr(base, 'LOG_FORMAT', '%(levelname)s:%(message)s'))
|
|
136
|
-
logging.captureWarnings(True)
|
|
137
|
-
|
|
138
|
-
try:
|
|
139
|
-
# get the command, do basic checks and switch
|
|
140
|
-
command: str = args.command.lower().strip() if args.command else ''
|
|
141
|
-
repeats: int = 1 if args.number < 1 else args.number
|
|
142
|
-
confidence: int = 55 if args.confidence < 55 else args.confidence
|
|
143
|
-
confidence = 99 if confidence > 99 else confidence
|
|
144
|
-
args.serial = True if (not args.serial and not args.parallel) else args.serial # make default
|
|
145
|
-
bits: tuple[int, ...] = tuple(int(x, 10) for x in args.bits.strip().split(','))
|
|
146
|
-
if len(bits) != 3:
|
|
147
|
-
raise base.InputError('-b/--bits should be 3 ints, like: start,stop,step; eg.: 1000,3000,500')
|
|
148
|
-
with base.Timer(emit_log=False) as tmr:
|
|
149
|
-
match command:
|
|
150
|
-
# -------- Primes ----------
|
|
151
|
-
case 'primes':
|
|
152
|
-
print(f'Starting {"SERIAL" if args.serial else "PARALLEL"} regular primes test')
|
|
153
|
-
_PrimeProfiler(
|
|
154
|
-
lambda n: modmath.NBitRandomPrimes(n, serial=args.serial, n_primes=1).pop(),
|
|
155
|
-
repeats, bits, confidence / 100.0)
|
|
156
|
-
|
|
157
|
-
case 'dsa':
|
|
158
|
-
print(f'Starting {"SERIAL" if args.serial else "PARALLEL"} DSA primes test')
|
|
159
|
-
_PrimeProfiler(
|
|
160
|
-
lambda n: dsa.NBitRandomDSAPrimes(n, n // 2, serial=args.serial)[0],
|
|
161
|
-
repeats, bits, confidence / 100.0)
|
|
162
|
-
|
|
163
|
-
# -------- Documentation ----------
|
|
164
|
-
case 'doc':
|
|
165
|
-
doc_command: str = (
|
|
166
|
-
args.doc_command.lower().strip() if getattr(args, 'doc_command', '') else '')
|
|
167
|
-
match doc_command:
|
|
168
|
-
case 'md':
|
|
169
|
-
print(base.GenerateCLIMarkdown(
|
|
170
|
-
'profiler', _BuildParser(), description=(
|
|
171
|
-
'`profiler` is a command-line utility that provides stats on TransCrypto '
|
|
172
|
-
'performance.')))
|
|
173
|
-
case _:
|
|
174
|
-
raise NotImplementedError()
|
|
175
|
-
|
|
176
|
-
case _:
|
|
177
|
-
parser.print_help()
|
|
178
|
-
|
|
179
|
-
if command not in ('doc',):
|
|
180
|
-
print(f'Finished in {tmr}')
|
|
181
|
-
|
|
182
|
-
except NotImplementedError as err:
|
|
183
|
-
print(f'Invalid command: {err}')
|
|
184
|
-
except (base.Error, ValueError) as err:
|
|
185
|
-
print(str(err))
|
|
186
|
-
|
|
187
|
-
return 0
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
if __name__ == '__main__':
|
|
191
|
-
sys.exit(main())
|
|
216
|
+
prime_callable: abc.Callable[[int], int],
|
|
217
|
+
console: rich_console.Console,
|
|
218
|
+
repeats: int,
|
|
219
|
+
n_bits_range: tuple[int, int, int],
|
|
220
|
+
confidence: float,
|
|
221
|
+
/,
|
|
222
|
+
) -> None:
|
|
223
|
+
with base.Timer(emit_log=False) as total_time:
|
|
224
|
+
primes: dict[int, list[float]] = {}
|
|
225
|
+
for n_bits in range(*n_bits_range):
|
|
226
|
+
# investigate for size n_bits
|
|
227
|
+
primes[n_bits] = []
|
|
228
|
+
for _ in range(repeats):
|
|
229
|
+
with base.Timer(emit_log=False) as run_time:
|
|
230
|
+
pr: int = prime_callable(n_bits)
|
|
231
|
+
assert pr # noqa: S101
|
|
232
|
+
assert pr.bit_length() == n_bits # noqa: S101
|
|
233
|
+
primes[n_bits].append(run_time.elapsed)
|
|
234
|
+
# finished collecting n_bits-sized primes
|
|
235
|
+
measurements: str = base.HumanizedMeasurements(
|
|
236
|
+
primes[n_bits], parser=base.HumanizedSeconds, confidence=confidence
|
|
237
|
+
)
|
|
238
|
+
console.print(f'{n_bits} → {measurements}')
|
|
239
|
+
console.print(f'Finished in {total_time}')
|