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 +1 -1
- transcrypto/cli/aeshash.py +14 -12
- transcrypto/cli/bidsecret.py +19 -16
- transcrypto/cli/clibase.py +22 -142
- transcrypto/cli/intmath.py +24 -21
- transcrypto/cli/publicalgos.py +28 -26
- transcrypto/core/__init__.py +3 -0
- transcrypto/{aes.py → core/aes.py} +17 -29
- transcrypto/core/bid.py +161 -0
- transcrypto/{dsa.py → core/dsa.py} +28 -27
- transcrypto/{elgamal.py → core/elgamal.py} +33 -32
- transcrypto/core/hashes.py +96 -0
- transcrypto/core/key.py +735 -0
- transcrypto/{modmath.py → core/modmath.py} +91 -17
- transcrypto/{rsa.py → core/rsa.py} +51 -50
- transcrypto/{sss.py → core/sss.py} +27 -26
- transcrypto/profiler.py +25 -11
- transcrypto/transcrypto.py +25 -15
- transcrypto/utils/__init__.py +3 -0
- transcrypto/utils/base.py +72 -0
- transcrypto/utils/human.py +278 -0
- transcrypto/utils/logging.py +139 -0
- transcrypto/utils/saferandom.py +102 -0
- transcrypto/utils/stats.py +360 -0
- transcrypto/utils/timer.py +175 -0
- {transcrypto-1.8.0.dist-info → transcrypto-2.0.3.dist-info}/METADATA +101 -101
- transcrypto-2.0.3.dist-info/RECORD +33 -0
- {transcrypto-1.8.0.dist-info → transcrypto-2.0.3.dist-info}/WHEEL +1 -1
- transcrypto/base.py +0 -1637
- transcrypto-1.8.0.dist-info/RECORD +0 -23
- /transcrypto/{constants.py → core/constants.py} +0 -0
- {transcrypto-1.8.0.dist-info → transcrypto-2.0.3.dist-info}/entry_points.txt +0 -0
- {transcrypto-1.8.0.dist-info → transcrypto-2.0.3.dist-info}/licenses/LICENSE +0 -0
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__ = '
|
|
6
|
+
__version__ = '2.0.3' # remember to also update pyproject.toml
|
|
7
7
|
__author__ = 'Daniel Balparda <balparda@github.com>'
|
transcrypto/cli/aeshash.py
CHANGED
|
@@ -10,8 +10,10 @@ import re
|
|
|
10
10
|
import click
|
|
11
11
|
import typer
|
|
12
12
|
|
|
13
|
-
from transcrypto import
|
|
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:
|
|
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(
|
|
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:
|
|
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(
|
|
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:
|
|
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(
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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,
|
transcrypto/cli/bidsecret.py
CHANGED
|
@@ -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
|
|
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:
|
|
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:
|
|
49
|
-
bid_pub:
|
|
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:
|
|
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:
|
|
71
|
-
base_path + '.priv', config.protect,
|
|
73
|
+
bid_priv: bid.PrivateBid512 = transcrypto.LoadObj(
|
|
74
|
+
base_path + '.priv', config.protect, bid.PrivateBid512
|
|
72
75
|
)
|
|
73
|
-
bid_pub:
|
|
74
|
-
base_path + '.pub', config.protect,
|
|
76
|
+
bid_pub: bid.PublicBid512 = transcrypto.LoadObj(
|
|
77
|
+
base_path + '.pub', config.protect, bid.PublicBid512
|
|
75
78
|
)
|
|
76
|
-
bid_pub_expect:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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] = []
|
transcrypto/cli/clibase.py
CHANGED
|
@@ -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
|
-
|
|
44
|
-
|
|
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
|
-
|
|
99
|
-
|
|
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,
|
|
175
|
-
ctx = next((a for a in args if isinstance(a,
|
|
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,
|
|
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
|
|
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:
|
|
75
|
+
ctx: click.Context,
|
|
196
76
|
path: list[str],
|
|
197
77
|
/,
|
|
198
|
-
) -> abc.Iterator[tuple[list[str], click.Command,
|
|
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,
|
|
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:
|
|
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 =
|
|
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 =
|
|
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:
|
|
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
|
|
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,
|
transcrypto/cli/intmath.py
CHANGED
|
@@ -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
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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(
|
|
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:
|
|
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(
|
|
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:
|
|
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(
|
|
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:
|
|
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(
|
|
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:
|
|
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(
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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'),
|