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 @@
|
|
|
1
|
+
"""EdDSA Threshold package."""
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""EdDSA primitives."""
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""EdDSA signing algorithms."""
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
from typing import Callable
|
|
2
|
+
from eddsa_threshold.eddsa.curves.ed25519.ed25519_curve import Ed25519Curve
|
|
3
|
+
from eddsa_threshold.eddsa.curves.ed25519.scalar_ops import Ed25519ScalarOps
|
|
4
|
+
from eddsa_threshold.eddsa.curves.ed25519.constants import SCALAR_SIZE
|
|
5
|
+
from eddsa_threshold.eddsa.keys.ed25519_keypair import Ed25519Keypair
|
|
6
|
+
from eddsa_threshold.eddsa.util.dom import dom2
|
|
7
|
+
from eddsa_threshold.eddsa.util.hash_bindings import sha512
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Ed25519():
|
|
11
|
+
"""
|
|
12
|
+
EdDSA using the Ed25519 curve.
|
|
13
|
+
Implements signing and verification methods.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
@staticmethod
|
|
17
|
+
def sign(message: bytes, keypair: Ed25519Keypair) -> bytes:
|
|
18
|
+
"""Sign a message using the provided Ed25519 keypair."""
|
|
19
|
+
|
|
20
|
+
return Ed25519._sign(message, keypair, ph=lambda m: m, dom2=dom2(0, None))
|
|
21
|
+
|
|
22
|
+
@staticmethod
|
|
23
|
+
def verify(signature: bytes, message: bytes, public_key: bytes) -> bool:
|
|
24
|
+
"""Verify a signature for a message using the provided Ed25519 public key."""
|
|
25
|
+
|
|
26
|
+
return Ed25519._verify(signature, message, public_key, ph=lambda m: m, dom2=dom2(0, None))
|
|
27
|
+
|
|
28
|
+
@staticmethod
|
|
29
|
+
def _sign(message: bytes, keypair: Ed25519Keypair, ph: Callable, dom2: bytes) -> bytes:
|
|
30
|
+
"""Internal sign method as basis for subclasses."""
|
|
31
|
+
|
|
32
|
+
curve = Ed25519Curve()
|
|
33
|
+
|
|
34
|
+
# Sign message according to RFC 8032 Section 5.1.6
|
|
35
|
+
# 1. Get precomputed prefix
|
|
36
|
+
prefix = keypair.prefix
|
|
37
|
+
|
|
38
|
+
# 2. Compute the nonce
|
|
39
|
+
r = int.from_bytes(sha512(dom2 + prefix + ph(message)), byteorder='little')
|
|
40
|
+
|
|
41
|
+
# 3. Compute the R point
|
|
42
|
+
r = curve.scalar_ops.reduce(r)
|
|
43
|
+
R = curve.encode_extended_point(curve.scalar_mult(r))
|
|
44
|
+
|
|
45
|
+
# 4. Compute the challenge
|
|
46
|
+
k = int.from_bytes(sha512(dom2 + R + keypair.public_bytes + ph(message)), byteorder='little')
|
|
47
|
+
|
|
48
|
+
# 5. Compute the S value
|
|
49
|
+
k = curve.scalar_ops.reduce(k)
|
|
50
|
+
S = curve.scalar_ops.reduce(r + k * keypair.scalar)
|
|
51
|
+
|
|
52
|
+
return R + curve._encoding.encode_scalar(S)
|
|
53
|
+
|
|
54
|
+
@staticmethod
|
|
55
|
+
def _verify(signature: bytes, message: bytes, public_key: bytes, ph: Callable, dom2: bytes) -> bool:
|
|
56
|
+
"""Internal verify method as basis for subclasses."""
|
|
57
|
+
|
|
58
|
+
curve = Ed25519Curve()
|
|
59
|
+
|
|
60
|
+
# Verify signature according to RFC 8032 Section 5.1.7
|
|
61
|
+
if len(signature) != 64:
|
|
62
|
+
return False
|
|
63
|
+
|
|
64
|
+
try:
|
|
65
|
+
# 1. Decode R and S from the signature
|
|
66
|
+
R = curve.decode_point(signature[:SCALAR_SIZE])
|
|
67
|
+
S = curve._encoding.decode_scalar(signature[SCALAR_SIZE:])
|
|
68
|
+
if S >= curve.scalar_ops.order or S < 0:
|
|
69
|
+
return False
|
|
70
|
+
|
|
71
|
+
A = curve.decode_point(public_key)
|
|
72
|
+
|
|
73
|
+
# 2. Compute the challenge
|
|
74
|
+
k = int.from_bytes(sha512(dom2 + signature[:SCALAR_SIZE] + public_key + ph(message)), byteorder='little')
|
|
75
|
+
k = curve.scalar_ops.reduce(k)
|
|
76
|
+
|
|
77
|
+
# 3. Verify the equation [S]B = R + [k]A
|
|
78
|
+
left = curve.scalar_mult(S)
|
|
79
|
+
right = curve.add(curve.affine_to_extended(R), curve.scalar_mult(k, curve.affine_to_extended(A)))
|
|
80
|
+
|
|
81
|
+
return curve.extended_to_affine(left) == curve.extended_to_affine(right)
|
|
82
|
+
|
|
83
|
+
except ValueError:
|
|
84
|
+
return False
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
from eddsa_threshold.eddsa.algorithms.ed25519 import Ed25519
|
|
2
|
+
from eddsa_threshold.eddsa.keys.ed25519_keypair import Ed25519Keypair
|
|
3
|
+
from eddsa_threshold.eddsa.util.dom import dom2
|
|
4
|
+
from eddsa_threshold.eddsa.util.hash_bindings import sha512
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Ed25519CTX(Ed25519):
|
|
8
|
+
"""
|
|
9
|
+
EdDSA using the Ed25519 curve and context.
|
|
10
|
+
Implements signing and verification methods.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
@staticmethod
|
|
14
|
+
def sign(message: bytes, keypair: Ed25519Keypair, context: bytes = b"") -> bytes:
|
|
15
|
+
"""Sign a message using the provided Ed25519 keypair. Uses context (SHOULD not be emtpy)."""
|
|
16
|
+
|
|
17
|
+
return Ed25519CTX._sign(message, keypair, ph=lambda m: m, dom2=dom2(0, context))
|
|
18
|
+
|
|
19
|
+
@staticmethod
|
|
20
|
+
def verify(signature: bytes, message: bytes, public_key: bytes, context: bytes = b"") -> bool:
|
|
21
|
+
"""Verify a signature for a message using the provided Ed25519 public key. Uses context (SHOULD not be emtpy)."""
|
|
22
|
+
|
|
23
|
+
return Ed25519CTX._verify(signature, message, public_key, ph=lambda m: m, dom2=dom2(0, context))
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
from eddsa_threshold.eddsa.algorithms.ed25519 import Ed25519
|
|
2
|
+
from eddsa_threshold.eddsa.keys.ed25519_keypair import Ed25519Keypair
|
|
3
|
+
from eddsa_threshold.eddsa.util.dom import dom2
|
|
4
|
+
from eddsa_threshold.eddsa.util.hash_bindings import sha512
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Ed25519PH(Ed25519):
|
|
8
|
+
"""
|
|
9
|
+
EdDSA using the Ed25519 curve and pre-hashing.
|
|
10
|
+
Implements signing and verification methods.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
@staticmethod
|
|
14
|
+
def sign(message: bytes, keypair: Ed25519Keypair, context: bytes = b"") -> bytes:
|
|
15
|
+
"""Sign a message using the provided Ed25519 keypair. Uses pre-hashing and context (set to empty by default)."""
|
|
16
|
+
|
|
17
|
+
return Ed25519PH._sign(message, keypair, ph=sha512, dom2=dom2(1, context))
|
|
18
|
+
|
|
19
|
+
@staticmethod
|
|
20
|
+
def verify(signature: bytes, message: bytes, public_key: bytes, context: bytes = b"") -> bool:
|
|
21
|
+
"""Verify a signature for a message using the provided Ed25519 public key. Uses pre-hashing and context (set to empty by default)."""
|
|
22
|
+
|
|
23
|
+
return Ed25519PH._verify(signature, message, public_key, ph=sha512, dom2=dom2(1, context))
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
from typing import Callable
|
|
2
|
+
from eddsa_threshold.eddsa.curves.ed448.constants import SCALAR_SIZE, SIGNATURE_SIZE
|
|
3
|
+
from eddsa_threshold.eddsa.curves.ed448.ed448_curve import Ed448Curve
|
|
4
|
+
from eddsa_threshold.eddsa.curves.ed448.scalar_ops import Ed448ScalarOps
|
|
5
|
+
from eddsa_threshold.eddsa.keys.ed448_keypair import Ed448Keypair
|
|
6
|
+
from eddsa_threshold.eddsa.util.dom import dom4
|
|
7
|
+
from eddsa_threshold.eddsa.util.hash_bindings import shake256
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Ed448():
|
|
11
|
+
"""
|
|
12
|
+
EdDSA using the Ed448 curve.
|
|
13
|
+
Implements signing and verification methods.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
@staticmethod
|
|
17
|
+
def sign(message: bytes, keypair: Ed448Keypair, context: bytes) -> bytes:
|
|
18
|
+
"""Sign a message using the provided Ed448 keypair."""
|
|
19
|
+
|
|
20
|
+
return Ed448._sign(message, keypair, ph=lambda m: m, dom4=dom4(0, context))
|
|
21
|
+
|
|
22
|
+
@staticmethod
|
|
23
|
+
def verify(signature: bytes, message: bytes, public_key: bytes, context: bytes) -> bool:
|
|
24
|
+
"""Verify a signature for a message using the provided Ed448 public key."""
|
|
25
|
+
|
|
26
|
+
return Ed448._verify(signature, message, public_key, ph=lambda m: m, dom4=dom4(0, context))
|
|
27
|
+
|
|
28
|
+
@staticmethod
|
|
29
|
+
def _sign(message: bytes, keypair: Ed448Keypair, ph: Callable, dom4: bytes) -> bytes:
|
|
30
|
+
"""Internal sign method as basis for subclasses."""
|
|
31
|
+
|
|
32
|
+
curve = Ed448Curve()
|
|
33
|
+
|
|
34
|
+
# Sign message according to RFC 8032 Section 5.2.6
|
|
35
|
+
# 1. Get precomputed prefix
|
|
36
|
+
prefix = keypair.prefix
|
|
37
|
+
|
|
38
|
+
# 2. Compute the nonce
|
|
39
|
+
r = int.from_bytes(shake256(dom4 + prefix + ph(message), 114), byteorder='little')
|
|
40
|
+
|
|
41
|
+
# 3. Compute the R point
|
|
42
|
+
r = curve.scalar_ops.reduce(r)
|
|
43
|
+
R = curve.encode_extended_point(curve.scalar_mult(r))
|
|
44
|
+
|
|
45
|
+
# 4. Compute the challenge
|
|
46
|
+
k = int.from_bytes(shake256(dom4 + R + keypair.public_bytes + ph(message), 114), byteorder='little')
|
|
47
|
+
|
|
48
|
+
# 5. Compute the S value
|
|
49
|
+
k = curve.scalar_ops.reduce(k)
|
|
50
|
+
S = curve.scalar_ops.reduce(r + k * keypair.scalar)
|
|
51
|
+
|
|
52
|
+
return R + curve._encoding.encode_scalar(S)
|
|
53
|
+
|
|
54
|
+
@staticmethod
|
|
55
|
+
def _verify(signature: bytes, message: bytes, public_key: bytes, ph: Callable, dom4: bytes) -> bool:
|
|
56
|
+
"""Internal verify method as basis for subclasses."""
|
|
57
|
+
|
|
58
|
+
curve = Ed448Curve()
|
|
59
|
+
|
|
60
|
+
# Verify signature according to RFC 8032 Section 5.2.7
|
|
61
|
+
if len(signature) != SIGNATURE_SIZE:
|
|
62
|
+
return False
|
|
63
|
+
|
|
64
|
+
try:
|
|
65
|
+
# 1. Decode R and S from the signature
|
|
66
|
+
R = curve.decode_point(signature[:SCALAR_SIZE])
|
|
67
|
+
S = curve._encoding.decode_scalar(signature[SCALAR_SIZE:])
|
|
68
|
+
if S >= curve.scalar_ops.order or S < 0:
|
|
69
|
+
return False
|
|
70
|
+
|
|
71
|
+
A = curve.decode_point(public_key)
|
|
72
|
+
|
|
73
|
+
# 2. Compute the challenge
|
|
74
|
+
k = int.from_bytes(shake256(dom4 + signature[:SCALAR_SIZE] + public_key + ph(message), 114), byteorder='little')
|
|
75
|
+
k = curve.scalar_ops.reduce(k)
|
|
76
|
+
|
|
77
|
+
# 3. Verify the equation [S]B = R + [k]A
|
|
78
|
+
left = curve.scalar_mult(S)
|
|
79
|
+
right = curve.add(curve.affine_to_extended(R), curve.scalar_mult(k, curve.affine_to_extended(A)))
|
|
80
|
+
|
|
81
|
+
return curve.extended_to_affine(left) == curve.extended_to_affine(right)
|
|
82
|
+
|
|
83
|
+
except ValueError:
|
|
84
|
+
return False
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
from eddsa_threshold.eddsa.algorithms.ed448 import Ed448
|
|
2
|
+
from eddsa_threshold.eddsa.keys.ed448_keypair import Ed448Keypair
|
|
3
|
+
from eddsa_threshold.eddsa.util.dom import dom4
|
|
4
|
+
from eddsa_threshold.eddsa.util.hash_bindings import shake256
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Ed448PH(Ed448):
|
|
8
|
+
"""
|
|
9
|
+
EdDSA using the Ed448 curve and pre-hashing.
|
|
10
|
+
Implements signing and verification methods.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
@staticmethod
|
|
14
|
+
def sign(message: bytes, keypair: Ed448Keypair, context: bytes = b"") -> bytes:
|
|
15
|
+
"""Sign a message using the provided Ed448 keypair."""
|
|
16
|
+
|
|
17
|
+
return Ed448PH._sign(message, keypair, ph=lambda m: shake256(m, 64), dom4=dom4(1, context))
|
|
18
|
+
|
|
19
|
+
@staticmethod
|
|
20
|
+
def verify(signature: bytes, message: bytes, public_key: bytes, context: bytes = b"") -> bool:
|
|
21
|
+
"""Verify a signature for a message using the provided Ed448 public key."""
|
|
22
|
+
|
|
23
|
+
return Ed448PH._verify(signature, message, public_key, ph=lambda m: shake256(m, 64), dom4=dom4(1, context))
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Curve implementations."""
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Base curve abstractions."""
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
from typing import Tuple
|
|
3
|
+
|
|
4
|
+
from eddsa_threshold.eddsa.curves.base.encoding import Encoding
|
|
5
|
+
from eddsa_threshold.eddsa.curves.base.field_ops import FieldOps
|
|
6
|
+
from eddsa_threshold.eddsa.curves.base.scalar_ops import ScalarOps
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class EdwardsCurve(ABC):
|
|
10
|
+
"""
|
|
11
|
+
Abstract base class for Edwards curves.
|
|
12
|
+
|
|
13
|
+
Provides methods for point encoding/decoding, coordinate conversions, scalar multiplication, and negation.
|
|
14
|
+
|
|
15
|
+
Concrete implementations must provide field operations, encoding, the base point, and point addition/doubling.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
@property
|
|
19
|
+
@abstractmethod
|
|
20
|
+
def field(self) -> FieldOps:
|
|
21
|
+
"""Return the FieldOps implementation."""
|
|
22
|
+
raise NotImplementedError
|
|
23
|
+
|
|
24
|
+
@property
|
|
25
|
+
@abstractmethod
|
|
26
|
+
def encoding(self) -> Encoding:
|
|
27
|
+
"""Return the Encoding implementation."""
|
|
28
|
+
raise NotImplementedError
|
|
29
|
+
|
|
30
|
+
@property
|
|
31
|
+
@abstractmethod
|
|
32
|
+
def scalar_ops(self) -> ScalarOps:
|
|
33
|
+
"""Return the ScalarOps implementation."""
|
|
34
|
+
raise NotImplementedError
|
|
35
|
+
|
|
36
|
+
@property
|
|
37
|
+
@abstractmethod
|
|
38
|
+
def base_point(self) -> Tuple:
|
|
39
|
+
"""Return affine base point as (x, y)."""
|
|
40
|
+
raise NotImplementedError
|
|
41
|
+
|
|
42
|
+
def encode_extended_point(self, P: Tuple) -> bytes:
|
|
43
|
+
"""Encode point from (X, Y, Z, T) to bytes."""
|
|
44
|
+
return self.encode_affine_point(self.extended_to_affine(P))
|
|
45
|
+
|
|
46
|
+
def encode_affine_point(self, P: Tuple) -> bytes:
|
|
47
|
+
"""Encode point from (x, y) to bytes."""
|
|
48
|
+
return self.encoding.encode_point(P)
|
|
49
|
+
|
|
50
|
+
def decode_point(self, data: bytes) -> Tuple:
|
|
51
|
+
"""Decode point from bytes to (x, y)."""
|
|
52
|
+
return self.encoding.decode_point(data)
|
|
53
|
+
|
|
54
|
+
def affine_to_extended(self, P: Tuple):
|
|
55
|
+
"""Convert (x, y) → (X, Y, Z, T)."""
|
|
56
|
+
x, y = P
|
|
57
|
+
return (x, y, 1, self.field.mul(x, y))
|
|
58
|
+
|
|
59
|
+
def extended_to_affine(self, P: Tuple):
|
|
60
|
+
"""Convert (X, Y, Z, T) → (x, y)."""
|
|
61
|
+
X, Y, Z, _ = P
|
|
62
|
+
z_inv = self.field.inv(Z)
|
|
63
|
+
return (self.field.mul(X, z_inv), self.field.mul(Y, z_inv))
|
|
64
|
+
|
|
65
|
+
@abstractmethod
|
|
66
|
+
def add(self, P: Tuple, Q: Tuple) -> Tuple:
|
|
67
|
+
"""Add points P and Q."""
|
|
68
|
+
raise NotImplementedError
|
|
69
|
+
|
|
70
|
+
@abstractmethod
|
|
71
|
+
def double(self, P: Tuple) -> Tuple:
|
|
72
|
+
"""Double point P."""
|
|
73
|
+
raise NotImplementedError
|
|
74
|
+
|
|
75
|
+
def scalar_mult(self, k: int, P=None) -> Tuple:
|
|
76
|
+
"""Multiply point P by scalar k using double-and-add algorithm. If P is None, use the base point."""
|
|
77
|
+
if P is None:
|
|
78
|
+
P = self.affine_to_extended(self.base_point)
|
|
79
|
+
|
|
80
|
+
Q = (0, 1, 1, 0) # Neutral element in extended coordinates
|
|
81
|
+
for bit in reversed(bin(k)[2:]):
|
|
82
|
+
if bit == '1':
|
|
83
|
+
Q = self.add(Q, P)
|
|
84
|
+
P = self.double(P)
|
|
85
|
+
|
|
86
|
+
return Q
|
|
87
|
+
|
|
88
|
+
def negate(self, P: Tuple) -> Tuple:
|
|
89
|
+
"""Negate point P."""
|
|
90
|
+
X, Y, Z, T = P
|
|
91
|
+
return (self.field.neg(X), Y, Z, self.field.neg(T))
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
from typing import Tuple
|
|
3
|
+
|
|
4
|
+
from eddsa_threshold.eddsa.curves.base.field_ops import FieldOps
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Encoding(ABC):
|
|
8
|
+
"""
|
|
9
|
+
Abstract byte encoding/decoding layer.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
@abstractmethod
|
|
13
|
+
def __init__(self, FieldOps: FieldOps):
|
|
14
|
+
"""Initialize with field operations."""
|
|
15
|
+
raise NotImplementedError
|
|
16
|
+
|
|
17
|
+
@property
|
|
18
|
+
@abstractmethod
|
|
19
|
+
def scalar_size(self) -> int:
|
|
20
|
+
"""Size in bytes of a private scalar."""
|
|
21
|
+
raise NotImplementedError
|
|
22
|
+
|
|
23
|
+
@property
|
|
24
|
+
@abstractmethod
|
|
25
|
+
def group_order(self) -> int:
|
|
26
|
+
"""Group order of the field."""
|
|
27
|
+
raise NotImplementedError
|
|
28
|
+
|
|
29
|
+
@property
|
|
30
|
+
@abstractmethod
|
|
31
|
+
def point_size(self) -> int:
|
|
32
|
+
"""Size in bytes of an encoded public key point."""
|
|
33
|
+
raise NotImplementedError
|
|
34
|
+
|
|
35
|
+
@abstractmethod
|
|
36
|
+
def encode_point(self, P: Tuple) -> bytes:
|
|
37
|
+
raise NotImplementedError
|
|
38
|
+
|
|
39
|
+
@abstractmethod
|
|
40
|
+
def decode_point(self, data: bytes) -> Tuple:
|
|
41
|
+
raise NotImplementedError
|
|
42
|
+
|
|
43
|
+
def encode_scalar(self, x: int) -> bytes:
|
|
44
|
+
"""Serialize a scalar to bytes (little-endian)."""
|
|
45
|
+
return x.to_bytes(self.scalar_size, byteorder='little')
|
|
46
|
+
|
|
47
|
+
def decode_scalar(self, data: bytes) -> int:
|
|
48
|
+
"""Deserialize bytes to a scalar (little-endian)."""
|
|
49
|
+
if len(data) != self.scalar_size:
|
|
50
|
+
raise ValueError(f"Invalid scalar size: expected {self.scalar_size} bytes")
|
|
51
|
+
value = int.from_bytes(data, byteorder='little')
|
|
52
|
+
if value < 0 or value >= self.group_order:
|
|
53
|
+
raise ValueError("Deserialized scalar is out of range")
|
|
54
|
+
return value
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class FieldOps(ABC):
|
|
5
|
+
"""
|
|
6
|
+
Abstract base class for finite field operations modulo a prime p.
|
|
7
|
+
|
|
8
|
+
All operations return integers representing field elements.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
@property
|
|
12
|
+
@abstractmethod
|
|
13
|
+
def p(self) -> int:
|
|
14
|
+
"""Prime modulus for the field GF(p)."""
|
|
15
|
+
raise NotImplementedError
|
|
16
|
+
|
|
17
|
+
def add(self, x: int, y: int) -> int:
|
|
18
|
+
"""Addition in the field GF(p)."""
|
|
19
|
+
return (x + y) % self.p
|
|
20
|
+
|
|
21
|
+
def sub(self, x: int, y: int) -> int:
|
|
22
|
+
"""Subtraction in the field GF(p)."""
|
|
23
|
+
return (x - y) % self.p
|
|
24
|
+
|
|
25
|
+
def mul(self, x: int, y: int) -> int:
|
|
26
|
+
"""Multiplication in the field GF(p)."""
|
|
27
|
+
return (x * y) % self.p
|
|
28
|
+
|
|
29
|
+
def neg(self, x: int) -> int:
|
|
30
|
+
"""Negation in the field GF(p)."""
|
|
31
|
+
return -x % self.p
|
|
32
|
+
|
|
33
|
+
def inv(self, x: int) -> int:
|
|
34
|
+
"""Multiplicative inverse in the field GF(p)."""
|
|
35
|
+
# https://en.wikipedia.org/wiki/Finite_field_arithmetic
|
|
36
|
+
return pow(x, self.p - 2, self.p)
|
|
37
|
+
|
|
38
|
+
def sqr(self, x: int) -> int:
|
|
39
|
+
"""Squaring in the field GF(p)."""
|
|
40
|
+
return (x * x) % self.p
|
|
41
|
+
|
|
42
|
+
def pow(self, x: int, e: int) -> int:
|
|
43
|
+
"""Exponentiation in the field GF(p)."""
|
|
44
|
+
return pow(x, e, self.p)
|
|
45
|
+
|
|
46
|
+
def reduce(self, x: int) -> int:
|
|
47
|
+
"""Reduce x modulo p."""
|
|
48
|
+
return x % self.p
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
from secrets import randbelow
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class ScalarOps(ABC):
|
|
6
|
+
"""
|
|
7
|
+
Abstract class representing scalar-related math modulo the group order.
|
|
8
|
+
|
|
9
|
+
All operations return integers representing scalars.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
@property
|
|
13
|
+
@abstractmethod
|
|
14
|
+
def order(self) -> int:
|
|
15
|
+
raise NotImplementedError
|
|
16
|
+
|
|
17
|
+
@property
|
|
18
|
+
@abstractmethod
|
|
19
|
+
def identity(self) -> int:
|
|
20
|
+
"""Return the identity scalar."""
|
|
21
|
+
raise NotImplementedError
|
|
22
|
+
|
|
23
|
+
def inv(self, x: int) -> int:
|
|
24
|
+
"""Multiplicative inverse in the field GF(order)."""
|
|
25
|
+
# https://en.wikipedia.org/wiki/Finite_field_arithmetic
|
|
26
|
+
return pow(x, self.order - 2, self.order)
|
|
27
|
+
|
|
28
|
+
def reduce(self, k: int) -> int:
|
|
29
|
+
"""Reduce any integer modulo the group order."""
|
|
30
|
+
return k % self.order
|
|
31
|
+
|
|
32
|
+
def random_scalar(self) -> int:
|
|
33
|
+
"""Fine for this project; NOT PRODUCTION SECURE."""
|
|
34
|
+
return randbelow(self.order)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Ed25519 curve implementation."""
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Constants for Ed25519
|
|
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**255 - 19
|
|
10
|
+
|
|
11
|
+
# Bit size of the field
|
|
12
|
+
b = 256
|
|
13
|
+
|
|
14
|
+
# base 2 logarithm of cofactor
|
|
15
|
+
c = 3
|
|
16
|
+
|
|
17
|
+
n = 254
|
|
18
|
+
|
|
19
|
+
# Curve Parameters d/a
|
|
20
|
+
# d = -121665 * pow(121666, -1, p) % p
|
|
21
|
+
d = 37095705934669439343138083508754565189542113879843219016388785533085940283555
|
|
22
|
+
a = -1
|
|
23
|
+
|
|
24
|
+
# Base point of the curve
|
|
25
|
+
BASE_X = 15112221349535400772501151409588531511454012693041857206046113283949847762202
|
|
26
|
+
BASE_Y = 46316835694926478169428394003475163141307993866256225615783033603165251855960
|
|
27
|
+
BASE = (BASE_X, BASE_Y)
|
|
28
|
+
IDENTITY = (0, 1)
|
|
29
|
+
|
|
30
|
+
# Order of ed25519
|
|
31
|
+
L = 2**252+27742317777372353535851937790883648493
|
|
32
|
+
|
|
33
|
+
# Encoding sizes (in bytes)
|
|
34
|
+
SCALAR_SIZE = 32 # size of private scalar
|
|
35
|
+
PUBLIC_KEY_SIZE = 32 # size of encoded public key
|
|
36
|
+
SIGNATURE_SIZE = 64 # R || S
|
|
37
|
+
SEED_SIZE = 32 # seed length specified by RFC 8032
|
|
@@ -0,0 +1,79 @@
|
|
|
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 Ed25519ScalarOps
|
|
7
|
+
from .encoding import Ed25519Encoding
|
|
8
|
+
from .field_ops import Ed25519FieldOps
|
|
9
|
+
from .constants import d, BASE
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Ed25519Curve(EdwardsCurve):
|
|
13
|
+
"""
|
|
14
|
+
Ed25519 curve implementation.
|
|
15
|
+
|
|
16
|
+
See base class EdwardsCurve for method descriptions.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
def __init__(self):
|
|
20
|
+
self._field_ops = Ed25519FieldOps()
|
|
21
|
+
self._encoding = Ed25519Encoding(self._field_ops)
|
|
22
|
+
self._scalar_ops = Ed25519ScalarOps()
|
|
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, T1 = P
|
|
44
|
+
X2, Y2, Z2, T2 = Q
|
|
45
|
+
|
|
46
|
+
A = (Y1 - X1) * (Y2 - X2)
|
|
47
|
+
B = (Y1 + X1) * (Y2 + X2)
|
|
48
|
+
C = T1 * 2 * self.d * T2
|
|
49
|
+
D = Z1 * 2 * Z2
|
|
50
|
+
E = B - A
|
|
51
|
+
F = D - C
|
|
52
|
+
G = D + C
|
|
53
|
+
H = B + A
|
|
54
|
+
|
|
55
|
+
X3 = self._field_ops.mul(E, F)
|
|
56
|
+
Y3 = self._field_ops.mul(G, H)
|
|
57
|
+
T3 = self._field_ops.mul(E, H)
|
|
58
|
+
Z3 = self._field_ops.mul(F, G)
|
|
59
|
+
|
|
60
|
+
return (X3, Y3, Z3, T3)
|
|
61
|
+
|
|
62
|
+
# Point doubling
|
|
63
|
+
def double(self, P: Tuple) -> Tuple:
|
|
64
|
+
X1, Y1, Z1, T1 = P
|
|
65
|
+
|
|
66
|
+
A = X1**2
|
|
67
|
+
B = Y1**2
|
|
68
|
+
C = 2 * Z1**2
|
|
69
|
+
H = A + B
|
|
70
|
+
E = H - (X1 + Y1)**2
|
|
71
|
+
G = A - B
|
|
72
|
+
F = C + G
|
|
73
|
+
|
|
74
|
+
X3 = self._field_ops.mul(E, F)
|
|
75
|
+
Y3 = self._field_ops.mul(G, H)
|
|
76
|
+
T3 = self._field_ops.mul(E, H)
|
|
77
|
+
Z3 = self._field_ops.mul(F, G)
|
|
78
|
+
|
|
79
|
+
return (X3, Y3, Z3, T3)
|