transcrypto 1.8.0__py3-none-any.whl → 2.0.3__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 CHANGED
@@ -3,5 +3,5 @@
3
3
  """Basic cryptography primitives implementation."""
4
4
 
5
5
  __all__: list[str] = ['__author__', '__version__']
6
- __version__ = '1.8.0' # remember to also update pyproject.toml
6
+ __version__ = '2.0.3' # remember to also update pyproject.toml
7
7
  __author__ = 'Daniel Balparda <balparda@github.com>'
@@ -10,8 +10,10 @@ import re
10
10
  import click
11
11
  import typer
12
12
 
13
- from transcrypto import aes, base, transcrypto
13
+ from transcrypto import transcrypto
14
14
  from transcrypto.cli import clibase
15
+ from transcrypto.core import aes, hashes
16
+ from transcrypto.utils import base
15
17
 
16
18
  _HEX_RE = re.compile(r'^[0-9a-fA-F]+$')
17
19
 
@@ -39,12 +41,12 @@ transcrypto.app.add_typer(hash_app, name='hash')
39
41
  @clibase.CLIErrorGuard
40
42
  def Hash256( # documentation is help/epilog/args # noqa: D103
41
43
  *,
42
- ctx: typer.Context,
44
+ ctx: click.Context,
43
45
  data: str = typer.Argument(..., help='Input data (raw text; or `--input-format <hex|b64|bin>`)'),
44
46
  ) -> None:
45
47
  config: transcrypto.TransConfig = ctx.obj
46
48
  bt: bytes = transcrypto.BytesFromText(data, config.input_format)
47
- config.console.print(transcrypto.BytesToText(base.Hash256(bt), config.output_format))
49
+ config.console.print(transcrypto.BytesToText(hashes.Hash256(bt), config.output_format))
48
50
 
49
51
 
50
52
  @hash_app.command(
@@ -63,12 +65,12 @@ def Hash256( # documentation is help/epilog/args # noqa: D103
63
65
  @clibase.CLIErrorGuard
64
66
  def Hash512( # documentation is help/epilog/args # noqa: D103
65
67
  *,
66
- ctx: typer.Context,
68
+ ctx: click.Context,
67
69
  data: str = typer.Argument(..., help='Input data (raw text; or `--input-format <hex|b64|bin>`)'),
68
70
  ) -> None:
69
71
  config: transcrypto.TransConfig = ctx.obj
70
72
  bt: bytes = transcrypto.BytesFromText(data, config.input_format)
71
- config.console.print(transcrypto.BytesToText(base.Hash512(bt), config.output_format))
73
+ config.console.print(transcrypto.BytesToText(hashes.Hash512(bt), config.output_format))
72
74
 
73
75
 
74
76
  @hash_app.command(
@@ -84,7 +86,7 @@ def Hash512( # documentation is help/epilog/args # noqa: D103
84
86
  @clibase.CLIErrorGuard
85
87
  def HashFile( # documentation is help/epilog/args # noqa: D103
86
88
  *,
87
- ctx: typer.Context,
89
+ ctx: click.Context,
88
90
  path: pathlib.Path = typer.Argument( # noqa: B008
89
91
  ...,
90
92
  exists=True,
@@ -104,7 +106,7 @@ def HashFile( # documentation is help/epilog/args # noqa: D103
104
106
  ) -> None:
105
107
  config: transcrypto.TransConfig = ctx.obj
106
108
  config.console.print(
107
- transcrypto.BytesToText(base.FileHash(str(path), digest=digest), config.output_format)
109
+ transcrypto.BytesToText(hashes.FileHash(str(path), digest=digest), config.output_format)
108
110
  )
109
111
 
110
112
 
@@ -140,7 +142,7 @@ transcrypto.app.add_typer(aes_app, name='aes')
140
142
  @clibase.CLIErrorGuard
141
143
  def AESKeyFromPass( # documentation is help/epilog/args # noqa: D103
142
144
  *,
143
- ctx: typer.Context,
145
+ ctx: click.Context,
144
146
  password: str = typer.Argument(..., help='Password (leading/trailing spaces ignored)'),
145
147
  ) -> None:
146
148
  config: transcrypto.TransConfig = ctx.obj
@@ -174,7 +176,7 @@ def AESKeyFromPass( # documentation is help/epilog/args # noqa: D103
174
176
  @clibase.CLIErrorGuard
175
177
  def AESEncrypt( # documentation is help/epilog/args # noqa: D103
176
178
  *,
177
- ctx: typer.Context,
179
+ ctx: click.Context,
178
180
  plaintext: str = typer.Argument(..., help='Input data to encrypt (PT)'),
179
181
  key: str | None = typer.Option(
180
182
  None, '-k', '--key', help="Key if `-p`/`--key-path` wasn't used (32 bytes)"
@@ -226,7 +228,7 @@ def AESEncrypt( # documentation is help/epilog/args # noqa: D103
226
228
  @clibase.CLIErrorGuard
227
229
  def AESDecrypt( # documentation is help/epilog/args # noqa: D103
228
230
  *,
229
- ctx: typer.Context,
231
+ ctx: click.Context,
230
232
  ciphertext: str = typer.Argument(..., help='Input data to decrypt (CT)'),
231
233
  key: str | None = typer.Option(
232
234
  None, '-k', '--key', help="Key if `-p`/`--key-path` wasn't used (32 bytes)"
@@ -287,7 +289,7 @@ aes_app.add_typer(aes_ecb_app, name='ecb')
287
289
  @clibase.CLIErrorGuard
288
290
  def AESECBEncrypt( # documentation is help/epilog/args # noqa: D103
289
291
  *,
290
- ctx: typer.Context,
292
+ ctx: click.Context,
291
293
  plaintext: str = typer.Argument(..., help='Plaintext block as 32 hex chars (16-bytes)'),
292
294
  key: str | None = typer.Option(
293
295
  None,
@@ -336,7 +338,7 @@ def AESECBEncrypt( # documentation is help/epilog/args # noqa: D103
336
338
  @clibase.CLIErrorGuard
337
339
  def AESECBDecrypt( # documentation is help/epilog/args # noqa: D103
338
340
  *,
339
- ctx: typer.Context,
341
+ ctx: click.Context,
340
342
  ciphertext: str = typer.Argument(..., help='Ciphertext block as 32 hex chars (16-bytes)'),
341
343
  key: str | None = typer.Option(
342
344
  None,
@@ -6,10 +6,13 @@ from __future__ import annotations
6
6
 
7
7
  import glob
8
8
 
9
+ import click
9
10
  import typer
10
11
 
11
- from transcrypto import base, sss, transcrypto
12
+ from transcrypto import transcrypto
12
13
  from transcrypto.cli import clibase
14
+ from transcrypto.core import bid, sss
15
+ from transcrypto.utils import base
13
16
 
14
17
  # ================================== "BID" COMMAND =================================================
15
18
 
@@ -39,14 +42,14 @@ transcrypto.app.add_typer(bid_app, name='bid')
39
42
  @clibase.CLIErrorGuard
40
43
  def BidNew( # documentation is help/epilog/args # noqa: D103
41
44
  *,
42
- ctx: typer.Context,
45
+ ctx: click.Context,
43
46
  secret: str = typer.Argument(..., help='Input data to bid to, the protected "secret"'),
44
47
  ) -> None:
45
48
  config: transcrypto.TransConfig = ctx.obj
46
49
  base_path: str = transcrypto.RequireKeyPath(config, 'bid')
47
50
  secret_bytes: bytes = transcrypto.BytesFromText(secret, config.input_format)
48
- bid_priv: base.PrivateBid512 = base.PrivateBid512.New(secret_bytes)
49
- bid_pub: base.PublicBid512 = base.PublicBid512.Copy(bid_priv)
51
+ bid_priv: bid.PrivateBid512 = bid.PrivateBid512.New(secret_bytes)
52
+ bid_pub: bid.PublicBid512 = bid.PublicBid512.Copy(bid_priv)
50
53
  transcrypto.SaveObj(bid_priv, base_path + '.priv', config.protect)
51
54
  transcrypto.SaveObj(bid_pub, base_path + '.pub', config.protect)
52
55
  config.console.print(f'Bid private/public commitments saved to {base_path + ".priv/.pub"!r}')
@@ -64,16 +67,16 @@ def BidNew( # documentation is help/epilog/args # noqa: D103
64
67
  ),
65
68
  )
66
69
  @clibase.CLIErrorGuard
67
- def BidVerify(*, ctx: typer.Context) -> None: # documentation is help/epilog/args # noqa: D103
70
+ def BidVerify(*, ctx: click.Context) -> None: # documentation is help/epilog/args # noqa: D103
68
71
  config: transcrypto.TransConfig = ctx.obj
69
72
  base_path: str = transcrypto.RequireKeyPath(config, 'bid')
70
- bid_priv: base.PrivateBid512 = transcrypto.LoadObj(
71
- base_path + '.priv', config.protect, base.PrivateBid512
73
+ bid_priv: bid.PrivateBid512 = transcrypto.LoadObj(
74
+ base_path + '.priv', config.protect, bid.PrivateBid512
72
75
  )
73
- bid_pub: base.PublicBid512 = transcrypto.LoadObj(
74
- base_path + '.pub', config.protect, base.PublicBid512
76
+ bid_pub: bid.PublicBid512 = transcrypto.LoadObj(
77
+ base_path + '.pub', config.protect, bid.PublicBid512
75
78
  )
76
- bid_pub_expect: base.PublicBid512 = base.PublicBid512.Copy(bid_priv)
79
+ bid_pub_expect: bid.PublicBid512 = bid.PublicBid512.Copy(bid_priv)
77
80
  config.console.print(
78
81
  'Bid commitment: '
79
82
  + (
@@ -120,7 +123,7 @@ transcrypto.app.add_typer(sss_app, name='sss')
120
123
  @clibase.CLIErrorGuard
121
124
  def SSSNew( # documentation is help/epilog/args # noqa: D103
122
125
  *,
123
- ctx: typer.Context,
126
+ ctx: click.Context,
124
127
  minimum: int = typer.Argument(
125
128
  ..., min=2, help='Minimum number of shares required to recover secret, ≥ 2'
126
129
  ),
@@ -161,7 +164,7 @@ def SSSNew( # documentation is help/epilog/args # noqa: D103
161
164
  @clibase.CLIErrorGuard
162
165
  def SSSRawShares( # documentation is help/epilog/args # noqa: D103
163
166
  *,
164
- ctx: typer.Context,
167
+ ctx: click.Context,
165
168
  secret: str = typer.Argument(..., help='Integer secret to be protected, 1≤`secret`<*modulus*'),
166
169
  count: int = typer.Argument(
167
170
  ...,
@@ -206,7 +209,7 @@ def SSSRawShares( # documentation is help/epilog/args # noqa: D103
206
209
  ),
207
210
  )
208
211
  @clibase.CLIErrorGuard
209
- def SSSRawRecover(*, ctx: typer.Context) -> None: # documentation is help/epilog/args # noqa: D103
212
+ def SSSRawRecover(*, ctx: click.Context) -> None: # documentation is help/epilog/args # noqa: D103
210
213
  config: transcrypto.TransConfig = ctx.obj
211
214
  base_path: str = transcrypto.RequireKeyPath(config, 'sss')
212
215
  sss_pub: sss.ShamirSharedSecretPublic = transcrypto.LoadObj(
@@ -241,7 +244,7 @@ def SSSRawRecover(*, ctx: typer.Context) -> None: # documentation is help/epilo
241
244
  @clibase.CLIErrorGuard
242
245
  def SSSRawVerify( # documentation is help/epilog/args # noqa: D103
243
246
  *,
244
- ctx: typer.Context,
247
+ ctx: click.Context,
245
248
  secret: str = typer.Argument(..., help='Integer secret used to generate the shares, ≥ 1'),
246
249
  ) -> None:
247
250
  config: transcrypto.TransConfig = ctx.obj
@@ -273,7 +276,7 @@ def SSSRawVerify( # documentation is help/epilog/args # noqa: D103
273
276
  @clibase.CLIErrorGuard
274
277
  def SSSShares( # documentation is help/epilog/args # noqa: D103
275
278
  *,
276
- ctx: typer.Context,
279
+ ctx: click.Context,
277
280
  secret: str = typer.Argument(..., help='Secret to be protected'),
278
281
  count: int = typer.Argument(
279
282
  ...,
@@ -314,7 +317,7 @@ def SSSShares( # documentation is help/epilog/args # noqa: D103
314
317
  ),
315
318
  )
316
319
  @clibase.CLIErrorGuard
317
- def SSSRecover(*, ctx: typer.Context) -> None: # documentation is help/epilog/args # noqa: D103
320
+ def SSSRecover(*, ctx: click.Context) -> None: # documentation is help/epilog/args # noqa: D103
318
321
  config: transcrypto.TransConfig = ctx.obj
319
322
  base_path: str = transcrypto.RequireKeyPath(config, 'sss')
320
323
  subset: list[sss.ShamirSharePrivate] = []
@@ -7,8 +7,6 @@ from __future__ import annotations
7
7
  import dataclasses
8
8
  import functools
9
9
  import logging
10
- import os
11
- import threading
12
10
  from collections import abc
13
11
  from typing import cast
14
12
 
@@ -16,139 +14,21 @@ import click
16
14
  import typer
17
15
  from click import testing as click_testing
18
16
  from rich import console as rich_console
19
- from rich import logging as rich_logging
20
17
 
21
- from transcrypto import base
18
+ from transcrypto.utils import base
19
+ from transcrypto.utils import logging as tc_logging
22
20
 
23
- # Logging
24
- _LOG_FORMAT_NO_PROCESS: str = '%(funcName)s: %(message)s'
25
- _LOG_FORMAT_WITH_PROCESS: str = '%(processName)s/' + _LOG_FORMAT_NO_PROCESS
26
- _LOG_FORMAT_DATETIME: str = '[%Y%m%d-%H:%M:%S]' # e.g., [20240131-13:45:30]
27
- _LOG_LEVELS: dict[int, int] = {
28
- 0: logging.ERROR,
29
- 1: logging.WARNING,
30
- 2: logging.INFO,
31
- 3: logging.DEBUG,
32
- }
33
- _LOG_COMMON_PROVIDERS: set[str] = {
34
- 'werkzeug',
35
- 'gunicorn.error',
36
- 'gunicorn.access',
37
- 'uvicorn',
38
- 'uvicorn.error',
39
- 'uvicorn.access',
40
- 'django.server',
41
- }
42
21
 
43
- __console_lock: threading.RLock = threading.RLock()
44
- __console_singleton: rich_console.Console | None = None
45
-
46
-
47
- def Console() -> rich_console.Console:
48
- """Get the global console instance.
49
-
50
- Returns:
51
- rich.console.Console: The global console instance.
52
-
53
- """
54
- with __console_lock:
55
- if __console_singleton is None:
56
- return rich_console.Console() # fallback console if InitLogging hasn't been called yet
57
- return __console_singleton
58
-
59
-
60
- def ResetConsole() -> None:
61
- """Reset the global console instance."""
62
- global __console_singleton # noqa: PLW0603
63
- with __console_lock:
64
- __console_singleton = None
65
-
66
-
67
- def InitLogging(
68
- verbosity: int,
69
- /,
70
- *,
71
- include_process: bool = False,
72
- soft_wrap: bool = False,
73
- color: bool | None = False,
74
- ) -> tuple[rich_console.Console, int, bool]:
75
- """Initialize logger (with RichHandler) and get a rich.console.Console singleton.
76
-
77
- This method will also return the actual decided values for verbosity and color use.
78
- If you have a CLI app that uses this, its pytests should call `ResetConsole()` in a fixture, like:
79
-
80
- from transcrypto import logging
81
- @pytest.fixture(autouse=True)
82
- def _reset_base_logging() -> Generator[None, None, None]: # type: ignore
83
- logging.ResetConsole()
84
- yield # stop
85
-
86
- Args:
87
- verbosity (int): Logging verbosity level: 0==ERROR, 1==WARNING, 2==INFO, 3==DEBUG
88
- include_process (bool, optional): Whether to include process name in log output.
89
- soft_wrap (bool, optional): Whether to enable soft wrapping in the console.
90
- Default is False, and it means rich will hard-wrap long lines (by adding line breaks).
91
- color (bool | None, optional): Whether to enable/disable color output in the console.
92
- If None, respects NO_COLOR env var.
93
-
94
- Returns:
95
- tuple[rich_console.Console, int, bool]:
96
- (The initialized console instance, actual log level, actual color use)
22
+ @dataclasses.dataclass(kw_only=True, slots=True, frozen=True)
23
+ class CLIConfig:
24
+ """CLI global context, storing the configuration.
97
25
 
98
- Raises:
99
- RuntimeError: if you call this more than once
26
+ Attributes:
27
+ console (rich_console.Console): Rich console instance for output
28
+ verbose (int): Verbosity level (0-3)
29
+ color (bool | None): Color preference (None=auto, True=force, False=disable)
100
30
 
101
31
  """
102
- global __console_singleton # noqa: PLW0603
103
- with __console_lock:
104
- if __console_singleton is not None:
105
- raise RuntimeError(
106
- 'calling InitLogging() more than once is forbidden; '
107
- 'use Console() to get a console after first creation'
108
- )
109
- # set level
110
- logging_level: int = _LOG_LEVELS.get(min(verbosity, 3), logging.ERROR)
111
- # respect NO_COLOR unless the caller has already decided (treat env presence as "disable color")
112
- no_color: bool = (
113
- False
114
- if (os.getenv('NO_COLOR') is None and color is None)
115
- else ((os.getenv('NO_COLOR') is not None) if color is None else (not color))
116
- )
117
- # create console and configure logging
118
- console = rich_console.Console(soft_wrap=soft_wrap, no_color=no_color)
119
- logging.basicConfig(
120
- level=logging_level,
121
- format=_LOG_FORMAT_WITH_PROCESS if include_process else _LOG_FORMAT_NO_PROCESS,
122
- datefmt=_LOG_FORMAT_DATETIME,
123
- handlers=[
124
- rich_logging.RichHandler( # we show name/line, but want time & level
125
- console=console,
126
- rich_tracebacks=True,
127
- show_time=True,
128
- show_level=True,
129
- show_path=True,
130
- ),
131
- ],
132
- force=True, # force=True to override any previous logging config
133
- )
134
- # configure common loggers
135
- logging.captureWarnings(True)
136
- for name in _LOG_COMMON_PROVIDERS:
137
- log: logging.Logger = logging.getLogger(name)
138
- log.handlers.clear()
139
- log.propagate = True
140
- log.setLevel(logging_level)
141
- __console_singleton = console # need a global statement to re-bind this one
142
- logging.info(
143
- f'Logging initialized at level {logging.getLevelName(logging_level)} / '
144
- f'{"NO " if no_color else ""}COLOR'
145
- )
146
- return (console, logging_level, not no_color)
147
-
148
-
149
- @dataclasses.dataclass(kw_only=True, slots=True, frozen=True)
150
- class CLIConfig:
151
- """CLI global context, storing the configuration."""
152
32
 
153
33
  console: rich_console.Console
154
34
  verbose: int
@@ -171,10 +51,10 @@ def CLIErrorGuard[**P](fn: abc.Callable[P, None], /) -> abc.Callable[P, None]:
171
51
  except (base.Error, ValueError) as err:
172
52
  # get context
173
53
  ctx: object | None = dict(kwargs).get('ctx')
174
- if not isinstance(ctx, typer.Context):
175
- ctx = next((a for a in args if isinstance(a, typer.Context)), None)
54
+ if not isinstance(ctx, click.Context):
55
+ ctx = next((a for a in args if isinstance(a, click.Context)), None)
176
56
  # print error nicely
177
- if isinstance(ctx, typer.Context):
57
+ if isinstance(ctx, click.Context):
178
58
  # we have context
179
59
  obj: CLIConfig = cast('CLIConfig', ctx.obj)
180
60
  if obj.verbose >= 2: # verbose >= 2 means INFO level or more verbose # noqa: PLR2004
@@ -183,29 +63,29 @@ def CLIErrorGuard[**P](fn: abc.Callable[P, None], /) -> abc.Callable[P, None]:
183
63
  obj.console.print(str(err)) # print only error message
184
64
  # no context
185
65
  elif logging.getLogger().getEffectiveLevel() < logging.INFO:
186
- Console().print(str(err)) # print only error message (DEBUG level is verbose already)
66
+ tc_logging.Console().print(str(err)) # print only error message (DEBUG is verbose already)
187
67
  else:
188
- Console().print_exception() # print full traceback (less verbose mode needs it)
68
+ tc_logging.Console().print_exception() # print full traceback (less verbose mode needs it)
189
69
 
190
70
  return _Wrapper
191
71
 
192
72
 
193
73
  def _ClickWalk(
194
74
  command: click.Command,
195
- ctx: typer.Context,
75
+ ctx: click.Context,
196
76
  path: list[str],
197
77
  /,
198
- ) -> abc.Iterator[tuple[list[str], click.Command, typer.Context]]:
78
+ ) -> abc.Iterator[tuple[list[str], click.Command, click.Context]]:
199
79
  """Recursively walk Click commands/groups.
200
80
 
201
81
  Yields:
202
- tuple[list[str], click.Command, typer.Context]: path, command, ctx
82
+ tuple[list[str], click.Command, click.Context]: path, command, ctx
203
83
 
204
84
  """
205
85
  yield (path, command, ctx) # yield self
206
86
  # now walk subcommands, if any
207
87
  sub_cmd: click.Command | None
208
- sub_ctx: typer.Context
88
+ sub_ctx: click.Context
209
89
  # prefer the explicit `.commands` mapping when present; otherwise fall back to
210
90
  # click's `list_commands()`/`get_command()` for dynamic groups
211
91
  if not isinstance(command, click.Group):
@@ -213,7 +93,7 @@ def _ClickWalk(
213
93
  # explicit commands mapping
214
94
  if command.commands:
215
95
  for name, sub_cmd in sorted(command.commands.items()):
216
- sub_ctx = typer.Context(sub_cmd, info_name=name, parent=ctx)
96
+ sub_ctx = click.Context(sub_cmd, info_name=name, parent=ctx)
217
97
  yield from _ClickWalk(sub_cmd, sub_ctx, [*path, name])
218
98
  return
219
99
  # dynamic commands
@@ -221,7 +101,7 @@ def _ClickWalk(
221
101
  sub_cmd = command.get_command(ctx, name)
222
102
  if sub_cmd is None:
223
103
  continue # skip invalid subcommands
224
- sub_ctx = typer.Context(sub_cmd, info_name=name, parent=ctx)
104
+ sub_ctx = click.Context(sub_cmd, info_name=name, parent=ctx)
225
105
  yield from _ClickWalk(sub_cmd, sub_ctx, [*path, name])
226
106
 
227
107
 
@@ -260,14 +140,14 @@ def GenerateTyperHelpMarkdown(
260
140
  """
261
141
  # prepare Click root command and context
262
142
  click_root: click.Command = typer.main.get_command(typer_app)
263
- root_ctx: typer.Context = typer.Context(click_root, info_name=prog_name)
143
+ root_ctx: click.Context = click.Context(click_root, info_name=prog_name)
264
144
  runner = click_testing.CliRunner()
265
145
  parts: list[str] = []
266
146
  for path, _, _ in _ClickWalk(click_root, root_ctx, []):
267
147
  # build command path
268
148
  command_path: str = ' '.join([prog_name, *path]).strip()
269
149
  heading_prefix: str = '#' * max(1, heading_level + len(path))
270
- ResetConsole() # ensure clean state for each command (also it raises on duplicate loggers)
150
+ tc_logging.ResetConsole() # ensure clean state for the command
271
151
  # invoke --help for this command path
272
152
  result: click_testing.Result = runner.invoke(
273
153
  click_root,
@@ -4,10 +4,13 @@
4
4
 
5
5
  from __future__ import annotations
6
6
 
7
+ import click
7
8
  import typer
8
9
 
9
- from transcrypto import base, modmath, transcrypto
10
+ from transcrypto import transcrypto
10
11
  from transcrypto.cli import clibase
12
+ from transcrypto.core import modmath
13
+ from transcrypto.utils import base, saferandom
11
14
 
12
15
  # =============================== "PRIME"-like COMMANDS ============================================
13
16
 
@@ -26,7 +29,7 @@ from transcrypto.cli import clibase
26
29
  @clibase.CLIErrorGuard
27
30
  def IsPrimeCLI( # documentation is help/epilog/args # noqa: D103
28
31
  *,
29
- ctx: typer.Context,
32
+ ctx: click.Context,
30
33
  n: str = typer.Argument(..., help='Integer to test, ≥ 1'),
31
34
  ) -> None:
32
35
  config: transcrypto.TransConfig = ctx.obj
@@ -42,7 +45,7 @@ def IsPrimeCLI( # documentation is help/epilog/args # noqa: D103
42
45
  @clibase.CLIErrorGuard
43
46
  def PrimeGenCLI( # documentation is help/epilog/args # noqa: D103
44
47
  *,
45
- ctx: typer.Context,
48
+ ctx: click.Context,
46
49
  start: str = typer.Argument(..., help='Starting integer (inclusive), ≥ 0'),
47
50
  count: int = typer.Option(1, '-c', '--count', min=1, help='How many to print, ≥ 1'),
48
51
  ) -> None:
@@ -75,7 +78,7 @@ def PrimeGenCLI( # documentation is help/epilog/args # noqa: D103
75
78
  @clibase.CLIErrorGuard
76
79
  def MersenneCLI( # documentation is help/epilog/args # noqa: D103
77
80
  *,
78
- ctx: typer.Context,
81
+ ctx: click.Context,
79
82
  min_k: int = typer.Option(2, '-k', '--min-k', min=1, help='Starting exponent `k`, ≥ 2'),
80
83
  max_k: int = typer.Option(10000, '-m', '--max-k', min=1, help='Stop once `k` > `max-k`, ≥ 2'),
81
84
  ) -> None:
@@ -107,7 +110,7 @@ def MersenneCLI( # documentation is help/epilog/args # noqa: D103
107
110
  @clibase.CLIErrorGuard
108
111
  def GcdCLI( # documentation is help/epilog/args # noqa: D103
109
112
  *,
110
- ctx: typer.Context,
113
+ ctx: click.Context,
111
114
  a: str = typer.Argument(..., help='Integer, ≥ 0'),
112
115
  b: str = typer.Argument(..., help="Integer, ≥ 0 (can't be both zero)"),
113
116
  ) -> None:
@@ -116,7 +119,7 @@ def GcdCLI( # documentation is help/epilog/args # noqa: D103
116
119
  b_i: int = transcrypto.ParseInt(b, min_value=0)
117
120
  if a_i == 0 and b_i == 0:
118
121
  raise base.InputError("`a` and `b` can't both be zero")
119
- config.console.print(base.GCD(a_i, b_i))
122
+ config.console.print(modmath.GCD(a_i, b_i))
120
123
 
121
124
 
122
125
  @transcrypto.app.command(
@@ -138,7 +141,7 @@ def GcdCLI( # documentation is help/epilog/args # noqa: D103
138
141
  @clibase.CLIErrorGuard
139
142
  def XgcdCLI( # documentation is help/epilog/args # noqa: D103
140
143
  *,
141
- ctx: typer.Context,
144
+ ctx: click.Context,
142
145
  a: str = typer.Argument(..., help='Integer, ≥ 0'),
143
146
  b: str = typer.Argument(..., help="Integer, ≥ 0 (can't be both zero)"),
144
147
  ) -> None:
@@ -147,7 +150,7 @@ def XgcdCLI( # documentation is help/epilog/args # noqa: D103
147
150
  b_i: int = transcrypto.ParseInt(b, min_value=0)
148
151
  if a_i == 0 and b_i == 0:
149
152
  raise base.InputError("`a` and `b` can't both be zero")
150
- config.console.print(str(base.ExtendedGCD(a_i, b_i)))
153
+ config.console.print(str(modmath.ExtendedGCD(a_i, b_i)))
151
154
 
152
155
 
153
156
  # ================================= "RANDOM" COMMAND ===============================================
@@ -168,11 +171,11 @@ transcrypto.app.add_typer(random_app, name='random')
168
171
  @clibase.CLIErrorGuard
169
172
  def RandomBits( # documentation is help/epilog/args # noqa: D103
170
173
  *,
171
- ctx: typer.Context,
174
+ ctx: click.Context,
172
175
  bits: int = typer.Argument(..., min=8, help='Number of bits, ≥ 8'),
173
176
  ) -> None:
174
177
  config: transcrypto.TransConfig = ctx.obj
175
- config.console.print(base.RandBits(bits))
178
+ config.console.print(saferandom.RandBits(bits))
176
179
 
177
180
 
178
181
  @random_app.command(
@@ -183,14 +186,14 @@ def RandomBits( # documentation is help/epilog/args # noqa: D103
183
186
  @clibase.CLIErrorGuard
184
187
  def RandomInt( # documentation is help/epilog/args # noqa: D103
185
188
  *,
186
- ctx: typer.Context,
189
+ ctx: click.Context,
187
190
  min_: str = typer.Argument(..., help='Minimum, ≥ 0'),
188
191
  max_: str = typer.Argument(..., help='Maximum, > `min`'),
189
192
  ) -> None:
190
193
  config: transcrypto.TransConfig = ctx.obj
191
194
  min_i: int = transcrypto.ParseInt(min_, min_value=0)
192
195
  max_i: int = transcrypto.ParseInt(max_, min_value=min_i + 1)
193
- config.console.print(base.RandInt(min_i, max_i))
196
+ config.console.print(saferandom.RandInt(min_i, max_i))
194
197
 
195
198
 
196
199
  @random_app.command(
@@ -205,11 +208,11 @@ def RandomInt( # documentation is help/epilog/args # noqa: D103
205
208
  @clibase.CLIErrorGuard
206
209
  def RandomBytes( # documentation is help/epilog/args # noqa: D103
207
210
  *,
208
- ctx: typer.Context,
211
+ ctx: click.Context,
209
212
  n: int = typer.Argument(..., min=1, help='Number of bytes, ≥ 1'),
210
213
  ) -> None:
211
214
  config: transcrypto.TransConfig = ctx.obj
212
- config.console.print(transcrypto.BytesToText(base.RandBytes(n), config.output_format))
215
+ config.console.print(transcrypto.BytesToText(saferandom.RandBytes(n), config.output_format))
213
216
 
214
217
 
215
218
  @random_app.command(
@@ -220,7 +223,7 @@ def RandomBytes( # documentation is help/epilog/args # noqa: D103
220
223
  @clibase.CLIErrorGuard
221
224
  def RandomPrime( # documentation is help/epilog/args # noqa: D103
222
225
  *,
223
- ctx: typer.Context,
226
+ ctx: click.Context,
224
227
  bits: int = typer.Argument(..., min=11, help='Bit length, ≥ 11'),
225
228
  ) -> None:
226
229
  config: transcrypto.TransConfig = ctx.obj
@@ -256,7 +259,7 @@ transcrypto.app.add_typer(mod_app, name='mod')
256
259
  @clibase.CLIErrorGuard
257
260
  def ModInv( # documentation is help/epilog/args # noqa: D103
258
261
  *,
259
- ctx: typer.Context,
262
+ ctx: click.Context,
260
263
  a: str = typer.Argument(..., help='Integer to invert'),
261
264
  m: str = typer.Argument(..., help='Modulus `m`, ≥ 2'),
262
265
  ) -> None:
@@ -286,7 +289,7 @@ def ModInv( # documentation is help/epilog/args # noqa: D103
286
289
  @clibase.CLIErrorGuard
287
290
  def ModDiv( # documentation is help/epilog/args # noqa: D103
288
291
  *,
289
- ctx: typer.Context,
292
+ ctx: click.Context,
290
293
  x: str = typer.Argument(..., help='Integer'),
291
294
  y: str = typer.Argument(..., help='Integer, cannot be zero'),
292
295
  m: str = typer.Argument(..., help='Modulus `m`, ≥ 2'),
@@ -315,7 +318,7 @@ def ModDiv( # documentation is help/epilog/args # noqa: D103
315
318
  @clibase.CLIErrorGuard
316
319
  def ModExp( # documentation is help/epilog/args # noqa: D103
317
320
  *,
318
- ctx: typer.Context,
321
+ ctx: click.Context,
319
322
  a: str = typer.Argument(..., help='Integer value'),
320
323
  e: str = typer.Argument(..., help='Integer exponent, ≥ 0'),
321
324
  m: str = typer.Argument(..., help='Modulus `m`, ≥ 2'),
@@ -344,7 +347,7 @@ def ModExp( # documentation is help/epilog/args # noqa: D103
344
347
  @clibase.CLIErrorGuard
345
348
  def ModPoly( # documentation is help/epilog/args # noqa: D103
346
349
  *,
347
- ctx: typer.Context,
350
+ ctx: click.Context,
348
351
  x: str = typer.Argument(..., help='Evaluation point `x`'),
349
352
  m: str = typer.Argument(..., help='Modulus `m`, ≥ 2'),
350
353
  coeff: list[str] = typer.Argument( # noqa: B008
@@ -376,7 +379,7 @@ def ModPoly( # documentation is help/epilog/args # noqa: D103
376
379
  @clibase.CLIErrorGuard
377
380
  def ModLagrange( # documentation is help/epilog/args # noqa: D103
378
381
  *,
379
- ctx: typer.Context,
382
+ ctx: click.Context,
380
383
  x: str = typer.Argument(..., help='Evaluation point `x`'),
381
384
  m: str = typer.Argument(..., help='Modulus `m`, ≥ 2'),
382
385
  pt: list[str] = typer.Argument( # noqa: B008
@@ -410,7 +413,7 @@ def ModLagrange( # documentation is help/epilog/args # noqa: D103
410
413
  @clibase.CLIErrorGuard
411
414
  def ModCRT( # documentation is help/epilog/args # noqa: D103
412
415
  *,
413
- ctx: typer.Context,
416
+ ctx: click.Context,
414
417
  a1: str = typer.Argument(..., help='Integer residue for first congruence'),
415
418
  m1: str = typer.Argument(..., help='Modulus `m1`, ≥ 2'),
416
419
  a2: str = typer.Argument(..., help='Integer residue for second congruence'),