Skip to content

Commit 106798c

Browse files
authored
Merge pull request #259 from tlsfuzzer/low-level-eddsa
Low level EdDSA
2 parents 9557d97 + 6bdfff0 commit 106798c

File tree

5 files changed

+871
-8
lines changed

5 files changed

+871
-8
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
*.py[cod]
22
MANIFEST
3+
htmlcov
34

45
# C extensions
56
*.so

src/ecdsa/_compat.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
"""
44
import sys
55
import re
6+
import binascii
67
from six import integer_types
78

89

@@ -15,7 +16,6 @@ def str_idx_as_int(string, index):
1516

1617

1718
if sys.version_info < (3, 0): # pragma: no branch
18-
import binascii
1919
import platform
2020

2121
def normalise_bytes(buffer_object):
@@ -60,6 +60,12 @@ def bit_length(val):
6060
def b2a_hex(val):
6161
return binascii.b2a_hex(compat26_str(val))
6262

63+
def a2b_hex(val):
64+
try:
65+
return bytearray(binascii.a2b_hex(val))
66+
except Exception as e:
67+
raise ValueError("base16 error: %s" % e)
68+
6369
def bytes_to_int(val, byteorder):
6470
"""Convert bytes to an int."""
6571
if not val:
@@ -99,6 +105,9 @@ def hmac_compat(data):
99105
def hmac_compat(data):
100106
return data
101107

108+
def compat26_str(val):
109+
return val
110+
102111
def normalise_bytes(buffer_object):
103112
"""Cast the input into array of bytes."""
104113
return memoryview(buffer_object).cast("B")
@@ -107,6 +116,12 @@ def remove_whitespace(text):
107116
"""Removes all whitespace from passed in string"""
108117
return re.sub(r"\s+", "", text, flags=re.UNICODE)
109118

119+
def a2b_hex(val):
120+
try:
121+
return bytearray(binascii.a2b_hex(bytearray(val, "ascii")))
122+
except Exception as e:
123+
raise ValueError("base16 error: %s" % e)
124+
110125
# pylint: disable=invalid-name
111126
# pylint is stupid here and deson't notice it's a function, not
112127
# constant

src/ecdsa/eddsa.py

Lines changed: 156 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,15 @@
11
"""Implementation of Edwards Digital Signature Algorithm."""
22

3+
import hashlib
4+
from ._sha3 import shake_256
35
from . import ellipticcurve
4-
from ._compat import remove_whitespace
6+
from ._compat import (
7+
remove_whitespace,
8+
bit_length,
9+
bytes_to_int,
10+
int_to_bytes,
11+
compat26_str,
12+
)
513

614
# edwards25519, defined in RFC7748
715
_p = 2 ** 255 - 19
@@ -28,7 +36,12 @@
2836
)
2937
_r = 2 ** 252 + 0x14DEF9DEA2F79CD65812631A5CF5D3ED
3038

31-
curve_ed25519 = ellipticcurve.CurveEdTw(_p, _a, _d, _h)
39+
40+
def _sha512(data):
41+
return hashlib.new("sha512", compat26_str(data)).digest()
42+
43+
44+
curve_ed25519 = ellipticcurve.CurveEdTw(_p, _a, _d, _h, _sha512)
3245
generator_ed25519 = ellipticcurve.PointEdwards(
3346
curve_ed25519, _Gx, _Gy, 1, _Gx * _Gy % _p, _r
3447
)
@@ -56,7 +69,147 @@
5669
)
5770
_r = 2 ** 446 - 0x8335DC163BB124B65129C96FDE933D8D723A70AADC873D6D54A7BB0D
5871

59-
curve_ed448 = ellipticcurve.CurveEdTw(_p, _a, _d, _h)
72+
73+
def _shake256(data):
74+
return shake_256(data, 114)
75+
76+
77+
curve_ed448 = ellipticcurve.CurveEdTw(_p, _a, _d, _h, _shake256)
6078
generator_ed448 = ellipticcurve.PointEdwards(
6179
curve_ed448, _Gx, _Gy, 1, _Gx * _Gy % _p, _r
6280
)
81+
82+
83+
class PublicKey(object):
84+
"""Public key for the Edwards Digital Signature Algorithm."""
85+
86+
def __init__(self, generator, public_key, public_point=None):
87+
self.generator = generator
88+
self.curve = generator.curve()
89+
self.__encoded = public_key
90+
# plus one for the sign bit and round up
91+
self.baselen = (bit_length(self.curve.p()) + 1 + 7) // 8
92+
if len(public_key) != self.baselen:
93+
raise ValueError(
94+
"Incorrect size of the public key, expected: {0} bytes".format(
95+
self.baselen
96+
)
97+
)
98+
if public_point:
99+
self.__point = public_point
100+
else:
101+
self.__point = ellipticcurve.PointEdwards.from_bytes(
102+
self.curve, public_key
103+
)
104+
105+
def public_point(self):
106+
return self.__point
107+
108+
def public_key(self):
109+
return self.__encoded
110+
111+
def verify(self, data, signature):
112+
"""Verify a Pure EdDSA signature over data."""
113+
if len(signature) != 2 * self.baselen:
114+
raise ValueError(
115+
"Invalid signature length, expected: {0} bytes".format(
116+
2 * self.baselen
117+
)
118+
)
119+
R = ellipticcurve.PointEdwards.from_bytes(
120+
self.curve, signature[: self.baselen]
121+
)
122+
S = bytes_to_int(signature[self.baselen :], "little")
123+
if S >= self.generator.order():
124+
raise ValueError("Invalid signature")
125+
126+
dom = bytearray()
127+
if self.curve == curve_ed448:
128+
dom = bytearray(b"SigEd448" + b"\x00\x00")
129+
130+
k = bytes_to_int(
131+
self.curve.hash_func(dom + R.to_bytes() + self.__encoded + data),
132+
"little",
133+
)
134+
135+
if self.generator * S != self.__point * k + R:
136+
raise ValueError("Invalid signature")
137+
138+
return True
139+
140+
141+
class PrivateKey(object):
142+
"""Private key for the Edwards Digital Signature Algorithm."""
143+
144+
def __init__(self, generator, private_key):
145+
self.generator = generator
146+
self.curve = generator.curve()
147+
# plus one for the sign bit and round up
148+
self.baselen = (bit_length(self.curve.p()) + 1 + 7) // 8
149+
if len(private_key) != self.baselen:
150+
raise ValueError(
151+
"Incorrect size of private key, expected: {0} bytes".format(
152+
self.baselen
153+
)
154+
)
155+
self.__private_key = private_key
156+
self.__h = bytearray(self.curve.hash_func(private_key))
157+
self.__public_key = None
158+
159+
a = self.__h[: self.baselen]
160+
a = self._key_prune(a)
161+
scalar = bytes_to_int(a, "little")
162+
self.__s = scalar
163+
164+
def _key_prune(self, key):
165+
# make sure the key is not in a small subgroup
166+
h = self.curve.cofactor()
167+
if h == 4:
168+
h_log = 2
169+
elif h == 8:
170+
h_log = 3
171+
else:
172+
raise ValueError("Only cofactor 4 and 8 curves supported")
173+
key[0] &= ~((1 << h_log) - 1)
174+
175+
# ensure the highest bit is set but no higher
176+
l = bit_length(self.curve.p())
177+
if l % 8 == 0:
178+
key[-1] = 0
179+
key[-2] |= 0x80
180+
else:
181+
key[-1] = key[-1] & (1 << (l % 8)) - 1 | 1 << (l % 8) - 1
182+
return key
183+
184+
def public_key(self):
185+
"""Generate the public key based on the included private key"""
186+
if self.__public_key:
187+
return self.__public_key
188+
189+
public_point = self.generator * self.__s
190+
191+
self.__public_key = PublicKey(
192+
self.generator, public_point.to_bytes(), public_point
193+
)
194+
195+
return self.__public_key
196+
197+
def sign(self, data):
198+
"""Perform a Pure EdDSA signature over data."""
199+
A = self.public_key().public_key()
200+
201+
prefix = self.__h[self.baselen :]
202+
203+
dom = bytearray()
204+
if self.curve == curve_ed448:
205+
dom = bytearray(b"SigEd448" + b"\x00\x00")
206+
207+
r = bytes_to_int(self.curve.hash_func(dom + prefix + data), "little")
208+
R = (self.generator * r).to_bytes()
209+
210+
k = bytes_to_int(self.curve.hash_func(dom + R + A + data), "little")
211+
k %= self.generator.order()
212+
213+
S = (r + k * self.__s) % self.generator.order()
214+
215+
return R + int_to_bytes(S, self.baselen, "little")

0 commit comments

Comments
 (0)