transcrypto 1.7.0__py3-none-any.whl → 2.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. transcrypto/__init__.py +1 -1
  2. transcrypto/cli/__init__.py +3 -0
  3. transcrypto/cli/aeshash.py +370 -0
  4. transcrypto/cli/bidsecret.py +336 -0
  5. transcrypto/cli/clibase.py +183 -0
  6. transcrypto/cli/intmath.py +429 -0
  7. transcrypto/cli/publicalgos.py +878 -0
  8. transcrypto/core/__init__.py +3 -0
  9. transcrypto/{aes.py → core/aes.py} +17 -29
  10. transcrypto/core/bid.py +161 -0
  11. transcrypto/{dsa.py → core/dsa.py} +28 -27
  12. transcrypto/{elgamal.py → core/elgamal.py} +33 -32
  13. transcrypto/core/hashes.py +96 -0
  14. transcrypto/core/key.py +735 -0
  15. transcrypto/{modmath.py → core/modmath.py} +91 -17
  16. transcrypto/{rsa.py → core/rsa.py} +51 -50
  17. transcrypto/{sss.py → core/sss.py} +27 -26
  18. transcrypto/profiler.py +29 -13
  19. transcrypto/transcrypto.py +60 -1996
  20. transcrypto/utils/__init__.py +3 -0
  21. transcrypto/utils/base.py +72 -0
  22. transcrypto/utils/human.py +278 -0
  23. transcrypto/utils/logging.py +139 -0
  24. transcrypto/utils/saferandom.py +102 -0
  25. transcrypto/utils/stats.py +360 -0
  26. transcrypto/utils/timer.py +175 -0
  27. {transcrypto-1.7.0.dist-info → transcrypto-2.0.0.dist-info}/METADATA +111 -109
  28. transcrypto-2.0.0.dist-info/RECORD +33 -0
  29. transcrypto/base.py +0 -1918
  30. transcrypto-1.7.0.dist-info/RECORD +0 -17
  31. /transcrypto/{constants.py → core/constants.py} +0 -0
  32. {transcrypto-1.7.0.dist-info → transcrypto-2.0.0.dist-info}/WHEEL +0 -0
  33. {transcrypto-1.7.0.dist-info → transcrypto-2.0.0.dist-info}/entry_points.txt +0 -0
  34. {transcrypto-1.7.0.dist-info → transcrypto-2.0.0.dist-info}/licenses/LICENSE +0 -0
@@ -12,14 +12,15 @@ import logging
12
12
  from collections import abc
13
13
  from typing import Self
14
14
 
15
- from . import aes, base, modmath
15
+ from transcrypto.core import aes, hashes, key, modmath
16
+ from transcrypto.utils import base, saferandom
16
17
 
17
18
  # fixed prefixes: do NOT ever change! will break all encryption and signature schemes
18
19
  _SSS_ENCRYPTION_AAD_PREFIX = b'transcrypto.SSS.Sharing.1.0\x00'
19
20
 
20
21
 
21
22
  @dataclasses.dataclass(kw_only=True, slots=True, frozen=True, repr=False)
22
- class ShamirSharedSecretPublic(base.CryptoKey):
23
+ class ShamirSharedSecretPublic(key.CryptoKey):
23
24
  """Shamir Shared Secret (SSS) public part.
24
25
 
25
26
  No measures are taken here to prevent timing attacks.
@@ -38,7 +39,7 @@ class ShamirSharedSecretPublic(base.CryptoKey):
38
39
  """Check data.
39
40
 
40
41
  Raises:
41
- InputError: invalid inputs
42
+ base.InputError: invalid inputs
42
43
 
43
44
  """
44
45
  if self.modulus < 2 or not modmath.IsPrime(self.modulus) or self.minimum < 2: # noqa: PLR2004
@@ -82,8 +83,8 @@ class ShamirSharedSecretPublic(base.CryptoKey):
82
83
  no "excess" shares, there can be no way to know if the recovered secret is the correct one
83
84
 
84
85
  Raises:
85
- InputError: invalid inputs
86
- CryptoError: secret cannot be recovered (number of shares < `minimum`)
86
+ base.InputError: invalid inputs
87
+ key.CryptoError: secret cannot be recovered (number of shares < `minimum`)
87
88
 
88
89
  """
89
90
  # check that we have enough shares by de-duping them first
@@ -107,7 +108,7 @@ class ShamirSharedSecretPublic(base.CryptoKey):
107
108
  if force_recover and given_shares > 1:
108
109
  logging.error(f'recovering secret even though: {mess}')
109
110
  else:
110
- raise base.CryptoError(f'unrecoverable secret: {mess}')
111
+ raise key.CryptoError(f'unrecoverable secret: {mess}')
111
112
  # do the math
112
113
  return modmath.ModLagrangeInterpolate(0, share_points, self.modulus)
113
114
 
@@ -147,7 +148,7 @@ class ShamirSharedSecretPrivate(ShamirSharedSecretPublic):
147
148
  """Check data.
148
149
 
149
150
  Raises:
150
- InputError: invalid inputs
151
+ base.InputError: invalid inputs
151
152
 
152
153
  """
153
154
  super(ShamirSharedSecretPrivate, self).__post_init__()
@@ -172,7 +173,7 @@ class ShamirSharedSecretPrivate(ShamirSharedSecretPublic):
172
173
  return (
173
174
  'ShamirSharedSecretPrivate('
174
175
  f'{super(ShamirSharedSecretPrivate, self).__str__()}, '
175
- f'polynomial=[{", ".join(base.ObfuscateSecret(i) for i in self.polynomial)}])'
176
+ f'polynomial=[{", ".join(hashes.ObfuscateSecret(i) for i in self.polynomial)}])'
176
177
  )
177
178
 
178
179
  def RawShare(self, secret: int, /, *, share_key: int = 0) -> ShamirSharePrivate:
@@ -192,7 +193,7 @@ class ShamirSharedSecretPrivate(ShamirSharedSecretPublic):
192
193
  ShamirSharePrivate object
193
194
 
194
195
  Raises:
195
- InputError: invalid inputs
196
+ base.InputError: invalid inputs
196
197
 
197
198
  """
198
199
  # test inputs
@@ -202,7 +203,7 @@ class ShamirSharedSecretPrivate(ShamirSharedSecretPublic):
202
203
  if not share_key: # default is zero, and that means we generate it here
203
204
  share_key = 0
204
205
  while not share_key or share_key in self.polynomial or share_key >= self.modulus:
205
- share_key = base.RandBits(self.modulus.bit_length() - 1)
206
+ share_key = saferandom.RandBits(self.modulus.bit_length() - 1)
206
207
  else:
207
208
  raise base.InputError(f'invalid share_key: {share_key=}')
208
209
  # build object
@@ -229,7 +230,7 @@ class ShamirSharedSecretPrivate(ShamirSharedSecretPublic):
229
230
  ShamirSharePrivate object
230
231
 
231
232
  Raises:
232
- InputError: invalid inputs
233
+ base.InputError: invalid inputs
233
234
 
234
235
  """
235
236
  # test inputs
@@ -246,7 +247,7 @@ class ShamirSharedSecretPrivate(ShamirSharedSecretPublic):
246
247
  or share_key in used_keys
247
248
  or share_key >= self.modulus
248
249
  ):
249
- share_key = base.RandBits(self.modulus.bit_length() - 1)
250
+ share_key = saferandom.RandBits(self.modulus.bit_length() - 1)
250
251
  try:
251
252
  yield self.RawShare(secret, share_key=share_key)
252
253
  used_keys.add(share_key)
@@ -273,7 +274,7 @@ class ShamirSharedSecretPrivate(ShamirSharedSecretPublic):
273
274
  list[ShamirShareData]: the list of shares with encrypted data
274
275
 
275
276
  Raises:
276
- InputError: invalid inputs
277
+ base.InputError: invalid inputs
277
278
 
278
279
  """
279
280
  if total_shares < self.minimum:
@@ -281,7 +282,7 @@ class ShamirSharedSecretPrivate(ShamirSharedSecretPublic):
281
282
  k: int = self.modulus_size
282
283
  if k <= 32: # noqa: PLR2004
283
284
  raise base.InputError(f'modulus too small for key operations: {k} bytes')
284
- key256: bytes = base.RandBytes(32)
285
+ key256: bytes = saferandom.RandBytes(32)
285
286
  shares: list[ShamirSharePrivate] = list(
286
287
  self.RawShares(base.BytesToInt(key256), max_shares=total_shares)
287
288
  )
@@ -290,7 +291,7 @@ class ShamirSharedSecretPrivate(ShamirSharedSecretPublic):
290
291
  + base.IntToFixedBytes(self.minimum, 8)
291
292
  + base.IntToFixedBytes(self.modulus, k)
292
293
  )
293
- aead_key: bytes = base.Hash512(_SSS_ENCRYPTION_AAD_PREFIX + key256)
294
+ aead_key: bytes = hashes.Hash512(_SSS_ENCRYPTION_AAD_PREFIX + key256)
294
295
  ct: bytes = aes.AESKey(key256=aead_key[32:]).Encrypt(secret, associated_data=aad)
295
296
  return [
296
297
  ShamirShareData(
@@ -333,7 +334,7 @@ class ShamirSharedSecretPrivate(ShamirSharedSecretPublic):
333
334
  ShamirSharedSecretPrivate object ready for use
334
335
 
335
336
  Raises:
336
- InputError: invalid inputs
337
+ base.InputError: invalid inputs
337
338
 
338
339
  """
339
340
  # test inputs
@@ -348,7 +349,7 @@ class ShamirSharedSecretPrivate(ShamirSharedSecretPublic):
348
349
  modulus: int = max(ordered_primes)
349
350
  ordered_primes.remove(modulus)
350
351
  # make polynomial be a random order
351
- base.RandShuffle(ordered_primes)
352
+ saferandom.RandShuffle(ordered_primes)
352
353
  # build object
353
354
  return cls(minimum=minimum_shares, modulus=modulus, polynomial=ordered_primes)
354
355
 
@@ -373,7 +374,7 @@ class ShamirSharePrivate(ShamirSharedSecretPublic):
373
374
  """Check data.
374
375
 
375
376
  Raises:
376
- InputError: invalid inputs
377
+ base.InputError: invalid inputs
377
378
 
378
379
  """
379
380
  super(ShamirSharePrivate, self).__post_init__()
@@ -390,8 +391,8 @@ class ShamirSharePrivate(ShamirSharedSecretPublic):
390
391
  return (
391
392
  'ShamirSharePrivate('
392
393
  f'{super(ShamirSharePrivate, self).__str__()}, '
393
- f'share_key={base.ObfuscateSecret(self.share_key)}, '
394
- f'share_value={base.ObfuscateSecret(self.share_value)})'
394
+ f'share_key={hashes.ObfuscateSecret(self.share_key)}, '
395
+ f'share_value={hashes.ObfuscateSecret(self.share_value)})'
395
396
  )
396
397
 
397
398
  @classmethod
@@ -433,7 +434,7 @@ class ShamirShareData(ShamirSharePrivate):
433
434
  """Check data.
434
435
 
435
436
  Raises:
436
- InputError: invalid inputs
437
+ base.InputError: invalid inputs
437
438
 
438
439
  """
439
440
  super(ShamirShareData, self).__post_init__()
@@ -450,7 +451,7 @@ class ShamirShareData(ShamirSharePrivate):
450
451
  return (
451
452
  'ShamirShareData('
452
453
  f'{super(ShamirShareData, self).__str__()}, '
453
- f'encrypted_data={base.ObfuscateSecret(self.encrypted_data)})'
454
+ f'encrypted_data={hashes.ObfuscateSecret(self.encrypted_data)})'
454
455
  )
455
456
 
456
457
  def RecoverData(self, other_shares: list[ShamirSharePrivate]) -> bytes:
@@ -467,8 +468,8 @@ class ShamirShareData(ShamirSharePrivate):
467
468
  bytes: Decrypted plaintext bytes
468
469
 
469
470
  Raises:
470
- InputError: invalid inputs
471
- CryptoError: internal crypto failures, authentication failure, key mismatch, etc
471
+ base.InputError: invalid inputs
472
+ key.CryptoError: internal crypto failures, authentication failure, key mismatch, etc
472
473
 
473
474
  """
474
475
  k: int = self.modulus_size
@@ -477,12 +478,12 @@ class ShamirShareData(ShamirSharePrivate):
477
478
  # recover secret; raise if shares are invalid
478
479
  secret: int = self.RawRecoverSecret([self, *other_shares])
479
480
  if not 0 <= secret < (1 << 256):
480
- raise base.CryptoError('recovered key out of range for 256-bit key')
481
+ raise key.CryptoError('recovered key out of range for 256-bit key')
481
482
  key256: bytes = base.IntToFixedBytes(secret, 32)
482
483
  aad: bytes = (
483
484
  _SSS_ENCRYPTION_AAD_PREFIX
484
485
  + base.IntToFixedBytes(self.minimum, 8)
485
486
  + base.IntToFixedBytes(self.modulus, k)
486
487
  )
487
- aead_key: bytes = base.Hash512(_SSS_ENCRYPTION_AAD_PREFIX + key256)
488
+ aead_key: bytes = hashes.Hash512(_SSS_ENCRYPTION_AAD_PREFIX + key256)
488
489
  return aes.AESKey(key256=aead_key[32:]).Decrypt(self.encrypted_data, associated_data=aad)
transcrypto/profiler.py CHANGED
@@ -24,12 +24,25 @@ from collections import abc
24
24
  import typer
25
25
  from rich import console as rich_console
26
26
 
27
- from . import __version__, base, dsa, modmath
27
+ from transcrypto.cli import clibase
28
+ from transcrypto.core import dsa, modmath
29
+ from transcrypto.utils import human, timer
30
+ from transcrypto.utils import logging as tc_logging
31
+
32
+ from . import __version__
28
33
 
29
34
 
30
35
  @dataclasses.dataclass(kw_only=True, slots=True, frozen=True)
31
- class ProfilerConfig(base.CLIConfig):
32
- """CLI global context, storing the configuration."""
36
+ class ProfilerConfig(clibase.CLIConfig):
37
+ """CLI global context, storing the configuration.
38
+
39
+ Attributes:
40
+ serial (bool): Whether to run profiling serially (vs parallel)
41
+ repeats (int): Number of repetitions for each profiling run
42
+ confidence (int): Confidence level percentage for statistical analysis
43
+ bits (tuple[int, int, int]): Bit sizes range (start, stop, step) for profiling
44
+
45
+ """
33
46
 
34
47
  serial: bool
35
48
  repeats: int
@@ -62,6 +75,7 @@ def Run() -> None:
62
75
  invoke_without_command=True, # have only one; this is the "constructor"
63
76
  help='Profile TransCrypto library performance.',
64
77
  )
78
+ @clibase.CLIErrorGuard
65
79
  def Main( # documentation is help/epilog/args # noqa: D103
66
80
  *,
67
81
  ctx: typer.Context, # global context
@@ -121,7 +135,9 @@ def Main( # documentation is help/epilog/args # noqa: D103
121
135
  if version:
122
136
  typer.echo(__version__)
123
137
  raise typer.Exit(0)
124
- console, verbose, color = base.InitLogging(
138
+ # initialize logging and get console
139
+ console: rich_console.Console
140
+ console, verbose, color = tc_logging.InitLogging(
125
141
  verbose,
126
142
  color=color,
127
143
  include_process=False, # decide if you want process names in logs
@@ -156,7 +172,7 @@ def Main( # documentation is help/epilog/args # noqa: D103
156
172
  'Finished in 40.07 min'
157
173
  ),
158
174
  )
159
- @base.CLIErrorGuard
175
+ @clibase.CLIErrorGuard
160
176
  def Primes(*, ctx: typer.Context) -> None: # documentation is help/epilog/args # noqa: D103
161
177
  config: ProfilerConfig = ctx.obj # get application global config
162
178
  config.console.print(
@@ -186,7 +202,7 @@ def Primes(*, ctx: typer.Context) -> None: # documentation is help/epilog/args
186
202
  'Finished in 4.12 s'
187
203
  ),
188
204
  )
189
- @base.CLIErrorGuard
205
+ @clibase.CLIErrorGuard
190
206
  def DSA(*, ctx: typer.Context) -> None: # documentation is help/epilog/args # noqa: D103
191
207
  config: ProfilerConfig = ctx.obj # get application global config
192
208
  config.console.print(
@@ -206,10 +222,10 @@ def DSA(*, ctx: typer.Context) -> None: # documentation is help/epilog/args # n
206
222
  help='Emit Markdown docs for the CLI (see README.md section "Creating a New Version").',
207
223
  epilog='Example:\n\n\n\n$ poetry run profiler markdown > profiler.md\n\n<<saves CLI doc>>',
208
224
  )
209
- @base.CLIErrorGuard
225
+ @clibase.CLIErrorGuard
210
226
  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'))
227
+ console: rich_console.Console = tc_logging.Console()
228
+ console.print(clibase.GenerateTyperHelpMarkdown(app, prog_name='profiler'))
213
229
 
214
230
 
215
231
  def _PrimeProfiler(
@@ -220,20 +236,20 @@ def _PrimeProfiler(
220
236
  confidence: float,
221
237
  /,
222
238
  ) -> None:
223
- with base.Timer(emit_log=False) as total_time:
239
+ with timer.Timer(emit_log=False) as total_time:
224
240
  primes: dict[int, list[float]] = {}
225
241
  for n_bits in range(*n_bits_range):
226
242
  # investigate for size n_bits
227
243
  primes[n_bits] = []
228
244
  for _ in range(repeats):
229
- with base.Timer(emit_log=False) as run_time:
245
+ with timer.Timer(emit_log=False) as run_time:
230
246
  pr: int = prime_callable(n_bits)
231
247
  assert pr # noqa: S101
232
248
  assert pr.bit_length() == n_bits # noqa: S101
233
249
  primes[n_bits].append(run_time.elapsed)
234
250
  # finished collecting n_bits-sized primes
235
- measurements: str = base.HumanizedMeasurements(
236
- primes[n_bits], parser=base.HumanizedSeconds, confidence=confidence
251
+ measurements: str = human.HumanizedMeasurements(
252
+ primes[n_bits], parser=human.HumanizedSeconds, confidence=confidence
237
253
  )
238
254
  console.print(f'{n_bits} → {measurements}')
239
255
  console.print(f'Finished in {total_time}')