transcrypto 1.2.0__py3-none-any.whl → 1.4.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/sss.py CHANGED
@@ -314,9 +314,7 @@ class ShamirSharedSecretPrivate(ShamirSharedSecretPublic):
314
314
  if bit_length < 10:
315
315
  raise base.InputError(f'invalid bit length: {bit_length=}')
316
316
  # make the primes
317
- unique_primes: set[int] = set()
318
- while len(unique_primes) < minimum_shares:
319
- unique_primes.add(modmath.NBitRandomPrime(bit_length))
317
+ unique_primes: set[int] = modmath.NBitRandomPrimes(bit_length, n_primes=minimum_shares)
320
318
  # get the largest prime for the modulus
321
319
  ordered_primes: list[int] = list(unique_primes)
322
320
  modulus: int = max(ordered_primes)
@@ -10,13 +10,15 @@ Notes on the layout (quick mental model):
10
10
 
11
11
  isprime, primegen, mersenne
12
12
  gcd, xgcd, and grouped mod inv|div|exp|poly|lagrange|crt
13
- random bits|int|bytes|prime, hash sha256|sha512|file
13
+ random bits|int|bytes|prime
14
+ hash sha256|sha512|file
14
15
  aes key frompass, aes encrypt|decrypt (GCM), aes ecb encrypt|decrypt
15
16
  rsa new|encrypt|decrypt|sign|verify|rawencrypt|rawdecrypt|rawsign|rawverify
16
17
  elgamal shared|new|encrypt|decrypt|sign|verify|rawencrypt|rawdecrypt|rawsign|rawverify
17
18
  dsa shared|new|sign|verify|rawsign|rawverify
18
19
  bid new|verify
19
20
  sss new|shares|recover|rawshares|rawrecover|rawverify
21
+ doc md
20
22
  """
21
23
 
22
24
  from __future__ import annotations
@@ -27,7 +29,7 @@ import glob
27
29
  import logging
28
30
  # import pdb
29
31
  import sys
30
- from typing import Any, Iterable, Sequence
32
+ from typing import Any, Iterable
31
33
 
32
34
  from . import base, modmath, rsa, sss, elgamal, dsa, aes
33
35
 
@@ -119,179 +121,6 @@ def _LoadObj(path: str, password: str | None, expect: type, /) -> Any:
119
121
  return obj
120
122
 
121
123
 
122
- def _FlagNames(a: argparse.Action, /) -> list[str]:
123
- # Positional args have empty 'option_strings'; otherwise use them (e.g., ['-v','--verbose'])
124
- if a.option_strings:
125
- return list(a.option_strings)
126
- if a.nargs:
127
- if isinstance(a.metavar, str) and a.metavar:
128
- # e.g., nargs=2, metavar='FILE'
129
- return [a.metavar]
130
- if isinstance(a.metavar, tuple):
131
- # e.g., nargs=2, metavar=('FILE1', 'FILE2')
132
- return list(a.metavar)
133
- # Otherwise, it’s a positional arg with no flags, so return the destination name
134
- return [a.dest]
135
-
136
-
137
- def _ActionIsSubparser(a: argparse.Action, /) -> bool:
138
- return isinstance(a, argparse._SubParsersAction) # type: ignore[attr-defined] # pylint: disable=protected-access
139
-
140
-
141
- def _FormatDefault(a: argparse.Action, /) -> str:
142
- if a.default is argparse.SUPPRESS:
143
- return ''
144
- if isinstance(a.default, bool):
145
- return ' (default: on)' if a.default else ''
146
- if a.default in (None, '', 0, False):
147
- return ''
148
- return f' (default: {a.default})'
149
-
150
-
151
- def _FormatChoices(a: argparse.Action, /) -> str:
152
- return f' choices: {list(a.choices)}' if getattr(a, 'choices', None) else '' # type:ignore
153
-
154
-
155
- def _FormatType(a: argparse.Action, /) -> str:
156
- t: Any | None = getattr(a, 'type', None)
157
- if t is None:
158
- return ''
159
- # Show clean type names (int, str, float); for callables, just say 'custom'
160
- return f' type: {t.__name__ if hasattr(t, "__name__") else "custom"}'
161
-
162
-
163
- def _FormatNArgs(a: argparse.Action, /) -> str:
164
- return f' nargs: {a.nargs}' if getattr(a, 'nargs', None) not in (None, 0) else ''
165
-
166
-
167
- def _RowsForActions(actions: Sequence[argparse.Action], /) -> list[tuple[str, str]]:
168
- rows: list[tuple[str, str]] = []
169
- for a in actions:
170
- if _ActionIsSubparser(a):
171
- continue
172
- # skip the built-in help action; it’s implied
173
- if getattr(a, 'help', '') == argparse.SUPPRESS or isinstance(a, argparse._HelpAction): # type: ignore[attr-defined] # pylint: disable=protected-access
174
- continue
175
- flags: str = ', '.join(_FlagNames(a))
176
- meta: str = ''.join(
177
- (_FormatType(a), _FormatNArgs(a), _FormatChoices(a), _FormatDefault(a))).strip()
178
- desc: str = (a.help or '').strip()
179
- if meta:
180
- desc = f'{desc} [{meta}]' if desc else f'[{meta}]'
181
- rows.append((flags, desc))
182
- return rows
183
-
184
-
185
- def _MarkdownTable(
186
- rows: Sequence[tuple[str, str]],
187
- headers: tuple[str, str] = ('Option/Arg', 'Description'), /) -> str:
188
- if not rows:
189
- return ''
190
- out: list[str] = ['| ' + headers[0] + ' | ' + headers[1] + ' |', '|---|---|']
191
- for left, right in rows:
192
- out.append(f'| `{left}` | {right} |')
193
- return '\n'.join(out)
194
-
195
-
196
- def _WalkSubcommands(
197
- parser: argparse.ArgumentParser, path: list[str] | None = None, /) -> list[
198
- tuple[list[str], argparse.ArgumentParser, Any]]:
199
- path = path or []
200
- items: list[tuple[list[str], argparse.ArgumentParser, Any]] = []
201
- # sub_action = None
202
- name: str
203
- sp: argparse.ArgumentParser
204
- for action in parser._actions: # type: ignore[attr-defined] # pylint: disable=protected-access
205
- if _ActionIsSubparser(action):
206
- # sub_action = a # type: ignore[assignment]
207
- for name, sp in action.choices.items(): # type:ignore
208
- items.append((path + [name], sp, action)) # type:ignore
209
- items.extend(_WalkSubcommands(sp, path + [name])) # type:ignore
210
- return items
211
-
212
-
213
- def _HelpText(sub_parser: argparse.ArgumentParser, parent_sub_action: Any, /) -> str:
214
- if parent_sub_action is not None:
215
- for choice_action in parent_sub_action._choices_actions: # type: ignore # pylint: disable=protected-access
216
- if choice_action.dest == sub_parser.prog.split()[-1]:
217
- return choice_action.help or ''
218
- return ''
219
-
220
-
221
- def _GenerateCLIMarkdown() -> str: # pylint: disable=too-many-locals
222
- """Return a Markdown doc section that reflects the current _BuildParser() tree.
223
-
224
- Will treat epilog strings as examples, splitting on '$$' to get multiple examples.
225
- """
226
- parser: argparse.ArgumentParser = _BuildParser()
227
- assert parser.prog == 'poetry run transcrypto', 'should never happen: module name changed?'
228
- prog: str = 'transcrypto' # no '.py' needed because poetry run has an alias
229
- lines: list[str] = ['']
230
- # Header + global flags
231
- lines.append('## Command-Line Interface\n')
232
- lines.append(
233
- f'`{prog}` is a command-line utility that provides access to all core functionality '
234
- 'described in this documentation. It serves as a convenient wrapper over the Python APIs, '
235
- 'enabling **cryptographic operations**, **number theory functions**, **secure randomness '
236
- 'generation**, **hashing**, **AES**, **RSA**, **El-Gamal**, **DSA**, **bidding**, **SSS**, '
237
- 'and other utilities without writing code.\n')
238
- lines.append('Invoke with:\n')
239
- lines.append('```bash')
240
- lines.append(f'poetry run {prog} <command> [sub-command] [options...]')
241
- lines.append('```\n')
242
- # Global options table
243
- global_rows: list[tuple[str, str]] = _RowsForActions(parser._actions) # type: ignore[attr-defined] # pylint: disable=protected-access
244
- if global_rows:
245
- lines.append('### Global Options\n')
246
- lines.append(_MarkdownTable(global_rows))
247
- lines.append('')
248
- # Top-level commands summary
249
- lines.append('### Top-Level Commands\n')
250
- # Find top-level subparsers to list available commands
251
- top_subs: list[argparse.Action] = [a for a in parser._actions if _ActionIsSubparser(a)] # type: ignore[attr-defined] # pylint: disable=protected-access
252
- for action in top_subs:
253
- for name, sp in action.choices.items(): # type: ignore[union-attr]
254
- help_text: str = (sp.description or ' '.join(i.strip() for i in sp.format_usage().splitlines())).strip() # type:ignore
255
- short: str = (sp.help if hasattr(sp, 'help') else '') or '' # type:ignore
256
- help_text = short or help_text # type:ignore
257
- help_text = help_text.replace('usage: ', '').strip() # type:ignore
258
- lines.append(f'- **`{name}`** — `{help_text}`')
259
- lines.append('')
260
- if parser.epilog:
261
- lines.append('```bash')
262
- lines.append(parser.epilog)
263
- lines.append('```\n')
264
- # Detailed sections per (sub)command
265
- for path, sub_parser, parent_sub_action in _WalkSubcommands(parser):
266
- if len(path) == 1:
267
- lines.append('---\n') # horizontal rule between top-level commands
268
- header: str = ' '.join(path)
269
- lines.append(f'###{"" if len(path) == 1 else "#"} `{header}`') # (header level 3 or 4)
270
- # Usage block
271
- help_text = _HelpText(sub_parser, parent_sub_action)
272
- if help_text:
273
- lines.append(f'\n{help_text}')
274
- usage: str = sub_parser.format_usage().replace('usage: ', '').strip()
275
- lines.append('\n```bash')
276
- lines.append(str(usage))
277
- lines.append('```\n')
278
- # Options/args table
279
- rows: list[tuple[str, str]] = _RowsForActions(sub_parser._actions) # type: ignore[attr-defined] # pylint: disable=protected-access
280
- if rows:
281
- lines.append(_MarkdownTable(rows))
282
- lines.append('')
283
- # Examples (if any) - stored in epilog argument
284
- epilog: str = sub_parser.epilog.strip() if sub_parser.epilog else ''
285
- if epilog:
286
- lines.append('**Example:**\n')
287
- lines.append('```bash')
288
- for epilog_line in epilog.split('$$'):
289
- lines.append(f'$ poetry run {prog} {epilog_line.strip()}')
290
- lines.append('```\n')
291
- # join all lines as the markdown string
292
- return '\n'.join(lines)
293
-
294
-
295
124
  def _BuildParser() -> argparse.ArgumentParser: # pylint: disable=too-many-statements,too-many-locals
296
125
  """Construct the CLI argument parser (kept in sync with the docs)."""
297
126
  # ========================= main parser ==========================================================
@@ -321,12 +150,12 @@ def _BuildParser() -> argparse.ArgumentParser: # pylint: disable=too-many-state
321
150
  ' poetry run transcrypto mod crt 6 7 127 13\n\n'
322
151
  ' # --- Hashing ---\n'
323
152
  ' poetry run transcrypto hash sha256 xyz\n'
324
- ' poetry run transcrypto --b64 hash sha512 eHl6\n'
153
+ ' poetry run transcrypto --b64 hash sha512 -- eHl6\n'
325
154
  ' poetry run transcrypto hash file /etc/passwd --digest sha512\n\n'
326
155
  ' # --- AES ---\n'
327
156
  ' poetry run transcrypto --out-b64 aes key "correct horse battery staple"\n'
328
- ' poetry run transcrypto --b64 --out-b64 aes encrypt -k "<b64key>" "secret"\n'
329
- ' poetry run transcrypto --b64 --out-b64 aes decrypt -k "<b64key>" "<ciphertext>"\n'
157
+ ' poetry run transcrypto --b64 --out-b64 aes encrypt -k "<b64key>" -- "secret"\n'
158
+ ' poetry run transcrypto --b64 --out-b64 aes decrypt -k "<b64key>" -- "<ciphertext>"\n'
330
159
  ' poetry run transcrypto aes ecb -k "<b64key>" encrypt "<128bithexblock>"\n' # cspell:disable-line
331
160
  ' poetry run transcrypto aes ecb -k "<b64key>" decrypt "<128bithexblock>"\n\n' # cspell:disable-line
332
161
  ' # --- RSA ---\n'
@@ -335,6 +164,10 @@ def _BuildParser() -> argparse.ArgumentParser: # pylint: disable=too-many-state
335
164
  ' poetry run transcrypto -p rsa-key.priv rsa rawdecrypt <ciphertext>\n'
336
165
  ' poetry run transcrypto -p rsa-key.priv rsa rawsign <message>\n'
337
166
  ' poetry run transcrypto -p rsa-key.pub rsa rawverify <message> <signature>\n\n'
167
+ ' poetry run transcrypto --bin --out-b64 -p rsa-key.pub rsa encrypt -a <aad> <plaintext>\n'
168
+ ' poetry run transcrypto --b64 --out-bin -p rsa-key.priv rsa decrypt -a <aad> -- <ciphertext>\n'
169
+ ' poetry run transcrypto --bin --out-b64 -p rsa-key.priv rsa sign <message>\n'
170
+ ' poetry run transcrypto --b64 -p rsa-key.pub rsa verify -- <message> <signature>\n\n'
338
171
  ' # --- ElGamal ---\n'
339
172
  ' poetry run transcrypto -p eg-key elgamal shared --bits 2048\n'
340
173
  ' poetry run transcrypto -p eg-key elgamal new\n'
@@ -342,19 +175,27 @@ def _BuildParser() -> argparse.ArgumentParser: # pylint: disable=too-many-state
342
175
  ' poetry run transcrypto -p eg-key.priv elgamal rawdecrypt <c1:c2>\n'
343
176
  ' poetry run transcrypto -p eg-key.priv elgamal rawsign <message>\n'
344
177
  ' poetry run transcrypto-p eg-key.pub elgamal rawverify <message> <s1:s2>\n\n'
178
+ ' poetry run transcrypto --bin --out-b64 -p eg-key.pub elgamal encrypt <plaintext>\n'
179
+ ' poetry run transcrypto --b64 --out-bin -p eg-key.priv elgamal decrypt -- <ciphertext>\n'
180
+ ' poetry run transcrypto --bin --out-b64 -p eg-key.priv elgamal sign <message>\n'
181
+ ' poetry run transcrypto --b64 -p eg-key.pub elgamal verify -- <message> <signature>\n\n'
345
182
  ' # --- DSA ---\n'
346
183
  ' poetry run transcrypto -p dsa-key dsa shared --p-bits 2048 --q-bits 256\n'
347
184
  ' poetry run transcrypto -p dsa-key dsa new\n'
348
185
  ' poetry run transcrypto -p dsa-key.priv dsa rawsign <message>\n'
349
186
  ' poetry run transcrypto -p dsa-key.pub dsa rawverify <message> <s1:s2>\n\n'
187
+ ' poetry run transcrypto --bin --out-b64 -p dsa-key.priv dsa sign <message>\n'
188
+ ' poetry run transcrypto --b64 -p dsa-key.pub dsa verify -- <message> <signature>\n\n'
350
189
  ' # --- Public Bid ---\n'
351
190
  ' poetry run transcrypto --bin bid new "tomorrow it will rain"\n'
352
191
  ' poetry run transcrypto --out-bin bid verify\n\n'
353
192
  ' # --- Shamir Secret Sharing (SSS) ---\n'
354
193
  ' poetry run transcrypto -p sss-key sss new 3 --bits 1024\n'
355
- ' poetry run transcrypto -p sss-key sss rawshares <secret> 5\n'
194
+ ' poetry run transcrypto -p sss-key sss rawshares <secret> <n>\n'
356
195
  ' poetry run transcrypto -p sss-key sss rawrecover\n'
357
196
  ' poetry run transcrypto -p sss-key sss rawverify <secret>'
197
+ ' poetry run transcrypto --bin -p sss-key sss shares <secret> <n>\n'
198
+ ' poetry run transcrypto --out-bin -p sss-key sss recover\n'
358
199
  ),
359
200
  formatter_class=argparse.RawTextHelpFormatter)
360
201
  sub = parser.add_subparsers(dest='command')
@@ -368,7 +209,10 @@ def _BuildParser() -> argparse.ArgumentParser: # pylint: disable=too-many-state
368
209
  # --hex/--b64/--bin for input mode (default hex)
369
210
  in_grp = parser.add_mutually_exclusive_group()
370
211
  in_grp.add_argument('--hex', action='store_true', help='Treat inputs as hex string (default)')
371
- in_grp.add_argument('--b64', action='store_true', help='Treat inputs as base64url')
212
+ in_grp.add_argument(
213
+ '--b64', action='store_true',
214
+ help=('Treat inputs as base64url; sometimes base64 will start with "-" and that can '
215
+ 'conflict with flags, so use "--" before positional args if needed'))
372
216
  in_grp.add_argument('--bin', action='store_true', help='Treat inputs as binary (bytes)')
373
217
 
374
218
  # --out-hex/--out-b64/--out-bin for output mode (default hex)
@@ -557,7 +401,7 @@ def _BuildParser() -> argparse.ArgumentParser: # pylint: disable=too-many-state
557
401
  help='SHA-256 of input `data`.',
558
402
  epilog=('--bin hash sha256 xyz\n'
559
403
  '3608bca1e44ea6c4d268eb6db02260269892c0b42b86bbf1e77a6fa16c3c9282 $$'
560
- '--b64 hash sha256 eHl6 # "xyz" in base-64\n'
404
+ '--b64 hash sha256 -- eHl6 # "xyz" in base-64\n'
561
405
  '3608bca1e44ea6c4d268eb6db02260269892c0b42b86bbf1e77a6fa16c3c9282'))
562
406
  p_h256.add_argument('data', type=str, help='Input data (raw text; or use --hex/--b64/--bin)')
563
407
 
@@ -568,7 +412,7 @@ def _BuildParser() -> argparse.ArgumentParser: # pylint: disable=too-many-state
568
412
  epilog=('--bin hash sha512 xyz\n'
569
413
  '4a3ed8147e37876adc8f76328e5abcc1b470e6acfc18efea0135f983604953a5'
570
414
  '8e183c1a6086e91ba3e821d926f5fdeb37761c7ca0328a963f5e92870675b728 $$'
571
- '--b64 hash sha512 eHl6 # "xyz" in base-64\n'
415
+ '--b64 hash sha512 -- eHl6 # "xyz" in base-64\n'
572
416
  '4a3ed8147e37876adc8f76328e5abcc1b470e6acfc18efea0135f983604953a5'
573
417
  '8e183c1a6086e91ba3e821d926f5fdeb37761c7ca0328a963f5e92870675b728'))
574
418
  p_h512.add_argument('data', type=str, help='Input data (raw text; or use --hex/--b64/--bin)')
@@ -614,10 +458,10 @@ def _BuildParser() -> argparse.ArgumentParser: # pylint: disable=too-many-state
614
458
  'can use `--bin`/`--hex`/`--b64` flags. Attention: if you provide `-a`/`--aad` '
615
459
  '(associated data, AAD), you will need to provide the same AAD when decrypting '
616
460
  'and it is NOT included in the `ciphertext`/CT returned by this method!'),
617
- epilog=('--b64 --out-b64 aes encrypt -k DbWJ_ZrknLEEIoq_NpoCQwHYfjskGokpueN2O_eY0es= ' # cspell:disable-line
461
+ epilog=('--b64 --out-b64 aes encrypt -k DbWJ_ZrknLEEIoq_NpoCQwHYfjskGokpueN2O_eY0es= -- ' # cspell:disable-line
618
462
  'AAAAAAB4eXo=\nF2_ZLrUw5Y8oDnbTP5t5xCUWX8WtVILLD0teyUi_37_4KHeV-YowVA== $$ ' # cspell:disable-line
619
463
  '--b64 --out-b64 aes encrypt -k DbWJ_ZrknLEEIoq_NpoCQwHYfjskGokpueN2O_eY0es= -a eHl6 ' # cspell:disable-line
620
- 'AAAAAAB4eXo=\nxOlAHPUPpeyZHId-f3VQ_QKKMxjIW0_FBo9WOfIBrzjn0VkVV6xTRA==')) # cspell:disable-line
464
+ '-- AAAAAAB4eXo=\nxOlAHPUPpeyZHId-f3VQ_QKKMxjIW0_FBo9WOfIBrzjn0VkVV6xTRA==')) # cspell:disable-line
621
465
  p_aes_enc.add_argument('plaintext', type=str, help='Input data to encrypt (PT)')
622
466
  p_aes_enc.add_argument(
623
467
  '-k', '--key', type=str, default='', help='Key if `-p`/`--key-path` wasn\'t used (32 bytes)')
@@ -632,10 +476,10 @@ def _BuildParser() -> argparse.ArgumentParser: # pylint: disable=too-many-state
632
476
  '`-p`/`--key-path` keyfile. All inputs are raw, or you '
633
477
  'can use `--bin`/`--hex`/`--b64` flags. Attention: if you provided `-a`/`--aad` '
634
478
  '(associated data, AAD) during encryption, you will need to provide the same AAD now!'),
635
- epilog=('--b64 --out-b64 aes decrypt -k DbWJ_ZrknLEEIoq_NpoCQwHYfjskGokpueN2O_eY0es= ' # cspell:disable-line
636
- 'F2_ZLrUw5Y8oDnbTP5t5xCUWX8WtVILLD0teyUi_37_4KHeV-YowVA==\nAAAAAAB4eXo= $$ ' # cspell:disable-line
637
- '--b64 --out-b64 aes decrypt -k DbWJ_ZrknLEEIoq_NpoCQwHYfjskGokpueN2O_eY0es= ' # cspell:disable-line
638
- '-a eHl6 xOlAHPUPpeyZHId-f3VQ_QKKMxjIW0_FBo9WOfIBrzjn0VkVV6xTRA==\nAAAAAAB4eXo=')) # cspell:disable-line
479
+ epilog=('--b64 --out-b64 aes decrypt -k DbWJ_ZrknLEEIoq_NpoCQwHYfjskGokpueN2O_eY0es= -- ' # cspell:disable-line
480
+ 'F2_ZLrUw5Y8oDnbTP5t5xCUWX8WtVILLD0teyUi_37_4KHeV-YowVA==\nAAAAAAB4eXo= $$ ' # cspell:disable-line
481
+ '--b64 --out-b64 aes decrypt -k DbWJ_ZrknLEEIoq_NpoCQwHYfjskGokpueN2O_eY0es= ' # cspell:disable-line
482
+ '-a eHl6 -- xOlAHPUPpeyZHId-f3VQ_QKKMxjIW0_FBo9WOfIBrzjn0VkVV6xTRA==\nAAAAAAB4eXo=')) # cspell:disable-line
639
483
  p_aes_dec.add_argument('ciphertext', type=str, help='Input data to decrypt (CT)')
640
484
  p_aes_dec.add_argument(
641
485
  '-k', '--key', type=str, default='', help='Key if `-p`/`--key-path` wasn\'t used (32 bytes)')
@@ -724,8 +568,8 @@ def _BuildParser() -> argparse.ArgumentParser: # pylint: disable=too-many-state
724
568
  p_rsa_dec_safe: argparse.ArgumentParser = rsa_sub.add_parser(
725
569
  'decrypt',
726
570
  help='Decrypt `ciphertext` with private key.',
727
- epilog=('--b64 --out-bin -p rsa-key.priv rsa decrypt "AO6knI6xwq6TGR…Qy22jiFhXi1eQ==" '
728
- '-a "eHl6"\nabcde'))
571
+ epilog=('--b64 --out-bin -p rsa-key.priv rsa decrypt -a eHl6 -- '
572
+ 'AO6knI6xwq6TGR…Qy22jiFhXi1eQ==\nabcde'))
729
573
  p_rsa_dec_safe.add_argument('ciphertext', type=str, help='Ciphertext to decrypt')
730
574
  p_rsa_dec_safe.add_argument(
731
575
  '-a', '--aad', type=str, default='',
@@ -763,10 +607,10 @@ def _BuildParser() -> argparse.ArgumentParser: # pylint: disable=too-many-state
763
607
  p_rsa_ver_safe: argparse.ArgumentParser = rsa_sub.add_parser(
764
608
  'verify',
765
609
  help='Verify `signature` for `message` with public key.',
766
- epilog=('--b64 -p rsa-key.pub rsa verify "eHl6" '
767
- '"91TS7gC6LORiL…6RD23Aejsfxlw=="\nRSA signature: OK $$ ' # cspell:disable-line
768
- '--b64 -p rsa-key.pub rsa verify "eLl6" '
769
- '"91TS7gC6LORiL…6RD23Aejsfxlw=="\nRSA signature: INVALID')) # cspell:disable-line
610
+ epilog=('--b64 -p rsa-key.pub rsa verify -- eHl6 '
611
+ '91TS7gC6LORiL…6RD23Aejsfxlw==\nRSA signature: OK $$ ' # cspell:disable-line
612
+ '--b64 -p rsa-key.pub rsa verify -- eLl6 '
613
+ '91TS7gC6LORiL…6RD23Aejsfxlw==\nRSA signature: INVALID')) # cspell:disable-line
770
614
  p_rsa_ver_safe.add_argument('message', type=str, help='Message that was signed earlier')
771
615
  p_rsa_ver_safe.add_argument('signature', type=str, help='Putative signature for `message`')
772
616
  p_rsa_ver_safe.add_argument(
@@ -814,7 +658,7 @@ def _BuildParser() -> argparse.ArgumentParser: # pylint: disable=too-many-state
814
658
  p_eg_enc_safe: argparse.ArgumentParser = eg_sub.add_parser(
815
659
  'encrypt',
816
660
  help='Encrypt `message` with public key.',
817
- epilog=(' --bin --out-b64 -p eg-key.pub elgamal encrypt "abcde" -a "xyz"\n'
661
+ epilog=('--bin --out-b64 -p eg-key.pub elgamal encrypt "abcde" -a "xyz"\n'
818
662
  'CdFvoQ_IIPFPZLua…kqjhcUTspISxURg==')) # cspell:disable-line
819
663
  p_eg_enc_safe.add_argument('plaintext', type=str, help='Message to encrypt')
820
664
  p_eg_enc_safe.add_argument(
@@ -834,8 +678,8 @@ def _BuildParser() -> argparse.ArgumentParser: # pylint: disable=too-many-state
834
678
  p_eg_dec_safe: argparse.ArgumentParser = eg_sub.add_parser(
835
679
  'decrypt',
836
680
  help='Decrypt `ciphertext` with private key.',
837
- epilog=('--b64 --out-bin -p eg-key.priv elgamal decrypt '
838
- '"CdFvoQ_IIPFPZLua…kqjhcUTspISxURg==" -a "eHl6"\nabcde')) # cspell:disable-line
681
+ epilog=('--b64 --out-bin -p eg-key.priv elgamal decrypt -a eHl6 -- '
682
+ 'CdFvoQ_IIPFPZLua…kqjhcUTspISxURg==\nabcde')) # cspell:disable-line
839
683
  p_eg_dec_safe.add_argument('ciphertext', type=str, help='Ciphertext to decrypt')
840
684
  p_eg_dec_safe.add_argument(
841
685
  '-a', '--aad', type=str, default='',
@@ -877,9 +721,9 @@ def _BuildParser() -> argparse.ArgumentParser: # pylint: disable=too-many-state
877
721
  p_eg_ver_safe: argparse.ArgumentParser = eg_sub.add_parser(
878
722
  'verify',
879
723
  help='Verify `signature` for `message` with public key.',
880
- epilog=('--b64 -p eg-key.pub elgamal verify "eHl6" "Xl4hlYK8SHVGw…0fCKJE1XVzA=="\n' # cspell:disable-line
724
+ epilog=('--b64 -p eg-key.pub elgamal verify -- eHl6 Xl4hlYK8SHVGw…0fCKJE1XVzA==\n' # cspell:disable-line
881
725
  'El-Gamal signature: OK $$ '
882
- '--b64 -p eg-key.pub elgamal verify "eLl6" "Xl4hlYK8SHVGw…0fCKJE1XVzA=="\n' # cspell:disable-line
726
+ '--b64 -p eg-key.pub elgamal verify -- eLl6 Xl4hlYK8SHVGw…0fCKJE1XVzA==\n' # cspell:disable-line
883
727
  'El-Gamal signature: INVALID'))
884
728
  p_eg_ver_safe.add_argument('message', type=str, help='Message that was signed earlier')
885
729
  p_eg_ver_safe.add_argument('signature', type=str, help='Putative signature for `message`')
@@ -956,9 +800,9 @@ def _BuildParser() -> argparse.ArgumentParser: # pylint: disable=too-many-state
956
800
  p_dsa_verify_safe: argparse.ArgumentParser = dsa_sub.add_parser(
957
801
  'verify',
958
802
  help='Verify `signature` for `message` with public key.',
959
- epilog=('--b64 -p dsa-key.pub dsa verify "eHl6" "yq8InJVpViXh9…BD4par2XuA="\n'
803
+ epilog=('--b64 -p dsa-key.pub dsa verify -- eHl6 yq8InJVpViXh9…BD4par2XuA=\n'
960
804
  'DSA signature: OK $$ '
961
- '--b64 -p dsa-key.pub dsa verify "eLl6" "yq8InJVpViXh9…BD4par2XuA="\n'
805
+ '--b64 -p dsa-key.pub dsa verify -- eLl6 yq8InJVpViXh9…BD4par2XuA=\n'
962
806
  'DSA signature: INVALID'))
963
807
  p_dsa_verify_safe.add_argument('message', type=str, help='Message that was signed earlier')
964
808
  p_dsa_verify_safe.add_argument('signature', type=str, help='Putative signature for `message`')
@@ -1094,9 +938,7 @@ def _BuildParser() -> argparse.ArgumentParser: # pylint: disable=too-many-state
1094
938
  doc_sub.add_parser(
1095
939
  'md',
1096
940
  help='Emit Markdown docs for the CLI (see README.md section "Creating a New Version").',
1097
- epilog=('doc md > CLI.md\n'
1098
- '$ ./tools/inject_md_includes.py\n'
1099
- 'inject: README.md updated with included content'))
941
+ epilog='doc md > transcrypto.md\n<<saves file>>')
1100
942
 
1101
943
  return parser
1102
944
 
@@ -1544,7 +1386,7 @@ def main(argv: list[str] | None = None, /) -> int: # pylint: disable=invalid-na
1544
1386
  case 'bytes':
1545
1387
  print(base.BytesToHex(base.RandBytes(args.n)))
1546
1388
  case 'prime':
1547
- print(modmath.NBitRandomPrime(args.bits))
1389
+ print(modmath.NBitRandomPrimes(args.bits).pop())
1548
1390
  case _:
1549
1391
  raise NotImplementedError()
1550
1392
  case 'hash':
@@ -1589,7 +1431,14 @@ def main(argv: list[str] | None = None, /) -> int: # pylint: disable=invalid-na
1589
1431
  args.doc_command.lower().strip() if getattr(args, 'doc_command', '') else '')
1590
1432
  match doc_command:
1591
1433
  case 'md':
1592
- print(_GenerateCLIMarkdown())
1434
+ print(base.GenerateCLIMarkdown(
1435
+ 'transcrypto', _BuildParser(), description=(
1436
+ '`transcrypto` is a command-line utility that provides access to all core '
1437
+ 'functionality described in this documentation. It serves as a convenient '
1438
+ 'wrapper over the Python APIs, enabling **cryptographic operations**, '
1439
+ '**number theory functions**, **secure randomness generation**, **hashing**, '
1440
+ '**AES**, **RSA**, **El-Gamal**, **DSA**, **bidding**, **SSS**, '
1441
+ 'and other utilities without writing code.')))
1593
1442
  case _:
1594
1443
  raise NotImplementedError()
1595
1444