Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion lightecc/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

logger = Logger(module="lightecc/__init__.py")

VERSION = "0.0.6"
VERSION = "0.0.7"


# pylint: disable=too-few-public-methods, too-many-function-args
Expand Down
22 changes: 22 additions & 0 deletions lightecc/interfaces/elliptic_curve.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,28 @@ def double_and_add(self, G: Tuple[int, int], k: int) -> Tuple[int, int]:

return target_point

def __eq__(self, other: object) -> bool:
"""
Two curves are equal when they share the same form and defining
parameters (equation coefficients, modulo and order). This is a
value-based comparison rather than object identity so that a curve
keeps comparing equal to itself after being pickled - e.g. when an
EllipticCurvePoint is sent across a multiprocessing boundary.
"""
if not isinstance(other, EllipticCurve):
return NotImplemented
return (
type(self) is type(other)
and self.modulo == other.modulo
and self.n == other.n
and self.a == other.a
and self.b == other.b
and self.d == other.d
)

def __hash__(self) -> int:
return hash((type(self).__name__, self.modulo, self.n, self.a, self.b, self.d))


class EllipticCurvePoint:
"""
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

setuptools.setup(
name="lightecc",
version="0.0.6",
version="0.0.7",
author="Sefik Ilkin Serengil",
author_email="serengil@gmail.com",
description="A Lightweight Elliptic Curve Cryptography Arithmetic Library for Python with Support for Prime and Binary Fields",
Expand Down
106 changes: 106 additions & 0 deletions tests/test_curve_equality.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
# built-in dependencies
import pickle

# project dependencies
from lightecc import LightECC
from lightecc.interfaces.elliptic_curve import EllipticCurvePoint
from lightecc.commons.logger import Logger

logger = Logger(module="tests/test_curve_equality.py")

FORMS = ["weierstrass", "edwards", "koblitz"]


def test_curve_equality_is_value_based():
# two freshly constructed instances of the same curve are distinct
# objects but must compare equal (value based, not object identity)
for form in FORMS:
ec1 = LightECC(form_name=form, curve_name="test-curve")
ec2 = LightECC(form_name=form, curve_name="test-curve")

assert ec1.curve is not ec2.curve
assert ec1.curve == ec2.curve
assert not (ec1.curve != ec2.curve)

logger.info(f"✅ Value based curve equality tested for {form} form.")


def test_curves_with_different_parameters_are_not_equal():
# different forms describe different curves
ws = LightECC(form_name="weierstrass", curve_name="test-curve")
ed = LightECC(form_name="edwards", curve_name="test-curve")
assert ws.curve != ed.curve

# same form but different named curve must differ as well
ws_test = LightECC(form_name="weierstrass", curve_name="test-curve")
ws_real = LightECC(form_name="weierstrass", curve_name="secp256k1")
assert ws_test.curve != ws_real.curve

logger.info("✅ Curves with different parameters are not equal.")


def test_equal_curves_share_hash_and_are_usable_in_sets():
for form in FORMS:
ec1 = LightECC(form_name=form, curve_name="test-curve")
ec2 = LightECC(form_name=form, curve_name="test-curve")

# equal objects must have equal hashes
assert hash(ec1.curve) == hash(ec2.curve)

# and must collapse to a single entry in a set
assert len({ec1.curve, ec2.curve}) == 1

logger.info(f"✅ Curve hashing tested for {form} form.")


def test_curve_survives_pickle_round_trip():
for form in FORMS:
ec = LightECC(form_name=form, curve_name="test-curve")
restored = pickle.loads(pickle.dumps(ec.curve))

assert restored is not ec.curve
assert restored == ec.curve
assert hash(restored) == hash(ec.curve)

logger.info(f"✅ Curve pickle round-trip tested for {form} form.")


def test_point_equality_survives_pickle_round_trip():
# regression test: an EllipticCurvePoint carries its curve, and pickling
# rebuilds that curve as a fresh instance. Without value based curve
# equality the restored point would never compare equal to a point
# recomputed on the original curve, breaking any consumer that relies on
# `==` after a point crosses a serialization boundary (e.g. multiprocessing).
for form in FORMS:
ec = LightECC(form_name=form, curve_name="test-curve")

P = ec.G * 5
restored = pickle.loads(pickle.dumps(P))

assert restored is not P
assert (restored.x, restored.y) == (P.x, P.y)
assert restored == P

# a point recomputed on the original curve must also match the
# restored point - this mirrors the decryption comparison loop
recomputed = ec.G * 5
assert recomputed == restored

logger.info(f"✅ Point equality across pickle tested for {form} form.")


def test_point_equality_across_independent_curve_instances():
# same coordinates declared against two independent (but equal) curve
# instances must compare equal
for form in FORMS:
ec1 = LightECC(form_name=form, curve_name="test-curve")
ec2 = LightECC(form_name=form, curve_name="test-curve")

P = ec1.G * 3
Q = EllipticCurvePoint(x=P.x, y=P.y, curve=ec2.curve)

assert P == Q

logger.info(
f"✅ Point equality across independent curve instances tested for {form} form."
)
Loading