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.
@@ -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 dsa, elgamal, rsa, transcrypto
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: typer.Context,
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: typer.Context,
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: typer.Context,
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: typer.Context,
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: typer.Context,
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: typer.Context,
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: typer.Context,
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: typer.Context,
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: typer.Context,
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: typer.Context,
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: typer.Context) -> None: # documentation is help/epilog/args # noqa: D103
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: typer.Context,
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: typer.Context,
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: typer.Context,
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: typer.Context,
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: typer.Context,
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: typer.Context,
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: typer.Context,
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: typer.Context,
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: typer.Context,
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: typer.Context) -> None: # documentation is help/epilog/args # noqa: D103
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: typer.Context,
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: typer.Context,
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: typer.Context,
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: typer.Context,
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(
@@ -0,0 +1,3 @@
1
+ # SPDX-FileCopyrightText: Copyright 2026 Daniel Balparda <balparda@github.com>
2
+ # SPDX-License-Identifier: Apache-2.0
3
+ """Core crypto logic."""
@@ -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 base
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(base.CryptoKey, base.Encryptor, base.Decryptor):
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={base.ObfuscateSecret(self.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(base.Encryptor, base.Decryptor):
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 = base.RandBytes(16)
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 base.CryptoError('failed decryption') from err
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
@@ -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
+ )