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/aes.py +10 -2
- transcrypto/base.py +708 -100
- transcrypto/constants.py +1921 -0
- transcrypto/dsa.py +106 -49
- transcrypto/elgamal.py +14 -13
- transcrypto/modmath.py +108 -43
- transcrypto/profiler.py +191 -0
- transcrypto/rsa.py +17 -17
- transcrypto/safetrans.py +1231 -0
- transcrypto/sss.py +1 -3
- transcrypto/transcrypto.py +55 -206
- transcrypto-1.4.0.dist-info/METADATA +1071 -0
- transcrypto-1.4.0.dist-info/RECORD +18 -0
- transcrypto-1.2.0.dist-info/METADATA +0 -2515
- transcrypto-1.2.0.dist-info/RECORD +0 -15
- {transcrypto-1.2.0.dist-info → transcrypto-1.4.0.dist-info}/WHEEL +0 -0
- {transcrypto-1.2.0.dist-info → transcrypto-1.4.0.dist-info}/licenses/LICENSE +0 -0
- {transcrypto-1.2.0.dist-info → transcrypto-1.4.0.dist-info}/top_level.txt +0 -0
transcrypto/profiler.py
ADDED
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
#
|
|
3
|
+
# Copyright 2025 Daniel Balparda (balparda@github.com) - Apache-2.0 license
|
|
4
|
+
#
|
|
5
|
+
"""Balparda's TransCrypto Profiler command line interface.
|
|
6
|
+
|
|
7
|
+
See README.md for documentation on how to use.
|
|
8
|
+
|
|
9
|
+
Notes on the layout (quick mental model):
|
|
10
|
+
|
|
11
|
+
primes
|
|
12
|
+
dsa
|
|
13
|
+
doc md
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import argparse
|
|
19
|
+
import logging
|
|
20
|
+
# import pdb
|
|
21
|
+
import sys
|
|
22
|
+
from typing import Callable
|
|
23
|
+
|
|
24
|
+
from . import base, modmath, dsa
|
|
25
|
+
|
|
26
|
+
__author__ = 'balparda@github.com'
|
|
27
|
+
__version__: str = base.__version__ # version comes from base!
|
|
28
|
+
__version_tuple__: tuple[int, ...] = base.__version_tuple__
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _BuildParser() -> argparse.ArgumentParser: # pylint: disable=too-many-statements,too-many-locals
|
|
32
|
+
"""Construct the CLI argument parser (kept in sync with the docs)."""
|
|
33
|
+
# ========================= main parser ==========================================================
|
|
34
|
+
parser: argparse.ArgumentParser = argparse.ArgumentParser(
|
|
35
|
+
prog='poetry run profiler',
|
|
36
|
+
description=('profiler: CLI for TransCrypto Profiler, measure library performance.'),
|
|
37
|
+
epilog=(
|
|
38
|
+
'Examples:\n\n'
|
|
39
|
+
' # --- Primes ---\n'
|
|
40
|
+
' poetry run profiler -p -n 10 primes\n'
|
|
41
|
+
' poetry run profiler -n 20 dsa\n'
|
|
42
|
+
),
|
|
43
|
+
formatter_class=argparse.RawTextHelpFormatter)
|
|
44
|
+
sub = parser.add_subparsers(dest='command')
|
|
45
|
+
|
|
46
|
+
# ========================= global flags =========================================================
|
|
47
|
+
# -v/-vv/-vvv/-vvvv for ERROR/WARN/INFO/DEBUG
|
|
48
|
+
parser.add_argument(
|
|
49
|
+
'-v', '--verbose', action='count', default=0,
|
|
50
|
+
help='Increase verbosity (use -v/-vv/-vvv/-vvvv for ERROR/WARN/INFO/DEBUG)')
|
|
51
|
+
|
|
52
|
+
thread_grp = parser.add_mutually_exclusive_group()
|
|
53
|
+
thread_grp.add_argument(
|
|
54
|
+
'-s', '--serial', action='store_true',
|
|
55
|
+
help='If test can be serial, do it like that with no parallelization (default)')
|
|
56
|
+
thread_grp.add_argument(
|
|
57
|
+
'-p', '--parallel', action='store_true',
|
|
58
|
+
help='If test can be parallelized into processes, do it like that')
|
|
59
|
+
|
|
60
|
+
parser.add_argument(
|
|
61
|
+
'-n', '--number', type=int, default=15,
|
|
62
|
+
help='Number of experiments (repeats) for every measurement')
|
|
63
|
+
parser.add_argument(
|
|
64
|
+
'-c', '--confidence', type=int, default=98,
|
|
65
|
+
help=('Confidence level to evaluate measurements at as int percentage points [50,99], '
|
|
66
|
+
'inclusive, representing 50% to 99%'))
|
|
67
|
+
|
|
68
|
+
parser.add_argument(
|
|
69
|
+
'-b', '--bits', type=str, default='1000,9000,1000',
|
|
70
|
+
help=('Bit lengths to investigate as "int,int,int"; behaves like arguments for range(), '
|
|
71
|
+
'i.e., "start,stop,step", eg. "1000,3000,500" will investigate 1000,1500,2000,2500'))
|
|
72
|
+
|
|
73
|
+
# ========================= Prime Generation =====================================================
|
|
74
|
+
|
|
75
|
+
# Regular prime generation
|
|
76
|
+
sub.add_parser(
|
|
77
|
+
'primes',
|
|
78
|
+
help='Measure regular prime generation.',
|
|
79
|
+
epilog=('-n 30 -b 9000,11000,1000 primes\nStarting SERIAL regular primes test\n'
|
|
80
|
+
'9000 → 38.88 s ± 14.74 s [24.14 s … 53.63 s]98%CI@30\n'
|
|
81
|
+
'10000 → 41.26 s ± 22.82 s [18.44 s … 1.07 min]98%CI@30\nFinished in 40.07 min'))
|
|
82
|
+
|
|
83
|
+
# DSA primes generation
|
|
84
|
+
sub.add_parser(
|
|
85
|
+
'dsa',
|
|
86
|
+
help='Measure DSA prime generation.',
|
|
87
|
+
epilog=('-p -n 2 -b 1000,1500,100 -c 80 dsa\nStarting PARALLEL DSA primes test\n'
|
|
88
|
+
'1000 → 236.344 ms ± 273.236 ms [*0.00 s … 509.580 ms]80%CI@2\n'
|
|
89
|
+
'1100 → 319.308 ms ± 639.775 ms [*0.00 s … 959.083 ms]80%CI@2\n'
|
|
90
|
+
'1200 → 523.885 ms ± 879.981 ms [*0.00 s … 1.40 s]80%CI@2\n'
|
|
91
|
+
'1300 → 506.285 ms ± 687.153 ms [*0.00 s … 1.19 s]80%CI@2\n'
|
|
92
|
+
'1400 → 552.840 ms ± 47.012 ms [505.828 ms … 599.852 ms]80%CI@2\nFinished in 4.12 s'))
|
|
93
|
+
|
|
94
|
+
# ========================= Markdown Generation ==================================================
|
|
95
|
+
|
|
96
|
+
# Documentation generation
|
|
97
|
+
doc: argparse.ArgumentParser = sub.add_parser(
|
|
98
|
+
'doc', help='Documentation utilities. (Not for regular use: these are developer utils.)')
|
|
99
|
+
doc_sub = doc.add_subparsers(dest='doc_command')
|
|
100
|
+
doc_sub.add_parser(
|
|
101
|
+
'md',
|
|
102
|
+
help='Emit Markdown docs for the CLI (see README.md section "Creating a New Version").',
|
|
103
|
+
epilog='doc md > profiler.md\n<<saves file>>')
|
|
104
|
+
|
|
105
|
+
return parser
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def _PrimeProfiler(
|
|
109
|
+
prime_callable: Callable[[int], int],
|
|
110
|
+
repeats: int, n_bits_range: tuple[int, int, int], confidence: float, /) -> None:
|
|
111
|
+
primes: dict[int, list[float]] = {}
|
|
112
|
+
for n_bits in range(*n_bits_range):
|
|
113
|
+
# investigate for size n_bits
|
|
114
|
+
primes[n_bits] = []
|
|
115
|
+
for _ in range(repeats):
|
|
116
|
+
with base.Timer(emit_log=False) as tmr:
|
|
117
|
+
pr: int = prime_callable(n_bits)
|
|
118
|
+
assert pr and pr.bit_length() == n_bits
|
|
119
|
+
primes[n_bits].append(tmr.elapsed)
|
|
120
|
+
# finished collecting n_bits-sized primes
|
|
121
|
+
measurements: str = base.HumanizedMeasurements(
|
|
122
|
+
primes[n_bits], parser=base.HumanizedSeconds, confidence=confidence)
|
|
123
|
+
print(f'{n_bits} → {measurements}')
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def main(argv: list[str] | None = None, /) -> int: # pylint: disable=invalid-name,too-many-locals,too-many-branches,too-many-statements
|
|
127
|
+
"""Main entry point."""
|
|
128
|
+
# build the parser and parse args
|
|
129
|
+
parser: argparse.ArgumentParser = _BuildParser()
|
|
130
|
+
args: argparse.Namespace = parser.parse_args(argv)
|
|
131
|
+
# take care of global options
|
|
132
|
+
levels: list[int] = [logging.ERROR, logging.WARNING, logging.INFO, logging.DEBUG]
|
|
133
|
+
logging.basicConfig(
|
|
134
|
+
level=levels[min(args.verbose, len(levels) - 1)], # type: ignore
|
|
135
|
+
format=getattr(base, 'LOG_FORMAT', '%(levelname)s:%(message)s'))
|
|
136
|
+
logging.captureWarnings(True)
|
|
137
|
+
|
|
138
|
+
try:
|
|
139
|
+
# get the command, do basic checks and switch
|
|
140
|
+
command: str = args.command.lower().strip() if args.command else ''
|
|
141
|
+
repeats: int = 1 if args.number < 1 else args.number
|
|
142
|
+
confidence: int = 55 if args.confidence < 55 else args.confidence
|
|
143
|
+
confidence = 99 if confidence > 99 else confidence
|
|
144
|
+
args.serial = True if (not args.serial and not args.parallel) else args.serial # make default
|
|
145
|
+
bits: tuple[int, ...] = tuple(int(x, 10) for x in args.bits.strip().split(','))
|
|
146
|
+
if len(bits) != 3:
|
|
147
|
+
raise base.InputError('-b/--bits should be 3 ints, like: start,stop,step; eg.: 1000,3000,500')
|
|
148
|
+
with base.Timer(emit_log=False) as tmr:
|
|
149
|
+
match command:
|
|
150
|
+
# -------- Primes ----------
|
|
151
|
+
case 'primes':
|
|
152
|
+
print(f'Starting {"SERIAL" if args.serial else "PARALLEL"} regular primes test')
|
|
153
|
+
_PrimeProfiler(
|
|
154
|
+
lambda n: modmath.NBitRandomPrimes(n, serial=args.serial, n_primes=1).pop(),
|
|
155
|
+
repeats, bits, confidence / 100.0)
|
|
156
|
+
|
|
157
|
+
case 'dsa':
|
|
158
|
+
print(f'Starting {"SERIAL" if args.serial else "PARALLEL"} DSA primes test')
|
|
159
|
+
_PrimeProfiler(
|
|
160
|
+
lambda n: dsa.NBitRandomDSAPrimes(n, n // 2, serial=args.serial)[0],
|
|
161
|
+
repeats, bits, confidence / 100.0)
|
|
162
|
+
|
|
163
|
+
# -------- Documentation ----------
|
|
164
|
+
case 'doc':
|
|
165
|
+
doc_command: str = (
|
|
166
|
+
args.doc_command.lower().strip() if getattr(args, 'doc_command', '') else '')
|
|
167
|
+
match doc_command:
|
|
168
|
+
case 'md':
|
|
169
|
+
print(base.GenerateCLIMarkdown(
|
|
170
|
+
'profiler', _BuildParser(), description=(
|
|
171
|
+
'`profiler` is a command-line utility that provides stats on TransCrypto '
|
|
172
|
+
'performance.')))
|
|
173
|
+
case _:
|
|
174
|
+
raise NotImplementedError()
|
|
175
|
+
|
|
176
|
+
case _:
|
|
177
|
+
parser.print_help()
|
|
178
|
+
|
|
179
|
+
if command not in ('doc',):
|
|
180
|
+
print(f'Finished in {tmr}')
|
|
181
|
+
|
|
182
|
+
except NotImplementedError as err:
|
|
183
|
+
print(f'Invalid command: {err}')
|
|
184
|
+
except (base.Error, ValueError) as err:
|
|
185
|
+
print(str(err))
|
|
186
|
+
|
|
187
|
+
return 0
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
if __name__ == '__main__':
|
|
191
|
+
sys.exit(main())
|
transcrypto/rsa.py
CHANGED
|
@@ -14,6 +14,8 @@ import logging
|
|
|
14
14
|
# import pdb
|
|
15
15
|
from typing import Self
|
|
16
16
|
|
|
17
|
+
import gmpy2 # type:ignore
|
|
18
|
+
|
|
17
19
|
from . import base, modmath, aes
|
|
18
20
|
|
|
19
21
|
__author__ = 'balparda@github.com'
|
|
@@ -100,7 +102,7 @@ class RSAPublicKey(base.CryptoKey, base.Encryptor, base.Verifier):
|
|
|
100
102
|
if not 0 < message < self.public_modulus:
|
|
101
103
|
raise base.InputError(f'invalid message: {message=}')
|
|
102
104
|
# encrypt
|
|
103
|
-
return
|
|
105
|
+
return int(gmpy2.powmod(message, self.encrypt_exp, self.public_modulus)) # type:ignore # pylint:disable=no-member
|
|
104
106
|
|
|
105
107
|
def Encrypt(self, plaintext: bytes, /, *, associated_data: bytes | None = None) -> bytes:
|
|
106
108
|
"""Encrypt `plaintext` and return `ciphertext`.
|
|
@@ -290,8 +292,8 @@ class RSAObfuscationPair(RSAPublicKey):
|
|
|
290
292
|
if not 0 < message < self.public_modulus:
|
|
291
293
|
raise base.InputError(f'invalid message: {message=}')
|
|
292
294
|
# encrypt
|
|
293
|
-
return (message *
|
|
294
|
-
self.random_key, self.encrypt_exp, self.public_modulus)) % self.public_modulus
|
|
295
|
+
return (message * int(gmpy2.powmod( # type:ignore # pylint:disable=no-member
|
|
296
|
+
self.random_key, self.encrypt_exp, self.public_modulus))) % self.public_modulus
|
|
295
297
|
|
|
296
298
|
def RevealOriginalSignature(self, message: int, signature: int, /) -> int:
|
|
297
299
|
"""Recover original signature for `message` from obfuscated `signature`.
|
|
@@ -451,9 +453,9 @@ class RSAPrivateKey(RSAPublicKey, base.Decryptor, base.Signer): # pylint: disab
|
|
|
451
453
|
if not 0 <= ciphertext < self.public_modulus:
|
|
452
454
|
raise base.InputError(f'invalid message: {ciphertext=}')
|
|
453
455
|
# decrypt using CRT (Chinese Remainder Theorem); 4x speedup; all the below is equivalent
|
|
454
|
-
# of doing: return
|
|
455
|
-
m_p: int =
|
|
456
|
-
m_q: int =
|
|
456
|
+
# of doing: return pow(ciphertext, self.decrypt_exp, self.public_modulus)
|
|
457
|
+
m_p: int = int(gmpy2.powmod(ciphertext % self.modulus_p, self.remainder_p, self.modulus_p)) # type:ignore # pylint:disable=no-member
|
|
458
|
+
m_q: int = int(gmpy2.powmod(ciphertext % self.modulus_q, self.remainder_q, self.modulus_q)) # type:ignore # pylint:disable=no-member
|
|
457
459
|
h: int = (self.q_inverse_p * (m_p - m_q)) % self.modulus_p
|
|
458
460
|
return (m_q + h * self.modulus_q) % self.public_modulus
|
|
459
461
|
|
|
@@ -568,21 +570,19 @@ class RSAPrivateKey(RSAPublicKey, base.Decryptor, base.Signer): # pylint: disab
|
|
|
568
570
|
failures: int = 0
|
|
569
571
|
while True:
|
|
570
572
|
try:
|
|
571
|
-
primes:
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
primes
|
|
577
|
-
|
|
578
|
-
modulus =
|
|
573
|
+
primes: set[int] = set()
|
|
574
|
+
modulus: int = 0
|
|
575
|
+
p: int = 0
|
|
576
|
+
q: int = 0
|
|
577
|
+
while modulus.bit_length() != bit_length:
|
|
578
|
+
primes = modmath.NBitRandomPrimes((bit_length + 1) // 2, n_primes=2)
|
|
579
|
+
p, q = min(primes), max(primes) # "p" is always the smaller, "q" the larger
|
|
580
|
+
modulus = p * q
|
|
579
581
|
# build object
|
|
580
|
-
phi: int = (
|
|
582
|
+
phi: int = (p - 1) * (q - 1)
|
|
581
583
|
prime_exp: int = (_SMALL_ENCRYPTION_EXPONENT if phi <= _BIG_ENCRYPTION_EXPONENT else
|
|
582
584
|
_BIG_ENCRYPTION_EXPONENT)
|
|
583
585
|
decrypt_exp: int = modmath.ModInv(prime_exp, phi)
|
|
584
|
-
p: int = min(primes) # "p" is always the smaller
|
|
585
|
-
q: int = max(primes) # "q" is always the larger
|
|
586
586
|
return cls(
|
|
587
587
|
modulus_p=p,
|
|
588
588
|
modulus_q=q,
|