Skip to content

Commit ee8fea3

Browse files
authored
Merge pull request #261 from tlsfuzzer/eddsa-keys
High level API for EdDSA keys
2 parents 106798c + fbae631 commit ee8fea3

13 files changed

+633
-32
lines changed

.github/workflows/ci.yml

+3-3
Original file line numberDiff line numberDiff line change
@@ -259,17 +259,17 @@ jobs:
259259
MERGE_BASE=$(git merge-base origin/$BASE_REF HEAD)
260260
echo "MERGE_BASE:" $MERGE_BASE
261261
git checkout $MERGE_BASE
262-
instrumental -t ecdsa -i 'test.*|.*_version|.*_compat|.*_sha3' `which pytest` src/ecdsa/test*.py
262+
instrumental -t ecdsa -i '.*test_.*|.*_version|.*_compat|.*_sha3' `which pytest` src/ecdsa/test*.py
263263
instrumental -f .instrumental.cov -s
264264
instrumental -f .instrumental.cov -s | python diff-instrumental.py --save .diff-instrumental
265265
git checkout $GITHUB_SHA
266-
instrumental -t ecdsa -i 'test.*|.*_version|.*_compat|.*_sha3' `which pytest` src/ecdsa/test*.py
266+
instrumental -t ecdsa -i '.*test_.*|.*_version|.*_compat|.*_sha3' `which pytest` src/ecdsa/test*.py
267267
instrumental -f .instrumental.cov -sr
268268
instrumental -f .instrumental.cov -s | python diff-instrumental.py --read .diff-instrumental --fail-under 70 --max-difference -0.1
269269
- name: instrumental test coverage on push
270270
if: ${{ contains(matrix.opt-deps, 'instrumental') && !github.event.pull_request }}
271271
run: |
272-
instrumental -t ecdsa -i 'test.*|.*_version|.*_compat|.*_sha3' `which pytest` src/ecdsa
272+
instrumental -t ecdsa -i '.*test_.*|.*_version|.*_compat|.*_sha3' `which pytest` src/ecdsa
273273
instrumental -f .instrumental.cov -s
274274
# just log the values when merging
275275
instrumental -f .instrumental.cov -s | python diff-instrumental.py

speed.py

+2
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,8 @@ def do(setup_statements, statement):
9898
)
9999

100100
for curve in [i.name for i in curves]:
101+
if curve == "Ed25519" or curve == "Ed448":
102+
continue
101103
S1 = "from ecdsa import SigningKey, ECDH, {0}".format(curve)
102104
S2 = "our = SigningKey.generate({0})".format(curve)
103105
S3 = "remote = SigningKey.generate({0}).verifying_key".format(curve)

src/ecdsa/__init__.py

+4
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@
2626
SECP112r2,
2727
SECP128r1,
2828
SECP160r1,
29+
Ed25519,
30+
Ed448,
2931
)
3032
from .ecdh import (
3133
ECDH,
@@ -83,6 +85,8 @@
8385
SECP112r2,
8486
SECP128r1,
8587
SECP160r1,
88+
Ed25519,
89+
Ed448,
8690
six.b(""),
8791
]
8892
del _hush_pyflakes

src/ecdsa/curves.py

+36-4
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
from __future__ import division
22

33
from six import PY2
4-
from . import der, ecdsa, ellipticcurve
4+
from . import der, ecdsa, ellipticcurve, eddsa
55
from .util import orderlen, number_to_string, string_to_number
6-
from ._compat import normalise_bytes
6+
from ._compat import normalise_bytes, bit_length
77

88

99
# orderlen was defined in this module previously, so keep it in __all__,
@@ -33,6 +33,8 @@
3333
"BRAINPOOLP512r1",
3434
"PRIME_FIELD_OID",
3535
"CHARACTERISTIC_TWO_FIELD_OID",
36+
"Ed25519",
37+
"Ed448",
3638
]
3739

3840

@@ -51,8 +53,16 @@ def __init__(self, name, curve, generator, oid, openssl_name=None):
5153
self.curve = curve
5254
self.generator = generator
5355
self.order = generator.order()
54-
self.baselen = orderlen(self.order)
55-
self.verifying_key_length = 2 * orderlen(curve.p())
56+
if isinstance(curve, ellipticcurve.CurveEdTw):
57+
# EdDSA keys are special in that both private and public
58+
# are the same size (as it's defined only with compressed points)
59+
60+
# +1 for the sign bit and then round up
61+
self.baselen = (bit_length(curve.p()) + 1 + 7) // 8
62+
self.verifying_key_length = self.baselen
63+
else:
64+
self.baselen = orderlen(self.order)
65+
self.verifying_key_length = 2 * orderlen(curve.p())
5666
self.signature_length = 2 * self.baselen
5767
self.oid = oid
5868
if oid:
@@ -90,13 +100,23 @@ def to_der(self, encoding=None, point_encoding="uncompressed"):
90100
else:
91101
encoding = "explicit"
92102

103+
if encoding not in ("named_curve", "explicit"):
104+
raise ValueError(
105+
"Only 'named_curve' and 'explicit' encodings supported"
106+
)
107+
93108
if encoding == "named_curve":
94109
if not self.oid:
95110
raise UnknownCurveError(
96111
"Can't encode curve using named_curve encoding without "
97112
"associated curve OID"
98113
)
99114
return der.encode_oid(*self.oid)
115+
elif isinstance(self.curve, ellipticcurve.CurveEdTw):
116+
assert encoding == "explicit"
117+
raise UnknownCurveError(
118+
"Twisted Edwards curves don't support explicit encoding"
119+
)
100120

101121
# encode the ECParameters sequence
102122
curve_p = self.curve.p()
@@ -408,6 +428,16 @@ def from_pem(cls, string, valid_encodings=None):
408428
)
409429

410430

431+
Ed25519 = Curve(
432+
"Ed25519", eddsa.curve_ed25519, eddsa.generator_ed25519, (1, 3, 101, 112),
433+
)
434+
435+
436+
Ed448 = Curve(
437+
"Ed448", eddsa.curve_ed448, eddsa.generator_ed448, (1, 3, 101, 113),
438+
)
439+
440+
411441
# no order in particular, but keep previously added curves first
412442
curves = [
413443
NIST192p,
@@ -427,6 +457,8 @@ def from_pem(cls, string, valid_encodings=None):
427457
SECP112r2,
428458
SECP128r1,
429459
SECP160r1,
460+
Ed25519,
461+
Ed448,
430462
]
431463

432464

src/ecdsa/eddsa.py

+32-1
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,20 @@ def __init__(self, generator, public_key, public_point=None):
102102
self.curve, public_key
103103
)
104104

105+
def __eq__(self, other):
106+
if isinstance(other, PublicKey):
107+
return (
108+
self.curve == other.curve and self.__encoded == other.__encoded
109+
)
110+
return NotImplemented
111+
112+
def __ne__(self, other):
113+
return not self == other
114+
115+
@property
116+
def point(self):
117+
return self.__point
118+
105119
def public_point(self):
106120
return self.__point
107121

@@ -110,6 +124,7 @@ def public_key(self):
110124

111125
def verify(self, data, signature):
112126
"""Verify a Pure EdDSA signature over data."""
127+
data = compat26_str(data)
113128
if len(signature) != 2 * self.baselen:
114129
raise ValueError(
115130
"Invalid signature length, expected: {0} bytes".format(
@@ -152,7 +167,7 @@ def __init__(self, generator, private_key):
152167
self.baselen
153168
)
154169
)
155-
self.__private_key = private_key
170+
self.__private_key = bytes(private_key)
156171
self.__h = bytearray(self.curve.hash_func(private_key))
157172
self.__public_key = None
158173

@@ -161,6 +176,21 @@ def __init__(self, generator, private_key):
161176
scalar = bytes_to_int(a, "little")
162177
self.__s = scalar
163178

179+
@property
180+
def private_key(self):
181+
return self.__private_key
182+
183+
def __eq__(self, other):
184+
if isinstance(other, PrivateKey):
185+
return (
186+
self.curve == other.curve
187+
and self.__private_key == other.__private_key
188+
)
189+
return NotImplemented
190+
191+
def __ne__(self, other):
192+
return not self == other
193+
164194
def _key_prune(self, key):
165195
# make sure the key is not in a small subgroup
166196
h = self.curve.cofactor()
@@ -196,6 +226,7 @@ def public_key(self):
196226

197227
def sign(self, data):
198228
"""Perform a Pure EdDSA signature over data."""
229+
data = compat26_str(data)
199230
A = self.public_key().public_key()
200231

201232
prefix = self.__h[self.baselen :]

src/ecdsa/ellipticcurve.py

+4
Original file line numberDiff line numberDiff line change
@@ -1478,6 +1478,10 @@ def double(self):
14781478
return INFINITY
14791479
return PointEdwards(self.__curve, X3, Y3, Z3, T3, self.__order)
14801480

1481+
def __rmul__(self, other):
1482+
"""Multiply point by an integer."""
1483+
return self * other
1484+
14811485
def __mul__(self, other):
14821486
"""Multiply point by an integer."""
14831487
X2, Y2, Z2, T2 = self.__coords

0 commit comments

Comments
 (0)