diff options
| author | J08nY | 2020-06-13 01:59:22 +0200 |
|---|---|---|
| committer | J08nY | 2020-06-13 01:59:22 +0200 |
| commit | 4e17dfdb12707c814add7851c81eda4edb3dacde (patch) | |
| tree | 1f59c7adad2cc58e3a53d995b029c5a76f591411 /pyecsca/ec | |
| parent | 23b3638a496637c1810fb5a2bd610b63b1a72521 (diff) | |
| download | pyecsca-4e17dfdb12707c814add7851c81eda4edb3dacde.tar.gz pyecsca-4e17dfdb12707c814add7851c81eda4edb3dacde.tar.zst pyecsca-4e17dfdb12707c814add7851c81eda4edb3dacde.zip | |
Add docs and tests to the ECTester target.
Diffstat (limited to 'pyecsca/ec')
| -rw-r--r-- | pyecsca/ec/curve.py | 49 | ||||
| -rw-r--r-- | pyecsca/ec/mod.py | 78 |
2 files changed, 124 insertions, 3 deletions
diff --git a/pyecsca/ec/curve.py b/pyecsca/ec/curve.py index e8ae66c..a05ff3f 100644 --- a/pyecsca/ec/curve.py +++ b/pyecsca/ec/curve.py @@ -6,8 +6,8 @@ from public import public from .coordinates import CoordinateModel, AffineCoordinateModel from .mod import Mod -from .model import CurveModel -from .point import Point +from .model import CurveModel, ShortWeierstrassModel +from .point import Point, InfinityPoint @public @@ -93,17 +93,62 @@ class EllipticCurve(object): @property def neutral_is_affine(self): + """Whether the neurtal point is an affine point.""" return bool(self.model.base_neutral) def is_neutral(self, point: Point) -> bool: + """Check whether the point is the neutral point.""" return self.neutral == point def is_on_curve(self, point: Point) -> bool: + """Check whether the point is on the curve.""" if point.coordinate_model.curve_model != self.model: return False + if self.is_neutral(point): + return True loc = {**self.parameters, **point.to_affine().coords} return eval(compile(self.model.equation, "", mode="eval"), loc) + def to_affine(self) -> "EllipticCurve": + """Convert this curve into the affine coordinate model, if possible.""" + coord_model = AffineCoordinateModel(self.model) + return EllipticCurve(self.model, coord_model, self.prime, self.neutral.to_affine(), self.parameters) + + def decode_point(self, encoded: bytes) -> Point: + """Decode a point encoded as a sequence of bytes (ANSI X9.62).""" + if encoded[0] == 0x00 and len(encoded) == 1: + return InfinityPoint(self.coordinate_model) + coord_len = (self.prime.bit_length() + 7) // 8 + if encoded[0] in (0x04, 0x06): + data = encoded[1:] + if len(data) != coord_len * len(self.coordinate_model.variables): + raise ValueError("Encoded point has bad length") + coords = {} + for var in sorted(self.coordinate_model.variables): + coords[var] = Mod(int.from_bytes(data[:coord_len], "big"), self.prime) + data = data[coord_len:] + return Point(self.coordinate_model, **coords) + elif encoded[0] in (0x02, 0x03): + if isinstance(self.coordinate_model, AffineCoordinateModel) and isinstance(self.model, ShortWeierstrassModel): + data = encoded[1:] + if len(data) != coord_len: + raise ValueError("Encoded point has bad length") + x = Mod(int.from_bytes(data, "big"), self.prime) + rhs = x**3 + self.parameters["a"] * x + self.parameters["b"] + if not rhs.is_residue(): + raise ValueError("Point not on curve") + sqrt = rhs.sqrt() + yp = encoded[0] & 0x01 + if int(sqrt) & 0x01 == yp: + y = sqrt + else: + y = -sqrt + return Point(self.coordinate_model, x=x, y=y) + else: + raise NotImplementedError + else: + raise ValueError(f"Wrong encoding type: {hex(encoded[0])}, should be one of 0x04, 0x06, 0x02, 0x03 or 0x00") + def __eq__(self, other): if not isinstance(other, EllipticCurve): return False diff --git a/pyecsca/ec/mod.py b/pyecsca/ec/mod.py index ec5dfe6..421f521 100644 --- a/pyecsca/ec/mod.py +++ b/pyecsca/ec/mod.py @@ -1,5 +1,6 @@ +import random import secrets -from functools import wraps +from functools import wraps, lru_cache from public import public @@ -39,6 +40,34 @@ def extgcd(a, b): return x2, y2, a +@public +@lru_cache +def miller_rabin(n: int, rounds: int = 50) -> bool: + """Miller-Rabin probabilistic primality test.""" + if n == 2 or n == 3: + return True + + if n % 2 == 0: + return False + + r, s = 0, n - 1 + while s % 2 == 0: + r += 1 + s //= 2 + for _ in range(rounds): + a = random.randrange(2, n - 1) + x = pow(a, s, n) + if x == 1 or x == n - 1: + continue + for _ in range(r - 1): + x = pow(x, 2, n) + if x == n - 1: + break + else: + return False + return True + + def check(func): @wraps(func) def method(self, other): @@ -99,6 +128,53 @@ class Mod(object): def __invert__(self): return self.inverse() + def is_residue(self): + """Whether this element is a quadratic residue (only implemented for prime modulus).""" + if not miller_rabin(self.n): + raise NotImplementedError + if self.x == 0: + return True + if self.n == 2: + return self.x in (0, 1) + legendre = self ** ((self.n - 1) // 2) + return legendre == 1 + + def sqrt(self): + """ + The modular square root of this element (only implemented for prime modulus). + + Uses the `Tonelli-Shanks <https://en.wikipedia.org/wiki/Tonelli–Shanks_algorithm>`_ algorithm. + """ + if not miller_rabin(self.n): + raise NotImplementedError + q = self.n - 1 + s = 0 + while q % 2 == 0: + q //= 2 + s += 1 + + z = 2 + while Mod(z, self.n).is_residue(): + z += 1 + + m = s + c = Mod(z, self.n) ** q + t = self ** q + r_exp = (q + 1) // 2 + r = self ** r_exp + + while t != 1: + i = 1 + while not (t ** (2**i)) == 1: + i += 1 + two_exp = m - (i + 1) + b = c ** int(Mod(2, self.n)**two_exp) + m = int(Mod(i, self.n)) + c = b ** 2 + t *= c + r *= b + return r + @check def __mul__(self, other): return Mod((self.x * other.x) % self.n, self.n) |
