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.
- transcrypto/__init__.py +1 -1
- transcrypto/cli/__init__.py +3 -0
- transcrypto/cli/aeshash.py +370 -0
- transcrypto/cli/bidsecret.py +336 -0
- transcrypto/cli/clibase.py +183 -0
- transcrypto/cli/intmath.py +429 -0
- transcrypto/cli/publicalgos.py +878 -0
- 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 +29 -13
- transcrypto/transcrypto.py +60 -1996
- 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.7.0.dist-info → transcrypto-2.0.0.dist-info}/METADATA +111 -109
- transcrypto-2.0.0.dist-info/RECORD +33 -0
- transcrypto/base.py +0 -1918
- transcrypto-1.7.0.dist-info/RECORD +0 -17
- /transcrypto/{constants.py → core/constants.py} +0 -0
- {transcrypto-1.7.0.dist-info → transcrypto-2.0.0.dist-info}/WHEEL +0 -0
- {transcrypto-1.7.0.dist-info → transcrypto-2.0.0.dist-info}/entry_points.txt +0 -0
- {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,
|
|
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(
|
|
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
|
|
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(
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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={
|
|
394
|
-
f'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={
|
|
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
|
|
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 =
|
|
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
|
|
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(
|
|
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
|
-
|
|
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
|
-
@
|
|
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
|
-
@
|
|
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
|
-
@
|
|
225
|
+
@clibase.CLIErrorGuard
|
|
210
226
|
def Markdown() -> None: # documentation is help/epilog/args # noqa: D103
|
|
211
|
-
console: rich_console.Console =
|
|
212
|
-
console.print(
|
|
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
|
|
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
|
|
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 =
|
|
236
|
-
primes[n_bits], parser=
|
|
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}')
|