4949
5050from six import python_2_unicode_compatible
5151from . import numbertheory
52- from ._compat import normalise_bytes
52+ from ._compat import normalise_bytes , int_to_bytes , bit_length , bytes_to_int
5353from .errors import MalformedPointError
5454from .util import orderlen , string_to_number , number_to_string
5555
@@ -278,6 +278,41 @@ def _from_hybrid(cls, data, raw_encoding_length, validate_encoding):
278278
279279 return x , y
280280
281+ @classmethod
282+ def _from_edwards (cls , curve , data ):
283+ """Decode a point on an Edwards curve."""
284+ data = bytearray (data )
285+ p = curve .p ()
286+ # add 1 for the sign bit and then round up
287+ exp_len = (bit_length (p ) + 1 + 7 ) // 8
288+ if len (data ) != exp_len :
289+ raise MalformedPointError ("Point length doesn't match the curve." )
290+ x_0 = (data [- 1 ] & 0x80 ) >> 7
291+
292+ data [- 1 ] &= 0x80 - 1
293+
294+ y = bytes_to_int (data , "little" )
295+ if GMPY :
296+ y = mpz (y )
297+
298+ x2 = (
299+ (y * y - 1 )
300+ * numbertheory .inverse_mod (curve .d () * y * y - curve .a (), p )
301+ % p
302+ )
303+
304+ try :
305+ x = numbertheory .square_root_mod_prime (x2 , p )
306+ except numbertheory .SquareRootError as e :
307+ raise MalformedPointError (
308+ "Encoding does not correspond to a point on curve" , e
309+ )
310+
311+ if x % 2 != x_0 :
312+ x = - x % p
313+
314+ return x , y
315+
281316 @classmethod
282317 def from_bytes (
283318 cls , curve , data , validate_encoding = True , valid_encodings = None
@@ -325,6 +360,10 @@ def from_bytes(
325360 "supported."
326361 )
327362 data = normalise_bytes (data )
363+
364+ if isinstance (curve , CurveEdTw ):
365+ return cls ._from_edwards (curve , data )
366+
328367 key_len = len (data )
329368 raw_encoding_length = 2 * orderlen (curve .p ())
330369 if key_len == raw_encoding_length and "raw" in valid_encodings :
@@ -381,6 +420,18 @@ def _hybrid_encode(self):
381420 return b"\x07 " + raw_enc
382421 return b"\x06 " + raw_enc
383422
423+ def _edwards_encode (self ):
424+ """Encode the point according to RFC8032 encoding."""
425+ self .scale ()
426+ x , y , p = self .x (), self .y (), self .curve ().p ()
427+
428+ # add 1 for the sign bit and then round up
429+ enc_len = (bit_length (p ) + 1 + 7 ) // 8
430+ y_str = int_to_bytes (y , enc_len , "little" )
431+ if x % 2 :
432+ y_str [- 1 ] |= 0x80
433+ return y_str
434+
384435 def to_bytes (self , encoding = "raw" ):
385436 """
386437 Convert the point to a byte string.
@@ -389,11 +440,17 @@ def to_bytes(self, encoding="raw"):
389440 by `encoding="raw"`. It can also output points in :term:`uncompressed`,
390441 :term:`compressed`, and :term:`hybrid` formats.
391442
443+ For points on Edwards curves `encoding` is ignored and only the
444+ encoding defined in RFC 8032 is supported.
445+
392446 :return: :term:`raw encoding` of a public on the curve
393447 :rtype: bytes
394448 """
395449 assert encoding in ("raw" , "uncompressed" , "compressed" , "hybrid" )
396- if encoding == "raw" :
450+ curve = self .curve ()
451+ if isinstance (curve , CurveEdTw ):
452+ return self ._edwards_encode ()
453+ elif encoding == "raw" :
397454 return self ._raw_encode ()
398455 elif encoding == "uncompressed" :
399456 return b"\x04 " + self ._raw_encode ()
@@ -1219,6 +1276,48 @@ def __init__(self, curve, x, y, z, t, order=None):
12191276 self .__coords = (x , y , z , t )
12201277 self .__order = order
12211278
1279+ @classmethod
1280+ def from_bytes (
1281+ cls ,
1282+ curve ,
1283+ data ,
1284+ validate_encoding = None ,
1285+ valid_encodings = None ,
1286+ order = None ,
1287+ generator = False ,
1288+ ):
1289+ """
1290+ Initialise the object from byte encoding of a point.
1291+
1292+ `validate_encoding` and `valid_encodings` are provided for
1293+ compatibility with Weierstrass curves, they are ignored for Edwards
1294+ points.
1295+
1296+ :param data: single point encoding of the public key
1297+ :type data: :term:`bytes-like object`
1298+ :param curve: the curve on which the public key is expected to lay
1299+ :type curve: ecdsa.ellipticcurve.CurveEdTw
1300+ :param None validate_encoding: Ignored, encoding is always validated
1301+ :param None valid_encodings: Ignored, there is just one encoding
1302+ supported
1303+ :param int order: the point order, must be non zero when using
1304+ generator=True
1305+ :param bool generator: Ignored, may be used in the future
1306+ to precompute point multiplication table.
1307+
1308+ :raises MalformedPointError: if the public point does not lay on the
1309+ curve or the encoding is invalid
1310+
1311+ :return: Initialised point on an Edwards curve
1312+ :rtype: PointEdwards
1313+ """
1314+ coord_x , coord_y = super (PointEdwards , cls ).from_bytes (
1315+ curve , data , validate_encoding , valid_encodings
1316+ )
1317+ return PointEdwards (
1318+ curve , coord_x , coord_y , 1 , coord_x * coord_y , order
1319+ )
1320+
12221321 def x (self ):
12231322 """Return affine x coordinate."""
12241323 X1 , _ , Z1 , _ = self .__coords
0 commit comments