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.
- eddsa_threshold/__init__.py +1 -0
- eddsa_threshold/eddsa/__init__.py +1 -0
- eddsa_threshold/eddsa/algorithms/__init__.py +1 -0
- eddsa_threshold/eddsa/algorithms/ed25519.py +84 -0
- eddsa_threshold/eddsa/algorithms/ed25519ctx.py +23 -0
- eddsa_threshold/eddsa/algorithms/ed25519ph.py +23 -0
- eddsa_threshold/eddsa/algorithms/ed448.py +84 -0
- eddsa_threshold/eddsa/algorithms/ed448ph.py +23 -0
- eddsa_threshold/eddsa/curves/__init__.py +1 -0
- eddsa_threshold/eddsa/curves/base/__init__.py +1 -0
- eddsa_threshold/eddsa/curves/base/edwards_curve.py +91 -0
- eddsa_threshold/eddsa/curves/base/encoding.py +54 -0
- eddsa_threshold/eddsa/curves/base/field_ops.py +48 -0
- eddsa_threshold/eddsa/curves/base/scalar_ops.py +34 -0
- eddsa_threshold/eddsa/curves/ed25519/__init__.py +1 -0
- eddsa_threshold/eddsa/curves/ed25519/constants.py +37 -0
- eddsa_threshold/eddsa/curves/ed25519/ed25519_curve.py +79 -0
- eddsa_threshold/eddsa/curves/ed25519/encoding.py +80 -0
- eddsa_threshold/eddsa/curves/ed25519/field_ops.py +12 -0
- eddsa_threshold/eddsa/curves/ed25519/scalar_ops.py +12 -0
- eddsa_threshold/eddsa/curves/ed448/__init__.py +1 -0
- eddsa_threshold/eddsa/curves/ed448/constants.py +37 -0
- eddsa_threshold/eddsa/curves/ed448/ed448_curve.py +76 -0
- eddsa_threshold/eddsa/curves/ed448/encoding.py +77 -0
- eddsa_threshold/eddsa/curves/ed448/field_ops.py +12 -0
- eddsa_threshold/eddsa/curves/ed448/scalar_ops.py +12 -0
- eddsa_threshold/eddsa/keys/__init__.py +1 -0
- eddsa_threshold/eddsa/keys/ed25519_keypair.py +46 -0
- eddsa_threshold/eddsa/keys/ed448_keypair.py +45 -0
- eddsa_threshold/eddsa/keys/keypair.py +50 -0
- eddsa_threshold/eddsa/util/__init__.py +1 -0
- eddsa_threshold/eddsa/util/dom.py +17 -0
- eddsa_threshold/eddsa/util/hash_bindings.py +12 -0
- eddsa_threshold/frost/__init__.py +1 -0
- eddsa_threshold/frost/coordinator.py +185 -0
- eddsa_threshold/frost/core/__init__.py +1 -0
- eddsa_threshold/frost/core/base/__init__.py +1 -0
- eddsa_threshold/frost/core/base/frost_hashing.py +27 -0
- eddsa_threshold/frost/core/ed25519/__init__.py +1 -0
- eddsa_threshold/frost/core/ed25519/frost_hashing.py +26 -0
- eddsa_threshold/frost/core/ed448/__init__.py +1 -0
- eddsa_threshold/frost/core/ed448/frost_hashing.py +27 -0
- eddsa_threshold/frost/core/frost_types.py +53 -0
- eddsa_threshold/frost/core/polynomial.py +39 -0
- eddsa_threshold/frost/core/secrets/__init__.py +1 -0
- eddsa_threshold/frost/core/secrets/secret_sharing.py +26 -0
- eddsa_threshold/frost/core/secrets/shamir_secret_sharing.py +41 -0
- eddsa_threshold/frost/core/util.py +109 -0
- eddsa_threshold/frost/participant.py +140 -0
- eddsa_threshold/frost/trusted_dealer.py +72 -0
- eddsa_threshold-0.2.0.dist-info/METADATA +39 -0
- eddsa_threshold-0.2.0.dist-info/RECORD +55 -0
- eddsa_threshold-0.2.0.dist-info/WHEEL +5 -0
- eddsa_threshold-0.2.0.dist-info/licenses/LICENSE +14 -0
- 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 @@
|
|
|
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 @@
|
|
|
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."""
|