4949
5050from six import python_2_unicode_compatible
5151from . import numbertheory
52- from ._rwlock import RWLock
5352
5453
5554@python_2_unicode_compatible
@@ -167,88 +166,64 @@ def __init__(self, curve, x, y, z, order=None, generator=False):
167166 cause to precompute multiplication table generation for it
168167 """
169168 self.__curve = curve
170- # since it's generally better (faster) to use scaled points vs unscaled
171- # ones, use writer-biased RWLock for locking:
172- self._update_lock = RWLock()
173169 if GMPY: # pragma: no branch
174- self.__x = mpz(x)
175- self.__y = mpz(y)
176- self.__z = mpz(z)
170+ self.__coords = (mpz(x), mpz(y), mpz(z))
177171 self.__order = order and mpz(order)
178172 else: # pragma: no branch
179- self.__x = x
180- self.__y = y
181- self.__z = z
173+ self.__coords = (x, y, z)
182174 self.__order = order
183175 self.__generator = generator
184176 self.__precompute = []
185177
186178 def _maybe_precompute(self):
187- if self.__generator:
188- # since we lack promotion of read-locks to write-locks, we do a
189- # "acquire-read-lock, check, acquire-write-lock plus recheck" cycle
190- try:
191- self._update_lock.reader_acquire()
192- if self.__precompute:
193- return
194- finally:
195- self._update_lock.reader_release()
196-
197- try:
198- self._update_lock.writer_acquire()
199- if self.__precompute:
200- return
201- order = self.__order
202- assert order
203- i = 1
204- order *= 2
205- doubler = PointJacobi(
206- self.__curve, self.__x, self.__y, self.__z, order
207- )
208- order *= 2
209- self.__precompute.append((doubler.x(), doubler.y()))
210-
211- while i < order:
212- i *= 2
213- doubler = doubler.double().scale()
214- self.__precompute.append((doubler.x(), doubler.y()))
215-
216- finally:
217- self._update_lock.writer_release()
179+ if not self.__generator or self.__precompute:
180+ return
181+
182+ # since this code will execute just once, and it's fully deterministic,
183+ # depend on atomicity of the last assignment to switch from empty
184+ # self.__precompute to filled one and just ignore the unlikely
185+ # situation when two threads execute it at the same time (as it won't
186+ # lead to inconsistent __precompute)
187+ order = self.__order
188+ assert order
189+ precompute = []
190+ i = 1
191+ order *= 2
192+ coord_x, coord_y, coord_z = self.__coords
193+ doubler = PointJacobi(self.__curve, coord_x, coord_y, coord_z, order)
194+ order *= 2
195+ precompute.append((doubler.x(), doubler.y()))
196+
197+ while i < order:
198+ i *= 2
199+ doubler = doubler.double().scale()
200+ precompute.append((doubler.x(), doubler.y()))
201+
202+ self.__precompute = precompute
218203
219204 def __getstate__(self):
220- try:
221- self._update_lock.reader_acquire()
222- state = self.__dict__.copy()
223- finally:
224- self._update_lock.reader_release()
225- del state["_update_lock"]
205+ # while this code can execute at the same time as _maybe_precompute()
206+ # is updating the __precompute or scale() is updating the __coords,
207+ # there is no requirement for consistency between __coords and
208+ # __precompute
209+ state = self.__dict__.copy()
226210 return state
227211
228212 def __setstate__(self, state):
229213 self.__dict__.update(state)
230- self._update_lock = RWLock()
231214
232215 def __eq__(self, other):
233216 """Compare for equality two points with each-other.
234217
235218 Note: only points that lay on the same curve can be equal.
236219 """
237- try:
238- self._update_lock.reader_acquire()
239- if other is INFINITY:
240- return not self.__y or not self.__z
241- x1, y1, z1 = self.__x, self.__y, self.__z
242- finally:
243- self._update_lock.reader_release()
220+ x1, y1, z1 = self.__coords
221+ if other is INFINITY:
222+ return not y1 or not z1
244223 if isinstance(other, Point):
245224 x2, y2, z2 = other.x(), other.y(), 1
246225 elif isinstance(other, PointJacobi):
247- try:
248- other._update_lock.reader_acquire()
249- x2, y2, z2 = other.__x, other.__y, other.__z
250- finally:
251- other._update_lock.reader_release()
226+ x2, y2, z2 = other.__coords
252227 else:
253228 return NotImplemented
254229 if self.__curve != other.curve():
@@ -289,14 +264,9 @@ def x(self):
289264 call x() and y() on the returned instance. Or call `scale()`
290265 and then x() and y() on the returned instance.
291266 """
292- try:
293- self._update_lock.reader_acquire()
294- if self.__z == 1:
295- return self.__x
296- x = self.__x
297- z = self.__z
298- finally:
299- self._update_lock.reader_release()
267+ x, _, z = self.__coords
268+ if z == 1:
269+ return x
300270 p = self.__curve.p()
301271 z = numbertheory.inverse_mod(z, p)
302272 return x * z ** 2 % p
@@ -310,14 +280,9 @@ def y(self):
310280 call x() and y() on the returned instance. Or call `scale()`
311281 and then x() and y() on the returned instance.
312282 """
313- try:
314- self._update_lock.reader_acquire()
315- if self.__z == 1:
316- return self.__y
317- y = self.__y
318- z = self.__z
319- finally:
320- self._update_lock.reader_release()
283+ _, y, z = self.__coords
284+ if z == 1:
285+ return y
321286 p = self.__curve.p()
322287 z = numbertheory.inverse_mod(z, p)
323288 return y * z ** 3 % p
@@ -328,37 +293,28 @@ def scale(self):
328293
329294 Modifies point in place, returns self.
330295 """
331- try:
332- self._update_lock.reader_acquire()
333- if self.__z == 1:
334- return self
335- finally:
336- self._update_lock.reader_release()
337-
338- try:
339- self._update_lock.writer_acquire()
340- # scaling already scaled point is safe (as inverse of 1 is 1) and
341- # quick so we don't need to optimise for the unlikely event when
342- # two threads hit the lock at the same time
343- p = self.__curve.p()
344- z_inv = numbertheory.inverse_mod(self.__z, p)
345- zz_inv = z_inv * z_inv % p
346- self.__x = self.__x * zz_inv % p
347- self.__y = self.__y * zz_inv * z_inv % p
348- # we are setting the z last so that the check above will return
349- # true only after all values were already updated
350- self.__z = 1
351- finally:
352- self._update_lock.writer_release()
296+ x, y, z = self.__coords
297+ if z == 1:
298+ return self
299+
300+ # scaling is deterministic, so even if two threads execute the below
301+ # code at the same time, they will set __coords to the same value
302+ p = self.__curve.p()
303+ z_inv = numbertheory.inverse_mod(z, p)
304+ zz_inv = z_inv * z_inv % p
305+ x = x * zz_inv % p
306+ y = y * zz_inv * z_inv % p
307+ self.__coords = (x, y, 1)
353308 return self
354309
355310 def to_affine(self):
356311 """Return point in affine form."""
357- if not self.__y or not self.__z:
312+ _, y, z = self.__coords
313+ if not y or not z:
358314 return INFINITY
359315 self.scale()
360- # after point is scaled, it's immutable, so no need to perform locking
361- return Point(self.__curve, self.__x, self.__y , self.__order)
316+ x, y, z = self.__coords
317+ return Point(self.__curve, x, y , self.__order)
362318
363319 @staticmethod
364320 def from_affine(point, generator=False):
@@ -423,17 +379,13 @@ def _double(self, X1, Y1, Z1, p, a):
423379
424380 def double(self):
425381 """Add a point to itself."""
426- if not self.__y:
382+ X1, Y1, Z1 = self.__coords
383+
384+ if not Y1:
427385 return INFINITY
428386
429387 p, a = self.__curve.p(), self.__curve.a()
430388
431- try:
432- self._update_lock.reader_acquire()
433- X1, Y1, Z1 = self.__x, self.__y, self.__z
434- finally:
435- self._update_lock.reader_release()
436-
437389 X3, Y3, Z3 = self._double(X1, Y1, Z1, p, a)
438390
439391 if not Y3 or not Z3:
@@ -546,16 +498,9 @@ def __add__(self, other):
546498 raise ValueError("The other point is on different curve")
547499
548500 p = self.__curve.p()
549- try:
550- self._update_lock.reader_acquire()
551- X1, Y1, Z1 = self.__x, self.__y, self.__z
552- finally:
553- self._update_lock.reader_release()
554- try:
555- other._update_lock.reader_acquire()
556- X2, Y2, Z2 = other.__x, other.__y, other.__z
557- finally:
558- other._update_lock.reader_release()
501+ X1, Y1, Z1 = self.__coords
502+ X2, Y2, Z2 = other.__coords
503+
559504 X3, Y3, Z3 = self._add(X1, Y1, Z1, X2, Y2, Z2, p)
560505
561506 if not Y3 or not Z3:
@@ -603,7 +548,7 @@ def _naf(mult):
603548
604549 def __mul__(self, other):
605550 """Multiply point by an integer."""
606- if not self.__y or not other:
551+ if not self.__coords[1] or not other:
607552 return INFINITY
608553 if other == 1:
609554 return self
@@ -615,8 +560,7 @@ def __mul__(self, other):
615560 return self._mul_precompute(other)
616561
617562 self = self.scale()
618- # once scaled, point is immutable, not need to lock
619- X2, Y2 = self.__x, self.__y
563+ X2, Y2, _ = self.__coords
620564 X3, Y3, Z3 = 0, 0, 1
621565 p, a = self.__curve.p(), self.__curve.a()
622566 _double = self._double
@@ -664,11 +608,10 @@ def mul_add(self, self_mul, other, other_mul):
664608
665609 # as we have 6 unique points to work with, we can't scale all of them,
666610 # but do scale the ones that are used most often
667- # (post scale() points are immutable so no need for locking)
668611 self.scale()
669- X1, Y1, Z1 = self.__x, self.__y, self.__z
612+ X1, Y1, Z1 = self.__coords
670613 other.scale()
671- X2, Y2, Z2 = other.__x, other.__y, other.__z
614+ X2, Y2, Z2 = other.__coords
672615
673616 _double = self._double
674617 _add = self._add
@@ -736,13 +679,8 @@ def mul_add(self, self_mul, other, other_mul):
736679
737680 def __neg__(self):
738681 """Return negated point."""
739- try:
740- self._update_lock.reader_acquire()
741- return PointJacobi(
742- self.__curve, self.__x, -self.__y, self.__z, self.__order
743- )
744- finally:
745- self._update_lock.reader_release()
682+ x, y, z = self.__coords
683+ return PointJacobi(self.__curve, x, -y, z, self.__order)
746684
747685
748686class Point(object):
0 commit comments