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.
@@ -0,0 +1,1071 @@
1
+ Metadata-Version: 2.4
2
+ Name: transcrypto
3
+ Version: 1.4.0
4
+ Summary: Basic crypto primitives, not intended for actual use, but as a companion to --Criptografia, Métodos e Algoritmos--
5
+ Author-email: Daniel Balparda <balparda@github.com>
6
+ License-Expression: Apache-2.0
7
+ Project-URL: Homepage, https://github.com/balparda/transcrypto
8
+ Project-URL: PyPI, https://pypi.org/project/transcrypto/
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: Programming Language :: Python :: 3.13
11
+ Classifier: Operating System :: OS Independent
12
+ Classifier: Topic :: Utilities
13
+ Classifier: Topic :: Security :: Cryptography
14
+ Requires-Python: <4.0,>=3.13.5
15
+ Description-Content-Type: text/markdown
16
+ License-File: LICENSE
17
+ Dynamic: license-file
18
+
19
+ # TransCrypto
20
+
21
+ Basic cryptography primitives implementation, a companion to *"Criptografia, Métodos e Algoritmos"*.
22
+
23
+ Started in July/2025, by Daniel Balparda. Since version 1.0.2 it is PyPI package:
24
+
25
+ <https://pypi.org/project/transcrypto/>
26
+
27
+ - [TransCrypto](#transcrypto)
28
+ - [License](#license)
29
+ - [Design assumptions / Disclaimers](#design-assumptions--disclaimers)
30
+ - [CLI Apps](#cli-apps)
31
+ - [Programming API](#programming-api)
32
+ - [Install](#install)
33
+ - [Base Library](#base-library)
34
+ - [Humanized Sizes (IEC binary)](#humanized-sizes-iec-binary)
35
+ - [Humanized Decimal Quantities (SI)](#humanized-decimal-quantities-si)
36
+ - [Humanized Durations](#humanized-durations)
37
+ - [Execution Timing](#execution-timing)
38
+ - [Context manager](#context-manager)
39
+ - [Decorator](#decorator)
40
+ - [Manual use](#manual-use)
41
+ - [Key points](#key-points)
42
+ - [Serialization Pipeline](#serialization-pipeline)
43
+ - [Serialize](#serialize)
44
+ - [DeSerialize](#deserialize)
45
+ - [Cryptographically Secure Randomness](#cryptographically-secure-randomness)
46
+ - [Fixed-size random integers](#fixed-size-random-integers)
47
+ - [Uniform random integers in a range](#uniform-random-integers-in-a-range)
48
+ - [In-place secure shuffle](#in-place-secure-shuffle)
49
+ - [Random byte strings](#random-byte-strings)
50
+ - [Computing the Greatest Common Divisor](#computing-the-greatest-common-divisor)
51
+ - [Fast Modular Arithmetic](#fast-modular-arithmetic)
52
+ - [Chinese Remainder Theorem (CRT) – Pair](#chinese-remainder-theorem-crt--pair)
53
+ - [Modular Polynomials \& Lagrange Interpolation](#modular-polynomials--lagrange-interpolation)
54
+ - [Primality testing \& Prime generators, Mersenne primes](#primality-testing--prime-generators-mersenne-primes)
55
+ - [Cryptographic Hashing](#cryptographic-hashing)
56
+ - [SHA-256 hashing](#sha-256-hashing)
57
+ - [SHA-512 hashing](#sha-512-hashing)
58
+ - [File hashing](#file-hashing)
59
+ - [Symmetric Encryption Interface](#symmetric-encryption-interface)
60
+ - [Crypto Objects General Properties (`CryptoKey`)](#crypto-objects-general-properties-cryptokey)
61
+ - [AES-256 Symmetric Encryption](#aes-256-symmetric-encryption)
62
+ - [Key creation](#key-creation)
63
+ - [AES-256 + GCM (default)](#aes-256--gcm-default)
64
+ - [AES-256 + ECB (unsafe, fixed block only)](#aes-256--ecb-unsafe-fixed-block-only)
65
+ - [RSA (Rivest-Shamir-Adleman) Public Cryptography](#rsa-rivest-shamir-adleman-public-cryptography)
66
+ - [El-Gamal Public-Key Cryptography](#el-gamal-public-key-cryptography)
67
+ - [DSA (Digital Signature Algorithm)](#dsa-digital-signature-algorithm)
68
+ - [Security notes](#security-notes)
69
+ - [Advanced: custom primes generator](#advanced-custom-primes-generator)
70
+ - [Public Bidding](#public-bidding)
71
+ - [SSS (Shamir Shared Secret)](#sss-shamir-shared-secret)
72
+ - [Appendix: Development Instructions](#appendix-development-instructions)
73
+ - [Setup](#setup)
74
+ - [Updating Dependencies](#updating-dependencies)
75
+ - [Creating a New Version](#creating-a-new-version)
76
+
77
+ ## License
78
+
79
+ Copyright 2025 Daniel Balparda <balparda@github.com>
80
+
81
+ Licensed under the ***Apache License, Version 2.0*** (the "License"); you may not use this file except in compliance with the License. You may obtain a [copy of the License here](http://www.apache.org/licenses/LICENSE-2.0).
82
+
83
+ Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
84
+
85
+ ## Design assumptions / Disclaimers
86
+
87
+ - The library is built to have reference, reliable, simple implementations of math and crypto primitives (e.g. `RawEncrypt()`/`RawSign()` and friends plus all the low-level primality and modular arithmetic). The issue is not that the library is unsafe, it is that the library is full of places that allow you to shoot yourself in the foot if you don't know what you are doing.
88
+ - The library also has advanced top-level methods that are cryptographically safe and might be used in real-world scenarios (e.g. `Encrypt()`/`Sign()` and friends).
89
+ - All library methods' `int` are tailored to be efficient with arbitrarily large integers.
90
+ - Everything **should work**, as the library is **extensively tested**, *but not necessarily the most efficient or safe for real-world cryptographic use.* For real-world crypto you might consider *other optimized/safe libraries* that were built to be resistant to malicious attacks.
91
+ - *All operations in this library may be vulnerable to timing attacks.* This may be a problem to your use-case or not depending on the situation.
92
+
93
+ All that being said, extreme care was taken that this is a good library with a solid safe implementation. *Have fun!*
94
+
95
+ ## CLI Apps
96
+
97
+ - [SafeTrans/`safetrans`](safetrans.md): Safe cryptographic operations;
98
+ - [TransCrypto/`transcrypto`](transcrypto.md): Does all the operations but allows you to shoot yourself in the foot;
99
+ - [Profiler/`profiler`](profiler.md): Measure transcrypto performance.
100
+
101
+ ## Programming API
102
+
103
+ ### Install
104
+
105
+ To use in your project just do:
106
+
107
+ ```sh
108
+ pip3 install transcrypto
109
+ ```
110
+
111
+ and then `from transcrypto import rsa` (or other parts of the library) for using it.
112
+
113
+ Known dependencies:
114
+
115
+ - [zstandard](https://pypi.org/project/zstandard/) ([docs](https://python-zstandard.readthedocs.org/))
116
+ - [cryptography](https://pypi.org/project/cryptography/) ([docs](https://cryptography.io/en/latest/))
117
+ - [gmpy2](https://pypi.org/project/gmpy2/) ([docs](https://gmpy2.readthedocs.io/en/latest/))
118
+ - [scipy](https://pypi.org/project/scipy/) ([docs](https://docs.scipy.org/doc/scipy/))
119
+
120
+ ### Base Library
121
+
122
+ #### Humanized Sizes (IEC binary)
123
+
124
+ ```py
125
+ from transcrypto import utils
126
+
127
+ utils.HumanizedBytes(512) # '512 B'
128
+ utils.HumanizedBytes(2048) # '2.00 KiB'
129
+ utils.HumanizedBytes(5 * 1024**3) # '5.00 GiB'
130
+ ```
131
+
132
+ Converts raw byte counts to binary-prefixed strings (`B`, `KiB`, `MiB`, `GiB`, `TiB`, `PiB`, `EiB`). Values under 1024 bytes are returned as integers with `B`; larger values use two decimals.
133
+
134
+ - standard: 1 KiB = 1024 B, 1 MiB = 1024 KiB, …
135
+ - errors: negative inputs raise `InputError`
136
+
137
+ #### Humanized Decimal Quantities (SI)
138
+
139
+ ```py
140
+ # Base (unitless)
141
+ utils.HumanizedDecimal(950) # '950'
142
+ utils.HumanizedDecimal(1500) # '1.50 k'
143
+
144
+ # With a unit (trimmed and attached)
145
+ utils.HumanizedDecimal(1500, ' Hz ') # '1.50 kHz'
146
+ utils.HumanizedDecimal(0.123456, 'V') # '0.1235 V'
147
+
148
+ # Large magnitudes
149
+ utils.HumanizedDecimal(3_200_000) # '3.20 M'
150
+ utils.HumanizedDecimal(7.2e12, 'B/s') # '7.20 TB/s'
151
+ ```
152
+
153
+ Scales by powers of 1000 using SI prefixes (`k`, `M`, `G`, `T`, `P`, `E`). For values `<1000`, integers are shown as-is; small floats show four decimals. For scaled values, two decimals are used and the unit (if provided) is attached without a space (e.g., `kHz`).
154
+
155
+ - unit handling: `unit` is stripped; `<1000` values include a space before the unit (`'950 Hz'`)
156
+ - errors: negative or non-finite inputs raise `InputError`
157
+
158
+ #### Humanized Durations
159
+
160
+ ```py
161
+ utils.HumanizedSeconds(0) # '0.00 s'
162
+ utils.HumanizedSeconds(0.000004) # '4.000 µs'
163
+ utils.HumanizedSeconds(0.25) # '250.000 ms'
164
+ utils.HumanizedSeconds(42) # '42.00 s'
165
+ utils.HumanizedSeconds(3661) # '1.02 h'
166
+ utils.HumanizedSeconds(172800) # '2.00 d'
167
+ ```
168
+
169
+ Chooses an appropriate time unit based on magnitude and formats with fixed precision:
170
+
171
+ - `< 1 ms`: microseconds with three decimals (`µs`)
172
+ - `< 1 s`: milliseconds with three decimals (`ms`)
173
+ - `< 60 s`: seconds with two decimals (`s`)
174
+ - `< 60 min`: minutes with two decimals (`min`)
175
+ - `< 24 h`: hours with two decimals (`h`)
176
+ - `≥ 24 h`: days with two decimals (`d`)
177
+ - special case: `0 → '0.00 s'`
178
+ - errors: negative or non-finite inputs raise `InputError`
179
+
180
+ #### Execution Timing
181
+
182
+ A flexible timing utility that works as a **context manager**, **decorator**, or **manual timer object**.
183
+
184
+ ```py
185
+ from transcrypto import base
186
+ import time
187
+ ```
188
+
189
+ ##### Context manager
190
+
191
+ ```py
192
+ with base.Timer('Block timing'):
193
+ time.sleep(1.2)
194
+ # → logs: "Block timing: 1.20 s" (default via logging.info)
195
+ ```
196
+
197
+ Starts timing on entry, stops on exit, and reports elapsed time automatically.
198
+
199
+ ##### Decorator
200
+
201
+ ```py
202
+ @base.Timer('Function timing')
203
+ def slow_function():
204
+ time.sleep(0.8)
205
+
206
+ slow_function()
207
+ # → logs: "Function timing: 0.80 s"
208
+ ```
209
+
210
+ Wraps a function so that each call is automatically timed.
211
+
212
+ ##### Manual use
213
+
214
+ ```py
215
+ tm = base.Timer('Inline timing', emit_print=True)
216
+ tm.Start()
217
+ time.sleep(0.1)
218
+ tm.Stop() # prints: "Inline timing: 0.10 s"
219
+ ```
220
+
221
+ Manual control over `Start()` and `Stop()` for precise measurement of custom intervals.
222
+
223
+ ##### Key points
224
+
225
+ - **Label**: required, shown in output; empty labels raise `InputError`
226
+ - **Output**:
227
+ - `emit_log=True` → `logging.info()` (default)
228
+ - `emit_print=True` → direct `print()`
229
+ - Both can be enabled
230
+ - **Format**: elapsed time is shown using `HumanizedSeconds()`
231
+ - **Safety**:
232
+ - Cannot start an already started timer
233
+ - Cannot stop an unstarted or already stopped timer
234
+ (raises `Error`)
235
+
236
+ #### Serialization Pipeline
237
+
238
+ These helpers turn arbitrary Python objects into compressed and/or encrypted binary blobs, and back again — with detailed timing and size logging.
239
+
240
+ ```py
241
+ from transcrypto import base
242
+ ```
243
+
244
+ ##### Serialize
245
+
246
+ ```py
247
+ data = {'x': 42, 'y': 'hello'}
248
+
249
+ # Basic serialization
250
+ blob = base.Serialize(data)
251
+
252
+ # With compression and encryption
253
+ blob = base.Serialize(
254
+ data,
255
+ compress=9, # compression level (-22..22, default=3)
256
+ key=my_symmetric_key # must implement SymmetricCrypto
257
+ )
258
+
259
+ # Save directly to file
260
+ base.Serialize(data, file_path='/tmp/data.blob')
261
+ ```
262
+
263
+ Serialization path:
264
+
265
+ ```text
266
+ obj → pickle → (compress) → (encrypt) → (save)
267
+ ```
268
+
269
+ At each stage:
270
+
271
+ - Data size is measured using `HumanizedBytes`
272
+ - Duration is timed with `Timer`
273
+ - Results are logged once at the end
274
+
275
+ Compression levels:
276
+
277
+ `compress` uses `zstandard`; see table below for speed/ratio trade-offs:
278
+
279
+ | Level | Speed | Compression ratio | Typical use case |
280
+ | -------- | ------------| --------------------------------- | --------------------------------------- |
281
+ | -5 to -1 | Fastest | Poor (better than no compression) | Real-time or very latency-sensitive |
282
+ | 0…3 | Very fast | Good ratio | Default CLI choice, safe baseline |
283
+ | 4…6 | Moderate | Better ratio | Good compromise for general persistence |
284
+ | 7…10 | Slower | Marginally better ratio | Only if storage space is precious |
285
+ | 11…15 | Much slower | Slight gains | Large archives, not for runtime use |
286
+ | 16…22 | Very slow | Tiny gains | Archival-only, multi-GB datasets |
287
+
288
+ Errors: invalid compression level is clamped to range; other input errors raise `InputError`.
289
+
290
+ ##### DeSerialize
291
+
292
+ ```py
293
+ # From in-memory blob
294
+ obj = base.DeSerialize(data=blob)
295
+
296
+ # From file
297
+ obj = base.DeSerialize(file_path='/tmp/data.blob')
298
+
299
+ # With decryption
300
+ obj = base.DeSerialize(data=blob, key=my_symmetric_key)
301
+ ```
302
+
303
+ Deserialization path:
304
+
305
+ ```text
306
+ data/file → (decrypt) → (decompress if Zstd) → unpickle
307
+ ```
308
+
309
+ - Compression is auto-detected via Zstandard magic numbers.
310
+ - All steps are timed/logged like in `Serialize`.
311
+
312
+ **Constraints & errors**:
313
+
314
+ - Exactly one of `data` or `file_path` must be provided.
315
+ - `file_path` must exist; `data` must be at least 4 bytes.
316
+ - Wrong key or corrupted data can raise `CryptoError`.
317
+
318
+ #### Cryptographically Secure Randomness
319
+
320
+ These helpers live in `base` and wrap Python’s `secrets` with additional checks and guarantees for crypto use-cases.
321
+
322
+ ```py
323
+ from transcrypto import base
324
+ ```
325
+
326
+ ##### Fixed-size random integers
327
+
328
+ ```py
329
+ # Generate a 256-bit integer (first bit always set)
330
+ r = base.RandBits(256)
331
+ assert r.bit_length() == 256
332
+ ```
333
+
334
+ Produces a crypto-secure random integer with exactly `n_bits` bits (`≥ 8`). The most significant bit is guaranteed to be `1`, so entropy is \~`n_bits−1` — negligible for large crypto sizes.
335
+
336
+ - errors: `n_bits < 8` → `InputError`
337
+
338
+ ##### Uniform random integers in a range
339
+
340
+ ```py
341
+ # Uniform between [10, 20] inclusive
342
+ n = base.RandInt(10, 20)
343
+ assert 10 <= n <= 20
344
+ ```
345
+
346
+ Returns a crypto-secure integer uniformly distributed over the closed interval `[min_int, max_int]`.
347
+
348
+ - constraints: `min_int ≥ 0` and `< max_int`
349
+ - errors: invalid bounds → `InputError`
350
+
351
+ ##### In-place secure shuffle
352
+
353
+ ```py
354
+ deck = list(range(10))
355
+ base.RandShuffle(deck)
356
+ print(deck) # securely shuffled order
357
+ ```
358
+
359
+ Performs an in-place Fisher–Yates shuffle using `secrets.randbelow`. Suitable for sensitive data ordering.
360
+
361
+ - constraints: sequence length ≥ 2
362
+ - errors: shorter sequences → `InputError`
363
+
364
+ ##### Random byte strings
365
+
366
+ ```py
367
+ # 32 random bytes
368
+ b = base.RandBytes(32)
369
+ assert len(b) == 32
370
+ ```
371
+
372
+ Generates `n_bytes` of high-quality crypto-secure random data.
373
+
374
+ - constraints: `n_bytes ≥ 1`
375
+ - errors: smaller values → `InputError`
376
+
377
+ #### Computing the Greatest Common Divisor
378
+
379
+ ```py
380
+ >>> from transcrypto import base
381
+ >>> base.GCD(462, 1071)
382
+ 21
383
+ >>> base.GCD(0, 17)
384
+ 17
385
+ ```
386
+
387
+ The function is `O(log(min(a, b)))` and handles arbitrarily large integers. To find Bézout coefficients `(x, y)` such that `ax + by = gcd(a, b)` do:
388
+
389
+ ```py
390
+ >>> base.ExtendedGCD(462, 1071)
391
+ (21, -2, 1)
392
+ >>> 462 * -2 + 1071 * 1
393
+ 21
394
+ ```
395
+
396
+ Use-cases:
397
+
398
+ - modular inverses: `inv = x % m` when `gcd(a, m) == 1`
399
+ - solving linear Diophantine equations
400
+ - RSA / ECC key generation internals
401
+
402
+ #### Fast Modular Arithmetic
403
+
404
+ ```py
405
+ from transcrypto import modmath
406
+
407
+ m = 2**256 - 189 # a large prime modulus
408
+
409
+ # Inverse ──────────────────────────────
410
+ x = 123456789
411
+ x_inv = modmath.ModInv(x, m)
412
+ assert (x * x_inv) % m == 1
413
+
414
+ # Division (x / y) mod m ──────────────
415
+ y = 987654321
416
+ z = modmath.ModDiv(x, y, m) # solves z·y ≡ x (mod m)
417
+ assert (z * y) % m == x % m
418
+
419
+ # Exponentiation ──────────────────────
420
+ exp = modmath.ModExp(3, 10**20, m) # ≈ log₂(y) time, handles huge exponents
421
+ ```
422
+
423
+ ##### Chinese Remainder Theorem (CRT) – Pair
424
+
425
+ ```py
426
+ from transcrypto import modmath
427
+
428
+ # Solve:
429
+ # x ≡ 2 (mod 3)
430
+ # x ≡ 3 (mod 5)
431
+ x = modmath.CRTPair(2, 3, 3, 5)
432
+ print(x) # 8
433
+ assert x % 3 == 2
434
+ assert x % 5 == 3
435
+ ```
436
+
437
+ Solves a system of two simultaneous congruences with **pairwise co-prime** moduli, returning the **least non-negative solution** `x` such that:
438
+
439
+ ```text
440
+ x ≡ a1 (mod m1)
441
+ x ≡ a2 (mod m2)
442
+ 0 ≤ x < m1 * m2
443
+ ```
444
+
445
+ - **Requirements**:
446
+ - `m1 ≥ 2`, `m2 ≥ 2`, `m1 != m2`
447
+ - `gcd(m1, m2) == 1` (co-prime)
448
+ - **Errors**:
449
+ - invalid modulus values → `InputError`
450
+ - non co-prime moduli → `ModularDivideError`
451
+
452
+ This function is a 2-modulus variant; for multiple moduli, apply it iteratively or use a general CRT solver.
453
+
454
+ ##### Modular Polynomials & Lagrange Interpolation
455
+
456
+ ```py
457
+ # f(t) = 7t³ − 3t² + 2t + 5 (coefficients constant-term first)
458
+ coefficients = [5, 2, -3, 7]
459
+ print(modmath.ModPolynomial(11, coefficients, 97)) # → 19
460
+
461
+ # Given three points build the degree-≤2 polynomial and evaluate it.
462
+ pts = {2: 4, 5: 3, 7: 1}
463
+ print(modmath.ModLagrangeInterpolate(9, pts, 11)) # → 2
464
+ ```
465
+
466
+ #### Primality testing & Prime generators, Mersenne primes
467
+
468
+ ```py
469
+ modmath.IsPrime(2**127 - 1) # True (Mersenne prime)
470
+ modmath.IsPrime(3825123056546413051) # False (strong pseudo-prime)
471
+
472
+ # Direct Miller–Rabin with custom witnesses
473
+ modmath.MillerRabinIsPrime(961748941, witnesses={2,7,61})
474
+
475
+ # Infinite iterator of primes ≥ 10⁶
476
+ for p in modmath.PrimeGenerator(1_000_000):
477
+ print(p)
478
+ if p > 1_000_100:
479
+ break
480
+
481
+ # Secure random 384-bit prime (for RSA/ECC experiments)
482
+ p384 = modmath.NBitRandomPrimes(384).pop()
483
+
484
+ for k, m_p, perfect in modmath.MersennePrimesGenerator(0):
485
+ print(f'p = {k:>8} M = {m_p} perfect = {perfect}')
486
+ if k > 10000: # stop after a few
487
+ break
488
+ ```
489
+
490
+ #### Cryptographic Hashing
491
+
492
+ Simple, fixed-output-size wrappers over Python’s `hashlib` for common digest operations, plus file hashing.
493
+
494
+ ```py
495
+ from transcrypto import base
496
+ ```
497
+
498
+ ##### SHA-256 hashing
499
+
500
+ ```py
501
+ h = base.Hash256(b'hello world')
502
+ assert len(h) == 32 # bytes
503
+ print(h.hex()) # 64 hex chars
504
+ ```
505
+
506
+ Computes the SHA-256 digest of a byte string, returning exactly 32 bytes (256 bits). Suitable for fingerprints, commitments, or internal crypto primitives.
507
+
508
+ ##### SHA-512 hashing
509
+
510
+ ```py
511
+ h = base.Hash512(b'hello world')
512
+ assert len(h) == 64 # bytes
513
+ print(h.hex()) # 128 hex chars
514
+ ```
515
+
516
+ Computes the SHA-512 digest of a byte string, returning exactly 64 bytes (512 bits). Higher collision resistance and larger output space than SHA-256.
517
+
518
+ ##### File hashing
519
+
520
+ ```py
521
+ # Default SHA-256
522
+ fh = base.FileHash('/path/to/file')
523
+ print(fh.hex())
524
+
525
+ # SHA-512
526
+ fh2 = base.FileHash('/path/to/file', digest='sha512')
527
+ ```
528
+
529
+ Hashes a file from disk in streaming mode. By default uses SHA-256; `digest='sha512'` switches to SHA-512.
530
+
531
+ - constraints:
532
+ - `digest` must be `'sha256'` or `'sha512'`
533
+ - `full_path` must exist
534
+ - errors: invalid digest or missing file → `InputError`
535
+
536
+ #### Symmetric Encryption Interface
537
+
538
+ `SymmetricCrypto` is an abstract base class that defines the **byte-in / byte-out** contract for symmetric ciphers.
539
+
540
+ - **Metadata handling** — if the algorithm uses a `nonce` or `tag`, the implementation must handle it internally (e.g., append it to ciphertext).
541
+ - **AEAD modes** — if supported, `associated_data` must be authenticated; otherwise, a non-`None` value should raise `InputError`.
542
+
543
+ ```py
544
+ class MyAES(base.SymmetricCrypto):
545
+ def Encrypt(self, plaintext: bytes, *, associated_data=None) -> bytes:
546
+ ...
547
+ def Decrypt(self, ciphertext: bytes, *, associated_data=None) -> bytes:
548
+ ...
549
+ ```
550
+
551
+ #### Crypto Objects General Properties (`CryptoKey`)
552
+
553
+ Cryptographic objects all derive from the `CryptoKey` class and will all have some important characteristics:
554
+
555
+ - Will be safe to log and print, i.e., implement safe `__str__()` and `__repr__()` methods (in actuality `repr` will be exactly the same as `str`). The `__str__()` should always fully print the public parts of the object and obfuscate the private ones. This obfuscation allows for some debugging, if needed, but if the secrets are "too short" then it can be defeated by brute force. For usual crypto defaults the obfuscation is fine. The obfuscation is the fist 4 bytes of the SHA-512 for the value followed by an ellipsis (e.g. `c9626f16…`).
556
+ - It will have a `_DebugDump()` method that **does print secrets** and can be used for **debugging only**.
557
+ - Can be easily serialized to `bytes` by the `blob` property and to base-64 encoded `str` by the `encoded` property.
558
+ - Can be serialized encrypted to `bytes` by the `Blob(key=[SymmetricCrypto])` method and to encrypted base-64 encoded `str` by the `Encoded(key=[SymmetricCrypto])` method.
559
+ - Can be instantiated back as an object from `str` or `bytes` using the `Load(data, key=[SymmetricCrypto] | None)` method. The `Load()` will decide how to build the object and will work universally with all the serialization options discussed above.
560
+
561
+ Example:
562
+
563
+ <!-- cspell:disable -->
564
+ ```py
565
+ from transcrypto import base, rsa, aes
566
+
567
+ priv = rsa.RSAPrivateKey.New(512) # small key, but good for this example
568
+ print(str(priv)) # safe, no secrets
569
+ # ▶ RSAPrivateKey(RSAPublicKey(public_modulus=pQaoxy-QeXSds1k9WsGjJw==, encrypt_exp=AQAB), modulus_p=f18141aa…, modulus_q=67494eb9…, decrypt_exp=c96db24a…)
570
+
571
+ print(priv._DebugDump()) # UNSAFE: prints secrets
572
+ # ▶ RSAPrivateKey(public_modulus=219357196311600536151291741191131996967, encrypt_exp=65537, modulus_p=13221374197986739361, modulus_q=16591104148992527047, decrypt_exp=37805202135275158391322585315542443073, remainder_p=9522084656682089473, remainder_q=8975656462800098363, q_inverse_p=11965562396596149292)
573
+
574
+ print(priv.blob)
575
+ # ▶ b"(\xb5/\xfd \x98\xc1\x04\x00\x80\x04\x95\x8d\x00\x00\x00\x00\x00\x00\x00\x8c\x0ftranscrypto.rsa\x94\x8c\rRSAPrivateKey\x94\x93\x94)\x81\x94]\x94(\x8a\x11'\xa3\xc1Z=Y\xb3\x9dty\x90/\xc7\xa8\x06\xa5\x00J\x01\x00\x01\x00\x8a\t\xa1\xc4\x83\x81\xc8\xc1{\xb7\x00\x8a\t\xc7\x8a5\xf0Qq?\xe6\x00\x8a\x10A$&\x82!\x1cy\x89r\xef\xeb\xa7_\x04q\x1c\x8a\t\x01\xbc\xbb\x8a\x8b=%\x84\x00\x8a\x08;\x94#s\xff\xef\x8f|\x8a\t,\x9c\xe2z\x9a7\x0e\xa6\x00eb."
576
+
577
+ print(priv.encoded)
578
+ # ▶ KLUv_WBwAIELAIAElWUBAAAAAAAAjA90cmFuc2NyeXB0by5yc2GUjA1SU0FQcml2YXRlS2V5lJOUKYGUXZQoikHf1EvsmZedAZve7TrLmobLAwuRIr_77TLG6G_0fsLGThERVJu075be8PLjUQYnLXcacZFQ5Fb1Iy1WtiE985euAEoBAAEAiiFR9ngiXMzkf41o5CRBY3h0D4DJVisDDhLmAWsiaHggzQCKIS_cmQ6MKXCtROtC7c_Mrsi9A-9NM8DksaHaRwvy6uTZAIpB4TVbsLxc41TEc19wIzpxbi9y5dW5gdfTkRQSSiz0ijmb8Xk3pyBfKAv8JbHp8Yv48gNZUfX67qq0J7yhJqeUoACKIbFb2kTNRzSqm3JRtjc2BPS-FnLFdadlFcV4-6IW7eqLAIogFZfzDN39gZLR9uTz4KHSTaqxWrJgP8-YYssjss6FlFKKIIItgCDv7ompNpY8gBs5bibN8XTsr-JOYSntDVT5Fe5vZWIu
579
+
580
+ key = aes.AESKey(key256=b'x' * 32)
581
+ print(key)
582
+ # ▶ AESKey(key256=86a86df7…)
583
+
584
+ encrypted = priv.Blob(key=key)
585
+ print(priv == rsa.RSAPrivateKey.Load(encrypted, key=key))
586
+ # ▶ True
587
+ ```
588
+ <!-- cspell:enable -->
589
+
590
+ #### AES-256 Symmetric Encryption
591
+
592
+ Implements AES-256 in **GCM mode** for authenticated encryption and decryption, plus an **ECB mode** helper for fixed-size block encoding.
593
+ Also includes a high-iteration PBKDF2-based key derivation from static passwords.
594
+
595
+ ##### Key creation
596
+
597
+ ```py
598
+ from transcrypto import aes
599
+
600
+ # From raw bytes (must be exactly 32 bytes)
601
+ key = aes.AESKey(key256=b'\x00' * 32)
602
+
603
+ # From a static password (slow, high-iteration PBKDF2-SHA256)
604
+ key = aes.AESKey.FromStaticPassword('correct horse battery staple')
605
+ print(key.encoded) # URL-safe Base64
606
+ ```
607
+
608
+ - **Length**: `key256` must be exactly 32 bytes
609
+ - `FromStaticPassword()`:
610
+ - Uses PBKDF2-HMAC-SHA256 with **fixed** salt and \~2 million iterations
611
+ - Designed for **interactive** password entry, **not** for password databases
612
+
613
+ ##### AES-256 + GCM (default)
614
+
615
+ ```py
616
+ data = b'secret message'
617
+ aad = b'metadata'
618
+
619
+ # Encrypt (returns IV + ciphertext + tag)
620
+ ct = key.Encrypt(data, associated_data=aad)
621
+
622
+ # Decrypt
623
+ pt = key.Decrypt(ct, associated_data=aad)
624
+ assert pt == data
625
+ ```
626
+
627
+ - **Security**:
628
+ - Random 128-bit IV (`iv`) per encryption
629
+ - Authenticated tag (128-bit) ensures integrity
630
+ - Optional `associated_data` is authenticated but not encrypted
631
+ - **Errors**:
632
+ - Tag mismatch or wrong key → `CryptoError`
633
+
634
+ ##### AES-256 + ECB (unsafe, fixed block only)
635
+
636
+ ```py
637
+ # ECB mode is for 16-byte block encoding ONLY
638
+ ecb = key.ECBEncoder()
639
+
640
+ block = b'16-byte string!!'
641
+ ct_block = ecb.Encrypt(block)
642
+ pt_block = ecb.Decrypt(ct_block)
643
+ assert pt_block == block
644
+
645
+ # Hex helpers
646
+ hex_ct = ecb.EncryptHex('00112233445566778899aabbccddeeff') # 128 bits (1 block)
647
+ hex_pt = ecb.DecryptHex(hex_ct)
648
+ assert hex_pt == '00112233445566778899aabbccddeeff'
649
+ hex_ct2 = ecb.EncryptHex256('00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff')
650
+ hex_pt2 = ecb.DecryptHex256(hex_ct2)
651
+ assert hex_pt2 == '00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff'
652
+ ```
653
+
654
+ - **ECB mode**:
655
+ - 16-byte plaintext ↔ 16-byte ciphertext
656
+ - No padding, no IV, no integrity — **do not use for general encryption**
657
+ - `associated_data` not supported
658
+
659
+ Key points:
660
+
661
+ - **GCM mode** is secure for general use; ECB mode is for special low-level operations
662
+ - **Static password derivation** is intentionally slow to resist brute force
663
+ - All sizes and parameters are validated with `InputError` on misuse
664
+
665
+ #### RSA (Rivest-Shamir-Adleman) Public Cryptography
666
+
667
+ <https://en.wikipedia.org/wiki/RSA_cryptosystem>
668
+
669
+ This implementation is raw RSA, no OAEP or PSS! It works on the actual integers. For real uses you should look for higher-level implementations.
670
+
671
+ By default and deliberate choice the *encryption exponent* will be either 7 or 65537, depending on the size of `phi=(p-1)*(q-1)`. If `phi` allows it the larger one will be chosen to avoid Coppersmith attacks.
672
+
673
+ ```py
674
+ from transcrypto import rsa
675
+
676
+ # Generate a key pair
677
+ priv = rsa.RSAPrivateKey.New(2048) # 2048-bit modulus
678
+ pub = rsa.RSAPublicKey.Copy(priv) # public half
679
+ print(priv.public_modulus.bit_length()) # 2048
680
+
681
+ # Safe Encrypt & decrypt
682
+ msg = b'xyz'
683
+ cipher = pub.Encrypt(msg, associated_data=b'aad')
684
+ plain = priv.Decrypt(cipher, associated_data=b'aad')
685
+ assert plain == msg
686
+
687
+ # Safe Sign & verify
688
+ signature = priv.Sign(msg) # can also have associated_data, optionally
689
+ assert pub.Verify(msg, signature)
690
+
691
+ # Raw Encrypt & decrypt
692
+ msg = 123456789 # (Zero is forbidden by design; smallest valid message is 1.)
693
+ cipher = pub.RawEncrypt(msg)
694
+ plain = priv.RawDecrypt(cipher)
695
+ assert plain == msg
696
+
697
+ # Raw Sign & verify
698
+ signature = priv.RawSign(msg)
699
+ assert pub.RawVerify(msg, signature)
700
+
701
+ # Blind signatures (obfuscation pair) - only works on raw RSA
702
+ pair = rsa.RSAObfuscationPair.New(pub)
703
+
704
+ blind_msg = pair.ObfuscateMessage(msg) # what you send to signer
705
+ blind_sig = priv.RawSign(blind_msg) # signer’s output
706
+
707
+ sig = pair.RevealOriginalSignature(msg, blind_sig)
708
+ assert pub.RawVerify(msg, sig)
709
+ ```
710
+
711
+ #### El-Gamal Public-Key Cryptography
712
+
713
+ [https://en.wikipedia.org/wiki/ElGamal\_encryption](https://en.wikipedia.org/wiki/ElGamal_encryption)
714
+
715
+ This is **raw El-Gamal** over a prime field — no padding, no hashing — and is **not** DSA.
716
+ For real-world deployments, use a high-level library with authenticated encryption and proper encoding.
717
+
718
+ ```py
719
+ from transcrypto import elgamal
720
+
721
+ # Shared parameters (prime modulus, group base) for a group
722
+ shared = elgamal.ElGamalSharedPublicKey.New(256)
723
+ print(shared.prime_modulus)
724
+ print(shared.group_base)
725
+
726
+ # Public key from private
727
+ priv = elgamal.ElGamalPrivateKey.New(shared)
728
+ pub = elgamal.ElGamalPublicKey.Copy(priv)
729
+
730
+ # Safe Encrypt & decrypt
731
+ msg = b'xyz'
732
+ cipher = pub.Encrypt(msg, associated_data=b'aad')
733
+ plain = priv.Decrypt(cipher, associated_data=b'aad')
734
+ assert plain == msg
735
+
736
+ # Safe Sign & verify
737
+ signature = priv.Sign(msg) # can also have associated_data, optionally
738
+ assert pub.Verify(msg, signature)
739
+
740
+ # Raw Encryption
741
+ msg = 42
742
+ cipher = pub.RawEncrypt(msg)
743
+ plain = priv.RawDecrypt(cipher)
744
+ assert plain == msg
745
+
746
+ # Raw Signature verify
747
+ sig = priv.RawSign(msg)
748
+ assert pub.RawVerify(msg, sig)
749
+ ```
750
+
751
+ Key points:
752
+
753
+ - **Security parameters**:
754
+ - Recommended `prime_modulus` bit length ≥ 2048 for real security
755
+ - Random values from `base.RandBits`
756
+ - **Ephemeral keys**:
757
+ - Fresh per encryption/signature
758
+ - Must satisfy `gcd(k, p-1) == 1`
759
+ - **Errors**:
760
+ - Bad ranges → `InputError`
761
+ - Invalid math relationships → `CryptoError`
762
+ - **Group sharing**:
763
+ - Multiple parties can share `(p, g)` but have different `(individual_base, decrypt_exp)`
764
+
765
+ #### DSA (Digital Signature Algorithm)
766
+
767
+ [https://en.wikipedia.org/wiki/Digital\_Signature\_Algorithm](https://en.wikipedia.org/wiki/Digital_Signature_Algorithm)
768
+
769
+ This is **raw DSA** over a prime field — **no hashing or padding**. You sign/verify **integers** modulo `q` (`prime_seed`). For real use, hash the message first (e.g., SHA-256) and then map to an integer `< q`.
770
+
771
+ ```py
772
+ from transcrypto import dsa
773
+
774
+ # Shared parameters (p, q, g)
775
+ shared = dsa.DSASharedPublicKey.New(p_bits=1024, q_bits=160)
776
+ print(shared.prime_modulus) # p
777
+ print(shared.prime_seed) # q (q | p-1)
778
+ print(shared.group_base) # g
779
+
780
+ # Individual key pair
781
+ priv = dsa.DSAPrivateKey.New(shared)
782
+ pub = dsa.DSAPublicKey.Copy(priv)
783
+
784
+ # Safe Sign & verify
785
+ msg = b'xyz'
786
+ signature = priv.Sign(msg) # can also have associated_data, optionally
787
+ assert pub.Verify(msg, signature)
788
+
789
+ # Raw Sign & verify (message must be 1 ≤ m < q)
790
+ msg = 123456789 % shared.prime_seed
791
+ sig = priv.RawSign(msg)
792
+ assert pub.RawVerify(msg, sig)
793
+ ```
794
+
795
+ - ranges:
796
+ - `1 ≤ message < q`
797
+ - signatures: `(s1, s2)` with `2 ≤ s1, s2 < q`
798
+ - errors:
799
+ - invalid ranges → `InputError`
800
+ - inconsistent parameters → `CryptoError`
801
+
802
+ ##### Security notes
803
+
804
+ - Choose **large** parameters (e.g., `p ≥ 2048 bits`, `q ≥ 224 bits`) for non-toy settings.
805
+ - In practice, compute `m = int.from_bytes(Hash(message), 'big') % q` before calling `Sign(m)`.
806
+
807
+ ##### Advanced: custom primes generator
808
+
809
+ ```py
810
+ # Generate primes (p, q) with q | (p-1); also returns m = (p-1)//q
811
+ p, q, m = dsa.NBitRandomDSAPrimes(p_bits=1024, q_bits=160)
812
+ assert (p - 1) % q == 0
813
+ ```
814
+
815
+ Used internally by `DSASharedPublicKey.New()`.
816
+ Search breadth and retry caps are bounded; repeated failures raise `CryptoError`.
817
+
818
+ #### Public Bidding
819
+
820
+ This is a way of bidding on some commitment (the `secret`) that can be cryptographically proved later to not have been changed. To do that the secret is combined with 2 nonces (random values, `n1` & `n2`) and a hash of it is taken (`H=SHA-512(n1||n2||secret)`). The hash `H` and one nonce `n1` are public and divulged. The other nonce `n2` and the `secret` are kept private and will be used to show `secret` was not changed since the beginning of the process. The nonces guarantee the `secret` cannot be brute-forced or changed after-the-fact. The whole process is as strong as SHA-512 collisions.
821
+
822
+ ```py
823
+ from transcrypto import base
824
+
825
+ # Generate the private and public bids
826
+ bid_priv = base.PrivateBid512.New(secret) # this one you keep private
827
+ bid_pub = base.PublicBid512.Copy(bid_priv) # this one you publish
828
+
829
+ # Checking that a bid is genuine requires the public bid and knowing the nonce and the secret:
830
+ print(bid_pub.VerifyBid(private_key, secret_bid)) # these come from a divulged private bid
831
+ # of course, you want to also make sure the provided private data matches your version of it, e.g.:
832
+ bid_pub_expected = base.PublicBid512.Copy(bid_priv)
833
+ print(bid_pub == bid_pub_expected)
834
+ ```
835
+
836
+ #### SSS (Shamir Shared Secret)
837
+
838
+ <https://en.wikipedia.org/wiki/Shamir's_secret_sharing>
839
+
840
+ This is the information-theoretic SSS but with no authentication or binding between share and secret. Malicious share injection is possible! Add MAC or digital signature in hostile settings. Use at least 128-bit modulus for non-toy deployments.
841
+
842
+ ```py
843
+ from transcrypto import sss
844
+
845
+ # Generate parameters: at least 3 of 5 shares needed,
846
+ # coefficients & modulus are 128-bit primes
847
+ priv = sss.ShamirSharedSecretPrivate.New(minimum_shares=3, bit_length=128)
848
+ pub = sss.ShamirSharedSecretPublic.Copy(priv) # what you publish
849
+
850
+ print(f'threshold : {pub.minimum}')
851
+ print(f'prime mod : {pub.modulus}')
852
+ print(f'poly coefficients: {priv.polynomial}') # keep these private!
853
+
854
+ # Safe Issuing shares
855
+
856
+ secret = b'xyz'
857
+ # Generate 5 shares, each has a copy of the encrypted secret
858
+ five_shares = priv.MakeDataShares(secret, 5)
859
+ for sh in five_shares:
860
+ print(sh)
861
+
862
+ # Raw Issuing shares
863
+
864
+ secret = 0xC0FFEE
865
+ # Generate an unlimited stream; here we take 5
866
+ five_shares = list(priv.RawShares(secret, max_shares=5))
867
+ for sh in five_shares:
868
+ print(f'share {sh.share_key} → {sh.share_value}')
869
+ ```
870
+
871
+ A single share object looks like `sss.ShamirSharePrivate(minimum=3, modulus=..., share_key=42, share_value=123456789)`.
872
+
873
+ ```py
874
+ # Safe Re-constructing the secret
875
+ secret = b'xyz'
876
+ five_shares = priv.MakeDataShares(secret, 5)
877
+ subset = five_shares[:3] # any 3 distinct shares
878
+ recovered = subset[0].RecoverData(subset) # each share has the encrypted data, so you ask it to join with the others
879
+ assert recovered == secret
880
+
881
+ # Raw Re-constructing the secret
882
+ secret = 0xC0FFEE
883
+ five_shares = list(priv.RawShares(secret, max_shares=5))
884
+ subset = five_shares[:3] # any 3 distinct shares
885
+ recovered = pub.RawRecoverSecret(subset)
886
+ assert recovered == secret
887
+ ```
888
+
889
+ If you supply fewer than minimum shares you get a `CryptoError`, unless you explicitly override:
890
+
891
+ ```py
892
+ try:
893
+ pub.RawRecoverSecret(five_shares[:2]) # raises
894
+ except Exception as e:
895
+ print(e) # "unrecoverable secret …"
896
+
897
+ # Force the interpolation even with 2 points (gives a wrong secret, of course)
898
+ print(pub.RawRecoverSecret(five_shares[:2], force_recover=True))
899
+
900
+ # Checking that a share is genuine
901
+
902
+ share = five_shares[0]
903
+ ok = priv.RawVerifyShare(secret, share) # ▶ True
904
+ tampered = sss.ShamirSharePrivate(
905
+ minimum=share.minimum,
906
+ modulus=share.modulus,
907
+ share_key=share.share_key,
908
+ share_value=(share.share_value + 1) % share.modulus)
909
+ print(priv.RawVerifyShare(secret, tampered)) # ▶ False
910
+ ```
911
+
912
+ ## Appendix: Development Instructions
913
+
914
+ ### Setup
915
+
916
+ If you want to develop for this project, first install python 3.13 and [Poetry](https://python-poetry.org/docs/cli/), but to get the versions you will need, we suggest you do it like this (*Linux*):
917
+
918
+ ```sh
919
+ sudo apt-get update
920
+ sudo apt-get upgrade
921
+ sudo apt-get install git python3 python3-pip pipx python3-dev python3-venv build-essential software-properties-common
922
+
923
+ sudo add-apt-repository ppa:deadsnakes/ppa # install arbitrary python version
924
+ sudo apt-get update
925
+ sudo apt-get install python3.13
926
+
927
+ sudo apt-get remove python3-poetry
928
+ python3.13 -m pipx ensurepath
929
+ # re-open terminal
930
+ pipx install poetry
931
+ poetry --version # should be >=2.1
932
+
933
+ poetry config virtualenvs.in-project true # creates .venv inside project directory
934
+ poetry config pypi-token.pypi <TOKEN> # add your personal PyPI project token, if any
935
+ ```
936
+
937
+ or this (*Mac*):
938
+
939
+ ```sh
940
+ brew update
941
+ brew upgrade
942
+ brew cleanup -s
943
+
944
+ brew install git python@3.13 # install arbitrary python version
945
+
946
+ brew uninstall poetry
947
+ python3.13 -m pip install --user pipx
948
+ python3.13 -m pipx ensurepath
949
+ # re-open terminal
950
+ pipx install poetry
951
+ poetry --version # should be >=2.1
952
+
953
+ poetry config virtualenvs.in-project true # creates .venv inside project directory
954
+ poetry config pypi-token.pypi <TOKEN> # add your personal PyPI project token, if any
955
+ ```
956
+
957
+ Now install the project:
958
+
959
+ ```sh
960
+ git clone https://github.com/balparda/transcrypto.git transcrypto
961
+ cd transcrypto
962
+
963
+ poetry env use python3.13 # creates the venv
964
+ poetry sync # sync env to project's poetry.lock file
965
+ poetry env info # no-op: just to check
966
+
967
+ poetry run pytest -vvv
968
+ # or any command as:
969
+ poetry run <any-command>
970
+ ```
971
+
972
+ To activate like a regular environment do:
973
+
974
+ ```sh
975
+ poetry env activate
976
+ # will print activation command which you next execute, or you can do:
977
+ source .venv/bin/activate # if .venv is local to the project
978
+ source "$(poetry env info --path)/bin/activate" # for other paths
979
+
980
+ pytest # or other commands
981
+
982
+ deactivate
983
+ ```
984
+
985
+ ### Updating Dependencies
986
+
987
+ To update `poetry.lock` file to more current versions do `poetry update`, it will ignore the current lock, update, and rewrite the `poetry.lock` file.
988
+
989
+ To add a new dependency you should do:
990
+
991
+ ```sh
992
+ poetry add "pkg>=1.2.3" # regenerates lock, updates env (adds dep to prod code)
993
+ poetry add -G dev "pkg>=1.2.3" # adds dep to dev code ("group" dev)
994
+ # also remember: "pkg@^1.2.3" = latest 1.* ; "pkg@~1.2.3" = latest 1.2.* ; "pkg@1.2.3" exact
995
+ ```
996
+
997
+ If you manually added a dependency to `pyproject.toml` you should ***very carefully*** recreate the environment and files:
998
+
999
+ ```sh
1000
+ rm -rf .venv .poetry poetry.lock
1001
+ poetry env use python3.13
1002
+ poetry install
1003
+ ```
1004
+
1005
+ Remember to check your diffs before submitting (especially `poetry.lock`) to avoid surprises!
1006
+
1007
+ When dependencies change, always regenerate `requirements.txt` by running:
1008
+
1009
+ ```sh
1010
+ poetry export --format requirements.txt --without-hashes --output requirements.txt
1011
+ ```
1012
+
1013
+ ### Creating a New Version
1014
+
1015
+ ```sh
1016
+ # bump the version!
1017
+ poetry version minor # updates 1.6 to 1.7, for example
1018
+ # or:
1019
+ poetry version patch # updates 1.6 to 1.6.1
1020
+ # or:
1021
+ poetry version <version-number>
1022
+ # (also updates `pyproject.toml` and `poetry.lock`)
1023
+
1024
+ # publish to GIT, including a TAG
1025
+ git commit -a -m "release version 1.0.2"
1026
+ git tag 1.0.2
1027
+ git push
1028
+ git push --tags
1029
+
1030
+ # prepare package for PyPI
1031
+ poetry build
1032
+ poetry publish
1033
+ ```
1034
+
1035
+ If you changed the CLI interface at all, in any tool, run:
1036
+
1037
+ ```sh
1038
+ ./tools/generate_docs.sh
1039
+ ```
1040
+
1041
+ You can find the 10 top slowest tests by running:
1042
+
1043
+ ```sh
1044
+ poetry run pytest -vvv -q --durations=30
1045
+
1046
+ poetry run pytest -vvv -q --durations=30 -m "not slow" # find slow > 0.1s
1047
+ poetry run pytest -vvv -q --durations=30 -m "not veryslow" # find veryslow > 1s
1048
+
1049
+ poetry run pytest -vvv -q --durations=30 -m slow # check
1050
+ poetry run pytest -vvv -q --durations=30 -m veryslow # check
1051
+ ```
1052
+
1053
+ You can search for flaky tests by running all tests 100 times, or more:
1054
+
1055
+ ```sh
1056
+ poetry run pytest --flake-finder --flake-runs=100
1057
+ poetry run pytest --flake-finder --flake-runs=500 -m "not veryslow"
1058
+ poetry run pytest --flake-finder --flake-runs=10000 -m "not slow"
1059
+ ```
1060
+
1061
+ You can instrument your code to find bottlenecks:
1062
+
1063
+ ```sh
1064
+ $ source .venv/bin/activate
1065
+ $ which transcrypto
1066
+ /path/to/.venv/bin/transcrypto # place this in the command below:
1067
+ $ pyinstrument -r html -o dsa_shared.html -- /path/to/.venv/bin/transcrypto -p rsa-key rsa new
1068
+ $ deactivate
1069
+ ```
1070
+
1071
+ Hint: 85%+ is inside `MillerRabinIsPrime()`/`gmpy2.powmod()`...