"""
.. module:: bn254
bn254 module
============
This module exports the classes :obj:`~oblivious.bn254.point`,
:obj:`~oblivious.bn254.scalar`, :obj:`~oblivious.bn254.point2`,
and :obj:`~oblivious.bn254.scalar2` for representing points and scalars. It
also exports the two wrapper classes/namespaces :obj:`~oblivious.bn254.python`
and :obj:`~oblivious.bn254.mcl` that encapsulate pure-Python and shared/dynamic
library variants of the above (respectively) and also include low-level
operations that correspond more directly to the functions found in the
underlying libraries.
* Under all conditions, the wrapper class :obj:`~oblivious.bn254.python`
is defined and encapsulates a pure-Python variant of class exported by this
module as a whole. It also includes pure-Python variants of low-level
operations that correspond to functions found in the underlying libraries.
* If the `mclbn256 <https://pypi.org/project/mclbn256>`__ package is installed
(which includes a bundled copy of the `mcl <https://github.com/herumi/mcl>`__
dynamic/shared libray), then the wrapper class :obj:`~oblivious.bn254.mcl`
is defined. Otherwise, the exported variable ``mcl`` is assigned ``None``.
* If the `mclbn256 <https://pypi.org/project/mclbn256>`__ package is installed,
all classes exported by this module correspond to the variants defined in
:obj:`~oblivious.bn254.mcl`. Otherwise, they correspond to the variants
defined in :obj:`~oblivious.bn254.python`.
For most users, the classes :obj:`~oblivious.bn254.point`,
:obj:`~oblivious.bn254.scalar`, :obj:`~oblivious.bn254.point2`,
and :obj:`~oblivious.bn254.scalar2` should be sufficient.
When using the classes within :obj:`~oblivious.bn254.python` and/or
:obj:`~oblivious.bn254.mcl`, users should be aware that objects corresponding
to one implementation (*e.g.*, instances of :obj:`oblivious.bn254.mcl.point`)
are not compatible with instances corresponding to the other implementation
(*e.g.*, the methods of the :obj:`oblivious.bn254.python.point` class). When
using the primitive operations that correspond to a specific implementation
(*e.g.*, :obj:`oblivious.bn254.mcl.add`), users are responsible for ensuring
that inputs have the type and/or representation appropriate for that
operation's internal implementation.
"""
from __future__ import annotations
from typing import Any, NoReturn, Union, Optional
import doctest
import hashlib
import base64
import secrets
from bn254.ecp import generator as get_base
from bn254.ecp2 import generator as get_base2
from bn254 import big as bn, Fp12 as Fp12_, Fp as _Fp, Fp2 as _Fp2
from bn254.ecp import ECp as ECp_
from bn254.ecp2 import ECp2 as ECp2_
from bn254.curve import r
#
# An attempt will be made later to import mclbn256. If the mcl shared/dynamic
# library file bundled with mclbn256 does not load, only pure-Python
# implementations of the functions and methods will be available.
#
mclbn256 = None
#
# Use pure-Python implementations of primitives by default.
#
class _ECp(ECp_): # pylint: disable=invalid-name
"""Internal class."""
# pylint: disable=missing-function-docstring
@classmethod
def random(cls) -> _ECp:
return cls(int(python.scalar.random()) * get_base())
@classmethod
def deserialize(cls, bs) -> _ECp:
p_mod = 0x2523648240000001ba344d80000000086121000000000013a700000000000013
d_inv = 0x1a7344bac91f117ea513ec0ed5682406b6c15140174d61b28b762ae9cf6d3b46
decode = lambda ns: (int.from_bytes(ns, 'little') * d_inv) % p_mod
x, y, z = decode(bs[:32]), decode(bs[32:64]), decode(bs[-32:])
# Compute affine coordinates
inv_z = bn.invmodp(z, p_mod)
x = (x * inv_z) % p_mod
y = (y * inv_z) % p_mod
p = cls()
assert p.setxy(x, y) or (x == 0 and y == 0)
return p
@classmethod
def mapfrom(cls, bs) -> _ECp:
# pylint: disable=unnecessary-direct-lambda-call
p_mod = (
(lambda x: x * (x * (x * ((36 * x) - 36) + 24) - 6) + 1)
((2 ** 62) + (2 ** 55) + 1)
)
while int.from_bytes(bs, 'little') >= p_mod:
bs = hashlib.sha256(bs).digest()
bs = bs[:-1] + bytes([bs[-1] & 0b00111111])
x = int.from_bytes(bs, 'little')# % p_mod
y = None
while True:
x3_2 = (pow(x, 3, p_mod) + 2) % p_mod
if pow(x3_2, (p_mod - 1) // 2, p_mod) == 1:
s = (p_mod-1) // 2
n = 2
while pow(n, (p_mod-1) // 2, p_mod) == -1 % p_mod:
n += 1
y = pow(x3_2, (s + 1) // 2, p_mod)
b = pow(x3_2, s, p_mod)
g = pow(n, s, p_mod)
r_ = 1
while True:
t = b
m = 0
for m in range(r_):
if t == 1:
break
t = pow(t, 2, p_mod) # pragma: no cover
if m == 0:
break
gs = pow(g, 2**(r_ - m - 1), p_mod) # pragma: no cover
g = (gs * gs) % p_mod # pragma: no cover
y = (y * gs) % p_mod # pragma: no cover
b = (b * g) % p_mod # pragma: no cover
r_ = m # pragma: no cover
if y is not None:
# pylint: disable=invalid-unary-operand-type
if y % 2 == 1:
y = -y
break
x += 1
p = cls()
assert p.setxy(x, y) or (x == 0 and y == 0)
return p
def __new__(cls, *args, **kwargs):
p = ECp_.__new__(cls)
_ECp.__init__(p, *args, **kwargs)
return p
def __init__(self, p=None):
ECp_.__init__(self)
if isinstance(p, (ECp_, _ECp)):
self.setxy(*p.get())
elif isinstance(p, python.point):
self.setxy(*_ECp.deserialize(p).get()) # pragma: no cover
def serialize(self) -> bytes:
d = 0x212ba4f27ffffff5a2c62effffffffcdb939ffffffffff8a15ffffffffffff8e
p = 0x2523648240000001ba344d80000000086121000000000013a700000000000013
x, y, z = (*self.get(), 1)
encode = lambda n: (n * d % p).to_bytes(32, 'little')
return encode(x) + encode(y) + encode(z)
class _ECp2(ECp2_): # pylint: disable=invalid-name
"""Internal class."""
# pylint: disable=missing-function-docstring
@classmethod
def random(cls) -> _ECp2:
return cls(int(python.scalar.random()) * get_base2())
@classmethod
def deserialize(cls, bs) -> _ECp2:
p_mod = 0x2523648240000001ba344d80000000086121000000000013a700000000000013
d_inv = 0x1a7344bac91f117ea513ec0ed5682406b6c15140174d61b28b762ae9cf6d3b46
decode = lambda ns: (int.from_bytes(ns, 'little') * d_inv) % p_mod
x1, y1, x2, y2, z1, z2 = (decode(bs[:32]), decode(bs[32:64]), decode(bs[64:64+32]),
decode(bs[64+32:128]), decode(bs[128:128+32]), decode(bs[-32:]))
z2 = z2-(z2&1)+1 # 1 if z2==0 else z2
# Compute affine coordinates
inv_z1, inv_z2 = bn.invmodp(z1, p_mod), bn.invmodp(z2, p_mod)
x1, x2 = (x1 * inv_z1) % p_mod, (x2 * inv_z2) % p_mod
y1, y2 = (y1 * inv_z1) % p_mod, (y2 * inv_z2) % p_mod
q = cls()
# pylint: disable=invalid-name
assert q.set(_Fp2(_Fp(x1), _Fp(y1)), _Fp2(_Fp(x2), _Fp(y2))) \
or (x1 == 0 and y1 == 0 and x2 == 0 and y2 == 0)
return q
@classmethod
def mapfrom(cls, h) -> _ECp2:
return cls((int.from_bytes(h, 'little') % r) * get_base2())
def __new__(cls, *args, **kwargs):
q = ECp2_.__new__(cls)
_ECp2.__init__(q, *args, **kwargs)
return q
def __init__(self, q=None):
ECp2_.__init__(self)
if isinstance(q, (ECp2_, _ECp2)):
self.set(*q.get())
elif isinstance(q, python.point2):
self.set(*_ECp2.deserialize(bytes(q)).get()) # pragma: no cover
def serialize(self) -> bytes:
d = 0x212ba4f27ffffff5a2c62effffffffcdb939ffffffffff8a15ffffffffffff8e
# BN254 modulus of *F_p*.
p = 0x2523648240000001ba344d80000000086121000000000013a700000000000013
p1, p2 = (*self.get(),)
x1, y1, z1, x2, y2, z2 = (*p1.get(), 1, *p2.get(), 0)
encode = lambda n: (n * d % p).to_bytes(32, 'little')
return encode(x1) + encode(y1) + encode(x2) + encode(y2) + encode(z1) + encode(z2)
class _Fp12(Fp12_): # pylint: disable=invalid-name
"""Internal class."""
# pylint: disable=missing-function-docstring
@classmethod
def deserialize(cls, bs) -> _Fp12:
p_mod = 0x2523648240000001ba344d80000000086121000000000013a700000000000013
d_inv = 0x1a7344bac91f117ea513ec0ed5682406b6c15140174d61b28b762ae9cf6d3b46
decode = lambda ns: (int.from_bytes(ns, 'little') * d_inv) % p_mod
s = _Fp12()
s.a.a.a.x, s.a.a.b.x = decode(bs[32*0:(32*0)+32]), decode(bs[32*1:(32*1)+32])
s.c.a.a.x, s.c.a.b.x = decode(bs[32*2:(32*2)+32]), decode(bs[32*3:(32*3)+32])
s.b.b.a.x, s.b.b.b.x = decode(bs[32*4:(32*4)+32]), decode(bs[32*5:(32*5)+32])
s.b.a.a.x, s.b.a.b.x = decode(bs[32*6:(32*6)+32]), decode(bs[32*7:(32*7)+32])
s.a.b.a.x, s.a.b.b.x = decode(bs[32*8:(32*8)+32]), decode(bs[32*9:(32*9)+32])
s.c.b.a.x, s.c.b.b.x = decode(bs[32*10:(32*10)+32]), decode(bs[32*11:(32*11)+32])
return s
def __new__(cls, *args, **kwargs):
q = Fp12_.__new__(cls)
_Fp12.__init__(q, *args, **kwargs)
return q
def __init__(self, s=None):
Fp12_.__init__(self)
if isinstance(s, (Fp12_, _Fp12)):
self.set(*s.get())
elif isinstance(s, python.scalar2):
self.set(*_Fp12.deserialize(s).get()) # pragma: no cover
def serialize(self) -> bytes:
d = 0x212ba4f27ffffff5a2c62effffffffcdb939ffffffffff8a15ffffffffffff8e
# BN254 modulus of *F_p*.
p = 0x2523648240000001ba344d80000000086121000000000013a700000000000013
encode = lambda n: (n * d % p).to_bytes(32, 'little')
return bytes(
encode(self.a.a.a.int()) + encode(self.a.a.b.int()) +
encode(self.c.a.a.int()) + encode(self.c.a.b.int()) +
encode(self.b.b.a.int()) + encode(self.b.b.b.int()) +
encode(self.b.a.a.int()) + encode(self.b.a.b.int()) +
encode(self.a.b.a.int()) + encode(self.a.b.b.int()) +
encode(self.c.b.a.int()) + encode(self.c.b.b.int())
)
[docs]class python:
"""
Wrapper class for pure-Python implementations of primitive operations.
This class encapsulates pure-Python variants of all classes exported by
this module and of all the underlying low-level operations:
:obj:`python.pnt <pnt>`, :obj:`python.bas <bas>`,
:obj:`python.can <can>`, :obj:`python.ser <ser>`,
:obj:`python.des <des>`, :obj:`python.mul <mul>`,
:obj:`python.add <add>`, :obj:`python.sub <sub>`,
:obj:`python.neg <neg>`, :obj:`python.par <par>`,
:obj:`python.rnd <rnd>`, :obj:`python.scl <scl>`,
:obj:`python.sse <sse>`, :obj:`python.sde <sde>`,
:obj:`python.inv <inv>`, :obj:`python.smu <smu>`,
:obj:`python.sad <sad>`, :obj:`python.ssu <ssu>`,
:obj:`python.sne <sne>`,
:obj:`python.pnt2 <pnt2>`, :obj:`python.bas2 <bas2>`,
:obj:`python.can2 <can2>`, :obj:`python.ser2 <ser2>`,
:obj:`python.des2 <des2>`, :obj:`python.mul2 <mul2>`,
:obj:`python.add2 <add2>`, :obj:`python.sub2 <sub2>`,
:obj:`python.neg2 <neg2>`,
:obj:`python.rnd2 <rnd2>`, :obj:`python.scl2 <scl2>`,
:obj:`python.sse2 <sse2>`, :obj:`python.sde2 <sde2>`,
:obj:`python.inv2 <inv2>`, :obj:`python.smu2 <smu2>`,
:obj:`python.sad2 <sad2>`,
:obj:`python.point <oblivious.bn254.python.point>`,
:obj:`python.scalar <oblivious.bn254.python.scalar>`,
:obj:`python.point2 <oblivious.bn254.python.point2>`, and
:obj:`python.scalar2 <oblivious.bn254.python.scalar2>`.
For example, you can perform multiplication of scalars
using the pure-Python scalar multiplication implementation.
>>> s = python.scl()
>>> t = python.scl()
>>> python.smu(s, t) == python.smu(t, s)
True
Pure-Python variants of the :obj:`python.point <point>`
and :obj:`python.scalar <scalar>` classes always employ pure
Python implementations of operations when their methods are
invoked.
>>> p = python.scalar()
>>> q = python.scalar()
>>> p * q == q * p
True
"""
[docs] @staticmethod
def pnt(h: Optional[bytes] = None) -> point:
"""
Construct a point from its 64-byte vector representation (normally
obtained via hashing).
>>> p = python.pnt(hashlib.sha512('123'.encode()).digest())
>>> p.hex()[:64]
'6d68495eb4d539db4df70fd24d54fae37c9adf7dfd8dc705ccb8de8630e7cf22'
"""
return bytes.__new__(
python.point,
(_ECp.random() if h is None else _ECp.mapfrom(h)).serialize()
)
[docs] @staticmethod
def bas(s: scalar) -> point:
"""
Return the base point multiplied by the supplied scalar.
>>> bytes(python.bas(python.scalar.hash('123'.encode()))).hex()[:64]
'2d66076815cda25556bab4a930244ac284412267e9345aceb98d71530308401a'
"""
return s * python.point.from_base64(
'hQAAAAAAAJGJAAAAAADnpzoAAACAHm4XDAAAwI+/9wO'
'O////////FYr//////zm5zf////8uxqL1//9/8qQrIY'
'7///////8Viv//////ObnN/////y7GovX//3/ypCsh'
)
[docs] @staticmethod
def can(p: point) -> point:
"""
Normalize the representation of a point into its canonical form
and return the result.
>>> a = python.point.hash('123'.encode())
>>> p = python.add(a, a)
>>> p_can = python.can(python.add(a, a))
It may be the case that ``ser(p_can) != ser(p)``, depending on the
implementation. It is the responsibility of the user to ensure
that only canonical forms are serialized if those serialized forms
must be compared.
>>> mclbn256 = p.__class__ != python.point
>>> (python.ser(p_can) != python.ser(p)) or not mclbn256
True
Normalization is idempotent.
>>> python.can(p) == python.can(p_can)
True
"""
return p # This instance's coordinates are already in normal affine form.
[docs] @staticmethod
def ser(p: point) -> bytes:
"""
Return the binary representation of a point.
>>> q = python.point2.hash('123'.encode())
>>> python.des(python.ser(q)) == q
True
"""
return bytes(b for b in p)
[docs] @staticmethod
def des(bs: bytes) -> point:
"""
Construct a point corresponding to the supplied binary representation.
>>> p = python.point.hash('123'.encode())
>>> python.ser_p = bytes.fromhex(
... '825aa78af4c88d6de4abaebabf1a96f668956b92876cfb5d3a44829899cb480f'
... 'b03c992ec97868be765b98048118a96f42bdc466a963c243c223b95196304209'
... '8effffffffffff158affffffffff39b9cdffffffff2ec6a2f5ffff7ff2a42b21'
... )
>>> python.des(python.ser_p) == p
True
>>> python.ser(python.des(python.ser_p)) == python.ser_p
True
"""
# It may be useful to debug with ``_ECp.deserialize(bs).serialize()``
# in place of just ``bs``.
return bytes.__new__(python.point, bs)
[docs] @staticmethod
def mul(s: scalar, p: point) -> point:
"""
Multiply a point by a scalar and return the result.
>>> p = python.pnt(hashlib.sha512('123'.encode()).digest())
>>> s = python.scl(bytes.fromhex(
... '35c141f1c2c43543de9d188805a210abca3cd39a1e986304991ceded42b11709'
... ))
>>> python.mul(s, p).hex()[:64]
'68b5dd61adaa83f1511efe7b4749481cc9f86e11bf82d82960b6c56373de0d24'
"""
return bytes.__new__(python.point, _ECp(int(s) * _ECp.deserialize(p)).serialize())
[docs] @staticmethod
def add(p: point, q: point) -> point:
"""
Return the sum of the supplied points.
>>> p = python.point.hash('123'.encode())
>>> q = python.point.hash('456'.encode())
>>> python.point.to_bytes(python.add(p, q)).hex()[:64]
'1ea48cab238fece46bd0c9fb562c859e318e17a8fb75517a4750d30ca79b911c'
>>> python.add(python.sub(p, q), q) == p
True
"""
return bytes.__new__(
python.point,
_ECp.serialize(_ECp.deserialize(p).add(_ECp.deserialize(q)))
)
[docs] @staticmethod
def sub(p: point, q: point) -> point:
"""
Return the result of subtracting the right-hand point from the
left-hand point.
>>> p = python.point.hash('123'.encode())
>>> q = python.point.hash('456'.encode())
>>> python.sub(p, q).hex()[:64]
'a43a5ce1931b1300b62e5d7e1b0c691203bfd85fafd9585dc5e47a7e2acfea22'
>>> python.sub(python.add(p, q), q) == p
True
"""
return bytes.__new__(
python.point,
_ECp.serialize(_ECp.deserialize(p).add(-1 * _ECp.deserialize(q)))
)
[docs] @staticmethod
def neg(p: point) -> point:
"""
Return the additive inverse of a point.
>>> p = python.point.hash('123'.encode())
>>> python.point.to_bytes(python.neg(p)).hex()[:64]
'825aa78af4c88d6de4abaebabf1a96f668956b92876cfb5d3a44829899cb480f'
"""
return bytes.__new__(python.point, _ECp.serialize(-1 * _ECp.deserialize(p)))
[docs] @staticmethod
def rnd() -> scalar:
"""
Return random non-zero scalar.
>>> isinstance(python.rnd(), python.scalar)
True
"""
# d = 0x212ba4f27ffffff5a2c62effffffffd00242ffffffffff9c39ffffffffffffb2
# return int.to_bytes(((secrets.randbelow(r-1)+1) * d) % r, 32, 'little')
return python.scalar(int.to_bytes(secrets.randbelow(r-1)+1, 32, 'little'))
[docs] @classmethod
def scl(cls, s: Union[bytes, bytearray, None] = None) -> Optional[scalar]:
"""
Construct a scalar if the supplied bytes-like object represents
a valid scalar; otherwise, return ``None``. If no byte vector is
supplied, return a random scalar.
>>> s = python.scl()
>>> t = python.scl(s)
>>> s == t
True
>>> python.scl(bytes([255] * 32)) is None
True
"""
if s is None:
return python.rnd()
if int.from_bytes(s, 'little') < r:
return bytes.__new__(python.scalar, s)
return None
[docs] @staticmethod
def sse(s: scalar) -> bytes:
"""
Return the binary representation of a scalar.
>>> s = python.scalar.hash('123'.encode())
>>> python.sde(python.sse(s)) == s
True
"""
return bytes(b for b in s)
[docs] @staticmethod
def sde(bs: bytes) -> scalar:
"""
Construct a scalar from its binary representation.
>>> s = python.scalar.hash('123'.encode())
>>> bs = bytes.fromhex(
... '93d829354cb3592743174133104b5405ba6992b67bb219fbde3e394d70505913'
... )
>>> python.sde(bs) == s
True
>>> python.sse(python.sde(bs)) == bs
True
"""
return bytes.__new__(python.scalar, bs)
[docs] @staticmethod
def inv(s: scalar) -> scalar:
"""
Return the inverse of a scalar (modulo
``r = 16798108731015832284940804142231733909759579603404752749028378864165570215949``
in the prime field `F*_r`).
>>> s = python.scl()
>>> p = python.pnt()
>>> python.mul(python.inv(s), python.mul(s, p)) == p
True
"""
return python.scalar.from_int(bn.invmodp(int(s), r))
[docs] @staticmethod
def smu(s: scalar, t: scalar) -> scalar:
"""
Return the product of two scalars.
>>> s = python.scl()
>>> t = python.scl()
>>> python.smu(s, t) == python.smu(t, s)
True
"""
n = (python.scalar.__int__(s) * python.scalar.__int__(t)) % r
return python.scalar.from_int(n)
[docs] @staticmethod
def sad(s: scalar, t: scalar) -> scalar:
"""
Return the sum of two scalars.
>>> s = python.scl() # Could be `python.scl()`.
>>> t = python.scl()
>>> python.sad(s, t) == python.sad(t, s)
True
"""
return python.scalar.from_int((int(s) + int(t)) % r)
[docs] @staticmethod
def ssu(s: scalar, t: scalar) -> scalar:
"""
Return the result of subtracting the right-hand scalar from the
left-hand scalar.
>>> s = python.scl()
>>> t = python.scl()
>>> python.ssu(s, t) == python.sad(s, python.sne(t))
True
>>> python.ssu(s, t) == python.sne(python.ssu(t, s))
True
"""
return python.scalar.from_int((int(s) - int(t)) % r)
[docs] @staticmethod
def sne(s: scalar) -> scalar:
"""
Return the additive inverse of a scalar.
>>> s = python.scl()
>>> t = python.scl()
>>> python.sne(python.sne(s)) == s
True
"""
return python.scalar.from_int(r - int(s))
[docs] @staticmethod
def pnt2(h: Optional[bytes] = None) -> point2:
"""
Construct a second-level point if the supplied bytes-like object
represents a valid second-level point; otherwise, return ``None``.
If no byte vector is supplied, return a random second-level point.
>>> p = python.pnt2(hashlib.sha512('123'.encode()).digest())
>>> python.point2.to_bytes(p).hex()[:128] == (
... '4c595542640a69c4a70bda55c27ef96c133cd1f4a5f83b3371e571960c018e19'
... 'c54aaec2069f8f10a00f12bcbb3511cdb7356201f5277ec5e47da91405be2809'
... )
True
"""
return bytes.__new__(
python.point2,
(_ECp2.random() if h is None else _ECp2.mapfrom(h)).serialize()
)
[docs] @staticmethod
def bas2(s: scalar) -> point2:
"""
Return the base second-level point multiplied by the supplied scalar.
>>> bytes(python.bas2(python.scalar.hash('123'.encode()))).hex()[:64]
'e7000fb12d206112c73fe1054e9d77b35c77881eba6598b7e035171d90b13e0c'
"""
# return s * _ECp2.__new__(python.point2, get_base2())
return bytes.__new__(python.point2, _ECp2(int(s) * get_base2()).serialize())
[docs] @staticmethod
def can2(p: point2) -> point2:
"""
Normalize the representation of a second-level point into its canonical
form and return the result.
>>> p = python.bas2(scalar.from_int(1))
>>> python.ser(python.can2(p)).hex()[:64]
'669e6563afaa45af7cbc013d23f092bb3763d4dc41b97aef555bdf61de713f17'
"""
return p # This instance's coordinates are already in normal affine form.
[docs] @staticmethod
def ser2(p: point2) -> bytes:
"""
Return the binary representation of a second-level point.
>>> p = python.point2.hash('123'.encode())
>>> python.des2(python.ser2(p)) == p
True
It is the responsibility of the user to ensure that only canonical
representations of points are serialized.
"""
return bytes(b for b in p)
[docs] @staticmethod
def des2(bs: bytes) -> point2:
"""
Return the second-level point corresponding to the supplied binary
representation thereof.
>>> p = python.point2.hash('123'.encode())
>>> ser_p = bytes.fromhex(
... '30326199f303fce7a77cff6d2fb0b3de8cd409d1d562f3543f7d064cdc58d309'
... '7e88038ad76e85e5df26e4a9486a657b0431c8e7e09b0a1abf90fc874c515207'
... '2c6a88bb448065eb748df632b1d872e02f54b6f56fdb84a7b1cb388fe551fb08'
... '04464efa186bd4b1371e53d6f31f0e2f50ff553b6264a43331b42c976a0c541f'
... '8effffffffffff158affffffffff39b9cdffffffff2ec6a2f5ffff7ff2a42b21'
... '0000000000000000000000000000000000000000000000000000000000000000'
... )
>>> python.des2(ser_p) == p
True
>>> python.ser(python.des2(ser_p)) == ser_p
True
"""
return bytes.__new__(python.point2, bs)
# It may be useful to debug with _ECp2.deserialize(bs).serialize() in place of just bs.
[docs] @staticmethod
def mul2(s: scalar, p: point2) -> point2:
"""
Multiply a second-level point by a scalar.
>>> p = python.point2.hash('123'.encode())
>>> s = python.scl(bytes.fromhex(
... '35c141f1c2c43543de9d188805a210abca3cd39a1e986304991ceded42b11709'
... ))
>>> python.point2.to_bytes(python.mul2(s, p)).hex() == (
... '5f6f2ace8566ca47354fbe244ae3e6a854c37011fb6d6ac56571c94169e4ab18'
... '650bea4cfed5c9603e5949fe3d7509b17e20db4ff1f05129aad0d0a3bffb0008'
... '3043c5a14b986882836b1c929952ea3881d04ca44d487d1ab2d4c0b171b87d14'
... '5dca6dabb4f0ea7be5c95a861ed319d146b15d70542d3952af995a8bb35b8314'
... '8effffffffffff158affffffffff39b9cdffffffff2ec6a2f5ffff7ff2a42b21'
... '0000000000000000000000000000000000000000000000000000000000000000'
... )
True
"""
return bytes.__new__(python.point2, _ECp2(int(s) * _ECp2.deserialize(p)).serialize())
[docs] @staticmethod
def add2(p: point2, q: point2) -> point2:
"""
Return sum of the supplied second-level points.
>>> p = python.point2.hash('123'.encode())
>>> q = python.point2.hash('456'.encode())
>>> python.point2.to_bytes(python.add2(p, q)).hex() == (
... 'cb0fc423c1bac2ac2df47bf5f5548a42b0d0a0da325bc77243d15dc587a7b221'
... '9808a1649991ddf770f0060333aab4d499580b123f109b5cb180f1f8a75a090e'
... '83dd34d9ecdd6fd639230f7f0cf44b218fae4d879111de6c6c037e6ffdcdc823'
... 'f5a48318143873ca90ad512a2ea1854200eea5537cd0ac93691d5b94ff36b212'
... '8effffffffffff158affffffffff39b9cdffffffff2ec6a2f5ffff7ff2a42b21'
... '0000000000000000000000000000000000000000000000000000000000000000'
... )
True
"""
return bytes.__new__(
python.point2,
_ECp2(_ECp2.deserialize(p).add(_ECp2.deserialize(q))).serialize()
)
[docs] @staticmethod
def sub2(p: point2, q: point2) -> point2:
"""
Return the result of subtracting right-hand second-level point from the
left-hand second-level point.
>>> p = python.point2.hash('123'.encode())
>>> q = python.point2.hash('456'.encode())
>>> python.point2.to_bytes(python.sub2(p, q)).hex() == (
... 'e97a70c4e3a5369ebbb1dcf0cc1135c8c8e04a4ec7cffdf875ac429d66846d0b'
... '191b090909c40a723027b07ac44435a6ade3813d04b3632a17c92c5c98718902'
... '407c58ed13cc0c0aaa43d0eafd44080080c8199401fe4f8ed7dd0eb5fba86817'
... '141f74341ce3c4884f86a97f51f7c0b208fe52be336b7651252fa9881c93d203'
... '8effffffffffff158affffffffff39b9cdffffffff2ec6a2f5ffff7ff2a42b21'
... '0000000000000000000000000000000000000000000000000000000000000000'
... )
True
"""
return bytes.__new__(
python.point2,
_ECp2(_ECp2.deserialize(p).add(-1 * _ECp2.deserialize(q))).serialize()
)
[docs] @staticmethod
def neg2(p: point2) -> point2:
"""
Return the negation of a second-level point.
>>> p = python.point2.hash('123'.encode())
>>> python.point2.to_bytes(python.neg2(p)).hex() == (
... '30326199f303fce7a77cff6d2fb0b3de8cd409d1d562f3543f7d064cdc58d309'
... '7e88038ad76e85e5df26e4a9486a657b0431c8e7e09b0a1abf90fc874c515207'
... 'e7957744bb7f9abb9e7209cd4e27ae80d8ab490a1072af125034c7b09c12281c'
... '0fbab105e7942bf5dbe1ac290ce01232b800aac41de98f86d04bd3a81758cf05'
... '8effffffffffff158affffffffff39b9cdffffffff2ec6a2f5ffff7ff2a42b21'
... '0000000000000000000000000000000000000000000000000000000000000000'
... )
True
"""
return bytes.__new__(python.point2, _ECp2(-1 * _ECp2.deserialize(p)).serialize())
[docs] @staticmethod
def rnd2() -> scalar2:
"""
Return random non-zero second-level scalar.
>>> isinstance(python.rnd2(), python.scalar2)
True
"""
return python.scalar2.hash(secrets.token_bytes(384))
[docs] @staticmethod
def scl2(s: Union[bytes, bytearray, None] = None) -> Optional[scalar2]:
"""
Construct a second-level scalar if the supplied bytes-like object
represents a valid second-level scalar; otherwise, return ``None``.
If no byte vector is supplied, return a random second-level scalar.
>>> bs = bytes.fromhex(
... '18d0e065798ffa4ecca0a7cc6e2b8d6d3269f7eb413525e59b731332b02ea805'
... '4b90c89ce93e4452557f53aae5f8f13221858dde2ea6a50373f1858f11287021'
... '09b3daf7a217a20a3d872632f8ced643d32649b241a67601bec855bd43f07710'
... '88df722bcacc8fd174f16ad7ef1b1d5e622d93b7cfec6385c353c13793a4e01c'
... '371ef54cd836a88e1b1f1e198d566d3bbe5aa225734305f14cac9af9c821351c'
... '86b365190105d1b7e71011d1362ed6ad9133a2c2c42898e9ebc3d1604a608f10'
... '8461d37b2463b4f88f0ccb427b5eea8714ee88f8c33daf7ee4a3f45ca8bca911'
... '87956abb4d27de1b49e5e059f904fbb475804715ab25a17306fa0e196f305215'
... '3a60dfa073ca78de49edeaac9c54cab3d0138750c23c0b68de7924a69d1ba002'
... 'a24ac592622a45c59c1ded7133794292a09602bd57a36601d35438846fcb370f'
... '39c0fa7a15b34d14ab6f95ee5bf5b26bd5def1e7ed07350922d445da07d93622'
... '2db5baa9dec152c2b2bcfc46cde6fd22e70271af8a164e77e5808ce602095a1f'
... )
>>> python.scalar2.to_bytes(python.scl2(bs)).hex()[700:]
'36222db5baa9dec152c2b2bcfc46cde6fd22e70271af8a164e77e5808ce602095a1f'
"""
if s is None:
return python.rnd2()
try:
return python.sde2(s)
except ValueError: # pragma: no cover
return None
[docs] @staticmethod
def sse2(s: scalar2) -> bytes:
"""
Return the binary representation of a second-level scalar.
>>> s = python.scalar2.hash('123'.encode())
>>> python.sde2(python.sse2(s)) == s
True
"""
return bytes(b for b in s)
[docs] @staticmethod
def sde2(bs: bytes) -> scalar2:
"""
Construct a second-level scalar from its binary representation.
>>> s = python.scalar2.hash('123'.encode())
>>> bs = bytes.fromhex(
... '36980a8359d40e106075488e80cf1479f2e6ba95d6a99a67832d21b7b94d8c1d'
... '5eb4d655f23e1d5d499d51d1c552b5e7df6943091427cd080f582e120613a021'
... '85898ef7d016e47a74a8df62316cc4ad975cb64bb63867ed9b5221f77bb9a121'
... '7bd89cd213eee0c3fdf2e0e13ef9e30383ea5607c8d13fc10e04448a6c964a00'
... '04a098a55beab09732220966319333608b2187ee2196eb5b4253bc2b1aea5303'
... '654260dd687a2eb176a494258ff7ef753f93105a6f0e9f46c926afdbe31ff124'
... '6bdd87c32537abcdb46ad542792edd74a229c9ba61abcd993f074237a91f5215'
... '8f6b07886895733edde15cb22129459162d89d3662826b74e4fcbe4e9e8c2420'
... 'bd53586a09f91ff8f67f92cba72c5b64a9c3965c01e93710200ab4e084955316'
... 'fb18950835b79fb4c2930efcc5fcaa9d82ee0faff036b80657daee233a445901'
... '7df3e57cb535ed26162b3ee0f8961131a93fe3198dc5393d277ed8bac5532411'
... '93b7ad15c52ca123fd26f592a2219b1bf118b3035893cc4abf614b422f978718'
... )
>>> python.sde2(bs) == s
True
>>> python.sse(python.sde2(bs)) == bs
True
"""
return bytes.__new__(python.scalar2, bs)
[docs] @staticmethod
def inv2(s: scalar2) -> scalar2:
"""
Return the inverse of a second-level scalar.
>>> s = python.scl2()
>>> python.smu2(s, python.smu2(s, python.inv2(s))) == s
True
>>> python.smu2(python.smu2(s, s), python.inv2(s)) == s
True
"""
return bytes.__new__(
python.scalar2,
_Fp12(_Fp12.deserialize(s).inverse()).serialize()
)
[docs] @staticmethod
def smu2(s: scalar2, t: scalar2) -> scalar2:
"""
Return second-level scalar multiplied by another scalar.
>>> s = python.scalar2.random()
>>> t = python.scalar2.random()
>>> python.smu2(s, t) == python.smu2(t, s)
True
"""
return bytes.__new__(
python.scalar2,
_Fp12(_Fp12.deserialize(s) * _Fp12.deserialize(t)).serialize()
)
[docs] @staticmethod
def sad2(s: scalar2, t: scalar2) -> scalar2:
"""
Return scalar2 added to another scalar2.
>>> s = python.scl2()
>>> t = python.scl2()
>>> python.sad2(s, t) == python.sad2(t, s)
True
"""
return bytes.__new__(
python.scalar2,
_Fp12(_Fp12.deserialize(s) + _Fp12.deserialize(t)).serialize()
)
# Indicate that data structures based on the dynamic/shared library
# in mclbn256 have not (yet, at least) been defined.
mclbn256 = False
#
# Attempt to load primitives from mclbn256, if it is present;
# otherwise, use the mclbn256 library.
#
try:
# Attempt to load mclbn256 with its (bundled) shared/dynamic library file.
from mclbn256 import Fr, G1, G2, GT # pylint: disable=import-error
# pylint: disable=C2801,W0621
[docs] class mcl:
"""
Wrapper class for binary implementations of primitive
operations.
When this module is imported, it makes a number of attempts
to locate an instance of the shared/dynamic library file of the
`mclbn256 <https://doc.mclbn256.org>`__ library on the host
system. The sequence of attempts is listed below, in order.
1. It uses ``ctypes.util.find_library`` to look for ``'mcl'``
or ``'mclbn256'``.
2. It attempts to find a file ``mclbn256.so`` or ``mclbn256.dll``
in the paths specified by the ``PATH`` and ``LD_LIBRARY_PATH``
environment variables.
3. If the `mclbn256 <https://pypi.org/project/mclbn256>`__ package is
installed, it reverts to the compiled subset of mclbn256
included in that package.
If all of the above fail, then :obj:`mcl` is assigned
the value ``None`` and all classes exported by this module default
to their pure-Python variants (*i.e.*, those encapsulated within
:obj:`~oblivious.bn254.python`). One way to confirm that a
dynamic/shared library *has been found* when this module
is imported is to evaluate the expression ``mcl is not None``.
If a shared/dynamic library file has been loaded successfully,
this class encapsulates shared/dynamic library variants of all classes
exported by this module and of all the underlying low-level operations:
:obj:`mcl.pnt <pnt>`, :obj:`mcl.bas <bas>`,
:obj:`mcl.can <can>`, :obj:`mcl.ser <ser>`,
:obj:`mcl.des <des>`, :obj:`mcl.mul <mul>`,
:obj:`mcl.add <add>`, :obj:`mcl.sub <sub>`,
:obj:`mcl.neg <neg>`, :obj:`mcl.par <par>`,
:obj:`mcl.rnd <rnd>`, :obj:`mcl.scl <scl>`,
:obj:`mcl.sse <sse>`, :obj:`mcl.sde <sde>`,
:obj:`mcl.inv <inv>`, :obj:`mcl.smu <smu>`,
:obj:`mcl.sad <sad>`, :obj:`mcl.ssu <ssu>`,
:obj:`mcl.sne <sne>`,
:obj:`mcl.pnt2 <pnt2>`, :obj:`mcl.bas2 <bas2>`,
:obj:`mcl.can2 <can2>`, :obj:`mcl.ser2 <ser2>`,
:obj:`mcl.des2 <des2>`, :obj:`mcl.mul2 <mul2>`,
:obj:`mcl.add2 <add2>`, :obj:`mcl.sub2 <sub2>`,
:obj:`mcl.neg2 <neg2>`,
:obj:`mcl.rnd2 <rnd2>`, :obj:`mcl.scl2 <scl2>`,
:obj:`mcl.sse2 <sse2>`, :obj:`mcl.sde2 <sde2>`,
:obj:`mcl.inv2 <inv2>`, :obj:`mcl.smu2 <smu2>`,
:obj:`mcl.sad2 <sad2>`,
:obj:`mcl.point <oblivious.bn254.mcl.point>`,
:obj:`mcl.scalar <oblivious.bn254.mcl.scalar>`,
:obj:`mcl.point2 <oblivious.bn254.mcl.point2>`, and
:obj:`mcl.scalar2 <oblivious.bn254.mcl.scalar2>`.
For example, you can perform addition of points using the point
addition implementation found in the shared/dynamic library
bundled with the instance of the package
`mclbn256 <https://pypi.org/project/mclbn256>`__ that is found
on the host system.
>>> p = mcl.pnt()
>>> q = mcl.pnt()
>>> mcl.add(p, q) == mcl.add(q, p)
True
Methods found in the shared/dynamic library variants of the
:obj:`point`, :obj:`scalar`, :obj:`point2`, and :obj:`scalar2`
classes are wrappers for the shared/dynamic library
implementations of the underlying operations.
>>> p = mcl.point()
>>> q = mcl.point()
>>> p + q == q + p
True
"""
# pylint: disable=too-many-public-methods
[docs] @staticmethod
def pnt(h: Union[bytes, bytearray, None] = None) -> G1:
"""
Construct a point if the supplied bytes-like object represents
a valid point; otherwise, return ``None``. If no byte vector is
supplied, return a random point.
>>> p = mcl.pnt(hashlib.sha512('123'.encode()).digest())
>>> p.__class__ = point
>>> mcl.point.to_bytes(p).hex()[:64]
'6d68495eb4d539db4df70fd24d54fae37c9adf7dfd8dc705ccb8de8630e7cf22'
"""
return G1.random() if h is None else G1.mapfrom(h)
[docs] @staticmethod
def bas(s: Fr) -> G1:
"""
Return the base point multiplied by the supplied scalar.
>>> p = mcl.bas(mcl.scalar.hash('123'.encode())).normalize().normalize()
>>> p.__class__ = point
>>> mcl.point.to_bytes(p).hex()[:64]
'2d66076815cda25556bab4a930244ac284412267e9345aceb98d71530308401a'
"""
return G1.base_point() * s
[docs] @staticmethod
def can(p: G1) -> G1:
"""
Normalize the representation of a point into its canonical form and
return the result.
>>> a = mcl.point.hash('123'.encode())
>>> p = mcl.add(a, a)
>>> p_can = mcl.can(mcl.add(a, a))
We may have ``ser(p_can) != ser(p)`` here, depending on the backend
implementation. Either normalization matters, or MCl is not the backend.
>>> (mcl.ser(p_can) != mcl.ser(p)) or not mclbn256
True
Normalization is idempotent.
>>> mcl.can(p) == mcl.can(p_can)
True
"""
return p.normalize() # Sets ``(x, y, z)`` to unique vector ``(x/z, y/z, 1)``.
[docs] @staticmethod
def ser(p: G1) -> bytes:
"""
Return the binary representation of a point.
>>> p = mcl.point.hash('123'.encode())
>>> mcl.des(mcl.ser(p)) == p
True
"""
IoEcProj, IoArrayRaw = 1024, 64 # Constants from mcl library. # pylint: disable=C0103
return p.tostr(IoEcProj|IoArrayRaw)[1:]
[docs] @staticmethod
def des(bs: bytes) -> G1:
"""
Construct a point corresponding to the supplied binary representation.
>>> p = mcl.point.hash('123'.encode())
>>> ser_p = bytes.fromhex(
... '825aa78af4c88d6de4abaebabf1a96f668956b92876cfb5d3a44829899cb480f'
... 'b03c992ec97868be765b98048118a96f42bdc466a963c243c223b95196304209'
... '8effffffffffff158affffffffff39b9cdffffffff2ec6a2f5ffff7ff2a42b21'
... )
>>> mcl.des(ser_p) == p
True
>>> mcl.ser(mcl.des(ser_p)) == ser_p
True
"""
IoEcProj, IoArrayRaw = 1024, 64 # MCl constants # pylint: disable=C0103
return G1.new_fromstr(b"4"+bs, IoEcProj|IoArrayRaw)
[docs] @staticmethod
def mul(s: Fr, p: G1) -> G1:
"""
Multiply a point by a scalar and return the result.
>>> p = mcl.pnt(hashlib.sha512('123'.encode()).digest())
>>> s = mcl.scl(bytes.fromhex(
... '35c141f1c2c43543de9d188805a210abca3cd39a1e986304991ceded42b11709'
... ))
>>> q = mcl.mul(s, p).normalize().normalize()
>>> q.__class__ = point
>>> mcl.point.to_bytes(q).hex()[:64]
'68b5dd61adaa83f1511efe7b4749481cc9f86e11bf82d82960b6c56373de0d24'
"""
return G1.__mul__(p, s)
[docs] @staticmethod
def add(p: G1, q: G1) -> G1:
"""
Return the sum of the supplied points.
>>> p = mcl.point.hash('123'.encode())
>>> q = mcl.point.hash('456'.encode())
>>> r = mcl.add(p, q).normalize().normalize()
>>> r.__class__ = point
>>> mcl.point.to_bytes(r).hex()[:64]
'1ea48cab238fece46bd0c9fb562c859e318e17a8fb75517a4750d30ca79b911c'
"""
return G1.__add__(p, q)
[docs] @staticmethod
def sub(p: G1, q: G1) -> G1:
"""
Return the result of subtracting the right-hand point from the
left-hand point.
>>> p = mcl.point.hash('123'.encode())
>>> q = mcl.point.hash('456'.encode())
>>> r = mcl.sub(p, q).normalize().normalize()
>>> r.__class__ = point
>>> mcl.point.to_bytes(r).hex()[:64]
'a43a5ce1931b1300b62e5d7e1b0c691203bfd85fafd9585dc5e47a7e2acfea22'
"""
return G1.__sub__(p, q)
[docs] @staticmethod
def neg(p: G1) -> G1:
"""
Return the additive inverse of a point.
>>> p = mcl.point.hash('123'.encode())
>>> q = mcl.neg(p)
>>> q.__class__ = point
>>> mcl.point.to_bytes(q).hex()[:64]
'825aa78af4c88d6de4abaebabf1a96f668956b92876cfb5d3a44829899cb480f'
"""
return G1.__neg__(p)
[docs] @staticmethod
def par(p: Union[G1, G2], q: Union[G1, G2]) -> GT:
"""
Compute the pairing function on two points.
>>> p = mcl.point.hash('123'.encode())
>>> q = mcl.point2.base(mcl.scalar.from_int(456))
>>> r = mcl.par(p, q)
>>> r.__class__ = mcl.scalar2
>>> mcl.scalar2.to_bytes(r).hex()[700:]
'd01f7e038b05acc5519eeda026c4aa111eb12f3483f274c60e34e6ec7571435df707'
The order of the two arguments is not important (as long as exactly
one argument is an instance of :obj:`point` and the other is an
instance of :obj:`point2`).
>>> r = mcl.par(q, p)
>>> r.__class__ = mcl.scalar2
>>> mcl.scalar2.to_bytes(r).hex()[700:]
'd01f7e038b05acc5519eeda026c4aa111eb12f3483f274c60e34e6ec7571435df707'
The pairing function is bilinear.
>>> p = mcl.point.random()
>>> s = mcl.scalar.random()
>>> t = mcl.scalar.random()
>>> q = mcl.point2.random()
>>> -((~s) * (s * p)) - p == mcl.scalar.from_int(-2) * p
True
>>> s * t * p @ q == s * p @ (t * q)
True
Suppose there are two points: one multiplied by the scalar ``s`` and the other
multiplied by the scalar ``t``. Their equality can be determined by using a
balancing point: ``g**(~s * t)``. If the pairing of ``t * x`` with ``g`` is the
same as the pairing with ``s * y`` and ``g**(~s * t)``, then ``x`` equals ``y``.
>>> x = y = p
>>> g = mcl.point2.base(mcl.scalar.from_int(1))
>>> b = mcl.point2.base(~s * t)
>>> (t * x) @ g == (s * y) @ b
True
This operation is defined only for a point and a second-level point.
Any attempt to invoke the operation on values or objects of other types
raises an exception.
>>> p @ (p + p)
Traceback (most recent call last):
...
TypeError: pairing is defined only for a point and a second-level point
>>> g @ b
Traceback (most recent call last):
...
TypeError: pairing is defined only for a point and a second-level point
Pairing is intended to be nonsingular.
>>> p @ q.clear()
Traceback (most recent call last):
...
TypeError: cannot meaningfully pair the infinity point
>>> p.clear() @ g
Traceback (most recent call last):
...
TypeError: cannot meaningfully pair the infinity point
"""
if p.zero() or q.zero():
raise TypeError('cannot meaningfully pair the infinity point')
if (
(isinstance(p, G1) and isinstance(q, G1)) or
(isinstance(p, G2) and isinstance(q, G2))
):
raise TypeError(
'pairing is defined only for a point and a second-level point'
)
if isinstance(p, G1):
return G1.__matmul__(G1.__new__(G1, p), G2.__new__(G2, q))
return G2.__matmul__(G2.__new__(G2, p), G1.__new__(G1, q))
[docs] @staticmethod
def rnd() -> Fr:
"""
Return random non-zero scalar.
>>> s = mcl.rnd()
>>> isinstance(s, Fr)
True
>>> s.__class__ = scalar
>>> len(mcl.scalar.to_bytes(s))
32
"""
return Fr().randomize()
[docs] @classmethod
def scl(cls, bs: Union[bytes, bytearray, None] = None) -> Optional[Fr]:
"""
Construct a scalar if the supplied bytes-like object represents
a valid scalar; otherwise, return ``None``. If no byte vector is
supplied, return a random scalar.
>>> s = mcl.scl()
>>> s.__class__ = scalar
>>> t = mcl.scl(mcl.scalar.to_bytes(s))
>>> s == t
True
>>> mcl.scl(bytes([255] * 32)) is None
True
"""
if bs is None:
return cls.rnd()
try:
s = cls.sde(bs)
return s
except ValueError: # pragma: no cover
return None
[docs] @staticmethod
def sse(s: Fr) -> bytes:
"""
Return the binary representation of a scalar.
>>> s = mcl.scalar.hash('123'.encode())
>>> mcl.sde(mcl.sse(s)) == s
True
"""
IoEcProj, IoArrayRaw = 1024, 64 # Constants from mcl library. # pylint: disable=C0103
return s.tostr(IoEcProj|IoArrayRaw)
[docs] @staticmethod
def sde(bs: bytes) -> Fr:
"""
Return a scalar from its binary representation.
>>> s = mcl.scalar.hash('123'.encode())
>>> bs = bytes.fromhex(
... '93d829354cb3592743174133104b5405ba6992b67bb219fbde3e394d70505913'
... )
>>> mcl.sde(bs) == s
True
>>> mcl.sse(mcl.sde(bs)) == bs
True
"""
IoEcProj, IoArrayRaw = 1024, 64 # MCl constants # pylint: disable=C0103
return Fr.new_fromstr(bs, IoEcProj|IoArrayRaw)
[docs] @staticmethod
def inv(s: Fr) -> Fr:
r"""
Return inverse of a scalar (modulo
``r = 16798108731015832284940804142231733909759579603404752749028378864165570215949``
in the prime field *F*\_*r*).
>>> (s, p) = (mcl.scl(), mcl.pnt())
>>> mcl.mul(mcl.inv(s), mcl.mul(s, p)) == p
True
"""
return Fr.__invert__(s)
[docs] @staticmethod
def smu(s: Fr, t: Fr) -> Fr:
"""
Return scalar multiplied by another scalar.
>>> (s, t) = (mcl.scl(), mcl.scl())
>>> mcl.smu(s, t) == mcl.smu(t, s)
True
"""
return Fr.__mul__(s, t)
[docs] @staticmethod
def sad(s: Fr, t: Fr) -> Fr:
"""
Return scalar added to another scalar.
>>> (s, t) = (mcl.scl(), mcl.scl())
>>> mcl.sad(s, t) == mcl.sad(t, s)
True
"""
return Fr.__add__(s, t)
[docs] @staticmethod
def ssu(s: Fr, t: Fr) -> Fr:
"""
Return the result of one scalar subtracted from another scalar.
>>> (s, t) = (mcl.scl(), mcl.scl())
>>> mcl.ssu(s, t) == mcl.sad(s, mcl.sne(t))
True
>>> mcl.ssu(s, t) == mcl.sne(mcl.ssu(t, s))
True
"""
return Fr.__sub__(s, t)
[docs] @staticmethod
def sne(s: Fr) -> Fr:
"""
Return the additive inverse of a scalar.
>>> (s, t) = (mcl.scl(), mcl.scl())
>>> mcl.sne(mcl.sne(s)) == s
True
"""
return Fr.__neg__(s)
[docs] @staticmethod
def pnt2(h: Optional[bytes] = None) -> G2:
"""
Construct a second-level point if the supplied bytes-like object
represents a valid second-level point; otherwise, return ``None``.
If no byte vector is supplied, return a random second-level point.
>>> p = mcl.pnt2(hashlib.sha512('123'.encode()).digest())
>>> p.__class__ = point2
>>> mcl.point2.to_bytes(p.canonical().canonical()).hex()[:128] == (
... '4c595542640a69c4a70bda55c27ef96c133cd1f4a5f83b3371e571960c018e19'
... 'c54aaec2069f8f10a00f12bcbb3511cdb7356201f5277ec5e47da91405be2809'
... )
True
"""
return G2.random() if h is None else G2.mapfrom(h)
[docs] @staticmethod
def bas2(s) -> G2:
"""
Return the base second-level point multiplied by the supplied scalar.
>>> r = mcl.bas2(mcl.scalar.hash('123'.encode())).normalize().normalize()
>>> r.__class__ = point2
>>> mcl.point2.to_bytes(r).hex()[:64]
'e7000fb12d206112c73fe1054e9d77b35c77881eba6598b7e035171d90b13e0c'
"""
# return s * G2.__new__(point2, G2.base_point())
return G2.base_point() * s
[docs] @staticmethod
def can2(p: G2) -> G2:
"""
Normalize the representation of a second-level point into its
canonical form and return the result.
>>> p = mcl.bas2(scalar.from_int(1))
>>> mcl.ser(mcl.can2(p)).hex()[:64]
'669e6563afaa45af7cbc013d23f092bb3763d4dc41b97aef555bdf61de713f17'
"""
return p.normalize() # Sets ``(x, y, z)`` to unique vector ``(x/z, y/z, 1)``.
[docs] @staticmethod
def ser2(p: G2) -> bytes:
"""
Return the binary representation of a second-level point.
>>> p = mcl.point2.hash('123'.encode())
>>> mcl.des2(mcl.ser2(p)) == p
True
It is the responsibility of the user to ensure that only canonical
representations of points are serialized.
"""
IoEcProj, IoArrayRaw = 1024, 64 # Constants from mcl library. # pylint: disable=C0103
return p.tostr(IoEcProj|IoArrayRaw)[1:]
[docs] @staticmethod
def des2(bs: bytes) -> G2:
"""
Return the second-level point corresponding to the supplied binary
representation thereof.
>>> p = mcl.point2.hash('123'.encode())
>>> mcl.ser_p = bytes.fromhex(
... 'b5b0a52e43ba71ae03317333da4ba9452dbdbbec353ade0c732348e0bea4ba1b'
... '8860718e5ba784d55799ab292459a638f6399738a6de348742e6a789674f300d'
... '7e59c60a595253ebf69bf0794b7a032e59b6b5037adba410d680b53ffac08517'
... 'cf5bc3be9d850ec64ea6939904cf66b66b6b4b82be03ee4f10661fedaf83841f'
... 'ba7e678442a658340a5b3c51eb5076d738cf88387ada6cbd1fe7f8d8a2268417'
... 'bc8aedbc99808b0450025d0c75b5f1ccb34bc69934cc620d9ea51038a1d98721'
... )
>>> mcl.des2(mcl.ser_p) == p
True
>>> mcl.ser(mcl.des2(mcl.ser_p)) == mcl.ser_p
True
"""
IoEcProj, IoArrayRaw = 1024, 64 # MCl constants # pylint: disable=C0103
return G2.new_fromstr(b"4"+bs, IoEcProj|IoArrayRaw)
[docs] @staticmethod
def mul2(s: Fr, p: G2) -> G2:
"""
Multiply a second-level point by a scalar.
>>> p = mcl.point2.hash('123'.encode())
>>> s = mcl.scl(bytes.fromhex(
... '35c141f1c2c43543de9d188805a210abca3cd39a1e986304991ceded42b11709'
... ))
>>> r = mcl.mul2(s, p).normalize().normalize()
>>> r.__class__ = point2
>>> mcl.point2.to_bytes(r).hex() == (
... '5f6f2ace8566ca47354fbe244ae3e6a854c37011fb6d6ac56571c94169e4ab18'
... '650bea4cfed5c9603e5949fe3d7509b17e20db4ff1f05129aad0d0a3bffb0008'
... '3043c5a14b986882836b1c929952ea3881d04ca44d487d1ab2d4c0b171b87d14'
... '5dca6dabb4f0ea7be5c95a861ed319d146b15d70542d3952af995a8bb35b8314'
... '8effffffffffff158affffffffff39b9cdffffffff2ec6a2f5ffff7ff2a42b21'
... '0000000000000000000000000000000000000000000000000000000000000000'
... )
True
"""
return G2.__mul__(p, s)
[docs] @staticmethod
def add2(p: G2, q: G2) -> G2:
"""
Return sum of the supplied second-level points.
>>> p = mcl.point2.hash('123'.encode())
>>> q = mcl.point2.hash('456'.encode())
>>> r = mcl.add2(p, q).normalize().normalize()
>>> r.__class__ = point2
>>> mcl.point2.to_bytes(r).hex() == (
... 'cb0fc423c1bac2ac2df47bf5f5548a42b0d0a0da325bc77243d15dc587a7b221'
... '9808a1649991ddf770f0060333aab4d499580b123f109b5cb180f1f8a75a090e'
... '83dd34d9ecdd6fd639230f7f0cf44b218fae4d879111de6c6c037e6ffdcdc823'
... 'f5a48318143873ca90ad512a2ea1854200eea5537cd0ac93691d5b94ff36b212'
... '8effffffffffff158affffffffff39b9cdffffffff2ec6a2f5ffff7ff2a42b21'
... '0000000000000000000000000000000000000000000000000000000000000000'
... )
True
"""
return G2.__add__(p, q)
[docs] @staticmethod
def sub2(p: G2, q: G2) -> G2:
"""
Return the result of subtracting the right-hand second-level point
from the left-hand second-level point.
>>> p = mcl.point2.hash('123'.encode())
>>> q = mcl.point2.hash('456'.encode())
>>> r = mcl.sub2(p, q).normalize().normalize()
>>> r.__class__ = point2
>>> mcl.point2.to_bytes(r).hex() == (
... 'e97a70c4e3a5369ebbb1dcf0cc1135c8c8e04a4ec7cffdf875ac429d66846d0b'
... '191b090909c40a723027b07ac44435a6ade3813d04b3632a17c92c5c98718902'
... '407c58ed13cc0c0aaa43d0eafd44080080c8199401fe4f8ed7dd0eb5fba86817'
... '141f74341ce3c4884f86a97f51f7c0b208fe52be336b7651252fa9881c93d203'
... '8effffffffffff158affffffffff39b9cdffffffff2ec6a2f5ffff7ff2a42b21'
... '0000000000000000000000000000000000000000000000000000000000000000'
... )
True
"""
return G2.__sub__(p, q)
[docs] @staticmethod
def neg2(p: G2) -> G2:
"""
Return the negation of a second-level point.
>>> p = mcl.point2.hash('123'.encode())
>>> r = mcl.neg2(p).normalize().normalize()
>>> r.__class__ = point2
>>> mcl.point2.to_bytes(r).hex() == (
... '30326199f303fce7a77cff6d2fb0b3de8cd409d1d562f3543f7d064cdc58d309'
... '7e88038ad76e85e5df26e4a9486a657b0431c8e7e09b0a1abf90fc874c515207'
... 'e7957744bb7f9abb9e7209cd4e27ae80d8ab490a1072af125034c7b09c12281c'
... '0fbab105e7942bf5dbe1ac290ce01232b800aac41de98f86d04bd3a81758cf05'
... '8effffffffffff158affffffffff39b9cdffffffff2ec6a2f5ffff7ff2a42b21'
... '0000000000000000000000000000000000000000000000000000000000000000'
... )
True
"""
return G2.__neg__(p)
[docs] @staticmethod
def rnd2() -> GT:
"""
Return random non-zero second-level scalar.
>>> isinstance(mcl.rnd2(), GT)
True
"""
return mcl.scalar2.hash(secrets.token_bytes(384))
[docs] @staticmethod
def scl2(s: Union[bytes, bytearray, None] = None) -> Optional[GT]:
"""
Construct a second-level scalar if the supplied bytes-like object
represents a valid second-level scalar; otherwise, return ``None``.
If no byte vector is supplied, return a random second-level scalar.
>>> bs = bytes.fromhex(
... '18d0e065798ffa4ecca0a7cc6e2b8d6d3269f7eb413525e59b731332b02ea805'
... '4b90c89ce93e4452557f53aae5f8f13221858dde2ea6a50373f1858f11287021'
... '09b3daf7a217a20a3d872632f8ced643d32649b241a67601bec855bd43f07710'
... '88df722bcacc8fd174f16ad7ef1b1d5e622d93b7cfec6385c353c13793a4e01c'
... '371ef54cd836a88e1b1f1e198d566d3bbe5aa225734305f14cac9af9c821351c'
... '86b365190105d1b7e71011d1362ed6ad9133a2c2c42898e9ebc3d1604a608f10'
... '8461d37b2463b4f88f0ccb427b5eea8714ee88f8c33daf7ee4a3f45ca8bca911'
... '87956abb4d27de1b49e5e059f904fbb475804715ab25a17306fa0e196f305215'
... '3a60dfa073ca78de49edeaac9c54cab3d0138750c23c0b68de7924a69d1ba002'
... 'a24ac592622a45c59c1ded7133794292a09602bd57a36601d35438846fcb370f'
... '39c0fa7a15b34d14ab6f95ee5bf5b26bd5def1e7ed07350922d445da07d93622'
... '2db5baa9dec152c2b2bcfc46cde6fd22e70271af8a164e77e5808ce602095a1f'
... )
>>> s = mcl.scl2(bs)
>>> s.__class__ = mcl.scalar2
>>> mcl.scalar2.to_bytes(s).hex()[700:]
'36222db5baa9dec152c2b2bcfc46cde6fd22e70271af8a164e77e5808ce602095a1f'
"""
if s is None:
return mcl.rnd2()
try:
return mcl.sde2(s)
except ValueError: # pragma: no cover
return None
[docs] @staticmethod
def sse2(s: scalar2) -> bytes:
"""
Return the binary representation of a second-level scalar.
>>> s = scalar2.hash('123'.encode())
>>> mcl.sde2(mcl.sse2(s)) == s
True
"""
IoEcProj, IoArrayRaw = 1024, 64 # Constants from mcl library. # pylint: disable=C0103
return s.tostr(IoEcProj|IoArrayRaw)
[docs] @staticmethod
def sde2(bs: bytes) -> GT:
"""
Return the second-level scalar corresponding to the supplied binary
representation thereof.
>>> s = mcl.scalar2.hash('123'.encode())
>>> bs = bytes.fromhex(
... '36980a8359d40e106075488e80cf1479f2e6ba95d6a99a67832d21b7b94d8c1d'
... '5eb4d655f23e1d5d499d51d1c552b5e7df6943091427cd080f582e120613a021'
... '85898ef7d016e47a74a8df62316cc4ad975cb64bb63867ed9b5221f77bb9a121'
... '7bd89cd213eee0c3fdf2e0e13ef9e30383ea5607c8d13fc10e04448a6c964a00'
... '04a098a55beab09732220966319333608b2187ee2196eb5b4253bc2b1aea5303'
... '654260dd687a2eb176a494258ff7ef753f93105a6f0e9f46c926afdbe31ff124'
... '6bdd87c32537abcdb46ad542792edd74a229c9ba61abcd993f074237a91f5215'
... '8f6b07886895733edde15cb22129459162d89d3662826b74e4fcbe4e9e8c2420'
... 'bd53586a09f91ff8f67f92cba72c5b64a9c3965c01e93710200ab4e084955316'
... 'fb18950835b79fb4c2930efcc5fcaa9d82ee0faff036b80657daee233a445901'
... '7df3e57cb535ed26162b3ee0f8961131a93fe3198dc5393d277ed8bac5532411'
... '93b7ad15c52ca123fd26f592a2219b1bf118b3035893cc4abf614b422f978718'
... )
>>> mcl.sde2(bs) == s
True
>>> mcl.sse(mcl.sde2(bs)) == bs
True
"""
IoEcProj, IoArrayRaw = 1024, 64 # MCl constants # pylint: disable=C0103
return GT.new_fromstr(bs, IoEcProj|IoArrayRaw)
[docs] @staticmethod
def inv2(s: GT) -> GT:
"""
Return the inverse of a second-level scalar.
>>> s = mcl.scl2()
>>> mcl.smu2(s, mcl.smu2(s, mcl.inv2(s))) == s
True
>>> mcl.smu2(mcl.smu2(s, s), mcl.inv2(s)) == s
True
"""
return GT.__inv__(s)
[docs] @staticmethod
def smu2(s: GT, t: GT) -> GT:
"""
Return the product of two second-level scalars.
>>> p1 = mcl.point.hash('123'.encode())
>>> p2 = mcl.point.hash('456'.encode())
>>> q1 = mcl.point2.base(mcl.scalar.hash('123'.encode()))
>>> q2 = mcl.point2.base(mcl.scalar.hash('456'.encode()))
>>> s = p1 @ q1
>>> t = p2 @ q2
>>> mcl.smu2(s, t) == mcl.smu2(t, s)
True
"""
return GT.__mul__(s, t)
[docs] @staticmethod
def sad2(s: GT, t: GT) -> GT:
"""
Return the sum of two second-level scalars.
>>> s = mcl.scl2()
>>> t = mcl.scl2()
>>> mcl.sad2(s, t) == mcl.sad2(t, s)
True
"""
return GT.__add__(s, t)
# Indicate that data structures based on the dynamic/shared library have
# successfully been defined.
mclbn256 = True
except: # pylint: disable=W0702 # pragma: no cover
mcl = None # Exported symbol.
#
# Dedicated point and scalar data structures for each implementation.
#
for (_implementation, _p_base_cls, _s_base_cls, _p2_base_cls, _s2_base_cls) in (
[(python, bytes, bytes, bytes, bytes)] +
([(mcl, G1, Fr, G2, GT)] if mcl is not None else [])
):
# pylint: disable=cell-var-from-loop
[docs] class point(_p_base_cls): # pylint: disable=W0621,E0102
"""
Wrapper class for a bytes-like object that corresponds
to a point.
"""
_implementation = _implementation
[docs] @classmethod
def random(cls) -> point:
"""
Return random point object.
>>> len(point.random())
96
"""
p = cls._implementation.pnt()
p.__class__ = cls
return p
[docs] @classmethod
def bytes(cls, bs: bytes) -> point:
"""
Return point object obtained by transforming supplied bytes-like
object if it is possible to do so; otherwise, return ``None``.
The bytes-like object need not be the binary representation
of a point or its coordinate(s). For a strict deserialization
from a bytes-like object, use :obj:`point.from_bytes`.
>>> p = point.bytes(hashlib.sha512('123'.encode()).digest())
>>> p.hex()[:64]
'6d68495eb4d539db4df70fd24d54fae37c9adf7dfd8dc705ccb8de8630e7cf22'
"""
p = cls._implementation.pnt(bs)
p.__class__ = cls
return p
[docs] @classmethod
def hash(cls, bs: bytes) -> point: # pylint: disable=arguments-differ
"""
Return point object by hashing supplied bytes-like object.
>>> point.hash('123'.encode()).hex()[:64]
'825aa78af4c88d6de4abaebabf1a96f668956b92876cfb5d3a44829899cb480f'
"""
p = cls._implementation.pnt(hashlib.sha512(bs).digest()[:32])
p.__class__ = cls
return p
[docs] @classmethod
def base(cls, s: scalar) -> point:
"""
Return base point multiplied by supplied scalar
if the scalar is valid.
>>> point.base(scalar.hash('123'.encode())).canonical().hex()[:64]
'2d66076815cda25556bab4a930244ac284412267e9345aceb98d71530308401a'
"""
p = cls._implementation.bas(s)
p.__class__ = cls
return p
[docs] @classmethod
def from_bytes(cls, bs: bytes) -> point:
"""
Deserialize the supplied binary representation of an instance and
return that instance.
>>> p = point.hash('123'.encode())
>>> bs = p.to_bytes()
>>> point.from_bytes(bs) == p
True
>>> type(bs) is bytes
True
"""
p = cls._implementation.des(bs)
p.__class__ = cls
return p
[docs] @classmethod
def fromhex(cls, s: str) -> point:
"""
Construct an instance from its hexadecimal UTF-8 string representation.
>>> point.fromhex(
... 'b89ec91191915a72d4ec4434be7b438893975880b21720995c2b2458962c4e0a'
... 'd0efebb5c303e4d1f8461b44ec768c587eca8b0abc01d4cb0d878b076154940d'
... '8effffffffffff158affffffffff39b9cdffffffff2ec6a2f5ffff7ff2a42b21'
... ).canonical().hex()[:64]
'b89ec91191915a72d4ec4434be7b438893975880b21720995c2b2458962c4e0a'
"""
return cls.from_bytes(bytes.fromhex(s))
[docs] @classmethod
def from_base64(cls, s: str) -> point:
"""
Construct an instance from its Base64 UTF-8 string representation.
>>> point.from_base64(
... 'hQIYpQRHupyyfPFoEm8rfmKV6i6VUP7vmngQWpxS3AEJD29fKVMW39l2oDLB+Ece'
... '5PqBuRzCyiRb8xYIelEII47///////8Viv//////ObnN/////y7GovX//3/ypCsh'
... ).canonical().hex()[:64]
'850218a50447ba9cb27cf168126f2b7e6295ea2e9550feef9a78105a9c52dc01'
"""
return cls.from_bytes(base64.standard_b64decode(s))
def __new__( # pylint: disable=arguments-differ
cls,
bs: Union[bytes, bytearray, None] = None
) -> point:
"""
If a bytes-like object is supplied, return a point object
corresponding to the supplied bytes-like object (no checking
is performed to confirm that the bytes-like object is a valid
point). If no argument is supplied, return a random point
object.
>>> bs = bytes.fromhex(
... 'a5db59a0a1450aee0e47e7226d992fded25f2eb5378493ba0eb3225fc7595809'
... 'c76c3dc4ba5a827be515cef65823ab1b113626348415f85aa966bad842457c03'
... '8effffffffffff158affffffffff39b9cdffffffff2ec6a2f5ffff7ff2a42b21'
... )
>>> point(bs).canonical().hex()[:64]
'a5db59a0a1450aee0e47e7226d992fded25f2eb5378493ba0eb3225fc7595809'
>>> len(point())
96
"""
return cls.from_bytes(bs) if bs is not None else cls.random()
[docs] def canonical(self: point) -> point:
"""
Normalize the representation of this point into its canonical form
and return the result. This takes the *z*-coordinate, which may not
always be equal to 1, and multiplies all coordinates *x*, *y*,
and *z* by *z*'s multiplicative inverse. The resulting canonical
representation is unique (*i.e.*, two points are equal if and only
if their canonical forms are equal) and in the form
(*x*/*z*, *y*/*z*, 1).
>>> a = point.hash('123'.encode())
>>> p = a + a + a + a
>>> p == p
True
>>> p.to_bytes() == p.to_bytes()
True
>>> p.to_bytes() == p.canonical().to_bytes() and p.__class__ != python.point
False
>>> p.canonical().to_bytes() == p.canonical().to_bytes()
True
>>> p.canonical().to_bytes() == p.canonical().canonical().to_bytes()
True
>>> point.from_bytes(p.to_bytes()) == p
True
>>> point.from_bytes(p.canonical().to_bytes()) == p
True
>>> point.from_bytes(p.to_bytes()) == point.from_bytes(p.canonical().to_bytes())
True
>>> type(p.canonical()) is point
True
"""
p = self._implementation.can(self)
p.__class__ = self.__class__
return p
[docs] def __mul__(self: point, other: Any) -> NoReturn:
"""
Use of this method is not permitted. A point cannot be a left-hand argument.
>>> point() * scalar()
Traceback (most recent call last):
...
TypeError: point must be on right-hand side of multiplication operator
"""
raise TypeError(
'point must be on right-hand side of multiplication operator'
)
[docs] def __rmul__(self: point, other: Any) -> NoReturn:
"""
This functionality is implemented exclusively in the method
:obj:`scalar.__mul__`, as that method pre-empts this method
when the second argument has the correct type (*i.e.*, it is
a :obj:`scalar` instance). This method is included so that an
exception can be raised if an incorrect argument is supplied.
>>> p = point.hash('123'.encode())
>>> 2 * p
Traceback (most recent call last):
...
TypeError: point can only be multiplied by a scalar
"""
raise TypeError('point can only be multiplied by a scalar')
[docs] def __add__(self: point, other: point) -> point:
"""
Return sum of this point and another point.
>>> p = point.hash('123'.encode())
>>> q = point.hash('456'.encode())
>>> (p + q).canonical().hex()[:64]
'1ea48cab238fece46bd0c9fb562c859e318e17a8fb75517a4750d30ca79b911c'
"""
p = self._implementation.add(self, other)
p.__class__ = self.__class__
return p
[docs] def __sub__(self: point, other: point) -> point:
"""
Return the result of subtracting another point from this point.
>>> p = point.hash('123'.encode())
>>> q = point.hash('456'.encode())
>>> (p - q).canonical().hex()[:64]
'a43a5ce1931b1300b62e5d7e1b0c691203bfd85fafd9585dc5e47a7e2acfea22'
"""
p = self._implementation.sub(self, other)
p.__class__ = self.__class__
return p
[docs] def __neg__(self: point) -> point:
"""
Return the negation (additive inverse) of this point
>>> p = point.hash('123'.encode())
>>> q = point.hash('456'.encode())
>>> (p + q).canonical().hex()[:64]
'1ea48cab238fece46bd0c9fb562c859e318e17a8fb75517a4750d30ca79b911c'
"""
p = self._implementation.neg(self)
p.__class__ = self.__class__
return p
[docs] def __matmul__(self: point, other: point2) -> scalar2:
"""
Return the result of pairing another second-level point with this
instance.
**This method is only defined for the classes**
:obj:`oblivious.bn254.mcl.point` **and** :obj:`oblivious.bn254.mcl.point2`
**that are available when the**
`mclbn256 <https://pypi.org/project/mclbn256>`__ **package is installed**.
Otherwise, :obj:`oblivious.bn254.point` and :obj:`oblivious.bn254.point2`
correspond to the pure-Python implementations of these classes (for which
this method is not defined).
>>> p = point.hash('123'.encode())
>>> q = point2.base(scalar.from_int(456))
>>> z = (p @ q).hex()[700:]
>>> z == 'd01f7e038b05acc5519eeda026c4aa111eb12f3483f274c60e34e6ec7571435df707'
True
The pairing function is bilinear.
>>> p = point.random()
>>> q = point2.random()
>>> s = scalar.random()
>>> t = scalar.random()
>>> -((~s) * (s * p)) - p == scalar.from_int(-2) * p
True
>>> (s * (t * p)) @ q == (s * p) @ (t * q)
True
Suppose there are two points: one multiplied by the scalar ``s`` and the other
multiplied by the scalar ``t``. Their equality can be determined by using a
balancing point: ``g**(~s * t)``. If the pairing of ``t * x`` with ``g`` is the
same as the pairing with ``s * y`` and ``g**(~s * t)``, then ``x`` equals ``y``.
>>> x = y = p
>>> g = point2.base(scalar.from_int(1))
>>> b = point2.base(~s * t)
>>> (t * x) @ g == (s * y) @ b
True
"""
s = self._implementation.par(self, other)
s.__class__ = self._implementation.scalar2
return s
[docs] def __len__(self: point) -> int:
"""
Return length (in bytes) of the binary representation of this instance.
>>> len(point())
96
"""
return bytes(self).__len__()
[docs] def __bytes__(self: point) -> bytes:
"""
Serialize this instance and return its binary representation.
>>> p = point.hash('123'.encode())
>>> bs = bytes(p)
>>> point.from_bytes(bs) == p
True
>>> type(bs) is bytes
True
>>> len(bs)
96
"""
return self._implementation.ser(self)
[docs] def to_bytes(self: point) -> bytes:
"""
Serialize this instance and return its binary representation.
>>> p = point.hash('123'.encode())
>>> bs = p.to_bytes()
>>> point.from_bytes(bs) == p
True
>>> type(bs) is bytes
True
>>> len(bs)
96
"""
return bytes(self)
[docs] def hex(self: point) -> str:
"""
Return a hexadecimal representation of this instance.
>>> p = point.hash('123'.encode())
>>> p.hex()[:64]
'825aa78af4c88d6de4abaebabf1a96f668956b92876cfb5d3a44829899cb480f'
"""
return bytes(self).hex()
[docs] def to_base64(self: point) -> str:
"""
Return the Base64 UTF-8 string representation of this instance.
>>> p = point.from_base64(
... 'hQIYpQRHupyyfPFoEm8rfmKV6i6VUP7vmngQWpxS3AEJD29fKVMW39l2oDLB+Ece'
... '5PqBuRzCyiRb8xYIelEII47///////8Viv//////ObnN/////y7GovX//3/ypCsh'
... )
>>> p.to_base64()[-64:]
'5PqBuRzCyiRb8xYIelEII47///////8Viv//////ObnN/////y7GovX//3/ypCsh'
"""
return base64.standard_b64encode(bytes(self)).decode('utf-8')
[docs] class scalar(_s_base_cls): # pylint: disable=E0102
"""
Class for representing a scalar.
"""
_implementation = _implementation
[docs] @classmethod
def random(cls) -> scalar:
"""
Return random non-zero scalar object.
>>> len(scalar.random())
32
"""
s = cls._implementation.rnd()
s.__class__ = cls
return s
[docs] @classmethod
def bytes(cls, bs: bytes) -> Optional[scalar]:
"""
Return scalar object obtained by transforming supplied bytes-like
object if it is possible to do so; otherwise, return ``None``.
>>> s = scalar()
>>> t = scalar.bytes(bytes(s))
>>> s.hex() == t.hex()
True
"""
s = cls._implementation.scl(bs)
if s is not None:
s.__class__ = cls
return s
[docs] @classmethod
def hash(cls, bs: bytes) -> scalar:
"""
Return scalar object by hashing supplied bytes-like object.
>>> scalar.hash('123'.encode()).hex()[:64]
'93d829354cb3592743174133104b5405ba6992b67bb219fbde3e394d70505913'
"""
h = hashlib.sha256(bs).digest()
s = cls._implementation.scl(h)
while s is None:
h = hashlib.sha256(h).digest()
s = cls._implementation.scl(h)
s.__class__ = cls
return s
[docs] @classmethod
def from_int(cls, i: int) -> scalar:
"""
Construct an instance from its corresponding integer (*i.e.*, residue)
representation.
The integer can be in the range from
``-16798108731015832284940804142231733909759579603404752749028378864165570215948``
to ``16798108731015832284940804142231733909759579603404752749028378864165570215948``
(or a corresponding one in the range from
``-8399054365507916142470402071115866954879789801702376374514189432082785107974``
to ``8399054365507916142470402071115866954879789801702376374514189432082785107974``).
Any values outside of this range will not be reduced, may be truncated, or may raise
a :obj:`ValueError`. Zero-valued scalars are technically allowed, but cannot be used
for point-scalar multiplication.
>>> int(scalar.from_int(
... 16798108731015832284940804142231733909759579603404752749028378864165570215948
... ))
-1
>>> int(scalar.from_int(
... -8399054365507916142470402071115866954879789801702376374514189432082785107974
... ))
-8399054365507916142470402071115866954879789801702376374514189432082785107974
>>> int(scalar.from_int(
... 12345678
... ))
12345678
"""
#r= 0x2523648240000001ba344d8000000007ff9f800000000010a10000000000000d
d = 0x212ba4f27ffffff5a2c62effffffffd00242ffffffffff9c39ffffffffffffb2
return cls.bytes(int.to_bytes((i * d) % r, 32, 'little'))
[docs] @classmethod
def from_bytes(cls, bs: bytes) -> scalar:
"""
Deserialize the supplied binary representation of an instance and
return that instance.
>>> s = scalar.hash('123'.encode())
>>> bs = s.to_bytes()
>>> scalar.from_bytes(bs) == s
True
>>> type(bs) is bytes
True
"""
s = cls._implementation.sde(bs)
s.__class__ = cls
return s
[docs] @classmethod
def fromhex(cls, s: str) -> scalar:
"""
Construct an instance from its hexadecimal UTF-8 string representation.
>>> scalar.fromhex(
... '3ab45f5b1c9339f1d25b878cce1c053b5492b4dc1affe689cbe141769f655e1e'
... ).hex()[:64]
'3ab45f5b1c9339f1d25b878cce1c053b5492b4dc1affe689cbe141769f655e1e'
"""
return cls.from_bytes(bytes.fromhex(s))
[docs] @classmethod
def from_base64(cls, s: str) -> scalar:
"""
Construct an instance from its Base64 UTF-8 string representation.
>>> scalar.from_base64('MS0MkTD2kVO+yfXQOGqVE160XuvxMK9fH+0cbtFfJQA=').hex()[:64]
'312d0c9130f69153bec9f5d0386a95135eb45eebf130af5f1fed1c6ed15f2500'
"""
return cls.from_bytes(base64.standard_b64decode(s))
def __new__( # pylint: disable=arguments-differ
cls,
bs: Union[bytes, bytearray, None] = None
) -> scalar:
"""
If a bytes-like object is supplied, return a scalar object
corresponding to the supplied bytes-like object (no checking
is performed to confirm that the bytes-like object is a valid
scalar). If no argument is supplied, return a random scalar
object.
>>> s = scalar()
>>> t = scalar(bytes(s))
>>> s.hex() == t.hex()
True
>>> len(scalar())
32
"""
return cls.from_bytes(bs) if bs is not None else cls.random()
[docs] def __invert__(self: scalar) -> scalar:
"""
Return the inverse of a scalar.
>>> s = scalar()
>>> p = point()
>>> ((~s) * (s * p)) == p
True
"""
s = self._implementation.inv(self)
s.__class__ = self.__class__
return s
[docs] def __mul__(
self: scalar,
other: Union[scalar, point, point2]
) -> Optional[Union[scalar, point, point2]]:
"""
Multiply supplied scalar, point, or second-level point by this
instance.
>>> s = scalar.from_base64('MS0MkTD2kVO+yfXQOGqVE160XuvxMK9fH+0cbtFfJQA=')
>>> (s * s).hex()[:64]
'0497a5b6a7992e7d77b59c07d4457e8bb3cf580603cfd19e05d1f31342141b00'
>>> isinstance(s * s, scalar)
True
>>> p = point.from_base64(
... 'hQIYpQRHupyyfPFoEm8rfmKV6i6VUP7vmngQWpxS3AEJD29fKVMW39l2oDLB+Ece'
... '5PqBuRzCyiRb8xYIelEII47///////8Viv//////ObnN/////y7GovX//3/ypCsh'
... )
>>> (s * p).canonical().hex()[:64]
'eee31d1780ea41771357da19a81eaddf2e7fa560142067b433764cbf98be9002'
>>> isinstance(s * p, point)
True
If the second argument is a :obj:`point2` object, this method
pre-empts :obj:`point2.__rmul__`.
>>> p = point2.hash('123'.encode())
>>> (s * p).canonical().hex()[:128] == (
... '451f144e06deecbfe5a1527f2b5cc6f12bbde91c1fdf0d5326ad79ffc53bb106'
... '6d800275af625de83d72d815335832027cc60c34f22e8c5f89f953740a409702'
... )
True
Any attempt to multiply a value or object of an incompatible type by this
instance raises an exception.
>>> s * 2
Traceback (most recent call last):
...
TypeError: multiplication by a scalar is defined only for scalars and points
"""
if isinstance(other, self._implementation.scalar):
s = self._implementation.smu(self, other)
s.__class__ = self.__class__
return s
if isinstance(other, self._implementation.point):
p = self._implementation.mul(self, other)
p.__class__ = other.__class__
return p
if isinstance(other, self._implementation.point2):
p = self._implementation.mul2(self, other)
p.__class__ = other.__class__
return p
raise TypeError(
'multiplication by a scalar is defined only for scalars and points'
)
[docs] def __rmul__(self: scalar, other: Any) -> NoReturn:
"""
A scalar cannot be on the right-hand side of a non-scalar.
>>> point() * scalar()
Traceback (most recent call last):
...
TypeError: point must be on right-hand side of multiplication operator
"""
raise TypeError(
'scalar must be on left-hand side of multiplication operator'
)
[docs] def __add__(self: scalar, other: scalar) -> scalar:
"""
Add another scalar to this instance.
>>> s = scalar.from_base64('MS0MkTD2kVO+yfXQOGqVE160XuvxMK9fH+0cbtFfJQA=')
>>> (s + s).hex()[:64]
'625a182261ec23a77c93eba171d42a27bc68bdd6e3615ebf3eda39dca2bf4a00'
>>> isinstance(s + s, scalar)
True
>>> z = scalar2.from_base64(
... 'GNDgZXmP+k7MoKfMbiuNbTJp9+tBNSXlm3MTMrAuqAVLkMic6T5EUlV/U6rl+PEy'
... 'IYWN3i6mpQNz8YWPEShwIQmz2veiF6IKPYcmMvjO1kPTJkmyQaZ2Ab7IVb1D8HcQ'
... 'iN9yK8rMj9F08WrX7xsdXmItk7fP7GOFw1PBN5Ok4Bw3HvVM2DaojhsfHhmNVm07'
... 'vlqiJXNDBfFMrJr5yCE1HIazZRkBBdG35xAR0TYu1q2RM6LCxCiY6evD0WBKYI8Q'
... 'hGHTeyRjtPiPDMtCe17qhxTuiPjDPa9+5KP0XKi8qRGHlWq7TSfeG0nl4Fn5BPu0'
... 'dYBHFasloXMG+g4ZbzBSFTpg36BzynjeSe3qrJxUyrPQE4dQwjwLaN55JKadG6AC'
... 'okrFkmIqRcWcHe1xM3lCkqCWAr1Xo2YB01Q4hG/LNw85wPp6FbNNFKtvle5b9bJr'
... '1d7x5+0HNQki1EXaB9k2Ii21uqnewVLCsrz8Rs3m/SLnAnGvihZOd+WAjOYCCVof'
... )
>>> (z + z).hex()[700:]
'4a1f476a7553bd83a5dd5179f98d9acddae4c505e25e95df6734c901198d83ad9019'
>>> isinstance(z + z, scalar2)
True
"""
s = self._implementation.sad(self, other)
s.__class__ = self.__class__
return s
[docs] def __sub__(self: scalar, other: scalar) -> scalar:
"""
Subtract this instance from another scalar.
>>> s = scalar.from_base64('MS0MkTD2kVO+yfXQOGqVE160XuvxMK9fH+0cbtFfJQA=')
>>> (s - s).hex() == '00' * len(s)
True
>>> isinstance(s - s, scalar)
True
"""
s = self._implementation.ssu(self, other)
s.__class__ = self.__class__
return s
[docs] def __neg__(self: scalar) -> scalar:
"""
Negate this instance.
>>> s = scalar.from_base64('MS0MkTD2kVO+yfXQOGqVE160XuvxMK9fH+0cbtFfJQA=')
>>> (-s).hex()
'dcd2f36ecf096e4d52360a2fc7150aeca94ba1148e1c855ae212e3d1b004fe24'
>>> isinstance(-s, scalar)
True
"""
s = self._implementation.sne(self)
s.__class__ = self.__class__
return s
[docs] def __len__(self: scalar) -> int:
"""
Return length (in bytes) of the binary representation of this instance.
>>> len(scalar())
32
"""
return bytes(self).__len__()
[docs] def __bytes__(self: scalar) -> bytes:
"""
Serialize this instance and return its binary representation.
>>> s = scalar.hash('123'.encode())
>>> bs = bytes(s)
>>> scalar.from_bytes(bs) == s
True
>>> type(bs) is bytes
True
"""
return self._implementation.sse(self)
[docs] def to_bytes(self: scalar) -> bytes:
"""
Serialize this instance and return its binary representation.
>>> s = scalar.hash('123'.encode())
>>> bs = s.to_bytes()
>>> scalar.from_bytes(bs) == s
True
>>> type(bs) is bytes
True
"""
return bytes(self)
[docs] def __int__(self: scalar) -> bytes:
"""
Compute and return the numerical representation of this instance.
>>> s = scalar.from_int(123)
>>> n = int(s)
>>> scalar.from_int(n) == s
True
>>> type(n) is int
True
"""
d_inv = 0x235f846d22752e25720e909a9e82a1b4ad47e882341d8fca46c142d23fa9bc45
n = (int.from_bytes(self._implementation.sse(self), 'little') * d_inv) % r
return n if (n <= r//2) else n - r
[docs] def to_int(self: scalar) -> bytes:
"""
Compute and return the numerical representation of this instance.
>>> s = scalar.from_int(123)
>>> n = s.to_int()
>>> scalar.from_int(n) == s
True
>>> type(n) is int
True
"""
return int(self)
[docs] def hex(self: scalar) -> str:
"""
Return a hexadecimal representation of this instance.
>>> s = scalar.hash('123'.encode())
>>> s.hex()
'93d829354cb3592743174133104b5405ba6992b67bb219fbde3e394d70505913'
"""
return self.to_bytes().hex()
[docs] def to_base64(self: scalar) -> str:
"""
Return the Base64 UTF-8 string representation of this instance.
>>> s = scalar.from_base64('MS0MkTD2kVO+yfXQOGqVE160XuvxMK9fH+0cbtFfJQA=')
>>> s.to_base64()
'MS0MkTD2kVO+yfXQOGqVE160XuvxMK9fH+0cbtFfJQA='
"""
return base64.standard_b64encode(self.to_bytes()).decode('utf-8')
[docs] class point2(_p2_base_cls): # pylint: disable=W0621,E0102
# pylint: disable=C0301 # Accommodate large outputs in doctests.
"""
Wrapper class for a bytes-like object that corresponds
to a point.
"""
_implementation = _implementation
[docs] @classmethod
def random(cls) -> point2:
"""
Return random instance.
>>> len(point2.random())
192
"""
p = cls._implementation.pnt2()
p.__class__ = cls
return p
[docs] @classmethod
def bytes(cls, bs: Union[bytes, bytearray]) -> point2:
"""
Construct an instance that corresponds to the supplied binary
representation.
>>> p = point2.bytes(hashlib.sha512('123'.encode()).digest())
>>> p.canonical().hex()[:128] == (
... '4c595542640a69c4a70bda55c27ef96c133cd1f4a5f83b3371e571960c018e19'
... 'c54aaec2069f8f10a00f12bcbb3511cdb7356201f5277ec5e47da91405be2809'
... )
True
"""
p = cls._implementation.pnt2(bs)
p.__class__ = cls
return p
[docs] @classmethod
def hash(cls, bs: Union[bytes, bytearray]) -> point2: # pylint: disable=W0221
"""
Construct an instance by hashing the supplied bytes-like object.
>>> point2.hash('123'.encode()).canonical().hex()[:128] == (
... '30326199f303fce7a77cff6d2fb0b3de8cd409d1d562f3543f7d064cdc58d309'
... '7e88038ad76e85e5df26e4a9486a657b0431c8e7e09b0a1abf90fc874c515207'
... )
True
"""
p = cls._implementation.pnt2(hashlib.sha512(bs).digest()[:32])
p.__class__ = cls
return p
[docs] @classmethod
def base(cls, s: scalar) -> point2:
"""
Return base second-level point multiplied by the supplied scalar
if the scalar is valid; otherwise, return ``None``.
>>> point2.base(scalar.hash('123'.encode())).canonical().hex()[:128] == (
... 'e7000fb12d206112c73fe1054e9d77b35c77881eba6598b7e035171d90b13e0c'
... '33c8ad2c92acb446fa958f3001b6c15aaf0f00092534a9d567541f9fadc64e09'
... )
True
"""
p = cls._implementation.bas2(s)
p.__class__ = cls
return p
[docs] @classmethod
def from_bytes(cls, bs: bytes) -> point2:
"""
Deserialize the supplied binary representation of an instance and
return that instance.
>>> p = point2.hash('123'.encode())
>>> bs = p.to_bytes()
>>> point2.from_bytes(bs) == p
True
>>> type(bs) is bytes
True
"""
p = cls._implementation.des2(bs)
p.__class__ = cls
return p
[docs] @classmethod
def fromhex(cls, s: str) -> point2:
"""
Construct an instance from its hexadecimal UTF-8 string representation.
>>> p = point2.fromhex(
... 'ab4efa2bcdeb825a67b12a10132ae1addca840ed248f83ae7dd987370dd47a05'
... '31c10b08ada0e24c0327d85b108e826a55bf3dc3286488327fac75e05e293b20'
... '01cbf919b53884d02b85aab9b0091eeda114fa65ca5d75620da26c4d164aa509'
... '2a2d55b6f311bfe52d24adf7b4b0b6ce12ed486a37c474d35a2b373be8a3f71c'
... '8effffffffffff158affffffffff39b9cdffffffff2ec6a2f5ffff7ff2a42b21'
... '0000000000000000000000000000000000000000000000000000000000000000'
... )
>>> p.canonical().hex()[:64]
'ab4efa2bcdeb825a67b12a10132ae1addca840ed248f83ae7dd987370dd47a05'
"""
p = cls.from_bytes(bytes.fromhex(s))
p.__class__ = cls
return p
[docs] @classmethod
def from_base64(cls, s: str) -> point2:
"""
Construct an instance from its Base64 UTF-8 string representation.
>>> p = point2.from_base64(
... 'xRuTJv/OWkIPMxRoCQIqNYoSixnWfMxeYwSJnjdJwxlp9E9f6oKefvbfYlJeygmK'
... 'YDQniir3r/EYExFuClZ7H5X00GEqz7TcoqDl5EpwLDAvrTW3GNA2lOpHvc1F/eQc'
... 'obJoTn35OzzK7qd/87Y3pOKNQaNENKO19DMzw9Lt+hiO////////FYr//////zm5'
... 'zf////8uxqL1//9/8qQrIQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
... )
>>> p.to_base64()[-64:]
'zf////8uxqL1//9/8qQrIQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
"""
p = cls.from_bytes(base64.standard_b64decode(s))
p.__class__ = cls
return p
def __new__( # pylint: disable=arguments-differ
cls,
bs: Union[bytes, bytearray, None] = None
) -> point2:
"""
If a bytes-like object is supplied, return a second-level point
object corresponding to the supplied bytes-like object (no check
is performed to confirm that the bytes-like object is a valid
point). If no argument is supplied, a random second-level point
is returned.
>>> bs = bytes.fromhex(
... 'ab4efa2bcdeb825a67b12a10132ae1addca840ed248f83ae7dd987370dd47a05'
... '31c10b08ada0e24c0327d85b108e826a55bf3dc3286488327fac75e05e293b20'
... '01cbf919b53884d02b85aab9b0091eeda114fa65ca5d75620da26c4d164aa509'
... '2a2d55b6f311bfe52d24adf7b4b0b6ce12ed486a37c474d35a2b373be8a3f71c'
... '8effffffffffff158affffffffff39b9cdffffffff2ec6a2f5ffff7ff2a42b21'
... '0000000000000000000000000000000000000000000000000000000000000000'
... )
>>> point2.from_bytes(bs).hex() == bs.hex()
True
>>> point2.from_bytes(bs).to_bytes() == bs
True
"""
p = cls.from_bytes(bs) if bs is not None else cls.random()
p.__class__ = cls # = point2
return p
[docs] def canonical(self: point2) -> point2:
"""
Normalize the representation of this point into its canonical form
and return the result. This takes the *z*-coordinate, which may not
always be equal to 1, and multiplies all coordinates *x*, *y*, and
*z* by *z*'s multiplicative inverse. The resulting canonical
representation is unique (*i.e.*, two second-level points are equal
if and only if their canonical forms are equal) and in the form
(*x1*/*z1*, *y1*/*z1*, *x2*/*z2*, *y2*/*z2*, 1, 0).
>>> a = point2.hash('123'.encode())
>>> q = a + a + a + a
>>> q == q
True
>>> q.to_bytes() == q.to_bytes()
True
>>> q.to_bytes() == q.canonical().to_bytes() and q.__class__ != python.point2
False
>>> q.canonical().to_bytes() == q.canonical().to_bytes()
True
>>> q.canonical().to_bytes() == q.canonical().canonical().to_bytes()
True
>>> point2.from_bytes(q.to_bytes()) == q
True
>>> point2.from_bytes(q.canonical().to_bytes()) == q
True
>>> point2.from_bytes(q.to_bytes()) == point2.from_bytes(bytes(q.canonical()))
True
>>> type(q.canonical()) is point2
True
"""
p = self._implementation.can(self)
p.__class__ = self.__class__
return p
[docs] def __mul__(self: point2, other: Any) -> NoReturn:
"""
Use of this method is not permitted. A point cannot be a left-hand argument.
>>> point2() * scalar()
Traceback (most recent call last):
...
TypeError: second-level point must be on right-hand side of multiplication operator
"""
raise TypeError(
'second-level point must be on right-hand side of multiplication operator'
)
[docs] def __rmul__(self: point2, other: Any) -> NoReturn:
"""
This functionality is implemented exclusively in the method
:obj:`scalar.__mul__`, as that method pre-empts this method
when the second argument has the correct type (*i.e.*, it is
a :obj:`scalar` instance). This method is included so that an
exception can be raised if an incorrect argument is supplied.
>>> p = point2.hash('123'.encode())
>>> 2 * p
Traceback (most recent call last):
...
TypeError: second-level point can only be multiplied by a scalar
"""
raise TypeError('second-level point can only be multiplied by a scalar')
[docs] def __add__(self: point2, other: point2) -> Optional[point2]:
"""
Return sum of this instance and another second-level point.
>>> p = point2.hash('123'.encode())
>>> q = point2.hash('456'.encode())
>>> (p + q).canonical().hex()[:128] == (
... 'cb0fc423c1bac2ac2df47bf5f5548a42b0d0a0da325bc77243d15dc587a7b221'
... '9808a1649991ddf770f0060333aab4d499580b123f109b5cb180f1f8a75a090e'
... )
True
"""
p = self._implementation.add2(self, other)
p.__class__ = self.__class__
return p
[docs] def __sub__(self: point2, other: point2) -> Optional[point2]:
"""
Return the result of subtracting another second-level point from
this instance.
>>> p = point2.hash('123'.encode())
>>> q = point2.hash('456'.encode())
>>> (p - q).canonical().hex()[:128] == (
... 'e97a70c4e3a5369ebbb1dcf0cc1135c8c8e04a4ec7cffdf875ac429d66846d0b'
... '191b090909c40a723027b07ac44435a6ade3813d04b3632a17c92c5c98718902'
... )
True
"""
p = self._implementation.sub2(self, other)
p.__class__ = self.__class__
return p
[docs] def __neg__(self: point2) -> point2:
"""
Return the negation (additive inverse) of this instance.
>>> p = point2.hash('123'.encode())
>>> (-p).canonical().hex()[:128] == (
... '30326199f303fce7a77cff6d2fb0b3de8cd409d1d562f3543f7d064cdc58d309'
... '7e88038ad76e85e5df26e4a9486a657b0431c8e7e09b0a1abf90fc874c515207'
... )
True
"""
p = self._implementation.neg2(self)
p.__class__ = self.__class__
return p
[docs] def __matmul__(self: point2, other: point) -> scalar2:
"""
Return the result of pairing another point with this instance.
Input-swapped alias of :obj:`point.__matmul__`.
"""
return self._implementation.point.__matmul__(other, self)
[docs] def __len__(self: point2) -> int:
"""
Return length (in bytes) of the binary representation of this instance.
>>> len(point2())
192
"""
return bytes(self).__len__()
[docs] def __bytes__(self: point2) -> bytes:
"""
Serialize this instance and return its binary representation.
>>> len(bytes(point2()))
192
"""
return self._implementation.ser(self)
[docs] def to_bytes(self: point2) -> bytes:
"""
Serialize this instance and return its binary representation.
>>> p = point2.hash('123'.encode())
>>> bs = p.to_bytes()
>>> point2.from_bytes(bs) == p
True
>>> type(bs) is bytes
True
"""
return bytes(self)
[docs] def hex(self: point2) -> str:
"""
Return a hexadecimal representation of this instance.
>>> p = point2.hash('123'.encode())
>>> p.canonical().hex() == (
... '30326199f303fce7a77cff6d2fb0b3de8cd409d1d562f3543f7d064cdc58d309'
... '7e88038ad76e85e5df26e4a9486a657b0431c8e7e09b0a1abf90fc874c515207'
... '2c6a88bb448065eb748df632b1d872e02f54b6f56fdb84a7b1cb388fe551fb08'
... '04464efa186bd4b1371e53d6f31f0e2f50ff553b6264a43331b42c976a0c541f'
... '8effffffffffff158affffffffff39b9cdffffffff2ec6a2f5ffff7ff2a42b21'
... '0000000000000000000000000000000000000000000000000000000000000000'
... )
True
"""
return self.to_bytes().hex()
[docs] def to_base64(self: point2) -> str:
"""
Return the Base64 UTF-8 string representation of this instance.
>>> p = point2.from_base64(
... 'zn07zy59PMhe396h9AQ+FY3LqfzmaRmbVmfwKaQqTxStH2ZPqGwBjv99STlWrenq'
... 'Mkfc3PCxRgM1xVaJGN+WExXhuDn4V40nkdpxtU85VFgE4aj0CMUoD99bqTEqBSYD'
... '50haF1C7mDxMRxmMXZinYDEMynRY69C1vTQ5IgcCdh+O////////FYr//////zm5'
... 'zf////8uxqL1//9/8qQrIQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
... )
>>> p.to_base64()[-64:]
'zf////8uxqL1//9/8qQrIQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
"""
return base64.standard_b64encode(self.to_bytes()).decode('utf-8')
[docs] class scalar2(_s2_base_cls): # pylint: disable=function-redefined
"""
Class for representing second-level scalars.
"""
_implementation = _implementation
[docs] @classmethod
def random(cls) -> scalar2:
"""
Return random non-zero second-level scalar.
>>> isinstance(scalar2.random(), scalar2)
True
>>> len(scalar2.random())
384
"""
s = cls._implementation.scl2()
s.__class__ = cls
return s
[docs] @classmethod
def bytes(cls, bs: bytes) -> Optional[scalar2]:
"""
Return second-level scalar object obtained by transforming the
supplied bytes-like object if it is possible to do so; otherwise,
return ``None``.
>>> s = scalar2()
>>> t = scalar2.bytes(bytes(s))
>>> s.hex() == t.hex()
True
"""
s = cls._implementation.scl2(bs)
if s is not None:
s.__class__ = cls
return s
[docs] @classmethod
def hash(cls, bs: Union[bytes, bytearray]) -> scalar2:
"""
Return an instance derived by hashing the supplied bytes-like object.
>>> s = python.scalar2.hash(bytes([123]))
>>> s.hex()[700:]
'e91ed56ea67d29047d588ffaf78f9ed317ff13e7f63e53276ff32988c49184e17b22'
>>> s0 = python.scalar2.hash(secrets.token_bytes(64))
>>> s1 = python.scalar2.hash(secrets.token_bytes(64))
>>> python.sse(python.smu2(python.smu2(s0, python.inv2(s0)), s1)) == python.sse(s1)
True
"""
def pick_exponent(bs):
i = int.from_bytes(bs, 'little')
# Use rejection sampling to get two valid components in F_p.
if i >= r-1:
return pick_exponent(hashlib.sha512(bs).digest()[:32])
return i+1
bs = hashlib.sha512(bs).digest()
exponent = pick_exponent(bs[:32])
# Generator of paired ``scalar2`` codomain, computed from pairing
# the base points for ``point`` and ``point2``.
z = cls._implementation.scalar2.from_base64('X+CkvfROeA1SsbpkudwsTzqOGQC9BkDmNcYpI0GHGg'
'TPr7fTv0yO88u5bybAlDc4QTCji5RvpdhGkWLT5oysD7UX5qE2ymq+HJXrl5MkiPp4J6kfZ6Obdjr9J/G4qs4U'
'hNgtOCecKCgdEwI4KyCbtYu5Wv2M+IgvJbWUx4ihaB1HlDwFb6rTmaa8ckaoFtY6AoM5kbbDPgNN71441LrNC5'
'Fp2QGPRod8+0WJ9wzl6R6cWLSV14MzoqWY6ZNDAyUPMpUaUGbIKZ3QqlpxM5EOFTTYmPQqOBY3K+tNL94yZRIM'
'ChE3W1ph9ypDcdFNd9xloWOw/APAa4FE538HbMZFEix4XpNKKIl3WPbGhTb/iY7DuUqKXouNdw8wSvzIVEYjBY'
'6IuE7e3fr7GMvd6K/8qO3Cep7EeuFzdMKxMO21PhuhBm2KLDPbzuzoNrMfmiMOhFJadkIa8F0NmWEEKEpyCIph'
'CwjHidMthVF/l932N4LL6EVktec8TQrotfCCEA0O')
def square_and_multiply_exp(s, exponent):
if exponent == 1:
return s
s = s * s
if exponent % 2 == 1:
s = s * z
return square_and_multiply_exp(s, exponent // 2)
return square_and_multiply_exp(z, exponent)
[docs] @classmethod
def from_bytes(cls, bs: bytes) -> scalar2:
"""
Deserialize the supplied binary representation of an instance and
return that instance.
>>> s = scalar2.hash('123'.encode())
>>> bs = s.to_bytes()
>>> scalar2.from_bytes(bs) == s
True
>>> type(bs) is bytes
True
"""
s = cls._implementation.sde2(bs)
s.__class__ = cls
return s
[docs] @classmethod
def fromhex(cls, s: str) -> scalar2:
"""
Construct an instance from its hexadecimal UTF-8 string representation.
>>> s_hex = (
... '18d0e065798ffa4ecca0a7cc6e2b8d6d3269f7eb413525e59b731332b02ea805'
... '4b90c89ce93e4452557f53aae5f8f13221858dde2ea6a50373f1858f11287021'
... '09b3daf7a217a20a3d872632f8ced643d32649b241a67601bec855bd43f07710'
... '88df722bcacc8fd174f16ad7ef1b1d5e622d93b7cfec6385c353c13793a4e01c'
... '371ef54cd836a88e1b1f1e198d566d3bbe5aa225734305f14cac9af9c821351c'
... '86b365190105d1b7e71011d1362ed6ad9133a2c2c42898e9ebc3d1604a608f10'
... '8461d37b2463b4f88f0ccb427b5eea8714ee88f8c33daf7ee4a3f45ca8bca911'
... '87956abb4d27de1b49e5e059f904fbb475804715ab25a17306fa0e196f305215'
... '3a60dfa073ca78de49edeaac9c54cab3d0138750c23c0b68de7924a69d1ba002'
... 'a24ac592622a45c59c1ded7133794292a09602bd57a36601d35438846fcb370f'
... '39c0fa7a15b34d14ab6f95ee5bf5b26bd5def1e7ed07350922d445da07d93622'
... '2db5baa9dec152c2b2bcfc46cde6fd22e70271af8a164e77e5808ce602095a1f'
... )
>>> s = scalar2.fromhex(s_hex)
>>> s.hex() == s_hex
True
"""
return cls.from_bytes(bytes.fromhex(s))
[docs] @classmethod
def from_base64(cls, s: str) -> scalar2:
"""
Construct an instance from its Base64 UTF-8 string representation.
>>> b64s = (
... 'GNDgZXmP+k7MoKfMbiuNbTJp9+tBNSXlm3MTMrAuqAVLkMic6T5EUlV/U6rl+PEy'
... 'IYWN3i6mpQNz8YWPEShwIQmz2veiF6IKPYcmMvjO1kPTJkmyQaZ2Ab7IVb1D8HcQ'
... 'iN9yK8rMj9F08WrX7xsdXmItk7fP7GOFw1PBN5Ok4Bw3HvVM2DaojhsfHhmNVm07'
... 'vlqiJXNDBfFMrJr5yCE1HIazZRkBBdG35xAR0TYu1q2RM6LCxCiY6evD0WBKYI8Q'
... 'hGHTeyRjtPiPDMtCe17qhxTuiPjDPa9+5KP0XKi8qRGHlWq7TSfeG0nl4Fn5BPu0'
... 'dYBHFasloXMG+g4ZbzBSFTpg36BzynjeSe3qrJxUyrPQE4dQwjwLaN55JKadG6AC'
... 'okrFkmIqRcWcHe1xM3lCkqCWAr1Xo2YB01Q4hG/LNw85wPp6FbNNFKtvle5b9bJr'
... '1d7x5+0HNQki1EXaB9k2Ii21uqnewVLCsrz8Rs3m/SLnAnGvihZOd+WAjOYCCVof'
... )
>>> s = scalar2.from_base64(b64s)
>>> s.to_base64() == b64s
True
"""
return cls.from_bytes(base64.standard_b64decode(s))
def __new__( # pylint: disable=arguments-differ
cls,
bs: Union[bytes, bytearray, None] = None
) -> scalar2:
"""
If a bytes-like object is supplied, return an instance that corresponds
to the supplied bytes-like object (no checking is performed to confirm
that the bytes-like object is a valid second-level scalar). If no
argument is supplied, return a random scalar object.
"""
return cls.from_bytes(bs) if bs is not None else cls.random()
[docs] def __invert__(self: scalar2) -> scalar2:
"""
Return the inverse of this instance.
>>> s = scalar2.hash('123'.encode())
>>> ~(~s) == s
True
>>> ~s == s
False
>>> bytes(~s).hex()[700:] == (
... 'ff13804852ea3ad35e8316d90a6d5dde854517e74cfc27ba676f429eb4fd52cd9b0c'
... )
True
"""
s = self._implementation.inv2(self)
s.__class__ = self.__class__
return s
[docs] def __mul__(self: scalar2, other: scalar2) -> scalar2:
"""
Multiply this instance by another second-level scalar.
>>> s = scalar2.from_base64(
... 'GNDgZXmP+k7MoKfMbiuNbTJp9+tBNSXlm3MTMrAuqAVLkMic6T5EUlV/U6rl+PEy'
... 'IYWN3i6mpQNz8YWPEShwIQmz2veiF6IKPYcmMvjO1kPTJkmyQaZ2Ab7IVb1D8HcQ'
... 'iN9yK8rMj9F08WrX7xsdXmItk7fP7GOFw1PBN5Ok4Bw3HvVM2DaojhsfHhmNVm07'
... 'vlqiJXNDBfFMrJr5yCE1HIazZRkBBdG35xAR0TYu1q2RM6LCxCiY6evD0WBKYI8Q'
... 'hGHTeyRjtPiPDMtCe17qhxTuiPjDPa9+5KP0XKi8qRGHlWq7TSfeG0nl4Fn5BPu0'
... 'dYBHFasloXMG+g4ZbzBSFTpg36BzynjeSe3qrJxUyrPQE4dQwjwLaN55JKadG6AC'
... 'okrFkmIqRcWcHe1xM3lCkqCWAr1Xo2YB01Q4hG/LNw85wPp6FbNNFKtvle5b9bJr'
... '1d7x5+0HNQki1EXaB9k2Ii21uqnewVLCsrz8Rs3m/SLnAnGvihZOd+WAjOYCCVof'
... )
>>> bytes(s * s).hex()[700:]
'6f11685b89b03431dac6dc9d129c6a31cc5e3036f7f781d7460ab9f532a06845bd15'
>>> scalar2() * point()
Traceback (most recent call last):
...
TypeError: second-level scalar can only be multiplied by another second-level scalar
"""
if isinstance(other, self._implementation.scalar2):
s = self._implementation.smu2(self, other)
s.__class__ = self.__class__
return s
raise TypeError(
'second-level scalar can only be multiplied by another second-level scalar'
)
[docs] def __rmul__(self: scalar2, other: Any) -> NoReturn:
"""
A second-level scalar cannot be on the right-hand side of a non-scalar.
>>> 2 * scalar2()
Traceback (most recent call last):
...
TypeError: second-level scalar must be on left-hand side of multiplication operator
"""
raise TypeError(
'second-level scalar must be on left-hand side of multiplication operator'
)
[docs] def __add__(self: scalar2, other: scalar2) -> scalar2:
"""
Add another second-level scalar to this instance.
>>> z = scalar2.from_base64(
... 'GNDgZXmP+k7MoKfMbiuNbTJp9+tBNSXlm3MTMrAuqAVLkMic6T5EUlV/U6rl+PEy'
... 'IYWN3i6mpQNz8YWPEShwIQmz2veiF6IKPYcmMvjO1kPTJkmyQaZ2Ab7IVb1D8HcQ'
... 'iN9yK8rMj9F08WrX7xsdXmItk7fP7GOFw1PBN5Ok4Bw3HvVM2DaojhsfHhmNVm07'
... 'vlqiJXNDBfFMrJr5yCE1HIazZRkBBdG35xAR0TYu1q2RM6LCxCiY6evD0WBKYI8Q'
... 'hGHTeyRjtPiPDMtCe17qhxTuiPjDPa9+5KP0XKi8qRGHlWq7TSfeG0nl4Fn5BPu0'
... 'dYBHFasloXMG+g4ZbzBSFTpg36BzynjeSe3qrJxUyrPQE4dQwjwLaN55JKadG6AC'
... 'okrFkmIqRcWcHe1xM3lCkqCWAr1Xo2YB01Q4hG/LNw85wPp6FbNNFKtvle5b9bJr'
... '1d7x5+0HNQki1EXaB9k2Ii21uqnewVLCsrz8Rs3m/SLnAnGvihZOd+WAjOYCCVof'
... )
>>> (z + z).hex()[700:]
'4a1f476a7553bd83a5dd5179f98d9acddae4c505e25e95df6734c901198d83ad9019'
>>> isinstance(z + z, scalar2)
True
"""
s = self._implementation.sad2(self, other)
s.__class__ = self.__class__
return s
[docs] def __len__(self: scalar2) -> int:
"""
Return length (in bytes) of the binary representation of this instance.
>>> len(scalar2.random())
384
"""
return bytes(self).__len__()
[docs] def __bytes__(self: scalar2) -> bytes:
"""
Serialize this instance and return its binary representation.
>>> s = scalar2.hash('123'.encode())
>>> bs = bytes(s)
>>> scalar2.from_bytes(bs) == s
True
>>> type(bs) is bytes
True
>>> len(bs)
384
"""
self.__class__ = self.__class__._implementation.scalar2
return self._implementation.sse(self)
[docs] def to_bytes(self: scalar2) -> bytes:
"""
Serialize this instance and return its binary representation.
>>> s = scalar2.hash('123'.encode())
>>> bs = s.to_bytes()
>>> scalar2.from_bytes(bs) == s
True
>>> type(bs) is bytes
True
>>> len(bs)
384
"""
return bytes(self)
[docs] def hex(self: scalar2) -> str:
"""
Return a hexadecimal representation of this instance.
>>> s = scalar2.from_base64(
... 'GNDgZXmP+k7MoKfMbiuNbTJp9+tBNSXlm3MTMrAuqAVLkMic6T5EUlV/U6rl+PEy'
... 'IYWN3i6mpQNz8YWPEShwIQmz2veiF6IKPYcmMvjO1kPTJkmyQaZ2Ab7IVb1D8HcQ'
... 'iN9yK8rMj9F08WrX7xsdXmItk7fP7GOFw1PBN5Ok4Bw3HvVM2DaojhsfHhmNVm07'
... 'vlqiJXNDBfFMrJr5yCE1HIazZRkBBdG35xAR0TYu1q2RM6LCxCiY6evD0WBKYI8Q'
... 'hGHTeyRjtPiPDMtCe17qhxTuiPjDPa9+5KP0XKi8qRGHlWq7TSfeG0nl4Fn5BPu0'
... 'dYBHFasloXMG+g4ZbzBSFTpg36BzynjeSe3qrJxUyrPQE4dQwjwLaN55JKadG6AC'
... 'okrFkmIqRcWcHe1xM3lCkqCWAr1Xo2YB01Q4hG/LNw85wPp6FbNNFKtvle5b9bJr'
... '1d7x5+0HNQki1EXaB9k2Ii21uqnewVLCsrz8Rs3m/SLnAnGvihZOd+WAjOYCCVof'
... )
>>> s.hex() == (
... '18d0e065798ffa4ecca0a7cc6e2b8d6d3269f7eb413525e59b731332b02ea805'
... '4b90c89ce93e4452557f53aae5f8f13221858dde2ea6a50373f1858f11287021'
... '09b3daf7a217a20a3d872632f8ced643d32649b241a67601bec855bd43f07710'
... '88df722bcacc8fd174f16ad7ef1b1d5e622d93b7cfec6385c353c13793a4e01c'
... '371ef54cd836a88e1b1f1e198d566d3bbe5aa225734305f14cac9af9c821351c'
... '86b365190105d1b7e71011d1362ed6ad9133a2c2c42898e9ebc3d1604a608f10'
... '8461d37b2463b4f88f0ccb427b5eea8714ee88f8c33daf7ee4a3f45ca8bca911'
... '87956abb4d27de1b49e5e059f904fbb475804715ab25a17306fa0e196f305215'
... '3a60dfa073ca78de49edeaac9c54cab3d0138750c23c0b68de7924a69d1ba002'
... 'a24ac592622a45c59c1ded7133794292a09602bd57a36601d35438846fcb370f'
... '39c0fa7a15b34d14ab6f95ee5bf5b26bd5def1e7ed07350922d445da07d93622'
... '2db5baa9dec152c2b2bcfc46cde6fd22e70271af8a164e77e5808ce602095a1f'
... )
True
"""
return self.to_bytes().hex()
[docs] def to_base64(self: scalar2) -> str:
"""
Return the Base64 UTF-8 string representation of this instance.
>>> b64s = (
... 'GNDgZXmP+k7MoKfMbiuNbTJp9+tBNSXlm3MTMrAuqAVLkMic6T5EUlV/U6rl+PEy'
... 'IYWN3i6mpQNz8YWPEShwIQmz2veiF6IKPYcmMvjO1kPTJkmyQaZ2Ab7IVb1D8HcQ'
... 'iN9yK8rMj9F08WrX7xsdXmItk7fP7GOFw1PBN5Ok4Bw3HvVM2DaojhsfHhmNVm07'
... 'vlqiJXNDBfFMrJr5yCE1HIazZRkBBdG35xAR0TYu1q2RM6LCxCiY6evD0WBKYI8Q'
... 'hGHTeyRjtPiPDMtCe17qhxTuiPjDPa9+5KP0XKi8qRGHlWq7TSfeG0nl4Fn5BPu0'
... 'dYBHFasloXMG+g4ZbzBSFTpg36BzynjeSe3qrJxUyrPQE4dQwjwLaN55JKadG6AC'
... 'okrFkmIqRcWcHe1xM3lCkqCWAr1Xo2YB01Q4hG/LNw85wPp6FbNNFKtvle5b9bJr'
... '1d7x5+0HNQki1EXaB9k2Ii21uqnewVLCsrz8Rs3m/SLnAnGvihZOd+WAjOYCCVof'
... )
>>> s = scalar2.from_base64(b64s)
>>> s.to_base64() == b64s
True
"""
return base64.standard_b64encode(bytes(self)).decode('utf-8')
# Encapsulate classes for this implementation, regardless of which are
# exported as the unqualified symbols.
_implementation.point = point
_implementation.scalar = scalar
_implementation.point2 = point2
_implementation.scalar2 = scalar2
# Remove method for which no pure-Python implementation exists.
delattr(python.point, '__matmul__')
delattr(python.point2, '__matmul__')
# Redefine top-level wrapper classes to ensure that they appear at the end of
# the auto-generated documentation.
python = python # pylint: disable=self-assigning-variable
mcl = mcl # pylint: disable=self-assigning-variable
if __name__ == '__main__':
doctest.testmod() # pragma: no cover