Source code for petrelic.native.pairing

r"""
This module provides a Python wrapper around RELIC's pairings using a native
interface: operations in :py:obj:`petrelic.native.pairings.G1` and
:py:obj:`petrelic.native.pairings.G2` are written additively, whereas operations in
:py:obj:`petrelic.native.pairings.GT` are written multiplicatively.

Let's see how we can use this interface to implement the Boney-Lynn-Shacham
signature scheme for type III pairings. First we generate a private key:

>>> sk = G1.order().random()

which is a random integer modulo the group order. Note that for this setting,
all three groups have the same order. Next, we generate the corresponding public
key:

>>> pk = (sk * G1.generator(), sk * G2.generator())

(For security in the type III setting, the first component is a necessary part
of the public key. It is not actually used in the scheme.) To sign a message
`m` we first hash it to the curve G1 using :py:meth:`G1.hash_to_point` and then
multiply it with the signing key `sk` to obtain a signature:

>>> m = b"Some message"
>>> signature = sk * G1.hash_to_point(m)

Finally, we can use the pairing operator to verify the signature:

>>> signature.pair(G2.generator()) == G1.hash_to_point(m).pair(pk[1])
True

Indeed, the pairing operator is bilinear. For example:

>>> a, b = 13, 29
>>> A = a * G1.generator()
>>> B = b * G2.generator()
>>> A.pair(B) == G1.generator().pair(G2.generator()) ** (a * b)
True

"""

import functools

from petrelic.bindings import _FFI, _C
from petrelic.bn import Bn, force_Bn_other

#
# Utility function
#
def check_same_type(func):
    """Enforce both arguments have same type"""
    @functools.wraps(func)
    def wrapper(a, b):
        if not type(a) == type(b):
            return NotImplemented
        return func(a, b)
    return wrapper


#
# Exceptions
#

[docs]class NoAffineCoordinateForECPoint(Exception): """Exception raised when an EC point can not be represented as affine coordinates.""" msg = 'No affine coordinates exists for the given EC point.'
# # Group pairs #
[docs]class BilinearGroupPair: """A bilinear group pair used to wrap the three groups G1, G2, GT.""" def __init__(self): self.gt = GT() self.g1 = G1() self.g2 = G2()
[docs] def groups(self): """ Returns the three groups in the following order : G1, G2, GT. """ return self.g1, self.g2, self.gt
# # Group and Elements # class _G1Base(object): """All Base G1 methods, will be used in all interfaces """ @classmethod def _element_type(cls): return G1Element @classmethod def _new_element(cls): return cls._element_type()() @classmethod def order(cls): """Return the order of the group as a Bn large integer. Example: >>> generator = G1.generator() >>> neutral = G1.neutral_element() >>> order = G1.order() >>> order * generator == neutral True """ order = Bn() _C.g1_get_ord(order.bn) return order @classmethod def generator(cls): """Return generator of the group. Example: >>> generator = G1.generator() >>> neutral = G1.neutral_element() >>> generator + neutral == generator True """ generator = cls._new_element() _C.g1_get_gen(generator.pt) generator._is_gen = True return generator @classmethod def neutral_element(cls): """Return the neutral element of the group G1. In this case, the point at infinity. Example: >>> generator = G1.generator() >>> neutral = G1.neutral_element() >>> generator + neutral == generator True """ neutral = cls._new_element() _C.g1_set_infty(neutral.pt) return neutral @classmethod def hash_to_point(cls, hinput): """Return group element obtained by hashing the input Example: >>> elem = G1.hash_to_point(b"foo") >>> elem.is_valid() True """ res = cls._new_element() _C.g1_map(res.pt, hinput, len(hinput)) return res
[docs]class G1(_G1Base): """The G1 group."""
[docs] @classmethod def sum(cls, elems): """Efficient sum of a number of elements In the current implementation this function is not optimized. Example: >>> elems = [ x * G1.generator() for x in [10, 25, 13]] >>> G1.sum(elems) == (10 + 25 + 13) * G1.generator() True """ res = cls.neutral_element() for el in elems: res += el return res
[docs] @classmethod def wsum(cls, weights, elems): """Efficient weighted product of a number of elements In the current implementation this function is not optimized. Example: >>> weights = [1, 2, 3] >>> elems = [ x * G1.generator() for x in [10, 25, 13]] >>> G1.wsum(weights, elems) == (1 * 10 + 2 * 25 + 3 * 13) * G1.generator() True """ res = cls.neutral_element() for w, el in zip(weights, elems): res += w * el return res
# # Aliases #
[docs] @classmethod def infinity(cls): """The point at infinity. Alias for :py:meth:`G1.neutral_element` """ return cls.neutral_element()
class _G1ElementBase(object): def __init__(self): """Initialize a new element of G1.""" self.pt = _FFI.new("g1_t") self._is_gen = False _C.g1_null(self.pt) _C.g1_new(self.pt) def __copy__(self): """Clone an element of G1.""" copy = self.__class__() _C.g1_copy(copy.pt, self.pt) copy._is_gen = self._is_gen return copy # # Misc # def is_valid(self): """Check if the element is a valid element on the curve. This method excludes the unity element. For that use `is_infinity`. Example: >>> elem = G1.hash_to_point(b"foo") >>> elem.is_valid() True >>> elem = G1.infinity() >>> elem.is_valid() False """ return bool(_C.g1_is_valid(self.pt)) def is_neutral_element(self): """Check if the object is the neutral element of G1. Example: >>> generator = G1.generator() >>> order = G1.order() >>> elem = order * generator >>> elem.is_neutral_element() True """ return bool(_C.g1_is_infty(self.pt)) def get_affine_coordinates(self): """Return the affine coordinates (x,y) of this EC Point. Example: >>> generator = G1.generator() >>> x, y = generator.get_affine_coordinates() >>> x Bn(3685416753713387016781088315183077757961620795782546409894578378688607592378376318836054947676345821548104185464507) >>> y Bn(1339506544944476473020471379941921221584933875938349620426543736416511423956333506472724655353366534992391756441569) """ if self.is_neutral_element(): raise NoAffineCoordinateForECPoint() x = Bn() y = Bn() _C.fp_prime_back(x.bn, self.pt[0].x) _C.fp_prime_back(y.bn, self.pt[0].y) return x, y def pair(self, other): """Pair element with another element in G2 Computes the bilinear pairing between self and another element in :py:obj:`petrelic.native.pairing.G2`. Examples: >>> g1, g2 = G1.generator(), G2.generator() >>> a, b = 10, 50 >>> A, B = a * g1, b * g2 >>> A.pair(B) == g1.pair(g2) ** (a * b) True >>> A.pair(g2) == g1.pair(a * g2) True >>> A.pair(g2) == g1.pair(g2) ** a True """ if not type(other) == G2Element: raise TypeError("Second parameter should be of type G2Element is {}".format(type(other))) res = GTElement() _C.pc_map(res.pt, self.pt, other.pt) return res def __hash__(self): """Hash function used internally by Python.""" return self.to_binary().__hash__() def __repr__(self): """String representation of the element of G1.""" pt_hex = self.to_binary().hex() return 'G1Element({})'.format(pt_hex) # # Serialization # @classmethod def from_binary(cls, sbin): """Deserialize a binary representation of the element of G1. Example: >>> generator = G1.generator() >>> bin_repr = generator.to_binary() >>> elem = G1Element.from_binary(bin_repr) >>> generator == elem True """ elem = cls() elem._is_gen = False _C.g1_read_bin(elem.pt, sbin, len(sbin)) return elem def to_binary(self, compressed=True): """Serialize the element of G1 into a binary representation. Example: >>> generator = G1.generator() >>> bin_repr = generator.to_binary() >>> elem = G1Element.from_binary(bin_repr) >>> generator == elem True """ flag = 1 if compressed else 0 length = _C.g1_size_bin(self.pt, flag) buf = _FFI.new("char[]", length) _C.g1_write_bin(buf, length, self.pt, flag) return _FFI.unpack(buf, length) # # Unary operators # def inverse(self): """Return the inverse of the element. Examples: >>> a = 30 >>> elem = a * G1.generator() >>> -elem == elem.inverse() True >>> elem.inverse() == (G1.order() - a) * G1.generator() True """ res = self.__class__() _C.g1_neg(res.pt, self.pt) return res def iinverse(self): """Inplace inverse of the current element Examples: >>> a = 30 >>> elem1 = a * G1.generator() >>> elem2 = a * G1.generator() >>> _ = elem1.iinverse() >>> elem1 == elem2.inverse() True """ _C.g1_neg(self.pt, self.pt) return self # # Comparison operators # def __eq__(self, other): """Check point equality.""" if not isinstance(other, self.__class__): return False return _C.g1_cmp(self.pt, other.pt) == _C.CONST_RLC_EQ def __ne__(self, other): """Check that the points are different.""" if not isinstance(other, self.__class__): return True return _C.g1_cmp(self.pt, other.pt) != _C.CONST_RLC_EQ # # Aliases # __deepcopy__ = __copy__ eq = __eq__ ne = __ne__
[docs]class G1Element(_G1ElementBase): """Element of the G1 group.""" group = G1
[docs] def double(self): """Return double of the current element Example: >>> generator = G1.generator() >>> elem = generator.double() >>> elem == 2 * generator True """ res = self.__class__() _C.g1_dbl(res.pt, self.pt) return res
[docs] def idouble(self): """Inplace double the current element. Example: >>> generator = G1.generator() >>> elem = G1.generator() >>> _ = elem.idouble() >>> elem == 2 * generator True """ self._is_gen = False _C.g1_dbl(self.pt, self.pt) return self
# # Binary operators # @check_same_type def __add__(self, other): """Add two points together. This method is aliased by `a + b`. Examples: >>> a = 10 * G1.generator() >>> b = 40 * G1.generator() >>> a + b == 50 * G1.generator() True >>> a.add(b) == 50 * G1.generator() True """ res = self.__class__() _C.g1_add(res.pt, self.pt, other.pt) return res @check_same_type def __iadd__(self, other): """Inplace add another point. Examples: >>> a = 10 * G1.generator() >>> b = 10 * G1.generator() >>> a += 3 * G1.generator() >>> _ = b.iadd(3 * G1.generator()) >>> a == b True >>> a == 13 * G1.generator() True """ self._is_gen = False _C.g1_add(self.pt, self.pt, other.pt) return self @check_same_type def __sub__(self, other): """Substract two points This method is aliased by `a - b`. Examples: >>> a = 50 * G1.generator() >>> b = 13 * G1.generator() >>> a - b == 37 * G1.generator() True >>> a.sub(b) == 37 * G1.generator() True """ res = self.__class__() _C.g1_sub(res.pt, self.pt, other.pt) return res @check_same_type def __isub__(self, other): """Inplace substract another point. Examples: >>> a = 10 * G1.generator() >>> b = 10 * G1.generator() >>> a -= 3 * G1.generator() >>> _ = b.isub(3 * G1.generator()) >>> a == b True >>> a == 7 * G1.generator() True """ self._is_gen = False _C.g1_sub(self.pt, self.pt, other.pt) return self @force_Bn_other def __mul__(self, other): """Multiply point by a scalar This method is aliased by `n * pt`. Examples: >>> g = G1.generator() >>> g + g == 2 * g True """ res = self.__class__() if self._is_gen: _C.g1_mul_gen(res.pt, other.bn) else: _C.g1_mul(res.pt, self.pt, other.bn) return res @force_Bn_other def __rmul__(self, other): res = self.__class__() if self._is_gen: _C.g1_mul_gen(res.pt, other.bn) else: _C.g1_mul(res.pt, self.pt, other.bn) return res @force_Bn_other def __imul__(self, other): """Inplace point multiplication by a scalar Examples: >>> a = G1.generator() >>> b = G1.generator() >>> a *= 10 >>> _ = b.imul(10) >>> a == b True >>> a == 10 * G1.generator() True """ if self._is_gen: _C.g1_mul_gen(self.pt, other.bn) self._is_gen = False else: _C.g1_mul(self.pt, self.pt, other.bn) return self # # Aliases # is_infinity = _G1ElementBase.is_neutral_element __neg__ = _G1ElementBase.inverse neg = __neg__ add = __add__ iadd = __iadd__ sub = __sub__ isub = __isub__ mul = __mul__ imul = __imul__
class _G2Base(object): """Internal base class for G2""" @classmethod def _element_type(cls): return G2Element @classmethod def _new_element(cls): return cls._element_type()() @classmethod def order(cls): """Return the order of the EC group as a Bn large integer. Example: >>> generator = G2.generator() >>> neutral = G2.neutral_element() >>> order = G2.order() >>> order * generator == neutral True """ order = Bn() _C.g2_get_ord(order.bn) return order @classmethod def generator(cls): """Return generator of the group. Example: >>> generator = G2.generator() >>> neutral = G2.neutral_element() >>> generator + neutral == generator True """ generator = cls._new_element() _C.g2_get_gen(generator.pt) return generator @classmethod def neutral_element(cls): """Return the neutral element of the group G2. In this case, the point at infinity. Example: >>> generator = G2.generator() >>> neutral = G2.neutral_element() >>> generator + neutral == generator True """ neutral = cls._new_element() _C.g2_set_infty(neutral.pt) return neutral @classmethod def hash_to_point(cls, hinput): """Return group element obtained by hashing the input Example: >>> elem = G2.hash_to_point(b"foo") >>> elem.is_valid() True """ res = cls()._new_element() _C.g2_map(res.pt, hinput, len(hinput)) return res
[docs]class G2(_G2Base): """G2 group."""
[docs] @classmethod def sum(cls, elems): """Efficient sum of a number of elements In the current implementation this function is not optimized. Example: >>> elems = [ x * G2.generator() for x in [10, 25, 13]] >>> G2.sum(elems) == (10 + 25 + 13) * G2.generator() True """ res = cls.neutral_element() for el in elems: res += el return res
[docs] @classmethod def wsum(cls, weights, elems): """Efficient weighted product of a number of elements In the current implementation this function is not optimized. Example: >>> weights = [1, 2, 3] >>> elems = [ x * G2.generator() for x in [10, 25, 13]] >>> G2.wsum(weights, elems) == (1 * 10 + 2 * 25 + 3 * 13) * G2.generator() True """ res = cls.neutral_element() for w, el in zip(weights, elems): res += w * el return res
# # Aliases #
[docs] @classmethod def infinity(cls): """The point at infinity. Alias for :py:meth:`G1.neutral_element` """ return cls.neutral_element()
class _G2ElementBase(object): def __init__(self): """Initialize a new element of G2.""" self.pt = _FFI.new("g2_t") _C.g2_null(self.pt) _C.g2_new(self.pt) def __copy__(self): """Clone an element of G2.""" copy = self.__class__() _C.g2_copy(copy.pt, self.pt) return copy # # Misc # def is_valid(self): return bool(_C.g2_is_valid(self.pt)) def is_neutral_element(self): _C.g2_norm(self.pt, self.pt) return bool(_C.g2_is_infty(self.pt)) def __hash__(self): """Hash function used internally by Python.""" return self.to_binary().__hash__() def __repr__(self): """String representation of the element of G2.""" pt_hex = self.to_binary().hex() return 'G2Element({})'.format(pt_hex) # # Serialization # @classmethod def from_binary(cls, sbin): """Deserialize a binary representation of the element of G2. Example: >>> generator = G2.generator() >>> bin_repr = generator.to_binary() >>> elem = G2Element.from_binary(bin_repr) >>> generator == elem True """ elem = cls() _C.g2_read_bin(elem.pt, sbin, len(sbin)) return elem def to_binary(self, compressed=True): flag = int(compressed) length = _C.g2_size_bin(self.pt, flag) buf = _FFI.new("char[]", length) _C.g2_write_bin(buf, length, self.pt, flag) return _FFI.unpack(buf, length) # # Unary operators # def inverse(self): res = self.__class__() _C.g2_neg(res.pt, self.pt) return res def iinverse(self): _C.g2_neg(self.pt, self.pt) return self # # Comparison operators # def __eq__(self, other): """Check that the points on the EC are equal.""" if not isinstance(other, self.__class__): return False return _C.g2_cmp(self.pt, other.pt) == _C.CONST_RLC_EQ def __ne__(self, other): """Check that the points on the EC are not equal.""" if not isinstance(other, self.__class__): return True return _C.g2_cmp(self.pt, other.pt) != _C.CONST_RLC_EQ # # Aliases # __deepcopy__ = __copy__ eq = __eq__ ne = __ne__ # Copy documentation from G1Element to_binary.__doc__ = G1Element.to_binary.__doc__.replace("G1", "G2") is_valid.__doc__ = G1Element.is_valid.__doc__.replace("G1", "G2") is_neutral_element.__doc__ = G1Element.is_neutral_element.__doc__.replace("G1", "G2") inverse.__doc__ = G1Element.inverse.__doc__.replace("G1", "G2") iinverse.__doc__ = G1Element.iinverse.__doc__.replace("G1", "G2")
[docs]class G2Element(_G2ElementBase): """Element of the G2 group.""" # # Unary operators #
[docs] def double(self): res = self.__class__() _C.g2_dbl(res.pt, self.pt) return res
[docs] def idouble(self): _C.g2_dbl(self.pt, self.pt) return self
# # Binary operators # @check_same_type def __add__(self, other): res = self.__class__() _C.g2_add(res.pt, self.pt, other.pt) return res @check_same_type def __iadd__(self, other): _C.g2_add(self.pt, self.pt, other.pt) return self @check_same_type def __sub__(self, other): res = self.__class__() _C.g2_sub(res.pt, self.pt, other.pt) return res @check_same_type def __isub__(self, other): _C.g2_sub(self.pt, self.pt, other.pt) return self @force_Bn_other def __mul__(self, other): res = self.__class__() _C.g2_mul(res.pt, self.pt, other.bn) return res @force_Bn_other def __rmul__(self, other): res = self.__class__() _C.g2_mul(res.pt, self.pt, other.bn) return res @force_Bn_other def __imul__(self, other): _C.g2_mul(self.pt, self.pt, other.bn) return self # Copy documentation from G1Element double.__doc__ = G1Element.double.__doc__.replace("G1", "G2") idouble.__doc__ = G1Element.idouble.__doc__.replace("G1", "G2") __add__.__doc__ = G1Element.__add__.__doc__.replace("G1", "G2") __iadd__.__doc__ = G1Element.__add__.__doc__.replace("G1", "G2") __sub__.__doc__ = G1Element.__sub__.__doc__.replace("G1", "G2") __isub__.__doc__ = G1Element.__isub__.__doc__.replace("G1", "G2") __mul__.__doc__ = G1Element.__mul__.__doc__.replace("G1", "G2") __imul__.__doc__ = G1Element.__imul__.__doc__.replace("G1", "G2") # # Aliases # is_infinity = _G2ElementBase.is_neutral_element __neg__ = _G2ElementBase.inverse neg = __neg__ add = __add__ iadd = __iadd__ sub = __sub__ isub = __isub__ mul = __mul__ imul = __imul__
class _GTBase(object): """Internal base class for GT""" @classmethod def _element_type(cls): return GTElement @classmethod def _new_element(cls): return cls._element_type()() @classmethod def order(cls): """Return the order of the EC group as a Bn large integer. Example: >>> generator = GT.generator() >>> neutral = GT.neutral_element() >>> order = GT.order() >>> generator ** order == neutral True """ order = Bn() _C.gt_get_ord(order.bn) return order @classmethod def generator(cls): """Return generator of the EC group. Example: >>> generator = GT.generator() >>> neutral = GT.neutral_element() >>> generator * neutral == generator True """ generator = cls._new_element() _C.gt_get_gen(generator.pt) return generator @classmethod def neutral_element(cls): """Return the neutral element of the group GT. In this case, the unity point. Example: >>> generator = GT.generator() >>> neutral = GT.neutral_element() >>> generator * neutral == generator True """ neutral = cls._new_element() _C.gt_set_unity(neutral.pt) return neutral
[docs]class GT(_GTBase): """GT group."""
[docs] @classmethod def prod(cls, elems): """Efficient product of a number of elements In the current implementation this function is not optimized. Example: >>> elems = [ GT.generator() ** x for x in [10, 25, 13]] >>> GT.prod(elems) == GT.generator() ** (10 + 25 + 13) True """ res = cls.neutral_element() for el in elems: res *= el return res
[docs] @classmethod def wprod(cls, weights, elems): """Efficient weighted product of a number of elements In the current implementation this function is not optimized. Example: >>> weights = [1, 2, 3] >>> elems = [ GT.generator() ** x for x in [10, 25, 13]] >>> GT.wprod(weights, elems) == GT.generator() ** (1 * 10 + 2 * 25 + 3 * 13) True """ res = cls.neutral_element() for w, el in zip(weights, elems): res *= el ** w return res
# # Aliases #
[docs] @classmethod def unity(cls): """The unity elements Alias for :py:meth:`GT.neutral_element` """ return cls.neutral_element()
class _GTElementBase(object): def __init__(self): """Initialize a new element of GT.""" self.pt = _FFI.new("gt_t") _C.gt_null(self.pt) _C.gt_new(self.pt) def __copy__(self): """Clone an element of GT.""" copy = self.__class__() _C.gt_copy(copy.pt, self.pt) return copy # # Misc # def is_valid(self): """Check if the element is in the group Example: >>> elem = GT.generator() ** 1337 >>> elem.is_valid() True """ # TODO: gt_is_valid does not accept unity. if bool(_C.gt_is_unity(self.pt)): return True return bool(_C.gt_is_valid(self.pt)) def is_neutral_element(self): """Check if the object is the neutral element of GT. Example: >>> generator = GT.generator() >>> order = GT.order() >>> elem = generator ** order >>> elem.is_neutral_element() True """ return bool(_C.gt_is_unity(self.pt)) def __hash__(self): """Hash function used internally by Python.""" return self.to_binary().__hash__() def __repr__(self): """String representation of the element of G2.""" pt_hex = self.to_binary().hex() return 'GTElement({})'.format(pt_hex) # # Serialization # @classmethod def from_binary(cls, sbin): """Deserialize a binary representation of the element of GT. Example: >>> generator = GT.generator() >>> bin_repr = generator.to_binary() >>> elem = GTElement.from_binary(bin_repr) >>> generator == elem True """ ret = cls() _C.gt_read_bin(ret.pt, sbin, len(sbin)) return ret def to_binary(self, compressed=True): flag = int(compressed) length = _C.gt_size_bin(self.pt, flag) buf = _FFI.new("char[]", length) _C.gt_write_bin(buf, length, self.pt, flag) return _FFI.unpack(buf, length) to_binary.__doc__ = G1Element.to_binary.__doc__.replace("G1", "GT") # # Unary operators # def inverse(self): """Return the inverse of the element. Examples: >>> a = 30 >>> elem = GT.generator() ** a >>> elem.inverse() == GT.generator() ** (G1.order() - a) True """ res = self.__class__() _C.gt_inv(res.pt, self.pt) return res def iinverse(self): """Inplace inverse of the current element Examples: >>> a = 30 >>> elem1 = GT.generator() ** a >>> elem2 = GT.generator() ** a >>> _ = elem1.iinverse() >>> elem1 == elem2.inverse() True """ _C.gt_inv(self.pt, self.pt) return self # # Comparison operators # def __eq__(self, other): """Check that the points are equal.""" if not isinstance(other, self.__class__): return False return _C.gt_cmp(self.pt, other.pt) == _C.CONST_RLC_EQ def __ne__(self, other): """Check that the points on the EC are not equal.""" if not isinstance(other, self.__class__): return True return _C.gt_cmp(self.pt, other.pt) != _C.CONST_RLC_EQ # # Aliases # __deepcopy__ = __copy__ eq = __eq__ ne = __ne__
[docs]class GTElement(_GTElementBase): """GT element.""" group = GT
[docs] def square(self): """Return the square of the current element Example: >>> generator = GT.generator() >>> elem = generator.square() >>> elem == generator ** 2 True """ res = self.__class__() _C.gt_sqr(res.pt, self.pt) return res
[docs] def isquare(self): """Inplace square of the current element. Example: >>> elem = GT.generator() >>> _ = elem.isquare() >>> elem == GT.generator() ** 2 True """ _C.gt_sqr(self.pt, self.pt) return self
# # Binary operators # @check_same_type def __mul__(self, other): """Multiply two elements This method is aliased by `a * b`. Examples: >>> a = GT.generator() ** 10 >>> b = GT.generator() ** 40 >>> a * b == GT.generator() ** 50 True >>> a.mul(b) == GT.generator() ** 50 True """ res = self.__class__() _C.gt_mul(res.pt, self.pt, other.pt) return res @check_same_type def __imul__(self, other): """Inplace multiplication by another element Examples: >>> a = GT.generator() ** 10 >>> b = GT.generator() ** 10 >>> a *= GT.generator() ** 3 >>> _ = b.imul(GT.generator() ** 3) >>> a == b True >>> a == GT.generator() ** 13 True """ _C.gt_mul(self.pt, self.pt, other.pt) return self @check_same_type def __truediv__(self, other): """Divide two points This method is aliased by `a / b` and `a // b`. Examples: >>> a = GT.generator() ** 50 >>> b = GT.generator() ** 13 >>> a / b == GT.generator() ** 37 True >>> a // b == GT.generator() ** 37 True >>> a.div(b) == GT.generator() ** 37 True """ res = other.inverse() _C.gt_mul(res.pt, self.pt, res.pt) return res @check_same_type def __itruediv__(self, other): """Inplace division by another point Examples: >>> a = GT.generator() ** 10 >>> b = GT.generator() ** 10 >>> a /= GT.generator() ** 3 >>> _ = b.idiv(GT.generator() ** 3) >>> a == b True >>> a == GT.generator() ** 7 True """ other_inv = other.inverse() _C.gt_mul(self.pt, self.pt, other_inv.pt) return self @force_Bn_other def __pow__(self, other): """Raise element to the power of a scalar This method is aliased by `el ** n`. Examples: >>> g = GT.generator() >>> g * g == g ** 2 True >>> g * g == g.pow(2) True """ res = self.__class__() exponent = other.mod(self.group.order()) _C.gt_exp(res.pt, self.pt, exponent.bn) return res @force_Bn_other def __ipow__(self, other): """Inplace raise element to the power of a scalar Examples: >>> g = GT.generator() >>> a = GT.generator() >>> _ = a.ipow(3) >>> g * g * g == a True """ exponent = other.mod(self.group.order()) _C.gt_exp(self.pt, self.pt, exponent.bn) return self # # Aliases # is_unity = _GTElementBase.is_neutral_element mul = __mul__ imul = __imul__ div = __truediv__ idiv = __itruediv__ __floordiv__ = __truediv__ __ifloordiv__ = __itruediv__ pow = __pow__ ipow = __ipow__