transcrypto 1.6.0__tar.gz → 1.7.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 (27) hide show
  1. {transcrypto-1.6.0/src/transcrypto.egg-info → transcrypto-1.7.0}/PKG-INFO +78 -58
  2. {transcrypto-1.6.0 → transcrypto-1.7.0}/README.md +59 -51
  3. transcrypto-1.7.0/pyproject.toml +313 -0
  4. transcrypto-1.7.0/src/transcrypto/__init__.py +7 -0
  5. {transcrypto-1.6.0 → transcrypto-1.7.0}/src/transcrypto/aes.py +150 -44
  6. {transcrypto-1.6.0 → transcrypto-1.7.0}/src/transcrypto/base.py +587 -442
  7. transcrypto-1.7.0/src/transcrypto/constants.py +20085 -0
  8. {transcrypto-1.6.0 → transcrypto-1.7.0}/src/transcrypto/dsa.py +132 -99
  9. {transcrypto-1.6.0 → transcrypto-1.7.0}/src/transcrypto/elgamal.py +116 -84
  10. {transcrypto-1.6.0 → transcrypto-1.7.0}/src/transcrypto/modmath.py +88 -78
  11. transcrypto-1.7.0/src/transcrypto/profiler.py +239 -0
  12. {transcrypto-1.6.0 → transcrypto-1.7.0}/src/transcrypto/rsa.py +126 -90
  13. {transcrypto-1.6.0 → transcrypto-1.7.0}/src/transcrypto/sss.py +122 -70
  14. transcrypto-1.7.0/src/transcrypto/transcrypto.py +2407 -0
  15. transcrypto-1.6.0/PKG-INFO +0 -1071
  16. transcrypto-1.6.0/pyproject.toml +0 -79
  17. transcrypto-1.6.0/setup.cfg +0 -4
  18. transcrypto-1.6.0/src/transcrypto/__init__.py +0 -0
  19. transcrypto-1.6.0/src/transcrypto/constants.py +0 -1921
  20. transcrypto-1.6.0/src/transcrypto/profiler.py +0 -189
  21. transcrypto-1.6.0/src/transcrypto/safetrans.py +0 -1228
  22. transcrypto-1.6.0/src/transcrypto/transcrypto.py +0 -1465
  23. transcrypto-1.6.0/src/transcrypto.egg-info/SOURCES.txt +0 -20
  24. transcrypto-1.6.0/src/transcrypto.egg-info/dependency_links.txt +0 -1
  25. transcrypto-1.6.0/src/transcrypto.egg-info/top_level.txt +0 -1
  26. {transcrypto-1.6.0 → transcrypto-1.7.0}/LICENSE +0 -0
  27. {transcrypto-1.6.0 → transcrypto-1.7.0}/src/transcrypto/py.typed +0 -0
@@ -1,20 +1,31 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: transcrypto
3
- Version: 1.6.0
3
+ Version: 1.7.0
4
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
5
  License-Expression: Apache-2.0
7
- Project-URL: Homepage, https://github.com/balparda/transcrypto
8
- Project-URL: PyPI, https://pypi.org/project/transcrypto/
6
+ License-File: LICENSE
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
+ Author: Daniel Balparda
9
+ Author-email: balparda@github.com
10
+ Requires-Python: >=3.13
9
11
  Classifier: Programming Language :: Python :: 3
10
12
  Classifier: Programming Language :: Python :: 3.13
11
13
  Classifier: Operating System :: OS Independent
12
14
  Classifier: Topic :: Utilities
13
15
  Classifier: Topic :: Security :: Cryptography
14
- Requires-Python: <4.0,>=3.13.5
16
+ Requires-Dist: cryptography (>=46.0)
17
+ Requires-Dist: gmpy2 (>=2.2)
18
+ Requires-Dist: platformdirs (>=4.5)
19
+ Requires-Dist: rich (>=14.3)
20
+ Requires-Dist: scipy (>=1.17)
21
+ Requires-Dist: typer (>=0.21)
22
+ Requires-Dist: zstandard (>=0.25)
23
+ Project-URL: Changelog, https://github.com/balparda/transcrypto/blob/main/CHANGELOG.md
24
+ Project-URL: Homepage, https://github.com/balparda/transcrypto
25
+ Project-URL: Issues, https://github.com/balparda/transcrypto/issues
26
+ Project-URL: PyPI, https://pypi.org/project/transcrypto/
27
+ Project-URL: Repository, https://github.com/balparda/transcrypto
15
28
  Description-Content-Type: text/markdown
16
- License-File: LICENSE
17
- Dynamic: license-file
18
29
 
19
30
  # TransCrypto
20
31
 
@@ -76,7 +87,7 @@ Started in July/2025, by Daniel Balparda. Since version 1.0.2 it is PyPI package
76
87
 
77
88
  ## License
78
89
 
79
- Copyright 2025 Daniel Balparda <balparda@github.com>
90
+ Copyright 2026 Daniel Balparda <balparda@github.com>
80
91
 
81
92
  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
93
 
@@ -94,7 +105,6 @@ All that being said, extreme care was taken that this is a good library with a s
94
105
 
95
106
  ## CLI Apps
96
107
 
97
- - [SafeTrans/`safetrans`](safetrans.md): Safe cryptographic operations;
98
108
  - [TransCrypto/`transcrypto`](transcrypto.md): Does all the operations but allows you to shoot yourself in the foot;
99
109
  - [Profiler/`profiler`](profiler.md): Measure transcrypto performance.
100
110
 
@@ -122,14 +132,18 @@ Known dependencies:
122
132
  #### Humanized Sizes (IEC binary)
123
133
 
124
134
  ```py
125
- from transcrypto import utils
135
+ from transcrypto import base
126
136
 
127
- utils.HumanizedBytes(512) # '512 B'
128
- utils.HumanizedBytes(2048) # '2.00 KiB'
129
- utils.HumanizedBytes(5 * 1024**3) # '5.00 GiB'
137
+ base.HumanizedBytes(512) # '512 B'
138
+ base.HumanizedBytes(2048) # '2.000 KiB'
139
+ base.HumanizedBytes(5 * 1024**3) # '5.000 GiB'
130
140
  ```
131
141
 
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.
142
+ Converts raw byte counts to binary-prefixed strings (`B`, `KiB`, `MiB`, `GiB`, `TiB`, `PiB`, `EiB`).
143
+
144
+ - For integer inputs `<1024`, returns an integer count with `B` (e.g. `'512 B'`).
145
+ - For float inputs `<1024`, returns 3 decimals with `B` (e.g. `'51.200 B'`).
146
+ - For values `≥1024`, returns 3 decimals.
133
147
 
134
148
  - standard: 1 KiB = 1024 B, 1 MiB = 1024 KiB, …
135
149
  - errors: negative inputs raise `InputError`
@@ -138,43 +152,47 @@ Converts raw byte counts to binary-prefixed strings (`B`, `KiB`, `MiB`, `GiB`, `
138
152
 
139
153
  ```py
140
154
  # Base (unitless)
141
- utils.HumanizedDecimal(950) # '950'
142
- utils.HumanizedDecimal(1500) # '1.50 k'
155
+ base.HumanizedDecimal(950) # '950'
156
+ base.HumanizedDecimal(1500) # '1.500 k'
143
157
 
144
158
  # With a unit (trimmed and attached)
145
- utils.HumanizedDecimal(1500, ' Hz ') # '1.50 kHz'
146
- utils.HumanizedDecimal(0.123456, 'V') # '0.1235 V'
159
+ base.HumanizedDecimal(1500, unit=' Hz ') # '1.500 kHz'
160
+ base.HumanizedDecimal(0.123456, unit='V') # '123.456 mV'
147
161
 
148
162
  # Large magnitudes
149
- utils.HumanizedDecimal(3_200_000) # '3.20 M'
150
- utils.HumanizedDecimal(7.2e12, 'B/s') # '7.20 TB/s'
163
+ base.HumanizedDecimal(3_200_000) # '3.200 M'
164
+ base.HumanizedDecimal(7.2e12, unit='B/s') # '7.200 TB/s'
151
165
  ```
152
166
 
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`).
167
+ Scales by powers of 1000 using SI prefixes to keep the displayed value in roughly `[1, 1000)` when possible.
168
+
169
+ - Supported large prefixes: `k`, `M`, `G`, `T`, `P`, `E`
170
+ - Supported small prefixes: `m`, `µ`, `n`, `p`, `f`, `a`
171
+ - Formatting uses 3 decimals for non-integer/unscaled values and for scaled values.
154
172
 
155
173
  - unit handling: `unit` is stripped; `<1000` values include a space before the unit (`'950 Hz'`)
156
- - errors: negative or non-finite inputs raise `InputError`
174
+ - errors: non-finite inputs raise `InputError` (negative values are supported and keep a leading `-`)
157
175
 
158
176
  #### Humanized Durations
159
177
 
160
178
  ```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'
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'
167
185
  ```
168
186
 
169
187
  Chooses an appropriate time unit based on magnitude and formats with fixed precision:
170
188
 
171
189
  - `< 1 ms`: microseconds with three decimals (`µs`)
172
190
  - `< 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'`
191
+ - `< 60 s`: seconds with three decimals (`s`)
192
+ - `< 60 min`: minutes with three decimals (`min`)
193
+ - `< 24 h`: hours with three decimals (`h`)
194
+ - `≥ 24 h`: days with three decimals (`d`)
195
+ - special case: `0 → '0.000 s'`
178
196
  - errors: negative or non-finite inputs raise `InputError`
179
197
 
180
198
  #### Execution Timing
@@ -191,7 +209,7 @@ import time
191
209
  ```py
192
210
  with base.Timer('Block timing'):
193
211
  time.sleep(1.2)
194
- # → logs: "Block timing: 1.20 s" (default via logging.info)
212
+ # → logs: "Block timing: 1.200 s" (default via logging.info)
195
213
  ```
196
214
 
197
215
  Starts timing on entry, stops on exit, and reports elapsed time automatically.
@@ -204,7 +222,7 @@ def slow_function():
204
222
  time.sleep(0.8)
205
223
 
206
224
  slow_function()
207
- # → logs: "Function timing: 0.80 s"
225
+ # → logs: "Function timing: 0.800 s"
208
226
  ```
209
227
 
210
228
  Wraps a function so that each call is automatically timed.
@@ -215,17 +233,17 @@ Wraps a function so that each call is automatically timed.
215
233
  tm = base.Timer('Inline timing', emit_print=True)
216
234
  tm.Start()
217
235
  time.sleep(0.1)
218
- tm.Stop() # prints: "Inline timing: 0.10 s"
236
+ tm.Stop() # prints: "Inline timing: 0.100 s"
219
237
  ```
220
238
 
221
239
  Manual control over `Start()` and `Stop()` for precise measurement of custom intervals.
222
240
 
223
241
  ##### Key points
224
242
 
225
- - **Label**: required, shown in output; empty labels raise `InputError`
243
+ - **Label**: optional; if empty, output omits the label prefix
226
244
  - **Output**:
227
245
  - `emit_log=True` → `logging.info()` (default)
228
- - `emit_print=True` → direct `print()`
246
+ - `emit_print=True` → prints via `rich.console.Console().print()`
229
247
  - Both can be enabled
230
248
  - **Format**: elapsed time is shown using `HumanizedSeconds()`
231
249
  - **Safety**:
@@ -253,7 +271,7 @@ blob = base.Serialize(data)
253
271
  blob = base.Serialize(
254
272
  data,
255
273
  compress=9, # compression level (-22..22, default=3)
256
- key=my_symmetric_key # must implement SymmetricCrypto
274
+ key=my_encryptor # must implement `base.Encryptor` (e.g., `aes.AESKey`)
257
275
  )
258
276
 
259
277
  # Save directly to file
@@ -297,7 +315,7 @@ obj = base.DeSerialize(data=blob)
297
315
  obj = base.DeSerialize(file_path='/tmp/data.blob')
298
316
 
299
317
  # With decryption
300
- obj = base.DeSerialize(data=blob, key=my_symmetric_key)
318
+ obj = base.DeSerialize(data=blob, key=my_decryptor)
301
319
  ```
302
320
 
303
321
  Deserialization path:
@@ -313,7 +331,8 @@ data/file → (decrypt) → (decompress if Zstd) → unpickle
313
331
 
314
332
  - Exactly one of `data` or `file_path` must be provided.
315
333
  - `file_path` must exist; `data` must be at least 4 bytes.
316
- - Wrong key or corrupted data can raise `CryptoError`.
334
+ - Wrong key / authentication failure can raise `CryptoError`.
335
+ - Corrupted compressed blobs typically raise `zstandard.ZstdError` during decompression.
317
336
 
318
337
  #### Cryptographically Secure Randomness
319
338
 
@@ -388,8 +407,8 @@ The function is `O(log(min(a, b)))` and handles arbitrarily large integers. To f
388
407
 
389
408
  ```py
390
409
  >>> base.ExtendedGCD(462, 1071)
391
- (21, -2, 1)
392
- >>> 462 * -2 + 1071 * 1
410
+ (21, 7, -3)
411
+ >>> 462 * 7 + 1071 * (-3)
393
412
  21
394
413
  ```
395
414
 
@@ -535,13 +554,13 @@ Hashes a file from disk in streaming mode. By default uses SHA-256; `digest='sha
535
554
 
536
555
  #### Symmetric Encryption Interface
537
556
 
538
- `SymmetricCrypto` is an abstract base class that defines the **byte-in / byte-out** contract for symmetric ciphers.
557
+ `base.Encryptor` and `base.Decryptor` are runtime-checkable protocols that define the **byte-in / byte-out** contract for symmetric ciphers.
539
558
 
540
559
  - **Metadata handling** — if the algorithm uses a `nonce` or `tag`, the implementation must handle it internally (e.g., append it to ciphertext).
541
560
  - **AEAD modes** — if supported, `associated_data` must be authenticated; otherwise, a non-`None` value should raise `InputError`.
542
561
 
543
562
  ```py
544
- class MyAES(base.SymmetricCrypto):
563
+ class MyAES(base.Encryptor, base.Decryptor):
545
564
  def Encrypt(self, plaintext: bytes, *, associated_data=None) -> bytes:
546
565
  ...
547
566
  def Decrypt(self, ciphertext: bytes, *, associated_data=None) -> bytes:
@@ -555,8 +574,8 @@ Cryptographic objects all derive from the `CryptoKey` class and will all have so
555
574
  - 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
575
  - It will have a `_DebugDump()` method that **does print secrets** and can be used for **debugging only**.
557
576
  - 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.
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.
560
579
 
561
580
  Example:
562
581
 
@@ -719,7 +738,7 @@ For real-world deployments, use a high-level library with authenticated encrypti
719
738
  from transcrypto import elgamal
720
739
 
721
740
  # Shared parameters (prime modulus, group base) for a group
722
- shared = elgamal.ElGamalSharedPublicKey.New(256)
741
+ shared = elgamal.ElGamalSharedPublicKey.NewShared(256)
723
742
  print(shared.prime_modulus)
724
743
  print(shared.group_base)
725
744
 
@@ -771,8 +790,8 @@ This is **raw DSA** over a prime field — **no hashing or padding**. You sign/v
771
790
  ```py
772
791
  from transcrypto import dsa
773
792
 
774
- # Shared parameters (p, q, g)
775
- shared = dsa.DSASharedPublicKey.New(p_bits=1024, q_bits=160)
793
+ # Shared parameters (p, q, g) - Safe Sign/Verify requires q > 512 bits
794
+ shared = dsa.DSASharedPublicKey.NewShared(2048, 520)
776
795
  print(shared.prime_modulus) # p
777
796
  print(shared.prime_seed) # q (q | p-1)
778
797
  print(shared.group_base) # g
@@ -808,11 +827,11 @@ assert pub.RawVerify(msg, sig)
808
827
 
809
828
  ```py
810
829
  # 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)
830
+ p, q, m = dsa.NBitRandomDSAPrimes(1024, 160)
812
831
  assert (p - 1) % q == 0
813
832
  ```
814
833
 
815
- Used internally by `DSASharedPublicKey.New()`.
834
+ Used internally by `DSASharedPublicKey.NewShared()`.
816
835
  Search breadth and retry caps are bounded; repeated failures raise `CryptoError`.
817
836
 
818
837
  #### Public Bidding
@@ -837,14 +856,14 @@ print(bid_pub == bid_pub_expected)
837
856
 
838
857
  <https://en.wikipedia.org/wiki/Shamir's_secret_sharing>
839
858
 
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.
859
+ 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.
841
860
 
842
861
  ```py
843
862
  from transcrypto import sss
844
863
 
845
864
  # 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)
865
+ # coefficients & modulus are 264-bit primes (> 256 bits required for MakeDataShares)
866
+ priv = sss.ShamirSharedSecretPrivate.New(3, 264)
848
867
  pub = sss.ShamirSharedSecretPublic.Copy(priv) # what you publish
849
868
 
850
869
  print(f'threshold : {pub.minimum}')
@@ -874,8 +893,8 @@ A single share object looks like `sss.ShamirSharePrivate(minimum=3, modulus=...,
874
893
  # Safe Re-constructing the secret
875
894
  secret = b'xyz'
876
895
  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
896
+ subset = five_shares[:3] # any 3 distinct shares
897
+ recovered = subset[0].RecoverData(subset[1:]) # each share has the encrypted data, pass other shares
879
898
  assert recovered == secret
880
899
 
881
900
  # Raw Re-constructing the secret
@@ -1035,7 +1054,7 @@ poetry publish
1035
1054
  If you changed the CLI interface at all, in any tool, run:
1036
1055
 
1037
1056
  ```sh
1038
- ./tools/generate_docs.sh
1057
+ make docs
1039
1058
  ```
1040
1059
 
1041
1060
  You can find the 10 top slowest tests by running:
@@ -1069,3 +1088,4 @@ $ deactivate
1069
1088
  ```
1070
1089
 
1071
1090
  Hint: 85%+ is inside `MillerRabinIsPrime()`/`gmpy2.powmod()`...
1091
+
@@ -58,7 +58,7 @@ Started in July/2025, by Daniel Balparda. Since version 1.0.2 it is PyPI package
58
58
 
59
59
  ## License
60
60
 
61
- Copyright 2025 Daniel Balparda <balparda@github.com>
61
+ Copyright 2026 Daniel Balparda <balparda@github.com>
62
62
 
63
63
  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).
64
64
 
@@ -76,7 +76,6 @@ All that being said, extreme care was taken that this is a good library with a s
76
76
 
77
77
  ## CLI Apps
78
78
 
79
- - [SafeTrans/`safetrans`](safetrans.md): Safe cryptographic operations;
80
79
  - [TransCrypto/`transcrypto`](transcrypto.md): Does all the operations but allows you to shoot yourself in the foot;
81
80
  - [Profiler/`profiler`](profiler.md): Measure transcrypto performance.
82
81
 
@@ -104,14 +103,18 @@ Known dependencies:
104
103
  #### Humanized Sizes (IEC binary)
105
104
 
106
105
  ```py
107
- from transcrypto import utils
106
+ from transcrypto import base
108
107
 
109
- utils.HumanizedBytes(512) # '512 B'
110
- utils.HumanizedBytes(2048) # '2.00 KiB'
111
- utils.HumanizedBytes(5 * 1024**3) # '5.00 GiB'
108
+ base.HumanizedBytes(512) # '512 B'
109
+ base.HumanizedBytes(2048) # '2.000 KiB'
110
+ base.HumanizedBytes(5 * 1024**3) # '5.000 GiB'
112
111
  ```
113
112
 
114
- 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.
113
+ Converts raw byte counts to binary-prefixed strings (`B`, `KiB`, `MiB`, `GiB`, `TiB`, `PiB`, `EiB`).
114
+
115
+ - For integer inputs `<1024`, returns an integer count with `B` (e.g. `'512 B'`).
116
+ - For float inputs `<1024`, returns 3 decimals with `B` (e.g. `'51.200 B'`).
117
+ - For values `≥1024`, returns 3 decimals.
115
118
 
116
119
  - standard: 1 KiB = 1024 B, 1 MiB = 1024 KiB, …
117
120
  - errors: negative inputs raise `InputError`
@@ -120,43 +123,47 @@ Converts raw byte counts to binary-prefixed strings (`B`, `KiB`, `MiB`, `GiB`, `
120
123
 
121
124
  ```py
122
125
  # Base (unitless)
123
- utils.HumanizedDecimal(950) # '950'
124
- utils.HumanizedDecimal(1500) # '1.50 k'
126
+ base.HumanizedDecimal(950) # '950'
127
+ base.HumanizedDecimal(1500) # '1.500 k'
125
128
 
126
129
  # With a unit (trimmed and attached)
127
- utils.HumanizedDecimal(1500, ' Hz ') # '1.50 kHz'
128
- utils.HumanizedDecimal(0.123456, 'V') # '0.1235 V'
130
+ base.HumanizedDecimal(1500, unit=' Hz ') # '1.500 kHz'
131
+ base.HumanizedDecimal(0.123456, unit='V') # '123.456 mV'
129
132
 
130
133
  # Large magnitudes
131
- utils.HumanizedDecimal(3_200_000) # '3.20 M'
132
- utils.HumanizedDecimal(7.2e12, 'B/s') # '7.20 TB/s'
134
+ base.HumanizedDecimal(3_200_000) # '3.200 M'
135
+ base.HumanizedDecimal(7.2e12, unit='B/s') # '7.200 TB/s'
133
136
  ```
134
137
 
135
- 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`).
138
+ Scales by powers of 1000 using SI prefixes to keep the displayed value in roughly `[1, 1000)` when possible.
139
+
140
+ - Supported large prefixes: `k`, `M`, `G`, `T`, `P`, `E`
141
+ - Supported small prefixes: `m`, `µ`, `n`, `p`, `f`, `a`
142
+ - Formatting uses 3 decimals for non-integer/unscaled values and for scaled values.
136
143
 
137
144
  - unit handling: `unit` is stripped; `<1000` values include a space before the unit (`'950 Hz'`)
138
- - errors: negative or non-finite inputs raise `InputError`
145
+ - errors: non-finite inputs raise `InputError` (negative values are supported and keep a leading `-`)
139
146
 
140
147
  #### Humanized Durations
141
148
 
142
149
  ```py
143
- utils.HumanizedSeconds(0) # '0.00 s'
144
- utils.HumanizedSeconds(0.000004) # '4.000 µs'
145
- utils.HumanizedSeconds(0.25) # '250.000 ms'
146
- utils.HumanizedSeconds(42) # '42.00 s'
147
- utils.HumanizedSeconds(3661) # '1.02 h'
148
- utils.HumanizedSeconds(172800) # '2.00 d'
150
+ base.HumanizedSeconds(0) # '0.000 s'
151
+ base.HumanizedSeconds(0.000004) # '4.000 µs'
152
+ base.HumanizedSeconds(0.25) # '250.000 ms'
153
+ base.HumanizedSeconds(42) # '42.000 s'
154
+ base.HumanizedSeconds(3661) # '1.017 h'
155
+ base.HumanizedSeconds(172800) # '2.000 d'
149
156
  ```
150
157
 
151
158
  Chooses an appropriate time unit based on magnitude and formats with fixed precision:
152
159
 
153
160
  - `< 1 ms`: microseconds with three decimals (`µs`)
154
161
  - `< 1 s`: milliseconds with three decimals (`ms`)
155
- - `< 60 s`: seconds with two decimals (`s`)
156
- - `< 60 min`: minutes with two decimals (`min`)
157
- - `< 24 h`: hours with two decimals (`h`)
158
- - `≥ 24 h`: days with two decimals (`d`)
159
- - special case: `0 → '0.00 s'`
162
+ - `< 60 s`: seconds with three decimals (`s`)
163
+ - `< 60 min`: minutes with three decimals (`min`)
164
+ - `< 24 h`: hours with three decimals (`h`)
165
+ - `≥ 24 h`: days with three decimals (`d`)
166
+ - special case: `0 → '0.000 s'`
160
167
  - errors: negative or non-finite inputs raise `InputError`
161
168
 
162
169
  #### Execution Timing
@@ -173,7 +180,7 @@ import time
173
180
  ```py
174
181
  with base.Timer('Block timing'):
175
182
  time.sleep(1.2)
176
- # → logs: "Block timing: 1.20 s" (default via logging.info)
183
+ # → logs: "Block timing: 1.200 s" (default via logging.info)
177
184
  ```
178
185
 
179
186
  Starts timing on entry, stops on exit, and reports elapsed time automatically.
@@ -186,7 +193,7 @@ def slow_function():
186
193
  time.sleep(0.8)
187
194
 
188
195
  slow_function()
189
- # → logs: "Function timing: 0.80 s"
196
+ # → logs: "Function timing: 0.800 s"
190
197
  ```
191
198
 
192
199
  Wraps a function so that each call is automatically timed.
@@ -197,17 +204,17 @@ Wraps a function so that each call is automatically timed.
197
204
  tm = base.Timer('Inline timing', emit_print=True)
198
205
  tm.Start()
199
206
  time.sleep(0.1)
200
- tm.Stop() # prints: "Inline timing: 0.10 s"
207
+ tm.Stop() # prints: "Inline timing: 0.100 s"
201
208
  ```
202
209
 
203
210
  Manual control over `Start()` and `Stop()` for precise measurement of custom intervals.
204
211
 
205
212
  ##### Key points
206
213
 
207
- - **Label**: required, shown in output; empty labels raise `InputError`
214
+ - **Label**: optional; if empty, output omits the label prefix
208
215
  - **Output**:
209
216
  - `emit_log=True` → `logging.info()` (default)
210
- - `emit_print=True` → direct `print()`
217
+ - `emit_print=True` → prints via `rich.console.Console().print()`
211
218
  - Both can be enabled
212
219
  - **Format**: elapsed time is shown using `HumanizedSeconds()`
213
220
  - **Safety**:
@@ -235,7 +242,7 @@ blob = base.Serialize(data)
235
242
  blob = base.Serialize(
236
243
  data,
237
244
  compress=9, # compression level (-22..22, default=3)
238
- key=my_symmetric_key # must implement SymmetricCrypto
245
+ key=my_encryptor # must implement `base.Encryptor` (e.g., `aes.AESKey`)
239
246
  )
240
247
 
241
248
  # Save directly to file
@@ -279,7 +286,7 @@ obj = base.DeSerialize(data=blob)
279
286
  obj = base.DeSerialize(file_path='/tmp/data.blob')
280
287
 
281
288
  # With decryption
282
- obj = base.DeSerialize(data=blob, key=my_symmetric_key)
289
+ obj = base.DeSerialize(data=blob, key=my_decryptor)
283
290
  ```
284
291
 
285
292
  Deserialization path:
@@ -295,7 +302,8 @@ data/file → (decrypt) → (decompress if Zstd) → unpickle
295
302
 
296
303
  - Exactly one of `data` or `file_path` must be provided.
297
304
  - `file_path` must exist; `data` must be at least 4 bytes.
298
- - Wrong key or corrupted data can raise `CryptoError`.
305
+ - Wrong key / authentication failure can raise `CryptoError`.
306
+ - Corrupted compressed blobs typically raise `zstandard.ZstdError` during decompression.
299
307
 
300
308
  #### Cryptographically Secure Randomness
301
309
 
@@ -370,8 +378,8 @@ The function is `O(log(min(a, b)))` and handles arbitrarily large integers. To f
370
378
 
371
379
  ```py
372
380
  >>> base.ExtendedGCD(462, 1071)
373
- (21, -2, 1)
374
- >>> 462 * -2 + 1071 * 1
381
+ (21, 7, -3)
382
+ >>> 462 * 7 + 1071 * (-3)
375
383
  21
376
384
  ```
377
385
 
@@ -517,13 +525,13 @@ Hashes a file from disk in streaming mode. By default uses SHA-256; `digest='sha
517
525
 
518
526
  #### Symmetric Encryption Interface
519
527
 
520
- `SymmetricCrypto` is an abstract base class that defines the **byte-in / byte-out** contract for symmetric ciphers.
528
+ `base.Encryptor` and `base.Decryptor` are runtime-checkable protocols that define the **byte-in / byte-out** contract for symmetric ciphers.
521
529
 
522
530
  - **Metadata handling** — if the algorithm uses a `nonce` or `tag`, the implementation must handle it internally (e.g., append it to ciphertext).
523
531
  - **AEAD modes** — if supported, `associated_data` must be authenticated; otherwise, a non-`None` value should raise `InputError`.
524
532
 
525
533
  ```py
526
- class MyAES(base.SymmetricCrypto):
534
+ class MyAES(base.Encryptor, base.Decryptor):
527
535
  def Encrypt(self, plaintext: bytes, *, associated_data=None) -> bytes:
528
536
  ...
529
537
  def Decrypt(self, ciphertext: bytes, *, associated_data=None) -> bytes:
@@ -537,8 +545,8 @@ Cryptographic objects all derive from the `CryptoKey` class and will all have so
537
545
  - 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…`).
538
546
  - It will have a `_DebugDump()` method that **does print secrets** and can be used for **debugging only**.
539
547
  - Can be easily serialized to `bytes` by the `blob` property and to base-64 encoded `str` by the `encoded` property.
540
- - 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.
541
- - 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.
548
+ - 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.
549
+ - 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.
542
550
 
543
551
  Example:
544
552
 
@@ -701,7 +709,7 @@ For real-world deployments, use a high-level library with authenticated encrypti
701
709
  from transcrypto import elgamal
702
710
 
703
711
  # Shared parameters (prime modulus, group base) for a group
704
- shared = elgamal.ElGamalSharedPublicKey.New(256)
712
+ shared = elgamal.ElGamalSharedPublicKey.NewShared(256)
705
713
  print(shared.prime_modulus)
706
714
  print(shared.group_base)
707
715
 
@@ -753,8 +761,8 @@ This is **raw DSA** over a prime field — **no hashing or padding**. You sign/v
753
761
  ```py
754
762
  from transcrypto import dsa
755
763
 
756
- # Shared parameters (p, q, g)
757
- shared = dsa.DSASharedPublicKey.New(p_bits=1024, q_bits=160)
764
+ # Shared parameters (p, q, g) - Safe Sign/Verify requires q > 512 bits
765
+ shared = dsa.DSASharedPublicKey.NewShared(2048, 520)
758
766
  print(shared.prime_modulus) # p
759
767
  print(shared.prime_seed) # q (q | p-1)
760
768
  print(shared.group_base) # g
@@ -790,11 +798,11 @@ assert pub.RawVerify(msg, sig)
790
798
 
791
799
  ```py
792
800
  # Generate primes (p, q) with q | (p-1); also returns m = (p-1)//q
793
- p, q, m = dsa.NBitRandomDSAPrimes(p_bits=1024, q_bits=160)
801
+ p, q, m = dsa.NBitRandomDSAPrimes(1024, 160)
794
802
  assert (p - 1) % q == 0
795
803
  ```
796
804
 
797
- Used internally by `DSASharedPublicKey.New()`.
805
+ Used internally by `DSASharedPublicKey.NewShared()`.
798
806
  Search breadth and retry caps are bounded; repeated failures raise `CryptoError`.
799
807
 
800
808
  #### Public Bidding
@@ -819,14 +827,14 @@ print(bid_pub == bid_pub_expected)
819
827
 
820
828
  <https://en.wikipedia.org/wiki/Shamir's_secret_sharing>
821
829
 
822
- 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.
830
+ 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.
823
831
 
824
832
  ```py
825
833
  from transcrypto import sss
826
834
 
827
835
  # Generate parameters: at least 3 of 5 shares needed,
828
- # coefficients & modulus are 128-bit primes
829
- priv = sss.ShamirSharedSecretPrivate.New(minimum_shares=3, bit_length=128)
836
+ # coefficients & modulus are 264-bit primes (> 256 bits required for MakeDataShares)
837
+ priv = sss.ShamirSharedSecretPrivate.New(3, 264)
830
838
  pub = sss.ShamirSharedSecretPublic.Copy(priv) # what you publish
831
839
 
832
840
  print(f'threshold : {pub.minimum}')
@@ -856,8 +864,8 @@ A single share object looks like `sss.ShamirSharePrivate(minimum=3, modulus=...,
856
864
  # Safe Re-constructing the secret
857
865
  secret = b'xyz'
858
866
  five_shares = priv.MakeDataShares(secret, 5)
859
- subset = five_shares[:3] # any 3 distinct shares
860
- recovered = subset[0].RecoverData(subset) # each share has the encrypted data, so you ask it to join with the others
867
+ subset = five_shares[:3] # any 3 distinct shares
868
+ recovered = subset[0].RecoverData(subset[1:]) # each share has the encrypted data, pass other shares
861
869
  assert recovered == secret
862
870
 
863
871
  # Raw Re-constructing the secret
@@ -1017,7 +1025,7 @@ poetry publish
1017
1025
  If you changed the CLI interface at all, in any tool, run:
1018
1026
 
1019
1027
  ```sh
1020
- ./tools/generate_docs.sh
1028
+ make docs
1021
1029
  ```
1022
1030
 
1023
1031
  You can find the 10 top slowest tests by running: