4949
5050from six import python_2_unicode_compatible
5151from . import numbertheory
52+ from ._compat import normalise_bytes
53+ from .errors import MalformedPointError
54+ from .util import orderlen , string_to_number
5255
5356
5457@python_2_unicode_compatible
@@ -137,7 +140,161 @@ def __str__(self):
137140 )
138141
139142
140- class PointJacobi (object ):
143+ class AbstractPoint (object ):
144+ """Class for common methods of elliptic curve points."""
145+ @staticmethod
146+ def _from_raw_encoding (data , raw_encoding_length ):
147+ """
148+ Decode public point from :term:`raw encoding`.
149+
150+ :term:`raw encoding` is the same as the :term:`uncompressed` encoding,
151+ but without the 0x04 byte at the beginning.
152+ """
153+ # real assert, from_bytes() should not call us with different length
154+ assert len (data ) == raw_encoding_length
155+ xs = data [: raw_encoding_length // 2 ]
156+ ys = data [raw_encoding_length // 2 :]
157+ # real assert, raw_encoding_length is calculated by multiplying an
158+ # integer by two so it will always be even
159+ assert len (xs ) == raw_encoding_length // 2
160+ assert len (ys ) == raw_encoding_length // 2
161+ coord_x = string_to_number (xs )
162+ coord_y = string_to_number (ys )
163+
164+ return coord_x , coord_y
165+
166+ @staticmethod
167+ def _from_compressed (data , curve ):
168+ """Decode public point from compressed encoding."""
169+ if data [:1 ] not in (b"\x02 " , b"\x03 " ):
170+ raise MalformedPointError ("Malformed compressed point encoding" )
171+
172+ is_even = data [:1 ] == b"\x02 "
173+ x = string_to_number (data [1 :])
174+ p = curve .p ()
175+ alpha = (pow (x , 3 , p ) + (curve .a () * x ) + curve .b ()) % p
176+ try :
177+ beta = numbertheory .square_root_mod_prime (alpha , p )
178+ except numbertheory .SquareRootError as e :
179+ raise MalformedPointError (
180+ "Encoding does not correspond to a point on curve" , e
181+ )
182+ if is_even == bool (beta & 1 ):
183+ y = p - beta
184+ else :
185+ y = beta
186+ return x , y
187+
188+ @classmethod
189+ def _from_hybrid (cls , data , raw_encoding_length , validate_encoding ):
190+ """Decode public point from hybrid encoding."""
191+ # real assert, from_bytes() should not call us with different types
192+ assert data [:1 ] in (b"\x06 " , b"\x07 " )
193+
194+ # primarily use the uncompressed as it's easiest to handle
195+ x , y = cls ._from_raw_encoding (data [1 :], raw_encoding_length )
196+
197+ # but validate if it's self-consistent if we're asked to do that
198+ if validate_encoding and (
199+ y & 1
200+ and data [:1 ] != b"\x07 "
201+ or (not y & 1 )
202+ and data [:1 ] != b"\x06 "
203+ ):
204+ raise MalformedPointError ("Inconsistent hybrid point encoding" )
205+
206+ return x , y
207+
208+ @classmethod
209+ def from_bytes (
210+ cls ,
211+ curve ,
212+ data ,
213+ validate_encoding = True ,
214+ valid_encodings = None
215+ ):
216+ """
217+ Initialise the object from byte encoding of a point.
218+
219+ The method does accept and automatically detect the type of point
220+ encoding used. It supports the :term:`raw encoding`,
221+ :term:`uncompressed`, :term:`compressed`, and :term:`hybrid` encodings.
222+
223+ Note: generally you will want to call the ``from_bytes()`` method of
224+ either a child class, PointJacobi or Point.
225+
226+ :param data: single point encoding of the public key
227+ :type data: :term:`bytes-like object`
228+ :param curve: the curve on which the public key is expected to lay
229+ :type curve: ecdsa.ellipticcurve.CurveFp
230+ :param validate_encoding: whether to verify that the encoding of the
231+ point is self-consistent, defaults to True, has effect only
232+ on ``hybrid`` encoding
233+ :type validate_encoding: bool
234+ :param valid_encodings: list of acceptable point encoding formats,
235+ supported ones are: :term:`uncompressed`, :term:`compressed`,
236+ :term:`hybrid`, and :term:`raw encoding` (specified with ``raw``
237+ name). All formats by default (specified with ``None``).
238+ :type valid_encodings: :term:`set-like object`
239+
240+ :raises MalformedPointError: if the public point does not lay on the
241+ curve or the encoding is invalid
242+
243+ :return: x and y coordinates of the encoded point
244+ :rtype: tuple(int, int)
245+ """
246+ if not valid_encodings :
247+ valid_encodings = set (
248+ ["uncompressed" , "compressed" , "hybrid" , "raw" ]
249+ )
250+ if not all (
251+ i in set (("uncompressed" , "compressed" , "hybrid" , "raw" ))
252+ for i in valid_encodings
253+ ):
254+ raise ValueError (
255+ "Only uncompressed, compressed, hybrid or raw encoding "
256+ "supported."
257+ )
258+ data = normalise_bytes (data )
259+ key_len = len (data )
260+ raw_encoding_length = 2 * orderlen (curve .p ())
261+ if key_len == raw_encoding_length and "raw" in valid_encodings :
262+ coord_x , coord_y = cls ._from_raw_encoding (
263+ data , raw_encoding_length
264+ )
265+ elif key_len == raw_encoding_length + 1 and (
266+ "hybrid" in valid_encodings or "uncompressed" in valid_encodings
267+ ):
268+ if (
269+ data [:1 ] in (b"\x06 " , b"\x07 " )
270+ and "hybrid" in valid_encodings
271+ ):
272+ coord_x , coord_y = cls ._from_hybrid (
273+ data , raw_encoding_length , validate_encoding
274+ )
275+ elif data [:1 ] == b"\x04 " and "uncompressed" in valid_encodings :
276+ coord_x , coord_y = cls ._from_raw_encoding (
277+ data [1 :], raw_encoding_length
278+ )
279+ else :
280+ raise MalformedPointError (
281+ "Invalid X9.62 encoding of the public point"
282+ )
283+ elif (
284+ key_len == raw_encoding_length // 2 + 1
285+ and "compressed" in valid_encodings
286+ ):
287+ coord_x , coord_y = cls ._from_compressed (data , curve )
288+ else :
289+ raise MalformedPointError (
290+ "Length of string does not match lengths of "
291+ "any of the enabled ({0}) encodings of the "
292+ "curve." .format (", " .join (valid_encodings ))
293+ )
294+ return coord_x , coord_y
295+
296+
297+ class PointJacobi (AbstractPoint ):
141298 """
142299 Point on an elliptic curve. Uses Jacobi coordinates.
143300
@@ -165,6 +322,7 @@ def __init__(self, curve, x, y, z, order=None, generator=False):
165322 such, it will be commonly used with scalar multiplication. This will
166323 cause to precompute multiplication table generation for it
167324 """
325+ super (PointJacobi , self ).__init__ ()
168326 self .__curve = curve
169327 if GMPY : # pragma: no branch
170328 self .__coords = (mpz (x ), mpz (y ), mpz (z ))
@@ -175,6 +333,53 @@ def __init__(self, curve, x, y, z, order=None, generator=False):
175333 self .__generator = generator
176334 self .__precompute = []
177335
336+ @classmethod
337+ def from_bytes (
338+ cls ,
339+ curve ,
340+ data ,
341+ validate_encoding = True ,
342+ valid_encodings = None ,
343+ order = None ,
344+ generator = False
345+ ):
346+ """
347+ Initialise the object from byte encoding of a point.
348+
349+ The method does accept and automatically detect the type of point
350+ encoding used. It supports the :term:`raw encoding`,
351+ :term:`uncompressed`, :term:`compressed`, and :term:`hybrid` encodings.
352+
353+ :param data: single point encoding of the public key
354+ :type data: :term:`bytes-like object`
355+ :param curve: the curve on which the public key is expected to lay
356+ :type curve: ecdsa.ellipticcurve.CurveFp
357+ :param validate_encoding: whether to verify that the encoding of the
358+ point is self-consistent, defaults to True, has effect only
359+ on ``hybrid`` encoding
360+ :type validate_encoding: bool
361+ :param valid_encodings: list of acceptable point encoding formats,
362+ supported ones are: :term:`uncompressed`, :term:`compressed`,
363+ :term:`hybrid`, and :term:`raw encoding` (specified with ``raw``
364+ name). All formats by default (specified with ``None``).
365+ :type valid_encodings: :term:`set-like object`
366+ :param int order: the point order, must be non zero when using
367+ generator=True
368+ :param bool generator: the point provided is a curve generator, as
369+ such, it will be commonly used with scalar multiplication. This
370+ will cause to precompute multiplication table generation for it
371+
372+ :raises MalformedPointError: if the public point does not lay on the
373+ curve or the encoding is invalid
374+
375+ :return: Point on curve
376+ :rtype: PointJacobi
377+ """
378+ coord_x , coord_y = super (PointJacobi , cls ).from_bytes (
379+ curve , data , validate_encoding , valid_encodings
380+ )
381+ return PointJacobi (curve , coord_x , coord_y , 1 , order , generator )
382+
178383 def _maybe_precompute (self ):
179384 if not self .__generator or self .__precompute :
180385 return
@@ -683,12 +888,13 @@ def __neg__(self):
683888 return PointJacobi (self .__curve , x , - y , z , self .__order )
684889
685890
686- class Point (object ):
891+ class Point (AbstractPoint ):
687892 """A point on an elliptic curve. Altering x and y is forbidden,
688893 but they can be read by the x() and y() methods."""
689894
690895 def __init__ (self , curve , x , y , order = None ):
691896 """curve, x, y, order; order (optional) is the order of this point."""
897+ super (Point , self ).__init__ ()
692898 self .__curve = curve
693899 if GMPY :
694900 self .__x = x and mpz (x )
@@ -707,6 +913,50 @@ def __init__(self, curve, x, y, order=None):
707913 if curve and curve .cofactor () != 1 and order :
708914 assert self * order == INFINITY
709915
916+ @classmethod
917+ def from_bytes (
918+ cls ,
919+ curve ,
920+ data ,
921+ validate_encoding = True ,
922+ valid_encodings = None ,
923+ order = None
924+ ):
925+ """
926+ Initialise the object from byte encoding of a point.
927+
928+ The method does accept and automatically detect the type of point
929+ encoding used. It supports the :term:`raw encoding`,
930+ :term:`uncompressed`, :term:`compressed`, and :term:`hybrid` encodings.
931+
932+ :param data: single point encoding of the public key
933+ :type data: :term:`bytes-like object`
934+ :param curve: the curve on which the public key is expected to lay
935+ :type curve: ecdsa.ellipticcurve.CurveFp
936+ :param validate_encoding: whether to verify that the encoding of the
937+ point is self-consistent, defaults to True, has effect only
938+ on ``hybrid`` encoding
939+ :type validate_encoding: bool
940+ :param valid_encodings: list of acceptable point encoding formats,
941+ supported ones are: :term:`uncompressed`, :term:`compressed`,
942+ :term:`hybrid`, and :term:`raw encoding` (specified with ``raw``
943+ name). All formats by default (specified with ``None``).
944+ :type valid_encodings: :term:`set-like object`
945+ :param int order: the point order, must be non zero when using
946+ generator=True
947+
948+ :raises MalformedPointError: if the public point does not lay on the
949+ curve or the encoding is invalid
950+
951+ :return: Point on curve
952+ :rtype: Point
953+ """
954+ coord_x , coord_y = super (Point , cls ).from_bytes (
955+ curve , data , validate_encoding , valid_encodings
956+ )
957+ return Point (curve , coord_x , coord_y , order )
958+
959+
710960 def __eq__ (self , other ):
711961 """Return True if the points are identical, False otherwise.
712962
0 commit comments