transcrypto 1.7.0__tar.gz → 2.0.0__tar.gz

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-1.7.0 → transcrypto-2.0.0}/PKG-INFO +111 -109
  2. {transcrypto-1.7.0 → transcrypto-2.0.0}/README.md +107 -106
  3. {transcrypto-1.7.0 → transcrypto-2.0.0}/pyproject.toml +16 -15
  4. {transcrypto-1.7.0 → transcrypto-2.0.0}/src/transcrypto/__init__.py +1 -1
  5. transcrypto-2.0.0/src/transcrypto/cli/__init__.py +3 -0
  6. transcrypto-2.0.0/src/transcrypto/cli/aeshash.py +370 -0
  7. transcrypto-2.0.0/src/transcrypto/cli/bidsecret.py +336 -0
  8. transcrypto-2.0.0/src/transcrypto/cli/clibase.py +183 -0
  9. transcrypto-2.0.0/src/transcrypto/cli/intmath.py +429 -0
  10. transcrypto-2.0.0/src/transcrypto/cli/publicalgos.py +878 -0
  11. transcrypto-2.0.0/src/transcrypto/core/__init__.py +3 -0
  12. {transcrypto-1.7.0/src/transcrypto → transcrypto-2.0.0/src/transcrypto/core}/aes.py +17 -29
  13. transcrypto-2.0.0/src/transcrypto/core/bid.py +161 -0
  14. {transcrypto-1.7.0/src/transcrypto → transcrypto-2.0.0/src/transcrypto/core}/dsa.py +28 -27
  15. {transcrypto-1.7.0/src/transcrypto → transcrypto-2.0.0/src/transcrypto/core}/elgamal.py +33 -32
  16. transcrypto-2.0.0/src/transcrypto/core/hashes.py +96 -0
  17. transcrypto-2.0.0/src/transcrypto/core/key.py +735 -0
  18. {transcrypto-1.7.0/src/transcrypto → transcrypto-2.0.0/src/transcrypto/core}/modmath.py +91 -17
  19. {transcrypto-1.7.0/src/transcrypto → transcrypto-2.0.0/src/transcrypto/core}/rsa.py +51 -50
  20. {transcrypto-1.7.0/src/transcrypto → transcrypto-2.0.0/src/transcrypto/core}/sss.py +27 -26
  21. {transcrypto-1.7.0 → transcrypto-2.0.0}/src/transcrypto/profiler.py +29 -13
  22. transcrypto-2.0.0/src/transcrypto/transcrypto.py +471 -0
  23. transcrypto-2.0.0/src/transcrypto/utils/__init__.py +3 -0
  24. transcrypto-2.0.0/src/transcrypto/utils/base.py +72 -0
  25. transcrypto-2.0.0/src/transcrypto/utils/human.py +278 -0
  26. transcrypto-2.0.0/src/transcrypto/utils/logging.py +139 -0
  27. transcrypto-2.0.0/src/transcrypto/utils/saferandom.py +102 -0
  28. transcrypto-2.0.0/src/transcrypto/utils/stats.py +360 -0
  29. transcrypto-2.0.0/src/transcrypto/utils/timer.py +175 -0
  30. transcrypto-1.7.0/src/transcrypto/base.py +0 -1918
  31. transcrypto-1.7.0/src/transcrypto/transcrypto.py +0 -2407
  32. {transcrypto-1.7.0 → transcrypto-2.0.0}/LICENSE +0 -0
  33. {transcrypto-1.7.0/src/transcrypto → transcrypto-2.0.0/src/transcrypto/core}/constants.py +0 -0
  34. {transcrypto-1.7.0 → transcrypto-2.0.0}/src/transcrypto/py.typed +0 -0
@@ -1,15 +1,17 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: transcrypto
3
- Version: 1.7.0
3
+ Version: 2.0.0
4
4
  Summary: Basic crypto primitives, not intended for actual use, but as a companion to --Criptografia, Métodos e Algoritmos--
5
5
  License-Expression: Apache-2.0
6
6
  License-File: LICENSE
7
7
  Keywords: cryptography,validation,encryption,signing,random-generation,prime-numbers,aes-encryption,decryption,rsa-cryptography,elgamal-encryption,dsa-algorithm,modular-mathematics,rsa,dsa,elgamal,aes,python,poetry,typer,rich,cli
8
8
  Author: Daniel Balparda
9
9
  Author-email: balparda@github.com
10
- Requires-Python: >=3.13
10
+ Requires-Python: >=3.12
11
11
  Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3.12
12
13
  Classifier: Programming Language :: Python :: 3.13
14
+ Classifier: Programming Language :: Python :: 3.14
13
15
  Classifier: Operating System :: OS Independent
14
16
  Classifier: Topic :: Utilities
15
17
  Classifier: Topic :: Security :: Cryptography
@@ -17,7 +19,6 @@ Requires-Dist: cryptography (>=46.0)
17
19
  Requires-Dist: gmpy2 (>=2.2)
18
20
  Requires-Dist: platformdirs (>=4.5)
19
21
  Requires-Dist: rich (>=14.3)
20
- Requires-Dist: scipy (>=1.17)
21
22
  Requires-Dist: typer (>=0.21)
22
23
  Requires-Dist: zstandard (>=0.25)
23
24
  Project-URL: Changelog, https://github.com/balparda/transcrypto/blob/main/CHANGELOG.md
@@ -118,25 +119,24 @@ To use in your project just do:
118
119
  pip3 install transcrypto
119
120
  ```
120
121
 
121
- and then `from transcrypto import rsa` (or other parts of the library) for using it.
122
+ and then `from transcrypto.core import rsa` (or other parts of the library) for using it.
122
123
 
123
124
  Known dependencies:
124
125
 
125
126
  - [zstandard](https://pypi.org/project/zstandard/) ([docs](https://python-zstandard.readthedocs.org/))
126
127
  - [cryptography](https://pypi.org/project/cryptography/) ([docs](https://cryptography.io/en/latest/))
127
128
  - [gmpy2](https://pypi.org/project/gmpy2/) ([docs](https://gmpy2.readthedocs.io/en/latest/))
128
- - [scipy](https://pypi.org/project/scipy/) ([docs](https://docs.scipy.org/doc/scipy/))
129
129
 
130
130
  ### Base Library
131
131
 
132
132
  #### Humanized Sizes (IEC binary)
133
133
 
134
134
  ```py
135
- from transcrypto import base
135
+ from transcrypto.utils import human
136
136
 
137
- base.HumanizedBytes(512) # '512 B'
138
- base.HumanizedBytes(2048) # '2.000 KiB'
139
- base.HumanizedBytes(5 * 1024**3) # '5.000 GiB'
137
+ human.HumanizedBytes(512) # '512 B'
138
+ human.HumanizedBytes(2048) # '2.000 KiB'
139
+ human.HumanizedBytes(5 * 1024**3) # '5.000 GiB'
140
140
  ```
141
141
 
142
142
  Converts raw byte counts to binary-prefixed strings (`B`, `KiB`, `MiB`, `GiB`, `TiB`, `PiB`, `EiB`).
@@ -146,22 +146,24 @@ Converts raw byte counts to binary-prefixed strings (`B`, `KiB`, `MiB`, `GiB`, `
146
146
  - For values `≥1024`, returns 3 decimals.
147
147
 
148
148
  - standard: 1 KiB = 1024 B, 1 MiB = 1024 KiB, …
149
- - errors: negative inputs raise `InputError`
149
+ - errors: negative inputs raise `base.InputError`
150
150
 
151
151
  #### Humanized Decimal Quantities (SI)
152
152
 
153
153
  ```py
154
+ from transcrypto.utils import human
155
+
154
156
  # Base (unitless)
155
- base.HumanizedDecimal(950) # '950'
156
- base.HumanizedDecimal(1500) # '1.500 k'
157
+ human.HumanizedDecimal(950) # '950'
158
+ human.HumanizedDecimal(1500) # '1.500 k'
157
159
 
158
160
  # With a unit (trimmed and attached)
159
- base.HumanizedDecimal(1500, unit=' Hz ') # '1.500 kHz'
160
- base.HumanizedDecimal(0.123456, unit='V') # '123.456 mV'
161
+ human.HumanizedDecimal(1500, unit=' Hz ') # '1.500 kHz'
162
+ human.HumanizedDecimal(0.123456, unit='V') # '123.456 mV'
161
163
 
162
164
  # Large magnitudes
163
- base.HumanizedDecimal(3_200_000) # '3.200 M'
164
- base.HumanizedDecimal(7.2e12, unit='B/s') # '7.200 TB/s'
165
+ human.HumanizedDecimal(3_200_000) # '3.200 M'
166
+ human.HumanizedDecimal(7.2e12, unit='B/s') # '7.200 TB/s'
165
167
  ```
166
168
 
167
169
  Scales by powers of 1000 using SI prefixes to keep the displayed value in roughly `[1, 1000)` when possible.
@@ -171,17 +173,19 @@ Scales by powers of 1000 using SI prefixes to keep the displayed value in roughl
171
173
  - Formatting uses 3 decimals for non-integer/unscaled values and for scaled values.
172
174
 
173
175
  - unit handling: `unit` is stripped; `<1000` values include a space before the unit (`'950 Hz'`)
174
- - errors: non-finite inputs raise `InputError` (negative values are supported and keep a leading `-`)
176
+ - errors: non-finite inputs raise `base.InputError` (negative values are supported and keep a leading `-`)
175
177
 
176
178
  #### Humanized Durations
177
179
 
178
180
  ```py
179
- base.HumanizedSeconds(0) # '0.000 s'
180
- base.HumanizedSeconds(0.000004) # '4.000 µs'
181
- base.HumanizedSeconds(0.25) # '250.000 ms'
182
- base.HumanizedSeconds(42) # '42.000 s'
183
- base.HumanizedSeconds(3661) # '1.017 h'
184
- base.HumanizedSeconds(172800) # '2.000 d'
181
+ from transcrypto.utils import human
182
+
183
+ human.HumanizedSeconds(0) # '0.000 s'
184
+ human.HumanizedSeconds(0.000004) # '4.000 µs'
185
+ human.HumanizedSeconds(0.25) # '250.000 ms'
186
+ human.HumanizedSeconds(42) # '42.000 s'
187
+ human.HumanizedSeconds(3661) # '1.017 h'
188
+ human.HumanizedSeconds(172800) # '2.000 d'
185
189
  ```
186
190
 
187
191
  Chooses an appropriate time unit based on magnitude and formats with fixed precision:
@@ -193,21 +197,21 @@ Chooses an appropriate time unit based on magnitude and formats with fixed preci
193
197
  - `< 24 h`: hours with three decimals (`h`)
194
198
  - `≥ 24 h`: days with three decimals (`d`)
195
199
  - special case: `0 → '0.000 s'`
196
- - errors: negative or non-finite inputs raise `InputError`
200
+ - errors: negative or non-finite inputs raise `base.InputError`
197
201
 
198
202
  #### Execution Timing
199
203
 
200
204
  A flexible timing utility that works as a **context manager**, **decorator**, or **manual timer object**.
201
205
 
202
206
  ```py
203
- from transcrypto import base
207
+ from transcrypto.utils import timer
204
208
  import time
205
209
  ```
206
210
 
207
211
  ##### Context manager
208
212
 
209
213
  ```py
210
- with base.Timer('Block timing'):
214
+ with timer.Timer('Block timing'):
211
215
  time.sleep(1.2)
212
216
  # → logs: "Block timing: 1.200 s" (default via logging.info)
213
217
  ```
@@ -217,7 +221,7 @@ Starts timing on entry, stops on exit, and reports elapsed time automatically.
217
221
  ##### Decorator
218
222
 
219
223
  ```py
220
- @base.Timer('Function timing')
224
+ @timer.Timer('Function timing')
221
225
  def slow_function():
222
226
  time.sleep(0.8)
223
227
 
@@ -230,7 +234,7 @@ Wraps a function so that each call is automatically timed.
230
234
  ##### Manual use
231
235
 
232
236
  ```py
233
- tm = base.Timer('Inline timing', emit_print=True)
237
+ tm = timer.Timer('Inline timing', emit_print=True)
234
238
  tm.Start()
235
239
  time.sleep(0.1)
236
240
  tm.Stop() # prints: "Inline timing: 0.100 s"
@@ -256,7 +260,7 @@ Manual control over `Start()` and `Stop()` for precise measurement of custom int
256
260
  These helpers turn arbitrary Python objects into compressed and/or encrypted binary blobs, and back again — with detailed timing and size logging.
257
261
 
258
262
  ```py
259
- from transcrypto import base
263
+ from transcrypto.core import key
260
264
  ```
261
265
 
262
266
  ##### Serialize
@@ -265,17 +269,17 @@ from transcrypto import base
265
269
  data = {'x': 42, 'y': 'hello'}
266
270
 
267
271
  # Basic serialization
268
- blob = base.Serialize(data)
272
+ blob = key.Serialize(data)
269
273
 
270
274
  # With compression and encryption
271
- blob = base.Serialize(
275
+ blob = key.Serialize(
272
276
  data,
273
277
  compress=9, # compression level (-22..22, default=3)
274
- key=my_encryptor # must implement `base.Encryptor` (e.g., `aes.AESKey`)
278
+ encryption_key=my_encryptor # must implement `key.Encryptor` (e.g., `aes.AESKey`)
275
279
  )
276
280
 
277
281
  # Save directly to file
278
- base.Serialize(data, file_path='/tmp/data.blob')
282
+ key.Serialize(data, file_path='/tmp/data.blob')
279
283
  ```
280
284
 
281
285
  Serialization path:
@@ -303,19 +307,19 @@ Compression levels:
303
307
  | 11…15 | Much slower | Slight gains | Large archives, not for runtime use |
304
308
  | 16…22 | Very slow | Tiny gains | Archival-only, multi-GB datasets |
305
309
 
306
- Errors: invalid compression level is clamped to range; other input errors raise `InputError`.
310
+ Errors: invalid compression level is clamped to range; other input errors raise `base.InputError`.
307
311
 
308
312
  ##### DeSerialize
309
313
 
310
314
  ```py
311
315
  # From in-memory blob
312
- obj = base.DeSerialize(data=blob)
316
+ obj = key.DeSerialize(data=blob)
313
317
 
314
318
  # From file
315
- obj = base.DeSerialize(file_path='/tmp/data.blob')
319
+ obj = key.DeSerialize(file_path='/tmp/data.blob')
316
320
 
317
321
  # With decryption
318
- obj = base.DeSerialize(data=blob, key=my_decryptor)
322
+ obj = key.DeSerialize(data=blob, decryption_key=my_decryptor)
319
323
  ```
320
324
 
321
325
  Deserialization path:
@@ -331,82 +335,82 @@ data/file → (decrypt) → (decompress if Zstd) → unpickle
331
335
 
332
336
  - Exactly one of `data` or `file_path` must be provided.
333
337
  - `file_path` must exist; `data` must be at least 4 bytes.
334
- - Wrong key / authentication failure can raise `CryptoError`.
338
+ - Wrong key / authentication failure can raise `key.CryptoError`.
335
339
  - Corrupted compressed blobs typically raise `zstandard.ZstdError` during decompression.
336
340
 
337
341
  #### Cryptographically Secure Randomness
338
342
 
339
- These helpers live in `base` and wrap Python’s `secrets` with additional checks and guarantees for crypto use-cases.
343
+ These helpers live in `saferandom` and wrap Python’s `secrets` with additional checks and guarantees for crypto use-cases.
340
344
 
341
345
  ```py
342
- from transcrypto import base
346
+ from transcrypto.core import bid
343
347
  ```
344
348
 
345
349
  ##### Fixed-size random integers
346
350
 
347
351
  ```py
348
352
  # Generate a 256-bit integer (first bit always set)
349
- r = base.RandBits(256)
353
+ r = saferandom.RandBits(256)
350
354
  assert r.bit_length() == 256
351
355
  ```
352
356
 
353
357
  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.
354
358
 
355
- - errors: `n_bits < 8` → `InputError`
359
+ - errors: `n_bits < 8` → `base.InputError`
356
360
 
357
361
  ##### Uniform random integers in a range
358
362
 
359
363
  ```py
360
364
  # Uniform between [10, 20] inclusive
361
- n = base.RandInt(10, 20)
365
+ n = saferandom.RandInt(10, 20)
362
366
  assert 10 <= n <= 20
363
367
  ```
364
368
 
365
369
  Returns a crypto-secure integer uniformly distributed over the closed interval `[min_int, max_int]`.
366
370
 
367
371
  - constraints: `min_int ≥ 0` and `< max_int`
368
- - errors: invalid bounds → `InputError`
372
+ - errors: invalid bounds → `base.InputError`
369
373
 
370
374
  ##### In-place secure shuffle
371
375
 
372
376
  ```py
373
377
  deck = list(range(10))
374
- base.RandShuffle(deck)
378
+ saferandom.RandShuffle(deck)
375
379
  print(deck) # securely shuffled order
376
380
  ```
377
381
 
378
382
  Performs an in-place Fisher–Yates shuffle using `secrets.randbelow`. Suitable for sensitive data ordering.
379
383
 
380
384
  - constraints: sequence length ≥ 2
381
- - errors: shorter sequences → `InputError`
385
+ - errors: shorter sequences → `base.InputError`
382
386
 
383
387
  ##### Random byte strings
384
388
 
385
389
  ```py
386
390
  # 32 random bytes
387
- b = base.RandBytes(32)
391
+ b = saferandom.RandBytes(32)
388
392
  assert len(b) == 32
389
393
  ```
390
394
 
391
395
  Generates `n_bytes` of high-quality crypto-secure random data.
392
396
 
393
397
  - constraints: `n_bytes ≥ 1`
394
- - errors: smaller values → `InputError`
398
+ - errors: smaller values → `base.InputError`
395
399
 
396
400
  #### Computing the Greatest Common Divisor
397
401
 
398
402
  ```py
399
- >>> from transcrypto import base
400
- >>> base.GCD(462, 1071)
403
+ >>> from transcrypto.core import modmath
404
+ >>> modmath.GCD(462, 1071)
401
405
  21
402
- >>> base.GCD(0, 17)
406
+ >>> modmath.GCD(0, 17)
403
407
  17
404
408
  ```
405
409
 
406
410
  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:
407
411
 
408
412
  ```py
409
- >>> base.ExtendedGCD(462, 1071)
413
+ >>> modmath.ExtendedGCD(462, 1071)
410
414
  (21, 7, -3)
411
415
  >>> 462 * 7 + 1071 * (-3)
412
416
  21
@@ -421,7 +425,7 @@ Use-cases:
421
425
  #### Fast Modular Arithmetic
422
426
 
423
427
  ```py
424
- from transcrypto import modmath
428
+ from transcrypto.core import modmath
425
429
 
426
430
  m = 2**256 - 189 # a large prime modulus
427
431
 
@@ -442,7 +446,7 @@ exp = modmath.ModExp(3, 10**20, m) # ≈ log₂(y) time, handles huge exponent
442
446
  ##### Chinese Remainder Theorem (CRT) – Pair
443
447
 
444
448
  ```py
445
- from transcrypto import modmath
449
+ from transcrypto.core import modmath
446
450
 
447
451
  # Solve:
448
452
  # x ≡ 2 (mod 3)
@@ -465,7 +469,7 @@ x ≡ a2 (mod m2)
465
469
  - `m1 ≥ 2`, `m2 ≥ 2`, `m1 != m2`
466
470
  - `gcd(m1, m2) == 1` (co-prime)
467
471
  - **Errors**:
468
- - invalid modulus values → `InputError`
472
+ - invalid modulus values → `base.InputError`
469
473
  - non co-prime moduli → `ModularDivideError`
470
474
 
471
475
  This function is a 2-modulus variant; for multiple moduli, apply it iteratively or use a general CRT solver.
@@ -511,13 +515,13 @@ for k, m_p, perfect in modmath.MersennePrimesGenerator(0):
511
515
  Simple, fixed-output-size wrappers over Python’s `hashlib` for common digest operations, plus file hashing.
512
516
 
513
517
  ```py
514
- from transcrypto import base
518
+ from transcrypto.core import hashes
515
519
  ```
516
520
 
517
521
  ##### SHA-256 hashing
518
522
 
519
523
  ```py
520
- h = base.Hash256(b'hello world')
524
+ h = hashes.Hash256(b'hello world')
521
525
  assert len(h) == 32 # bytes
522
526
  print(h.hex()) # 64 hex chars
523
527
  ```
@@ -527,7 +531,7 @@ Computes the SHA-256 digest of a byte string, returning exactly 32 bytes (256 bi
527
531
  ##### SHA-512 hashing
528
532
 
529
533
  ```py
530
- h = base.Hash512(b'hello world')
534
+ h = hashes.Hash512(b'hello world')
531
535
  assert len(h) == 64 # bytes
532
536
  print(h.hex()) # 128 hex chars
533
537
  ```
@@ -538,11 +542,11 @@ Computes the SHA-512 digest of a byte string, returning exactly 64 bytes (512 bi
538
542
 
539
543
  ```py
540
544
  # Default SHA-256
541
- fh = base.FileHash('/path/to/file')
545
+ fh = hashes.FileHash('/path/to/file')
542
546
  print(fh.hex())
543
547
 
544
548
  # SHA-512
545
- fh2 = base.FileHash('/path/to/file', digest='sha512')
549
+ fh2 = hashes.FileHash('/path/to/file', digest='sha512')
546
550
  ```
547
551
 
548
552
  Hashes a file from disk in streaming mode. By default uses SHA-256; `digest='sha512'` switches to SHA-512.
@@ -550,17 +554,19 @@ Hashes a file from disk in streaming mode. By default uses SHA-256; `digest='sha
550
554
  - constraints:
551
555
  - `digest` must be `'sha256'` or `'sha512'`
552
556
  - `full_path` must exist
553
- - errors: invalid digest or missing file → `InputError`
557
+ - errors: invalid digest or missing file → `base.InputError`
554
558
 
555
559
  #### Symmetric Encryption Interface
556
560
 
557
- `base.Encryptor` and `base.Decryptor` are runtime-checkable protocols that define the **byte-in / byte-out** contract for symmetric ciphers.
561
+ `key.Encryptor` and `key.Decryptor` are runtime-checkable protocols that define the **byte-in / byte-out** contract for symmetric ciphers.
558
562
 
559
563
  - **Metadata handling** — if the algorithm uses a `nonce` or `tag`, the implementation must handle it internally (e.g., append it to ciphertext).
560
- - **AEAD modes** — if supported, `associated_data` must be authenticated; otherwise, a non-`None` value should raise `InputError`.
564
+ - **AEAD modes** — if supported, `associated_data` must be authenticated; otherwise, a non-`None` value should raise `base.InputError`.
561
565
 
562
566
  ```py
563
- class MyAES(base.Encryptor, base.Decryptor):
567
+ from transcrypto.core import key
568
+
569
+ class MyAES(key.Encryptor, key.Decryptor):
564
570
  def Encrypt(self, plaintext: bytes, *, associated_data=None) -> bytes:
565
571
  ...
566
572
  def Decrypt(self, ciphertext: bytes, *, associated_data=None) -> bytes:
@@ -574,14 +580,14 @@ Cryptographic objects all derive from the `CryptoKey` class and will all have so
574
580
  - 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…`).
575
581
  - It will have a `_DebugDump()` method that **does print secrets** and can be used for **debugging only**.
576
582
  - Can be easily serialized to `bytes` by the `blob` property and to base-64 encoded `str` by the `encoded` property.
577
- - Can be serialized encrypted to `bytes` by the `Blob(key=[base.Encryptor])` method and to encrypted base-64 encoded `str` by the `Encoded(key=[base.Encryptor])` method.
578
- - Can be instantiated back as an object from `str` or `bytes` using the `Load(data, key=[base.Decryptor] | None)` method. The `Load()` will decide how to build the object and will work universally with all the serialization options discussed above.
583
+ - Can be serialized encrypted to `bytes` by the `Blob(encryption_key=[key.Encryptor])` method and to encrypted base-64 encoded `str` by the `Encoded(encryption_key=[key.Encryptor])` method.
584
+ - Can be instantiated back as an object from `str` or `bytes` using the `Load(data, decryption_key=[key.Decryptor] | None)` method. The `Load()` will decide how to build the object and will work universally with all the serialization options discussed above.
579
585
 
580
586
  Example:
581
587
 
582
588
  <!-- cspell:disable -->
583
589
  ```py
584
- from transcrypto import base, rsa, aes
590
+ from transcrypto.core import aes, rsa
585
591
 
586
592
  priv = rsa.RSAPrivateKey.New(512) # small key, but good for this example
587
593
  print(str(priv)) # safe, no secrets
@@ -596,12 +602,12 @@ print(priv.blob)
596
602
  print(priv.encoded)
597
603
  # ▶ KLUv_WBwAIELAIAElWUBAAAAAAAAjA90cmFuc2NyeXB0by5yc2GUjA1SU0FQcml2YXRlS2V5lJOUKYGUXZQoikHf1EvsmZedAZve7TrLmobLAwuRIr_77TLG6G_0fsLGThERVJu075be8PLjUQYnLXcacZFQ5Fb1Iy1WtiE985euAEoBAAEAiiFR9ngiXMzkf41o5CRBY3h0D4DJVisDDhLmAWsiaHggzQCKIS_cmQ6MKXCtROtC7c_Mrsi9A-9NM8DksaHaRwvy6uTZAIpB4TVbsLxc41TEc19wIzpxbi9y5dW5gdfTkRQSSiz0ijmb8Xk3pyBfKAv8JbHp8Yv48gNZUfX67qq0J7yhJqeUoACKIbFb2kTNRzSqm3JRtjc2BPS-FnLFdadlFcV4-6IW7eqLAIogFZfzDN39gZLR9uTz4KHSTaqxWrJgP8-YYssjss6FlFKKIIItgCDv7ompNpY8gBs5bibN8XTsr-JOYSntDVT5Fe5vZWIu
598
604
 
599
- key = aes.AESKey(key256=b'x' * 32)
600
- print(key)
605
+ aes_key = aes.AESKey(key256=b'x' * 32)
606
+ print(aes_key)
601
607
  # ▶ AESKey(key256=86a86df7…)
602
608
 
603
- encrypted = priv.Blob(key=key)
604
- print(priv == rsa.RSAPrivateKey.Load(encrypted, key=key))
609
+ encrypted = priv.Blob(encryption_key=aes_key)
610
+ print(priv == rsa.RSAPrivateKey.Load(encrypted, decryption_key=aes_key))
605
611
  # ▶ True
606
612
  ```
607
613
  <!-- cspell:enable -->
@@ -614,14 +620,14 @@ Also includes a high-iteration PBKDF2-based key derivation from static passwords
614
620
  ##### Key creation
615
621
 
616
622
  ```py
617
- from transcrypto import aes
623
+ from transcrypto.core import aes
618
624
 
619
625
  # From raw bytes (must be exactly 32 bytes)
620
- key = aes.AESKey(key256=b'\x00' * 32)
626
+ aes_key = aes.AESKey(key256=b'\x00' * 32)
621
627
 
622
628
  # From a static password (slow, high-iteration PBKDF2-SHA256)
623
- key = aes.AESKey.FromStaticPassword('correct horse battery staple')
624
- print(key.encoded) # URL-safe Base64
629
+ aes_key = aes.AESKey.FromStaticPassword('correct horse battery staple')
630
+ print(aes_key.encoded) # URL-safe Base64
625
631
  ```
626
632
 
627
633
  - **Length**: `key256` must be exactly 32 bytes
@@ -636,10 +642,10 @@ data = b'secret message'
636
642
  aad = b'metadata'
637
643
 
638
644
  # Encrypt (returns IV + ciphertext + tag)
639
- ct = key.Encrypt(data, associated_data=aad)
645
+ ct = aes_key.Encrypt(data, associated_data=aad)
640
646
 
641
647
  # Decrypt
642
- pt = key.Decrypt(ct, associated_data=aad)
648
+ pt = aes_key.Decrypt(ct, associated_data=aad)
643
649
  assert pt == data
644
650
  ```
645
651
 
@@ -648,13 +654,13 @@ assert pt == data
648
654
  - Authenticated tag (128-bit) ensures integrity
649
655
  - Optional `associated_data` is authenticated but not encrypted
650
656
  - **Errors**:
651
- - Tag mismatch or wrong key → `CryptoError`
657
+ - Tag mismatch or wrong key → `key.CryptoError`
652
658
 
653
659
  ##### AES-256 + ECB (unsafe, fixed block only)
654
660
 
655
661
  ```py
656
662
  # ECB mode is for 16-byte block encoding ONLY
657
- ecb = key.ECBEncoder()
663
+ ecb = aes_key.ECBEncoder()
658
664
 
659
665
  block = b'16-byte string!!'
660
666
  ct_block = ecb.Encrypt(block)
@@ -679,7 +685,7 @@ Key points:
679
685
 
680
686
  - **GCM mode** is secure for general use; ECB mode is for special low-level operations
681
687
  - **Static password derivation** is intentionally slow to resist brute force
682
- - All sizes and parameters are validated with `InputError` on misuse
688
+ - All sizes and parameters are validated with `base.InputError` on misuse
683
689
 
684
690
  #### RSA (Rivest-Shamir-Adleman) Public Cryptography
685
691
 
@@ -690,7 +696,7 @@ This implementation is raw RSA, no OAEP or PSS! It works on the actual integers.
690
696
  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.
691
697
 
692
698
  ```py
693
- from transcrypto import rsa
699
+ from transcrypto.core import rsa
694
700
 
695
701
  # Generate a key pair
696
702
  priv = rsa.RSAPrivateKey.New(2048) # 2048-bit modulus
@@ -735,7 +741,7 @@ This is **raw El-Gamal** over a prime field — no padding, no hashing — and i
735
741
  For real-world deployments, use a high-level library with authenticated encryption and proper encoding.
736
742
 
737
743
  ```py
738
- from transcrypto import elgamal
744
+ from transcrypto.core import elgamal
739
745
 
740
746
  # Shared parameters (prime modulus, group base) for a group
741
747
  shared = elgamal.ElGamalSharedPublicKey.NewShared(256)
@@ -771,13 +777,13 @@ Key points:
771
777
 
772
778
  - **Security parameters**:
773
779
  - Recommended `prime_modulus` bit length ≥ 2048 for real security
774
- - Random values from `base.RandBits`
780
+ - Random values from `saferandom.RandBits`
775
781
  - **Ephemeral keys**:
776
782
  - Fresh per encryption/signature
777
783
  - Must satisfy `gcd(k, p-1) == 1`
778
784
  - **Errors**:
779
- - Bad ranges → `InputError`
780
- - Invalid math relationships → `CryptoError`
785
+ - Bad ranges → `base.InputError`
786
+ - Invalid math relationships → `key.CryptoError`
781
787
  - **Group sharing**:
782
788
  - Multiple parties can share `(p, g)` but have different `(individual_base, decrypt_exp)`
783
789
 
@@ -788,7 +794,7 @@ Key points:
788
794
  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`.
789
795
 
790
796
  ```py
791
- from transcrypto import dsa
797
+ from transcrypto.core import dsa
792
798
 
793
799
  # Shared parameters (p, q, g) - Safe Sign/Verify requires q > 512 bits
794
800
  shared = dsa.DSASharedPublicKey.NewShared(2048, 520)
@@ -815,8 +821,8 @@ assert pub.RawVerify(msg, sig)
815
821
  - `1 ≤ message < q`
816
822
  - signatures: `(s1, s2)` with `2 ≤ s1, s2 < q`
817
823
  - errors:
818
- - invalid ranges → `InputError`
819
- - inconsistent parameters → `CryptoError`
824
+ - invalid ranges → `base.InputError`
825
+ - inconsistent parameters → `key.CryptoError`
820
826
 
821
827
  ##### Security notes
822
828
 
@@ -832,23 +838,23 @@ assert (p - 1) % q == 0
832
838
  ```
833
839
 
834
840
  Used internally by `DSASharedPublicKey.NewShared()`.
835
- Search breadth and retry caps are bounded; repeated failures raise `CryptoError`.
841
+ Search breadth and retry caps are bounded; repeated failures raise `key.CryptoError`.
836
842
 
837
843
  #### Public Bidding
838
844
 
839
845
  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.
840
846
 
841
847
  ```py
842
- from transcrypto import base
848
+ from transcrypto.core import bid
843
849
 
844
850
  # Generate the private and public bids
845
- bid_priv = base.PrivateBid512.New(secret) # this one you keep private
846
- bid_pub = base.PublicBid512.Copy(bid_priv) # this one you publish
851
+ bid_priv = bid.PrivateBid512.New(secret) # this one you keep private
852
+ bid_pub = bid.PublicBid512.Copy(bid_priv) # this one you publish
847
853
 
848
854
  # Checking that a bid is genuine requires the public bid and knowing the nonce and the secret:
849
855
  print(bid_pub.VerifyBid(private_key, secret_bid)) # these come from a divulged private bid
850
856
  # of course, you want to also make sure the provided private data matches your version of it, e.g.:
851
- bid_pub_expected = base.PublicBid512.Copy(bid_priv)
857
+ bid_pub_expected = bid.PublicBid512.Copy(bid_priv)
852
858
  print(bid_pub == bid_pub_expected)
853
859
  ```
854
860
 
@@ -859,7 +865,7 @@ print(bid_pub == bid_pub_expected)
859
865
  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; `MakeDataShares()` requires > 256 bits.
860
866
 
861
867
  ```py
862
- from transcrypto import sss
868
+ from transcrypto.core import sss
863
869
 
864
870
  # Generate parameters: at least 3 of 5 shares needed,
865
871
  # coefficients & modulus are 264-bit primes (> 256 bits required for MakeDataShares)
@@ -905,7 +911,7 @@ recovered = pub.RawRecoverSecret(subset)
905
911
  assert recovered == secret
906
912
  ```
907
913
 
908
- If you supply fewer than minimum shares you get a `CryptoError`, unless you explicitly override:
914
+ If you supply fewer than minimum shares you get a `key.CryptoError`, unless you explicitly override:
909
915
 
910
916
  ```py
911
917
  try:
@@ -932,7 +938,7 @@ print(priv.RawVerifyShare(secret, tampered)) # ▶ False
932
938
 
933
939
  ### Setup
934
940
 
935
- 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*):
941
+ If you want to develop for this project, first install python 3.12 and [Poetry](https://python-poetry.org/docs/cli/), but to get the versions you will need, we suggest you do it like this (*Linux*):
936
942
 
937
943
  ```sh
938
944
  sudo apt-get update
@@ -941,10 +947,10 @@ sudo apt-get install git python3 python3-pip pipx python3-dev python3-venv build
941
947
 
942
948
  sudo add-apt-repository ppa:deadsnakes/ppa # install arbitrary python version
943
949
  sudo apt-get update
944
- sudo apt-get install python3.13
950
+ sudo apt-get install python3.12
945
951
 
946
952
  sudo apt-get remove python3-poetry
947
- python3.13 -m pipx ensurepath
953
+ python3.12 -m pipx ensurepath
948
954
  # re-open terminal
949
955
  pipx install poetry
950
956
  poetry --version # should be >=2.1
@@ -960,11 +966,11 @@ brew update
960
966
  brew upgrade
961
967
  brew cleanup -s
962
968
 
963
- brew install git python@3.13 # install arbitrary python version
969
+ brew install git python@3.12 # install arbitrary python version
964
970
 
965
971
  brew uninstall poetry
966
- python3.13 -m pip install --user pipx
967
- python3.13 -m pipx ensurepath
972
+ python3.12 -m pip install --user pipx
973
+ python3.12 -m pipx ensurepath
968
974
  # re-open terminal
969
975
  pipx install poetry
970
976
  poetry --version # should be >=2.1
@@ -979,7 +985,7 @@ Now install the project:
979
985
  git clone https://github.com/balparda/transcrypto.git transcrypto
980
986
  cd transcrypto
981
987
 
982
- poetry env use python3.13 # creates the venv
988
+ poetry env use python3.12 # creates the venv
983
989
  poetry sync # sync env to project's poetry.lock file
984
990
  poetry env info # no-op: just to check
985
991
 
@@ -1017,7 +1023,7 @@ If you manually added a dependency to `pyproject.toml` you should ***very carefu
1017
1023
 
1018
1024
  ```sh
1019
1025
  rm -rf .venv .poetry poetry.lock
1020
- poetry env use python3.13
1026
+ poetry env use python3.12
1021
1027
  poetry install
1022
1028
  ```
1023
1029
 
@@ -1026,7 +1032,7 @@ Remember to check your diffs before submitting (especially `poetry.lock`) to avo
1026
1032
  When dependencies change, always regenerate `requirements.txt` by running:
1027
1033
 
1028
1034
  ```sh
1029
- poetry export --format requirements.txt --without-hashes --output requirements.txt
1035
+ make req # or: poetry export --format requirements.txt --without-hashes --output requirements.txt
1030
1036
  ```
1031
1037
 
1032
1038
  ### Creating a New Version
@@ -1051,11 +1057,7 @@ poetry build
1051
1057
  poetry publish
1052
1058
  ```
1053
1059
 
1054
- If you changed the CLI interface at all, in any tool, run:
1055
-
1056
- ```sh
1057
- make docs
1058
- ```
1060
+ If you changed the CLI interface at all, in any tool, run `make docs` or even better `make ci`.
1059
1061
 
1060
1062
  You can find the 10 top slowest tests by running:
1061
1063