49
49
50
50
from six import python_2_unicode_compatible
51
51
from . import numbertheory
52
- from ._compat import normalise_bytes
52
+ from ._compat import normalise_bytes , int_to_bytes , bit_length , bytes_to_int
53
53
from .errors import MalformedPointError
54
54
from .util import orderlen , string_to_number , number_to_string
55
55
@@ -278,6 +278,41 @@ def _from_hybrid(cls, data, raw_encoding_length, validate_encoding):
278
278
279
279
return x , y
280
280
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
+
281
316
@classmethod
282
317
def from_bytes (
283
318
cls , curve , data , validate_encoding = True , valid_encodings = None
@@ -325,6 +360,10 @@ def from_bytes(
325
360
"supported."
326
361
)
327
362
data = normalise_bytes (data )
363
+
364
+ if isinstance (curve , CurveEdTw ):
365
+ return cls ._from_edwards (curve , data )
366
+
328
367
key_len = len (data )
329
368
raw_encoding_length = 2 * orderlen (curve .p ())
330
369
if key_len == raw_encoding_length and "raw" in valid_encodings :
@@ -381,6 +420,18 @@ def _hybrid_encode(self):
381
420
return b"\x07 " + raw_enc
382
421
return b"\x06 " + raw_enc
383
422
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
+
384
435
def to_bytes (self , encoding = "raw" ):
385
436
"""
386
437
Convert the point to a byte string.
@@ -389,11 +440,17 @@ def to_bytes(self, encoding="raw"):
389
440
by `encoding="raw"`. It can also output points in :term:`uncompressed`,
390
441
:term:`compressed`, and :term:`hybrid` formats.
391
442
443
+ For points on Edwards curves `encoding` is ignored and only the
444
+ encoding defined in RFC 8032 is supported.
445
+
392
446
:return: :term:`raw encoding` of a public on the curve
393
447
:rtype: bytes
394
448
"""
395
449
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" :
397
454
return self ._raw_encode ()
398
455
elif encoding == "uncompressed" :
399
456
return b"\x04 " + self ._raw_encode ()
@@ -1219,6 +1276,48 @@ def __init__(self, curve, x, y, z, t, order=None):
1219
1276
self .__coords = (x , y , z , t )
1220
1277
self .__order = order
1221
1278
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
+
1222
1321
def x (self ):
1223
1322
"""Return affine x coordinate."""
1224
1323
X1 , _ , Z1 , _ = self .__coords
0 commit comments