eddsa-threshold 0.2.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. eddsa_threshold/__init__.py +1 -0
  2. eddsa_threshold/eddsa/__init__.py +1 -0
  3. eddsa_threshold/eddsa/algorithms/__init__.py +1 -0
  4. eddsa_threshold/eddsa/algorithms/ed25519.py +84 -0
  5. eddsa_threshold/eddsa/algorithms/ed25519ctx.py +23 -0
  6. eddsa_threshold/eddsa/algorithms/ed25519ph.py +23 -0
  7. eddsa_threshold/eddsa/algorithms/ed448.py +84 -0
  8. eddsa_threshold/eddsa/algorithms/ed448ph.py +23 -0
  9. eddsa_threshold/eddsa/curves/__init__.py +1 -0
  10. eddsa_threshold/eddsa/curves/base/__init__.py +1 -0
  11. eddsa_threshold/eddsa/curves/base/edwards_curve.py +91 -0
  12. eddsa_threshold/eddsa/curves/base/encoding.py +54 -0
  13. eddsa_threshold/eddsa/curves/base/field_ops.py +48 -0
  14. eddsa_threshold/eddsa/curves/base/scalar_ops.py +34 -0
  15. eddsa_threshold/eddsa/curves/ed25519/__init__.py +1 -0
  16. eddsa_threshold/eddsa/curves/ed25519/constants.py +37 -0
  17. eddsa_threshold/eddsa/curves/ed25519/ed25519_curve.py +79 -0
  18. eddsa_threshold/eddsa/curves/ed25519/encoding.py +80 -0
  19. eddsa_threshold/eddsa/curves/ed25519/field_ops.py +12 -0
  20. eddsa_threshold/eddsa/curves/ed25519/scalar_ops.py +12 -0
  21. eddsa_threshold/eddsa/curves/ed448/__init__.py +1 -0
  22. eddsa_threshold/eddsa/curves/ed448/constants.py +37 -0
  23. eddsa_threshold/eddsa/curves/ed448/ed448_curve.py +76 -0
  24. eddsa_threshold/eddsa/curves/ed448/encoding.py +77 -0
  25. eddsa_threshold/eddsa/curves/ed448/field_ops.py +12 -0
  26. eddsa_threshold/eddsa/curves/ed448/scalar_ops.py +12 -0
  27. eddsa_threshold/eddsa/keys/__init__.py +1 -0
  28. eddsa_threshold/eddsa/keys/ed25519_keypair.py +46 -0
  29. eddsa_threshold/eddsa/keys/ed448_keypair.py +45 -0
  30. eddsa_threshold/eddsa/keys/keypair.py +50 -0
  31. eddsa_threshold/eddsa/util/__init__.py +1 -0
  32. eddsa_threshold/eddsa/util/dom.py +17 -0
  33. eddsa_threshold/eddsa/util/hash_bindings.py +12 -0
  34. eddsa_threshold/frost/__init__.py +1 -0
  35. eddsa_threshold/frost/coordinator.py +185 -0
  36. eddsa_threshold/frost/core/__init__.py +1 -0
  37. eddsa_threshold/frost/core/base/__init__.py +1 -0
  38. eddsa_threshold/frost/core/base/frost_hashing.py +27 -0
  39. eddsa_threshold/frost/core/ed25519/__init__.py +1 -0
  40. eddsa_threshold/frost/core/ed25519/frost_hashing.py +26 -0
  41. eddsa_threshold/frost/core/ed448/__init__.py +1 -0
  42. eddsa_threshold/frost/core/ed448/frost_hashing.py +27 -0
  43. eddsa_threshold/frost/core/frost_types.py +53 -0
  44. eddsa_threshold/frost/core/polynomial.py +39 -0
  45. eddsa_threshold/frost/core/secrets/__init__.py +1 -0
  46. eddsa_threshold/frost/core/secrets/secret_sharing.py +26 -0
  47. eddsa_threshold/frost/core/secrets/shamir_secret_sharing.py +41 -0
  48. eddsa_threshold/frost/core/util.py +109 -0
  49. eddsa_threshold/frost/participant.py +140 -0
  50. eddsa_threshold/frost/trusted_dealer.py +72 -0
  51. eddsa_threshold-0.2.0.dist-info/METADATA +39 -0
  52. eddsa_threshold-0.2.0.dist-info/RECORD +55 -0
  53. eddsa_threshold-0.2.0.dist-info/WHEEL +5 -0
  54. eddsa_threshold-0.2.0.dist-info/licenses/LICENSE +14 -0
  55. eddsa_threshold-0.2.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,80 @@
1
+ from typing import Tuple
2
+
3
+ from eddsa_threshold.eddsa.curves.base.encoding import Encoding
4
+ from eddsa_threshold.eddsa.curves.ed25519.field_ops import Ed25519FieldOps
5
+ from .constants import SCALAR_SIZE, PUBLIC_KEY_SIZE, d, a, L
6
+
7
+
8
+ class Ed25519Encoding(Encoding):
9
+ """
10
+ Byte encoding/decoding layer for Ed25519.
11
+ """
12
+
13
+ def __init__(self, field_ops: Ed25519FieldOps):
14
+ self._field = field_ops
15
+
16
+ @property
17
+ def scalar_size(self) -> int:
18
+ """Size in bytes of a private scalar."""
19
+ return SCALAR_SIZE
20
+
21
+ @property
22
+ def group_order(self) -> int:
23
+ """Group order of the field."""
24
+ return L
25
+
26
+ @property
27
+ def point_size(self) -> int:
28
+ """Size in bytes of an encoded public key point."""
29
+ return PUBLIC_KEY_SIZE
30
+
31
+ def encode_point(self, P: Tuple) -> bytes:
32
+ """Encode point P=(x, y) to bytes."""
33
+ x, y = P
34
+ y_bytes = y.to_bytes(self.point_size, byteorder='little')
35
+
36
+ y_bytes = bytearray(y_bytes)
37
+ if x & 1:
38
+ y_bytes[-1] |= 0x80
39
+
40
+ return bytes(y_bytes)
41
+
42
+ def decode_point(self, data: bytes) -> Tuple:
43
+ """Decode point from bytes to (x_lsb, y)."""
44
+ if len(data) != self.point_size:
45
+ raise ValueError("Invalid point size")
46
+
47
+ y_bytes = bytearray(data)
48
+ x_lsb = (y_bytes[-1] >> 7) & 1
49
+ y_bytes[-1] &= 0x7F # Clear the MSB
50
+
51
+ y = int.from_bytes(y_bytes, byteorder='little')
52
+ if y >= self._field.p:
53
+ raise ValueError("Invalid point encoding - y out of range")
54
+
55
+ y2 = self._field.pow(y, 2)
56
+ u = (y2 - 1) % self._field.p
57
+ v = (d * y2 - a) % self._field.p
58
+
59
+ v3 = self._field.pow(v, 3)
60
+ v7 = self._field.pow(v, 7)
61
+
62
+ w = (u * v3 * self._field.pow((u * v7), ((self._field.p - 5) // 8))) % self._field.p
63
+
64
+ w2 = self._field.sqr(w)
65
+
66
+ if (v * w2) % self._field.p == u:
67
+ x = w
68
+ elif (v * w2) % self._field.p == self._field.neg(u):
69
+ x = (w * self._field.pow(2, ((self._field.p - 1) // 4))) % self._field.p
70
+ else:
71
+ raise ValueError("Invalid point encoding - SQRT failure")
72
+
73
+ if x == 0 and x_lsb == 1:
74
+ raise ValueError("Invalid point encoding - x is zero but lsb is 1")
75
+
76
+ if (x % 2) == x_lsb:
77
+ return (x, y)
78
+ else:
79
+ return (self._field.p - x, y)
80
+
@@ -0,0 +1,12 @@
1
+ from eddsa_threshold.eddsa.curves.base.field_ops import FieldOps
2
+ from .constants import p
3
+
4
+
5
+ class Ed25519FieldOps(FieldOps):
6
+ """
7
+ Finite field operations modulo the prime p = 2^255 - 19 for Ed25519.
8
+ """
9
+
10
+ @property
11
+ def p(self) -> int:
12
+ return p
@@ -0,0 +1,12 @@
1
+ from eddsa_threshold.eddsa.curves.base.scalar_ops import ScalarOps
2
+ from .constants import L, SCALAR_SIZE
3
+
4
+
5
+ class Ed25519ScalarOps(ScalarOps):
6
+ @property
7
+ def order(self) -> int:
8
+ return L
9
+
10
+ @property
11
+ def identity(self) -> int:
12
+ return 0
@@ -0,0 +1 @@
1
+ """Ed448 curve implementation."""
@@ -0,0 +1,37 @@
1
+ """
2
+ Constants for Ed448
3
+
4
+ These values are taken from RFC 8032.
5
+ RFC: https://datatracker.ietf.org/doc/html/rfc8032
6
+ """
7
+
8
+ # Finite field modulus p
9
+ p = 2**448 - 2**224 - 1
10
+
11
+ # Bit size of the field
12
+ b = 456
13
+
14
+ # base 2 logarithm of cofactor
15
+ c = 2
16
+
17
+ n = 447
18
+
19
+ # Curve Parameters d/a
20
+ # d = -121665 * pow(121666, -1, p) % p
21
+ d = -39081
22
+ a = 1
23
+
24
+ # Base point of the curve
25
+ BASE_X = 224580040295924300187604334099896036246789641632564134246125461686950415467406032909029192869357953282578032075146446173674602635247710
26
+ BASE_Y = 298819210078481492676017930443930673437544040154080242095928241372331506189835876003536878655418784733982303233503462500531545062832660
27
+ BASE = (BASE_X, BASE_Y)
28
+ IDENTITY = (0, 1)
29
+
30
+ # Order of ed25519
31
+ L = 2**446 - 13818066809895115352007386748515426880336692474882178609894547503885
32
+
33
+ # Encoding sizes (in bytes)
34
+ SCALAR_SIZE = 57 # size of private scalar
35
+ PUBLIC_KEY_SIZE = 57 # size of encoded public key
36
+ SIGNATURE_SIZE = 114 # R || S
37
+ SEED_SIZE = 57 # seed length specified by RFC 8032
@@ -0,0 +1,76 @@
1
+ from typing import Tuple
2
+ from eddsa_threshold.eddsa.curves.base.edwards_curve import EdwardsCurve
3
+ from eddsa_threshold.eddsa.curves.base.encoding import Encoding
4
+ from eddsa_threshold.eddsa.curves.base.field_ops import FieldOps
5
+ from eddsa_threshold.eddsa.curves.base.scalar_ops import ScalarOps
6
+ from .scalar_ops import Ed448ScalarOps
7
+ from .encoding import Ed448Encoding
8
+ from .field_ops import Ed448FieldOps
9
+ from .constants import d, BASE
10
+
11
+
12
+ class Ed448Curve(EdwardsCurve):
13
+ """
14
+ Ed448 curve implementation.
15
+
16
+ See base class EdwardsCurve for method descriptions.
17
+ """
18
+
19
+ def __init__(self):
20
+ self._field_ops = Ed448FieldOps()
21
+ self._encoding = Ed448Encoding(self._field_ops)
22
+ self._scalar_ops = Ed448ScalarOps()
23
+ self.d = d
24
+
25
+ @property
26
+ def field(self) -> FieldOps:
27
+ return self._field_ops
28
+
29
+ @property
30
+ def encoding(self) -> Encoding:
31
+ return self._encoding
32
+
33
+ @property
34
+ def scalar_ops(self) -> ScalarOps:
35
+ return self._scalar_ops
36
+
37
+ @property
38
+ def base_point(self) -> Tuple:
39
+ return BASE
40
+
41
+ # Point addition
42
+ def add(self, P: Tuple, Q: Tuple) -> Tuple:
43
+ X1, Y1, Z1, _ = P
44
+ X2, Y2, Z2, _ = Q
45
+
46
+ A = Z1 * Z2
47
+ B = A**2
48
+ C = X1 * X2
49
+ D = Y1 * Y2
50
+ E = d * C * D
51
+ F = B - E
52
+ G = B + E
53
+ H = (Y1 + X1) * (Y2 + X2)
54
+
55
+ X3 = self._field_ops.mul(A, F*(H - C - D))
56
+ Y3 = self._field_ops.mul(A, G*(D - C))
57
+ Z3 = self._field_ops.mul(F, G)
58
+
59
+ return (X3, Y3, Z3, _)
60
+
61
+ # Point doubling
62
+ def double(self, P: Tuple) -> Tuple:
63
+ X1, Y1, Z1, _ = P
64
+
65
+ B = (X1+Y1) ** 2
66
+ C = X1**2
67
+ D = Y1**2
68
+ E = C + D
69
+ H = Z1**2
70
+ J = E - 2 * H
71
+
72
+ X3 = self._field_ops.mul((B - E), J)
73
+ Y3 = self._field_ops.mul(E, (C - D))
74
+ Z3 = self._field_ops.mul(E, J)
75
+
76
+ return (X3, Y3, Z3, _)
@@ -0,0 +1,77 @@
1
+ from typing import Tuple
2
+
3
+ from eddsa_threshold.eddsa.curves.base.encoding import Encoding
4
+ from eddsa_threshold.eddsa.curves.ed448.field_ops import Ed448FieldOps
5
+ from .constants import SCALAR_SIZE, PUBLIC_KEY_SIZE, d, a, L
6
+
7
+
8
+ class Ed448Encoding(Encoding):
9
+ """
10
+ Byte encoding/decoding layer for Ed448.
11
+ """
12
+
13
+ def __init__(self, field_ops: Ed448FieldOps):
14
+ self._field = field_ops
15
+
16
+ @property
17
+ def scalar_size(self) -> int:
18
+ """Size in bytes of a private scalar."""
19
+ return SCALAR_SIZE
20
+
21
+ @property
22
+ def group_order(self) -> int:
23
+ """Group order of the field."""
24
+ return L
25
+
26
+ @property
27
+ def point_size(self) -> int:
28
+ """Size in bytes of an encoded public key point."""
29
+ return PUBLIC_KEY_SIZE
30
+
31
+ def encode_point(self, P: Tuple) -> bytes:
32
+ """Encode point P=(x, y) to bytes."""
33
+ x, y = P
34
+ y_bytes = y.to_bytes(self.point_size, byteorder='little')
35
+
36
+ y_bytes = bytearray(y_bytes)
37
+ if x & 1:
38
+ y_bytes[-1] |= 0x80
39
+
40
+ return bytes(y_bytes)
41
+
42
+ def decode_point(self, data: bytes) -> Tuple:
43
+ """Decode point from bytes to (x_lsb, y)."""
44
+ if len(data) != self.point_size:
45
+ raise ValueError("Invalid point size")
46
+
47
+ y_bytes = bytearray(data)
48
+ x_lsb = (y_bytes[-1] >> 7) & 1
49
+ y_bytes[-1] &= 0x7F # Clear the MSB
50
+
51
+ y = int.from_bytes(y_bytes, byteorder='little')
52
+ if y >= self._field.p:
53
+ raise ValueError("Invalid point encoding - y out of range")
54
+
55
+ y2 = self._field.pow(y, 2)
56
+ u = (y2 - 1) % self._field.p
57
+ v = (d * y2 - a) % self._field.p
58
+
59
+ u3 = self._field.pow(u, 3)
60
+ v3 = self._field.pow(v, 3)
61
+ u5 = self._field.pow(u, 5)
62
+
63
+ w = (u3 * v * self._field.pow((u5 * v3), ((self._field.p - 3) // 4))) % self._field.p
64
+
65
+ if (v * w**2) % self._field.p == u:
66
+ x = w
67
+ else:
68
+ raise ValueError("Invalid point encoding - SQRT failure")
69
+
70
+ if x == 0 and x_lsb == 1:
71
+ raise ValueError("Invalid point encoding - x is zero but lsb is 1")
72
+
73
+ if (x % 2) == x_lsb:
74
+ return (x, y)
75
+ else:
76
+ return (self._field.p - x, y)
77
+
@@ -0,0 +1,12 @@
1
+ from eddsa_threshold.eddsa.curves.base.field_ops import FieldOps
2
+ from .constants import p
3
+
4
+
5
+ class Ed448FieldOps(FieldOps):
6
+ """
7
+ Finite field operations modulo the prime p = 2^448 - 2^224 - 1 for Ed448.
8
+ """
9
+
10
+ @property
11
+ def p(self) -> int:
12
+ return p
@@ -0,0 +1,12 @@
1
+ from eddsa_threshold.eddsa.curves.base.scalar_ops import ScalarOps
2
+ from .constants import L, SCALAR_SIZE
3
+
4
+
5
+ class Ed448ScalarOps(ScalarOps):
6
+ @property
7
+ def order(self) -> int:
8
+ return L
9
+
10
+ @property
11
+ def identity(self) -> int:
12
+ return 0
@@ -0,0 +1 @@
1
+ """Keypair helpers."""
@@ -0,0 +1,46 @@
1
+ import os
2
+ from eddsa_threshold.eddsa.curves.ed25519.ed25519_curve import Ed25519Curve
3
+ from eddsa_threshold.eddsa.keys.keypair import Keypair
4
+ from eddsa_threshold.eddsa.curves.ed25519.constants import SEED_SIZE
5
+ from eddsa_threshold.eddsa.util.hash_bindings import sha512
6
+
7
+
8
+ class Ed25519Keypair(Keypair):
9
+ """
10
+ Ed25519 keypair implementation.
11
+
12
+ Provides methods to create an Ed25519 keypair from a private seed or to generate a fresh random keypair.
13
+ """
14
+
15
+ @classmethod
16
+ def from_private_bytes(cls, seed: bytes) -> Ed25519Keypair:
17
+ """Create Ed25519 keypair from 32-byte seed."""
18
+ if len(seed) != SEED_SIZE:
19
+ raise ValueError(f"Invalid seed size: {len(seed)} bytes (expected {SEED_SIZE} bytes)")
20
+
21
+ # Derive key according to RFC 8032 Section 5.1.5
22
+ # 1. Hash with SHA-512
23
+ hashed = sha512(seed)
24
+
25
+ # 2. Prune bits
26
+ a_bytes = bytearray(hashed[:32])
27
+ a_bytes[0] &= 248
28
+ a_bytes[31] &= 127
29
+ a_bytes[31] |= 64
30
+
31
+ # 3. Convert to integer scalar
32
+ scalar = int.from_bytes(a_bytes, byteorder='little')
33
+
34
+ # 4. Compute prefix
35
+ prefix = hashed[32:]
36
+
37
+ # 5. Compute public key
38
+ curve = Ed25519Curve()
39
+ public_bytes = curve.encode_extended_point(curve.scalar_mult(scalar))
40
+
41
+ return cls(seed, scalar, prefix, public_bytes)
42
+
43
+ @classmethod
44
+ def generate(cls) -> Ed25519Keypair:
45
+ """Generate a fresh Ed25519 keypair from a random 32-byte seed."""
46
+ return cls.from_private_bytes(os.urandom(SEED_SIZE))
@@ -0,0 +1,45 @@
1
+ import os
2
+ from eddsa_threshold.eddsa.curves.ed448.ed448_curve import Ed448Curve
3
+ from eddsa_threshold.eddsa.keys.keypair import Keypair
4
+ from eddsa_threshold.eddsa.curves.ed448.constants import SEED_SIZE
5
+ from eddsa_threshold.eddsa.util.hash_bindings import shake256
6
+
7
+
8
+ class Ed448Keypair(Keypair):
9
+ """
10
+ Ed448 keypair implementation.
11
+
12
+ Provides methods to create an Ed448 keypair from a private seed or to generate a fresh random keypair.
13
+ """
14
+
15
+ @classmethod
16
+ def from_private_bytes(cls, seed: bytes) -> Ed448Keypair:
17
+ """Create Ed448 keypair from 57-byte seed."""
18
+ if len(seed) != SEED_SIZE:
19
+ raise ValueError(f"Invalid seed size: {len(seed)} bytes (expected {SEED_SIZE} bytes)")
20
+
21
+ # Derive key according to RFC 8032 Section 5.2.5
22
+ # 1. Hash with SHAKE-256(x, 114)
23
+ hashed = shake256(seed, 114)
24
+
25
+ # 2. Prune bits
26
+ a_bytes = bytearray(hashed[:57])
27
+ a_bytes[0] &= 252
28
+ a_bytes[56] &= 0
29
+ a_bytes[55] |= 128
30
+
31
+ # 3. Convert to integer scalar
32
+ scalar = int.from_bytes(a_bytes, byteorder='little')
33
+
34
+ # 4. Compute prefix
35
+ prefix = hashed[57:]
36
+
37
+ # 5. Compute public key
38
+ curve = Ed448Curve()
39
+ public_bytes = curve.encode_extended_point(curve.scalar_mult(scalar))
40
+
41
+ return cls(seed, scalar, prefix, public_bytes)
42
+ @classmethod
43
+ def generate(cls) -> Ed448Keypair:
44
+ """Generate a fresh Ed448 keypair from a random 57-byte seed."""
45
+ return cls.from_private_bytes(os.urandom(SEED_SIZE))
@@ -0,0 +1,50 @@
1
+ from abc import ABC, abstractmethod
2
+
3
+
4
+ class Keypair(ABC):
5
+ """
6
+ Abstract base class for EdDSA keypairs.
7
+ Provides access to private seed, scalar, prefix and public key bytes.
8
+
9
+ Also defines abstract methods for keypair generation.
10
+ These allow for keypair creation from a private seed or generation of a fresh random keypair.
11
+ """
12
+
13
+ def __init__(self, seed: bytes, scalar: int, prefix: bytes, public_bytes: bytes):
14
+ """Initialize Keypair with private seed, scalar, prefix, and public key bytes."""
15
+ self._private_bytes = seed
16
+ self._scalar = scalar
17
+ self._prefix = prefix
18
+ self._public_bytes = public_bytes
19
+
20
+ @property
21
+ def private_bytes(self) -> bytes:
22
+ """Return the private seed bytes used to generate the keypair."""
23
+ return self._private_bytes
24
+
25
+ @property
26
+ def scalar(self) -> int:
27
+ """Return the integer scalar derived from the private seed."""
28
+ return self._scalar
29
+
30
+ @property
31
+ def prefix(self) -> bytes:
32
+ """Return the prefix bytes derived from the private seed."""
33
+ return self._prefix
34
+
35
+ @property
36
+ def public_bytes(self) -> bytes:
37
+ """Return the public key bytes corresponding to the keypair."""
38
+ return self._public_bytes
39
+
40
+ @classmethod
41
+ @abstractmethod
42
+ def from_private_bytes(cls, seed: bytes) -> Keypair:
43
+ """Create a keypair from the given private seed bytes."""
44
+ raise NotImplementedError
45
+
46
+ @classmethod
47
+ @abstractmethod
48
+ def generate(cls) -> Keypair:
49
+ """Generate a fresh random keypair."""
50
+ raise NotImplementedError
@@ -0,0 +1 @@
1
+ """Utility helpers."""
@@ -0,0 +1,17 @@
1
+ from typing import Optional
2
+
3
+
4
+ def dom2(phflag: int, context: Optional[bytes]) -> bytes:
5
+ """Create the DOM2 prefix for EdDSA signatures."""
6
+ if context is None:
7
+ return b""
8
+ if len(context) > 255:
9
+ raise ValueError("Context length must be at most 255 bytes.")
10
+ return (b"SigEd25519 no Ed25519 collisions" + bytes([phflag]) + bytes([len(context)]) + context)
11
+
12
+
13
+ def dom4(phflag: int, context: bytes) -> bytes:
14
+ """Create the DOM4 prefix for EdDSA signatures."""
15
+ if len(context) > 255:
16
+ raise ValueError("Context length must be at most 255 bytes.")
17
+ return (b"SigEd448" + bytes([phflag]) + bytes([len(context)]) + context)
@@ -0,0 +1,12 @@
1
+ import hashlib
2
+
3
+
4
+ def sha512(data: bytes) -> bytes:
5
+ """Compute SHA-512 hash of the input using hashlib."""
6
+ return hashlib.sha512(data).digest()
7
+
8
+ def shake256(data: bytes, outlen: int) -> bytes:
9
+ """Compute SHAKE-256 hash of the input using hashlib."""
10
+ shake = hashlib.shake_256()
11
+ shake.update(data)
12
+ return shake.digest(outlen)
@@ -0,0 +1 @@
1
+ """FROST protocol implementation."""