MikroTikPatch/toyecc/PointOps.py

259 lines
9.0 KiB
Python
Raw Normal View History

2024-06-15 02:18:14 +03:00
#
# toyecc - A small Elliptic Curve Cryptography Demonstration.
# Copyright (C) 2011-2022 Johannes Bauer
#
# This file is part of toyecc.
#
# toyecc is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; this program is ONLY licensed under
# version 3 of the License, later versions are explicitly excluded.
#
# toyecc is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with toyecc; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# Johannes Bauer <JohannesBauer@gmx.de>
#
from . import Tools
from .FieldElement import FieldElement
from .Exceptions import UnsupportedPointFormatException
class PointOpEDDSAEncoding(object):
def eddsa_encode(self):
"""Performs serialization of the point as required by EdDSA."""
coordlen = (self.curve.B + 7) // 8
bitlen = (coordlen * 8) - 1
enc_value = int(self.y)
enc_value &= ((1 << bitlen) - 1)
enc_value |= (int(self.x) & 1) << bitlen
return Tools.inttobytes_le(enc_value, (self.curve.B + 7) // 8)
@staticmethod
def __eddsa_recoverx(curve, y):
x = 0
xx = (y * y - 1) // (curve.d * y * y - curve.a)
if curve.p % 8 == 5:
x = xx ** ((curve.p + 3) // 8)
if x * x == -xx:
x = x * (FieldElement(2, curve.p) ** ((curve.p - 1) // 4))
elif curve.p % 4 == 3:
x = xx ** ((curve.p + 1) // 4)
if x * x != xx:
x = 0
return int(x)
@classmethod
def eddsa_decode(cls, curve, data):
"""Performs deserialization of the point as required by EdDSA."""
assert(curve.curvetype == "twistededwards")
coordlen = (curve.B + 7) // 8
bitlen = (coordlen * 8) - 1
enc_value = int.from_bytes(data, byteorder = "little")
y = enc_value & ((1 << bitlen) - 1)
x = PointOpEDDSAEncoding.__eddsa_recoverx(curve, y)
hibit = (enc_value >> bitlen) & 1
if (x & 1) != hibit:
x = curve.p - x
return cls(x, y, curve)
class PointOpCurveConversion(object):
@staticmethod
def __pconv_twed_mont_scalefactor(twedcurve, montcurve):
native_b = 4 // (twedcurve.a - twedcurve.d)
if native_b == montcurve.b:
# Scaling is not necessary, already native curve format
scale_factor = 1
else:
# Scaling of montgomery y component (v) is needed
if twedcurve.hasgenerator and montcurve.hasgenerator:
# Convert the generator point of the twisted edwards source
# point to unscaled Montgomery space
Gv = (1 + twedcurve.G.y) // ((1 - twedcurve.G.y) * twedcurve.G.x)
# And calculate a multiplicative scaling factor so that the
# point will result in the target curve's generator point Y
scale_factor = montcurve.G.y // Gv
elif native_b.is_qr:
# If b is a quadradic residue mod p then any other
# quadratic residue can serve as a surrgate b coefficient
# to yield an isomorphous curve. Only y coordinate of the
# resulting points needs to be scaled. Calculate a scaling
# ratio.
scale_factors = (montcurve.b // native_b).sqrt()
# At least one of the curves lacks a generator point,
# select just any scale factor
scale_factor = scale_factors[0].inverse()
else:
# Native B is a quadratic non-residue module B; Not sure
# how to handle this case
# TODO: Implement this
raise Exception(NotImplemented)
return scale_factor
def convert(self, targetcurve):
"""Convert the affine curve point to a point on a birationally
equivalent target curve."""
if self.is_neutral:
return targetcurve.neutral()
if (self.curve.curvetype == "twistededwards") and (targetcurve.curvetype == "montgomery"):
# (x, y) are Edwards coordinates
# (u, v) are Montgomery coordonates
(x, y) = (self.x, self.y)
u = (1 + y) // (1 - y)
v = (1 + y) // ((1 - y) * x)
# Montgomery coordinates are unscaled to the actual B coefficient
# of the curve right now. Calculate scaling factor and scale v
# appropriately
scaling_factor = self.__pconv_twed_mont_scalefactor(self.curve, targetcurve)
v = v * scaling_factor
point = self.__class__(int(u), int(v), targetcurve)
elif (self.curve.curvetype == "montgomery") and (targetcurve.curvetype == "twistededwards"):
# (x, y) are Edwards coordinates
# (u, v) are Montgomery coordonates
(u, v) = (self.x, self.y)
y = (u - 1) // (u + 1)
x = -(1 + y) // (v * (y - 1))
# Twisted Edwards coordinates are unscaled to the actual B
# coefficient of the curve right now. Calculate scaling factor and
# scale x appropriately
scaling_factor = self.__pconv_twed_mont_scalefactor(targetcurve, self.curve)
x = x * scaling_factor
point = self.__class__(int(x), int(y), targetcurve)
else:
raise Exception(NotImplemented)
assert(point.oncurve())
return point
class PointOpNaiveOrderCalculation(object):
def naive_order_calculation(self):
"""Calculates the order of the point naively, i.e. by walking through
all points until the given neutral element is hit. Note that this only
works for smallest of curves and is not computationally feasible for
anything else."""
curpt = self
order = 1
while not curpt.is_neutral:
order += 1
curpt += self
return order
class PointOpSerialization(object):
def serialize_uncompressed(self):
"""Serializes the point into a bytes object in uncompressed form."""
length = (self.curve.p.bit_length() + 7) // 8
serialized = bytes([ 0x04 ]) + Tools.inttobytes(int(self.x), length) + Tools.inttobytes(int(self.y), length)
return serialized
@classmethod
def deserialize_uncompressed(cls, data, curve = None):
"""Deserializes a curve point which is given in uncompressed form. A
curve may be passed with the 'curve' argument in which case an
AffineCurvePoint is returned from this method. Otherwise the affine X
and Y coordinates are returned as a tuple."""
if data[0] != 0x04:
raise UnsupportedPointFormatException("Generator point of explicitly encoded curve is given in unsupported form (0x%x)." % (data[0]))
data = data[1:]
assert((len(data) % 2) == 0)
Px = Tools.bytestoint(data[ : len(data) // 2])
Py = Tools.bytestoint(data[len(data) // 2 : ])
if curve is not None:
return cls(Px, Py, curve)
else:
return (Px, Py)
class PointOpScalarMultiplicationXOnly():
"""Compute an X-only ladder scalar multiplication of the private key and
the X coordinate of a given point."""
def _x_double(self, x):
"""Doubling of point with coordinate x."""
if x is None:
return None
den = 4 * (x**3 + self.curve.a * x + self.curve.b)
if den == 0:
# Point at infinity
return None
num = (x**2 - self.curve.a)**2 - (8 * self.curve.b * x)
return num // den
def _x_add_multiplicative(self, x1, x2, x3prime):
"""Multiplicative formula addition of x1 + x2, where x3' is the
difference in X of P1 - P2. Using this function only makes sense where
(P1 - P2) is fixed, as it is in the ladder implementation."""
if x1 is None:
return x2
elif x2 is None:
return x1
elif x1 == x2:
return None
num = -4 * self.curve.b * (x1 + x2) + (x1 * x2 - self.curve.a)**2
den = x3prime * (x1 - x2)**2
result = num // den
return result
def _x_add_additive(self, x1, x2, x3prime):
"""Additive formula addition of x1 + x2, where x3' is the difference in
X of P1 - P2. Using this function only makes sense where (P1 - P2) is
fixed, as it is in the ladder implementation."""
if x1 is None:
return x2
elif x2 is None:
return x1
elif x1 == x2:
return None
num = 2 * (x1 + x2) * (x1 * x2 + self.curve.a) + 4 * self.curve.b
den = (x1 - x2) ** 2
result = num // den - x3prime
return result
def _x_add(self, x1, x2, x3prime):
"""There are two equivalent implementations, one using the
multiplicative and the other using the additive representation. Both
should work equally well."""
return self._x_add_multiplicative(x1, x2, x3prime)
#return self._x_add_additive(x1, x2, x3prime)
def scalar_mul_xonly(self, scalar):
"""This implements the X-coordinate-only multiplication algorithm of a
Short Weierstrass curve with the X coordinate of a given point.
Reference is "Izu and Takagi: A Fast Parallel Elliptic Curve
Multiplication Resistant against Side Channel Attacks" (2002)"""
if self.curve.curvetype != "shortweierstrass":
raise NotImplementedError("X-only ladder multiplication is only implemented for Short Weierstrass curves")
if self.is_neutral:
# Point at infinity is input
return None
elif scalar == 0:
# Multiplication with zero -> point at infinity is output
return None
x_coordinate = int(self.x)
if not isinstance(x_coordinate, FieldElement):
x_coordinate = FieldElement(x_coordinate, self.curve.p)
Q = [ x_coordinate, self._x_double(x_coordinate), None ]
for bitno in reversed(range(scalar.bit_length() - 1)):
bit = (scalar >> bitno) & 1
Q[2] = self._x_double(Q[bit])
Q[1] = self._x_add(Q[0], Q[1], x_coordinate)
Q[0] = Q[2 - bit]
Q[1] = Q[1 + bit]
return Q[0]