transcrypto 1.8.0__py3-none-any.whl → 2.0.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- transcrypto/__init__.py +1 -1
- transcrypto/cli/aeshash.py +14 -12
- transcrypto/cli/bidsecret.py +19 -16
- transcrypto/cli/clibase.py +22 -142
- transcrypto/cli/intmath.py +24 -21
- transcrypto/cli/publicalgos.py +28 -26
- transcrypto/core/__init__.py +3 -0
- transcrypto/{aes.py → core/aes.py} +17 -29
- transcrypto/core/bid.py +161 -0
- transcrypto/{dsa.py → core/dsa.py} +28 -27
- transcrypto/{elgamal.py → core/elgamal.py} +33 -32
- transcrypto/core/hashes.py +96 -0
- transcrypto/core/key.py +735 -0
- transcrypto/{modmath.py → core/modmath.py} +91 -17
- transcrypto/{rsa.py → core/rsa.py} +51 -50
- transcrypto/{sss.py → core/sss.py} +27 -26
- transcrypto/profiler.py +25 -11
- transcrypto/transcrypto.py +25 -15
- transcrypto/utils/__init__.py +3 -0
- transcrypto/utils/base.py +72 -0
- transcrypto/utils/human.py +278 -0
- transcrypto/utils/logging.py +139 -0
- transcrypto/utils/saferandom.py +102 -0
- transcrypto/utils/stats.py +360 -0
- transcrypto/utils/timer.py +175 -0
- {transcrypto-1.8.0.dist-info → transcrypto-2.0.3.dist-info}/METADATA +101 -101
- transcrypto-2.0.3.dist-info/RECORD +33 -0
- {transcrypto-1.8.0.dist-info → transcrypto-2.0.3.dist-info}/WHEEL +1 -1
- transcrypto/base.py +0 -1637
- transcrypto-1.8.0.dist-info/RECORD +0 -23
- /transcrypto/{constants.py → core/constants.py} +0 -0
- {transcrypto-1.8.0.dist-info → transcrypto-2.0.3.dist-info}/entry_points.txt +0 -0
- {transcrypto-1.8.0.dist-info → transcrypto-2.0.3.dist-info}/licenses/LICENSE +0 -0
transcrypto/cli/publicalgos.py
CHANGED
|
@@ -4,10 +4,12 @@
|
|
|
4
4
|
|
|
5
5
|
from __future__ import annotations
|
|
6
6
|
|
|
7
|
+
import click
|
|
7
8
|
import typer
|
|
8
9
|
|
|
9
|
-
from transcrypto import
|
|
10
|
+
from transcrypto import transcrypto
|
|
10
11
|
from transcrypto.cli import clibase
|
|
12
|
+
from transcrypto.core import dsa, elgamal, rsa
|
|
11
13
|
|
|
12
14
|
# ================================== "RSA" COMMAND =================================================
|
|
13
15
|
|
|
@@ -42,7 +44,7 @@ transcrypto.app.add_typer(rsa_app, name='rsa')
|
|
|
42
44
|
@clibase.CLIErrorGuard
|
|
43
45
|
def RSANew( # documentation is help/epilog/args # noqa: D103
|
|
44
46
|
*,
|
|
45
|
-
ctx:
|
|
47
|
+
ctx: click.Context,
|
|
46
48
|
bits: int = typer.Option(
|
|
47
49
|
3332,
|
|
48
50
|
'-b',
|
|
@@ -74,7 +76,7 @@ def RSANew( # documentation is help/epilog/args # noqa: D103
|
|
|
74
76
|
@clibase.CLIErrorGuard
|
|
75
77
|
def RSARawEncrypt( # documentation is help/epilog/args # noqa: D103
|
|
76
78
|
*,
|
|
77
|
-
ctx:
|
|
79
|
+
ctx: click.Context,
|
|
78
80
|
message: str = typer.Argument(..., help='Integer message to encrypt, 1≤`message`<*modulus*'),
|
|
79
81
|
) -> None:
|
|
80
82
|
config: transcrypto.TransConfig = ctx.obj
|
|
@@ -101,7 +103,7 @@ def RSARawEncrypt( # documentation is help/epilog/args # noqa: D103
|
|
|
101
103
|
@clibase.CLIErrorGuard
|
|
102
104
|
def RSARawDecrypt( # documentation is help/epilog/args # noqa: D103
|
|
103
105
|
*,
|
|
104
|
-
ctx:
|
|
106
|
+
ctx: click.Context,
|
|
105
107
|
ciphertext: str = typer.Argument(
|
|
106
108
|
..., help='Integer ciphertext to decrypt, 1≤`ciphertext`<*modulus*'
|
|
107
109
|
),
|
|
@@ -125,7 +127,7 @@ def RSARawDecrypt( # documentation is help/epilog/args # noqa: D103
|
|
|
125
127
|
@clibase.CLIErrorGuard
|
|
126
128
|
def RSARawSign( # documentation is help/epilog/args # noqa: D103
|
|
127
129
|
*,
|
|
128
|
-
ctx:
|
|
130
|
+
ctx: click.Context,
|
|
129
131
|
message: str = typer.Argument(..., help='Integer message to sign, 1≤`message`<*modulus*'),
|
|
130
132
|
) -> None:
|
|
131
133
|
config: transcrypto.TransConfig = ctx.obj
|
|
@@ -152,7 +154,7 @@ def RSARawSign( # documentation is help/epilog/args # noqa: D103
|
|
|
152
154
|
@clibase.CLIErrorGuard
|
|
153
155
|
def RSARawVerify( # documentation is help/epilog/args # noqa: D103
|
|
154
156
|
*,
|
|
155
|
-
ctx:
|
|
157
|
+
ctx: click.Context,
|
|
156
158
|
message: str = typer.Argument(
|
|
157
159
|
..., help='Integer message that was signed earlier, 1≤`message`<*modulus*'
|
|
158
160
|
),
|
|
@@ -185,7 +187,7 @@ def RSARawVerify( # documentation is help/epilog/args # noqa: D103
|
|
|
185
187
|
@clibase.CLIErrorGuard
|
|
186
188
|
def RSAEncrypt( # documentation is help/epilog/args # noqa: D103
|
|
187
189
|
*,
|
|
188
|
-
ctx:
|
|
190
|
+
ctx: click.Context,
|
|
189
191
|
plaintext: str = typer.Argument(..., help='Message to encrypt'),
|
|
190
192
|
aad: str = typer.Option(
|
|
191
193
|
'',
|
|
@@ -216,7 +218,7 @@ def RSAEncrypt( # documentation is help/epilog/args # noqa: D103
|
|
|
216
218
|
@clibase.CLIErrorGuard
|
|
217
219
|
def RSADecrypt( # documentation is help/epilog/args # noqa: D103
|
|
218
220
|
*,
|
|
219
|
-
ctx:
|
|
221
|
+
ctx: click.Context,
|
|
220
222
|
ciphertext: str = typer.Argument(..., help='Ciphertext to decrypt'),
|
|
221
223
|
aad: str = typer.Option(
|
|
222
224
|
'',
|
|
@@ -246,7 +248,7 @@ def RSADecrypt( # documentation is help/epilog/args # noqa: D103
|
|
|
246
248
|
@clibase.CLIErrorGuard
|
|
247
249
|
def RSASign( # documentation is help/epilog/args # noqa: D103
|
|
248
250
|
*,
|
|
249
|
-
ctx:
|
|
251
|
+
ctx: click.Context,
|
|
250
252
|
message: str = typer.Argument(..., help='Message to sign'),
|
|
251
253
|
aad: str = typer.Option(
|
|
252
254
|
'',
|
|
@@ -280,7 +282,7 @@ def RSASign( # documentation is help/epilog/args # noqa: D103
|
|
|
280
282
|
@clibase.CLIErrorGuard
|
|
281
283
|
def RSAVerify( # documentation is help/epilog/args # noqa: D103
|
|
282
284
|
*,
|
|
283
|
-
ctx:
|
|
285
|
+
ctx: click.Context,
|
|
284
286
|
message: str = typer.Argument(..., help='Message that was signed earlier'),
|
|
285
287
|
signature: str = typer.Argument(..., help='Putative signature for `message`'),
|
|
286
288
|
aad: str = typer.Option(
|
|
@@ -338,7 +340,7 @@ transcrypto.app.add_typer(eg_app, name='elgamal')
|
|
|
338
340
|
@clibase.CLIErrorGuard
|
|
339
341
|
def ElGamalShared( # documentation is help/epilog/args # noqa: D103
|
|
340
342
|
*,
|
|
341
|
-
ctx:
|
|
343
|
+
ctx: click.Context,
|
|
342
344
|
bits: int = typer.Option(
|
|
343
345
|
3332,
|
|
344
346
|
'-b',
|
|
@@ -364,7 +366,7 @@ def ElGamalShared( # documentation is help/epilog/args # noqa: D103
|
|
|
364
366
|
),
|
|
365
367
|
)
|
|
366
368
|
@clibase.CLIErrorGuard
|
|
367
|
-
def ElGamalNew(*, ctx:
|
|
369
|
+
def ElGamalNew(*, ctx: click.Context) -> None: # documentation is help/epilog/args # noqa: D103
|
|
368
370
|
config: transcrypto.TransConfig = ctx.obj
|
|
369
371
|
base_path: str = transcrypto.RequireKeyPath(config, 'elgamal')
|
|
370
372
|
shared_eg: elgamal.ElGamalSharedPublicKey = transcrypto.LoadObj(
|
|
@@ -392,7 +394,7 @@ def ElGamalNew(*, ctx: typer.Context) -> None: # documentation is help/epilog/a
|
|
|
392
394
|
@clibase.CLIErrorGuard
|
|
393
395
|
def ElGamalRawEncrypt( # documentation is help/epilog/args # noqa: D103
|
|
394
396
|
*,
|
|
395
|
-
ctx:
|
|
397
|
+
ctx: click.Context,
|
|
396
398
|
message: str = typer.Argument(..., help='Integer message to encrypt, 1≤`message`<*modulus*'),
|
|
397
399
|
) -> None:
|
|
398
400
|
config: transcrypto.TransConfig = ctx.obj
|
|
@@ -423,7 +425,7 @@ def ElGamalRawEncrypt( # documentation is help/epilog/args # noqa: D103
|
|
|
423
425
|
@clibase.CLIErrorGuard
|
|
424
426
|
def ElGamalRawDecrypt( # documentation is help/epilog/args # noqa: D103
|
|
425
427
|
*,
|
|
426
|
-
ctx:
|
|
428
|
+
ctx: click.Context,
|
|
427
429
|
ciphertext: str = typer.Argument(
|
|
428
430
|
...,
|
|
429
431
|
help=(
|
|
@@ -456,7 +458,7 @@ def ElGamalRawDecrypt( # documentation is help/epilog/args # noqa: D103
|
|
|
456
458
|
@clibase.CLIErrorGuard
|
|
457
459
|
def ElGamalRawSign( # documentation is help/epilog/args # noqa: D103
|
|
458
460
|
*,
|
|
459
|
-
ctx:
|
|
461
|
+
ctx: click.Context,
|
|
460
462
|
message: str = typer.Argument(..., help='Integer message to sign, 1≤`message`<*modulus*'),
|
|
461
463
|
) -> None:
|
|
462
464
|
config: transcrypto.TransConfig = ctx.obj
|
|
@@ -490,7 +492,7 @@ def ElGamalRawSign( # documentation is help/epilog/args # noqa: D103
|
|
|
490
492
|
@clibase.CLIErrorGuard
|
|
491
493
|
def ElGamalRawVerify( # documentation is help/epilog/args # noqa: D103
|
|
492
494
|
*,
|
|
493
|
-
ctx:
|
|
495
|
+
ctx: click.Context,
|
|
494
496
|
message: str = typer.Argument(
|
|
495
497
|
..., help='Integer message that was signed earlier, 1≤`message`<*modulus*'
|
|
496
498
|
),
|
|
@@ -527,7 +529,7 @@ def ElGamalRawVerify( # documentation is help/epilog/args # noqa: D103
|
|
|
527
529
|
@clibase.CLIErrorGuard
|
|
528
530
|
def ElGamalEncrypt( # documentation is help/epilog/args # noqa: D103
|
|
529
531
|
*,
|
|
530
|
-
ctx:
|
|
532
|
+
ctx: click.Context,
|
|
531
533
|
plaintext: str = typer.Argument(..., help='Message to encrypt'),
|
|
532
534
|
aad: str = typer.Option(
|
|
533
535
|
'',
|
|
@@ -560,7 +562,7 @@ def ElGamalEncrypt( # documentation is help/epilog/args # noqa: D103
|
|
|
560
562
|
@clibase.CLIErrorGuard
|
|
561
563
|
def ElGamalDecrypt( # documentation is help/epilog/args # noqa: D103
|
|
562
564
|
*,
|
|
563
|
-
ctx:
|
|
565
|
+
ctx: click.Context,
|
|
564
566
|
ciphertext: str = typer.Argument(..., help='Ciphertext to decrypt'),
|
|
565
567
|
aad: str = typer.Option(
|
|
566
568
|
'',
|
|
@@ -592,7 +594,7 @@ def ElGamalDecrypt( # documentation is help/epilog/args # noqa: D103
|
|
|
592
594
|
@clibase.CLIErrorGuard
|
|
593
595
|
def ElGamalSign( # documentation is help/epilog/args # noqa: D103
|
|
594
596
|
*,
|
|
595
|
-
ctx:
|
|
597
|
+
ctx: click.Context,
|
|
596
598
|
message: str = typer.Argument(..., help='Message to sign'),
|
|
597
599
|
aad: str = typer.Option(
|
|
598
600
|
'',
|
|
@@ -628,7 +630,7 @@ def ElGamalSign( # documentation is help/epilog/args # noqa: D103
|
|
|
628
630
|
@clibase.CLIErrorGuard
|
|
629
631
|
def ElGamalVerify( # documentation is help/epilog/args # noqa: D103
|
|
630
632
|
*,
|
|
631
|
-
ctx:
|
|
633
|
+
ctx: click.Context,
|
|
632
634
|
message: str = typer.Argument(..., help='Message that was signed earlier'),
|
|
633
635
|
signature: str = typer.Argument(..., help='Putative signature for `message`'),
|
|
634
636
|
aad: str = typer.Option(
|
|
@@ -689,7 +691,7 @@ transcrypto.app.add_typer(dsa_app, name='dsa')
|
|
|
689
691
|
@clibase.CLIErrorGuard
|
|
690
692
|
def DSAShared( # documentation is help/epilog/args # noqa: D103
|
|
691
693
|
*,
|
|
692
|
-
ctx:
|
|
694
|
+
ctx: click.Context,
|
|
693
695
|
p_bits: int = typer.Option(
|
|
694
696
|
4096,
|
|
695
697
|
'-b',
|
|
@@ -725,7 +727,7 @@ def DSAShared( # documentation is help/epilog/args # noqa: D103
|
|
|
725
727
|
),
|
|
726
728
|
)
|
|
727
729
|
@clibase.CLIErrorGuard
|
|
728
|
-
def DSANew(*, ctx:
|
|
730
|
+
def DSANew(*, ctx: click.Context) -> None: # documentation is help/epilog/args # noqa: D103
|
|
729
731
|
config: transcrypto.TransConfig = ctx.obj
|
|
730
732
|
base_path: str = transcrypto.RequireKeyPath(config, 'dsa')
|
|
731
733
|
dsa_shared: dsa.DSASharedPublicKey = transcrypto.LoadObj(
|
|
@@ -753,7 +755,7 @@ def DSANew(*, ctx: typer.Context) -> None: # documentation is help/epilog/args
|
|
|
753
755
|
@clibase.CLIErrorGuard
|
|
754
756
|
def DSARawSign( # documentation is help/epilog/args # noqa: D103
|
|
755
757
|
*,
|
|
756
|
-
ctx:
|
|
758
|
+
ctx: click.Context,
|
|
757
759
|
message: str = typer.Argument(..., help='Integer message to sign, 1≤`message`<`q`'),
|
|
758
760
|
) -> None:
|
|
759
761
|
config: transcrypto.TransConfig = ctx.obj
|
|
@@ -784,7 +786,7 @@ def DSARawSign( # documentation is help/epilog/args # noqa: D103
|
|
|
784
786
|
@clibase.CLIErrorGuard
|
|
785
787
|
def DSARawVerify( # documentation is help/epilog/args # noqa: D103
|
|
786
788
|
*,
|
|
787
|
-
ctx:
|
|
789
|
+
ctx: click.Context,
|
|
788
790
|
message: str = typer.Argument(
|
|
789
791
|
..., help='Integer message that was signed earlier, 1≤`message`<`q`'
|
|
790
792
|
),
|
|
@@ -821,7 +823,7 @@ def DSARawVerify( # documentation is help/epilog/args # noqa: D103
|
|
|
821
823
|
@clibase.CLIErrorGuard
|
|
822
824
|
def DSASign( # documentation is help/epilog/args # noqa: D103
|
|
823
825
|
*,
|
|
824
|
-
ctx:
|
|
826
|
+
ctx: click.Context,
|
|
825
827
|
message: str = typer.Argument(..., help='Message to sign'),
|
|
826
828
|
aad: str = typer.Option(
|
|
827
829
|
'',
|
|
@@ -855,7 +857,7 @@ def DSASign( # documentation is help/epilog/args # noqa: D103
|
|
|
855
857
|
@clibase.CLIErrorGuard
|
|
856
858
|
def DSAVerify( # documentation is help/epilog/args # noqa: D103
|
|
857
859
|
*,
|
|
858
|
-
ctx:
|
|
860
|
+
ctx: click.Context,
|
|
859
861
|
message: str = typer.Argument(..., help='Message that was signed earlier'),
|
|
860
862
|
signature: str = typer.Argument(..., help='Putative signature for `message`'),
|
|
861
863
|
aad: str = typer.Option(
|
|
@@ -25,7 +25,8 @@ from cryptography.hazmat.primitives import hashes as hazmat_hashes
|
|
|
25
25
|
from cryptography.hazmat.primitives.ciphers import algorithms, modes
|
|
26
26
|
from cryptography.hazmat.primitives.kdf import pbkdf2 as hazmat_pbkdf2
|
|
27
27
|
|
|
28
|
-
from . import
|
|
28
|
+
from transcrypto.core import hashes, key
|
|
29
|
+
from transcrypto.utils import base, saferandom
|
|
29
30
|
|
|
30
31
|
# these fixed salt/iterations are for password->key generation only; NEVER use them to
|
|
31
32
|
# build a database of passwords because it would not be safe; NEVER change them or the
|
|
@@ -41,7 +42,7 @@ assert _PASSWORD_ITERATIONS == (6075308 + 1) // 3, 'should never happen: constan
|
|
|
41
42
|
|
|
42
43
|
|
|
43
44
|
@dataclasses.dataclass(kw_only=True, slots=True, frozen=True, repr=False)
|
|
44
|
-
class AESKey(
|
|
45
|
+
class AESKey(key.CryptoKey, key.Encryptor, key.Decryptor):
|
|
45
46
|
"""Advanced Encryption Standard (AES) 256 bits key (32 bytes).
|
|
46
47
|
|
|
47
48
|
No measures are taken here to prevent timing attacks.
|
|
@@ -57,7 +58,7 @@ class AESKey(base.CryptoKey, base.Encryptor, base.Decryptor):
|
|
|
57
58
|
"""Check data.
|
|
58
59
|
|
|
59
60
|
Raises:
|
|
60
|
-
InputError: invalid inputs
|
|
61
|
+
base.InputError: invalid inputs
|
|
61
62
|
|
|
62
63
|
"""
|
|
63
64
|
if len(self.key256) != 32: # noqa: PLR2004
|
|
@@ -70,7 +71,7 @@ class AESKey(base.CryptoKey, base.Encryptor, base.Decryptor):
|
|
|
70
71
|
string representation of AESKey without leaking secrets
|
|
71
72
|
|
|
72
73
|
"""
|
|
73
|
-
return f'AESKey(key256={
|
|
74
|
+
return f'AESKey(key256={hashes.ObfuscateSecret(self.key256)})'
|
|
74
75
|
|
|
75
76
|
@classmethod
|
|
76
77
|
def FromStaticPassword(cls, str_password: str, /) -> Self:
|
|
@@ -98,7 +99,7 @@ class AESKey(base.CryptoKey, base.Encryptor, base.Decryptor):
|
|
|
98
99
|
AESKey crypto key to use (URL-safe base64-encoded 32-byte key)
|
|
99
100
|
|
|
100
101
|
Raises:
|
|
101
|
-
InputError: empty password
|
|
102
|
+
base.InputError: empty password
|
|
102
103
|
|
|
103
104
|
"""
|
|
104
105
|
str_password = str_password.strip()
|
|
@@ -112,7 +113,7 @@ class AESKey(base.CryptoKey, base.Encryptor, base.Decryptor):
|
|
|
112
113
|
)
|
|
113
114
|
return cls(key256=kdf.derive(str_password.encode('utf-8')))
|
|
114
115
|
|
|
115
|
-
class ECBEncoderClass(
|
|
116
|
+
class ECBEncoderClass(key.Encryptor, key.Decryptor):
|
|
116
117
|
"""The simplest encryption possible (UNSAFE if misused): 128 bit block AES-ECB, 256 bit key.
|
|
117
118
|
|
|
118
119
|
Note: Due to ECB encoding, this class is only safe-ish for blocks of random-looking data,
|
|
@@ -162,7 +163,7 @@ class AESKey(base.CryptoKey, base.Encryptor, base.Decryptor):
|
|
|
162
163
|
bytes: Ciphertext, a block of 128 bits (16 bytes)
|
|
163
164
|
|
|
164
165
|
Raises:
|
|
165
|
-
InputError: invalid inputs
|
|
166
|
+
base.InputError: invalid inputs
|
|
166
167
|
|
|
167
168
|
"""
|
|
168
169
|
if associated_data is not None:
|
|
@@ -189,7 +190,7 @@ class AESKey(base.CryptoKey, base.Encryptor, base.Decryptor):
|
|
|
189
190
|
bytes: Decrypted plaintext, a block of 128 bits (16 bytes)
|
|
190
191
|
|
|
191
192
|
Raises:
|
|
192
|
-
InputError: invalid inputs
|
|
193
|
+
base.InputError: invalid inputs
|
|
193
194
|
|
|
194
195
|
"""
|
|
195
196
|
if associated_data is not None:
|
|
@@ -227,7 +228,7 @@ class AESKey(base.CryptoKey, base.Encryptor, base.Decryptor):
|
|
|
227
228
|
str: encrypted hexadecimal block (length==64)
|
|
228
229
|
|
|
229
230
|
Raises:
|
|
230
|
-
InputError: invalid inputs
|
|
231
|
+
base.InputError: invalid inputs
|
|
231
232
|
|
|
232
233
|
"""
|
|
233
234
|
if len(plaintext_hex) != 64: # noqa: PLR2004
|
|
@@ -262,7 +263,7 @@ class AESKey(base.CryptoKey, base.Encryptor, base.Decryptor):
|
|
|
262
263
|
str: plaintext hexadecimal block (length==64)
|
|
263
264
|
|
|
264
265
|
Raises:
|
|
265
|
-
InputError: invalid inputs
|
|
266
|
+
base.InputError: invalid inputs
|
|
266
267
|
|
|
267
268
|
"""
|
|
268
269
|
if len(ciphertext_hex) != 64: # noqa: PLR2004
|
|
@@ -297,7 +298,7 @@ class AESKey(base.CryptoKey, base.Encryptor, base.Decryptor):
|
|
|
297
298
|
must encode it within the returned bytes (or document how to retrieve it)
|
|
298
299
|
|
|
299
300
|
"""
|
|
300
|
-
iv: bytes =
|
|
301
|
+
iv: bytes = saferandom.RandBytes(16)
|
|
301
302
|
cipher: ciphers.Cipher[modes.GCM] = ciphers.Cipher(
|
|
302
303
|
algorithms.AES256(self.key256), modes.GCM(iv)
|
|
303
304
|
)
|
|
@@ -339,12 +340,14 @@ class AESKey(base.CryptoKey, base.Encryptor, base.Decryptor):
|
|
|
339
340
|
bytes: Decrypted plaintext bytes
|
|
340
341
|
|
|
341
342
|
Raises:
|
|
342
|
-
InputError: invalid inputs
|
|
343
|
-
CryptoError: internal crypto failures, authentication failure, key mismatch, etc
|
|
343
|
+
base.InputError: invalid inputs
|
|
344
|
+
key.CryptoError: internal crypto failures, authentication failure, key mismatch, etc
|
|
344
345
|
|
|
345
346
|
"""
|
|
346
347
|
if len(ciphertext) < 32: # noqa: PLR2004
|
|
347
348
|
raise base.InputError(f'AES256+GCM should have ≥32 bytes IV/CT/tag: {len(ciphertext)}')
|
|
349
|
+
iv: bytes
|
|
350
|
+
tag: bytes
|
|
348
351
|
iv, tag = ciphertext[:16], ciphertext[-16:]
|
|
349
352
|
decryptor: ciphers.CipherContext = ciphers.Cipher(
|
|
350
353
|
algorithms.AES256(self.key256), modes.GCM(iv, tag)
|
|
@@ -354,19 +357,4 @@ class AESKey(base.CryptoKey, base.Encryptor, base.Decryptor):
|
|
|
354
357
|
try:
|
|
355
358
|
return decryptor.update(ciphertext[16:-16]) + decryptor.finalize()
|
|
356
359
|
except crypt_exceptions.InvalidTag as err:
|
|
357
|
-
raise
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
def _TestCryptoKeyEncoding(obj: base.CryptoKey, tp: type[base.CryptoKey]) -> None: # pyright: ignore[reportUnusedFunction]
|
|
361
|
-
"""Test encoding for a CryptoKey instance. Only for use from test modules."""
|
|
362
|
-
assert tp.FromJSON(obj.json) == obj # noqa: S101
|
|
363
|
-
assert tp.FromJSON(obj.formatted_json) == obj # noqa: S101
|
|
364
|
-
assert tp.Load(obj.blob) == obj # noqa: S101
|
|
365
|
-
assert tp.Load(obj.encoded) == obj # noqa: S101
|
|
366
|
-
assert tp.Load(obj.hex) == obj # noqa: S101
|
|
367
|
-
assert tp.Load(obj.raw) == obj # noqa: S101
|
|
368
|
-
key = AESKey(key256=b'x' * 32)
|
|
369
|
-
assert tp.Load(obj.Blob(key=key), key=key) == obj # noqa: S101
|
|
370
|
-
assert tp.Load(obj.Encoded(key=key), key=key) == obj # noqa: S101
|
|
371
|
-
assert tp.Load(obj.Hex(key=key), key=key) == obj # noqa: S101
|
|
372
|
-
assert tp.Load(obj.Raw(key=key), key=key) == obj # noqa: S101
|
|
360
|
+
raise key.CryptoError('failed decryption') from err
|
transcrypto/core/bid.py
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: Copyright 2026 Daniel Balparda <balparda@github.com>
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
"""Balparda's TransCrypto bidding protocols."""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import dataclasses
|
|
8
|
+
from typing import Self
|
|
9
|
+
|
|
10
|
+
from transcrypto.core import hashes, key
|
|
11
|
+
from transcrypto.utils import base, saferandom
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclasses.dataclass(kw_only=True, slots=True, frozen=True, repr=False)
|
|
15
|
+
class PublicBid512(key.CryptoKey):
|
|
16
|
+
"""Public commitment to a (cryptographically secure) bid that can be revealed/validated later.
|
|
17
|
+
|
|
18
|
+
Bid is computed as: public_hash = Hash512(public_key || private_key || secret_bid)
|
|
19
|
+
|
|
20
|
+
Everything is bytes. The public part is (public_key, public_hash) and the private
|
|
21
|
+
part is (private_key, secret_bid). The whole computation can be checked later.
|
|
22
|
+
|
|
23
|
+
No measures are taken here to prevent timing attacks (probably not a concern).
|
|
24
|
+
|
|
25
|
+
Attributes:
|
|
26
|
+
public_key (bytes): 512-bits random value
|
|
27
|
+
public_hash (bytes): SHA-512 hash of (public_key || private_key || secret_bid)
|
|
28
|
+
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
public_key: bytes
|
|
32
|
+
public_hash: bytes
|
|
33
|
+
|
|
34
|
+
def __post_init__(self) -> None:
|
|
35
|
+
"""Check data.
|
|
36
|
+
|
|
37
|
+
Raises:
|
|
38
|
+
base.InputError: invalid inputs
|
|
39
|
+
|
|
40
|
+
"""
|
|
41
|
+
if len(self.public_key) != 64 or len(self.public_hash) != 64: # noqa: PLR2004
|
|
42
|
+
raise base.InputError(f'invalid public_key or public_hash: {self}')
|
|
43
|
+
|
|
44
|
+
def __str__(self) -> str:
|
|
45
|
+
"""Safe string representation of the PublicBid.
|
|
46
|
+
|
|
47
|
+
Returns:
|
|
48
|
+
string representation of PublicBid
|
|
49
|
+
|
|
50
|
+
"""
|
|
51
|
+
return (
|
|
52
|
+
'PublicBid512('
|
|
53
|
+
f'public_key={base.BytesToEncoded(self.public_key)}, '
|
|
54
|
+
f'public_hash={base.BytesToHex(self.public_hash)})'
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
def VerifyBid(self, private_key: bytes, secret: bytes, /) -> bool:
|
|
58
|
+
"""Verify a bid. True if OK; False if failed verification.
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
private_key (bytes): 512-bits private key
|
|
62
|
+
secret (bytes): Any number of bytes (≥1) to bid on (e.g., UTF-8 encoded string)
|
|
63
|
+
|
|
64
|
+
Returns:
|
|
65
|
+
True if bid is valid, False otherwise
|
|
66
|
+
|
|
67
|
+
"""
|
|
68
|
+
try:
|
|
69
|
+
# creating the PrivateBid object will validate everything; InputError we allow to propagate
|
|
70
|
+
PrivateBid512(
|
|
71
|
+
public_key=self.public_key,
|
|
72
|
+
public_hash=self.public_hash,
|
|
73
|
+
private_key=private_key,
|
|
74
|
+
secret_bid=secret,
|
|
75
|
+
)
|
|
76
|
+
return True # if we got here, all is good
|
|
77
|
+
except key.CryptoError:
|
|
78
|
+
return False # bid does not match the public commitment
|
|
79
|
+
|
|
80
|
+
@classmethod
|
|
81
|
+
def Copy(cls, other: PublicBid512, /) -> Self:
|
|
82
|
+
"""Initialize a public bid by taking the public parts of a public/private bid.
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
other (PublicBid512): the bid to copy from
|
|
86
|
+
|
|
87
|
+
Returns:
|
|
88
|
+
Self: an initialized PublicBid512
|
|
89
|
+
|
|
90
|
+
"""
|
|
91
|
+
return cls(public_key=other.public_key, public_hash=other.public_hash)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
@dataclasses.dataclass(kw_only=True, slots=True, frozen=True, repr=False)
|
|
95
|
+
class PrivateBid512(PublicBid512):
|
|
96
|
+
"""Private bid that can be revealed and validated against a public commitment (see PublicBid).
|
|
97
|
+
|
|
98
|
+
Attributes:
|
|
99
|
+
private_key (bytes): 512-bits random value
|
|
100
|
+
secret_bid (bytes): Any number of bytes (≥1) to bid on (e.g., UTF-8 encoded string)
|
|
101
|
+
|
|
102
|
+
"""
|
|
103
|
+
|
|
104
|
+
private_key: bytes
|
|
105
|
+
secret_bid: bytes
|
|
106
|
+
|
|
107
|
+
def __post_init__(self) -> None:
|
|
108
|
+
"""Check data.
|
|
109
|
+
|
|
110
|
+
Raises:
|
|
111
|
+
base.InputError: invalid inputs
|
|
112
|
+
key.CryptoError: bid does not match the public commitment
|
|
113
|
+
|
|
114
|
+
"""
|
|
115
|
+
super(PrivateBid512, self).__post_init__()
|
|
116
|
+
if len(self.private_key) != 64 or len(self.secret_bid) < 1: # noqa: PLR2004
|
|
117
|
+
raise base.InputError(f'invalid private_key or secret_bid: {self}')
|
|
118
|
+
if self.public_hash != hashes.Hash512(self.public_key + self.private_key + self.secret_bid):
|
|
119
|
+
raise key.CryptoError(f'inconsistent bid: {self}')
|
|
120
|
+
|
|
121
|
+
def __str__(self) -> str:
|
|
122
|
+
"""Safe (no secrets) string representation of the PrivateBid.
|
|
123
|
+
|
|
124
|
+
Returns:
|
|
125
|
+
string representation of PrivateBid without leaking secrets
|
|
126
|
+
|
|
127
|
+
"""
|
|
128
|
+
return (
|
|
129
|
+
'PrivateBid512('
|
|
130
|
+
f'{super(PrivateBid512, self).__str__()}, '
|
|
131
|
+
f'private_key={hashes.ObfuscateSecret(self.private_key)}, '
|
|
132
|
+
f'secret_bid={hashes.ObfuscateSecret(self.secret_bid)})'
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
@classmethod
|
|
136
|
+
def New(cls, secret: bytes, /) -> Self:
|
|
137
|
+
"""Make the `secret` into a new bid.
|
|
138
|
+
|
|
139
|
+
Args:
|
|
140
|
+
secret (bytes): Any number of bytes (≥1) to bid on (e.g., UTF-8 encoded string)
|
|
141
|
+
|
|
142
|
+
Returns:
|
|
143
|
+
PrivateBid object ready for use (use PublicBid.Copy() to get the public part)
|
|
144
|
+
|
|
145
|
+
Raises:
|
|
146
|
+
base.InputError: invalid inputs
|
|
147
|
+
|
|
148
|
+
"""
|
|
149
|
+
# test inputs
|
|
150
|
+
if len(secret) < 1:
|
|
151
|
+
raise base.InputError(f'invalid secret length: {len(secret)}')
|
|
152
|
+
# generate random values
|
|
153
|
+
public_key: bytes = saferandom.RandBytes(64) # 512 bits
|
|
154
|
+
private_key: bytes = saferandom.RandBytes(64) # 512 bits
|
|
155
|
+
# build object
|
|
156
|
+
return cls(
|
|
157
|
+
public_key=public_key,
|
|
158
|
+
public_hash=hashes.Hash512(public_key + private_key + secret),
|
|
159
|
+
private_key=private_key,
|
|
160
|
+
secret_bid=secret,
|
|
161
|
+
)
|