From aa606925980e9bee838951962e211e6eb5146e68 Mon Sep 17 00:00:00 2001 From: Isak Ruas Date: Thu, 3 Oct 2024 22:46:13 -0300 Subject: [PATCH 1/2] feature: update dependencies and cache config --- .github/workflows/{ci.yml => codecov.yml} | 5 + CONTRIBUTING.md | 1 + requirements-docs.txt | 7 +- requirements-tests.txt | 7 +- src/ecutils/algorithms.py | 15 +-- src/ecutils/core.py | 29 ++--- src/ecutils/curves.py | 3 +- src/ecutils/protocols.py | 17 +-- src/ecutils/settings.py | 4 + src/ecutils/utils.py | 4 +- tests/test_elliptic_curve_operations.py | 122 ++++++++++++++++++++++ tests/test_koblitz.py | 32 +++++- tests/test_massey_omura.py | 22 ++++ tests/test_utils.py | 56 ++++++++++ 14 files changed, 283 insertions(+), 41 deletions(-) rename .github/workflows/{ci.yml => codecov.yml} (80%) create mode 100644 src/ecutils/settings.py create mode 100644 tests/test_utils.py diff --git a/.github/workflows/ci.yml b/.github/workflows/codecov.yml similarity index 80% rename from .github/workflows/ci.yml rename to .github/workflows/codecov.yml index 9afc097..6c09ccc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/codecov.yml @@ -21,7 +21,12 @@ jobs: pip install -r requirements-tests.txt pip install pytest pytest-cov + - name: Export environment variable + run: echo "LRU_CACHE_MAXSIZE=0" >> $GITHUB_ENV + - name: Run tests with coverage + env: + LRU_CACHE_MAXSIZE: ${{ env.LRU_CACHE_MAXSIZE }} run: pytest --cov tests - name: Upload coverage reports to Codecov diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2040906..dc9edb5 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -45,6 +45,7 @@ It's essential that your contributions are well-tested: ```bash pytest --cov=ecutils tests/ + pytest --cov=ecutils --cov-report=html tests/ ``` Ensure that your contributions pass all tests and that the overall test coverage isn’t compromised. diff --git a/requirements-docs.txt b/requirements-docs.txt index 8d22760..b61591d 100644 --- a/requirements-docs.txt +++ b/requirements-docs.txt @@ -1,5 +1,4 @@ -mkdocs==1.5.3 -mkdocs-material==9.5.1 +mkdocs==1.6.1 +mkdocs-material==9.5.39 mkdocs-material-extensions==1.3.1 -mkdocs-minify-plugin==0.7.1 -zipp>=3.19.1 # not directly required, pinned by Snyk to avoid a vulnerability \ No newline at end of file +mkdocs-minify-plugin==0.8.0 \ No newline at end of file diff --git a/requirements-tests.txt b/requirements-tests.txt index 948a12a..5ccd51e 100644 --- a/requirements-tests.txt +++ b/requirements-tests.txt @@ -1,4 +1,3 @@ -coverage==7.3.2 -pytest==7.4.3 -pytest-cov==4.1.0 -zipp>=3.19.1 # not directly required, pinned by Snyk to avoid a vulnerability +coverage==7.6.1 +pytest==8.3.3 +pytest-cov==5.0.0 diff --git a/src/ecutils/algorithms.py b/src/ecutils/algorithms.py index e83b1cf..727e7a6 100644 --- a/src/ecutils/algorithms.py +++ b/src/ecutils/algorithms.py @@ -6,6 +6,7 @@ from ecutils.core import EllipticCurve, Point from ecutils.curves import get as get_curve +from ecutils.settings import LRU_CACHE_MAXSIZE @dataclass(frozen=True) @@ -22,7 +23,7 @@ class Koblitz: curve_name: str = "secp521r1" @property - @lru_cache(maxsize=1024, typed=True) + @lru_cache(maxsize=LRU_CACHE_MAXSIZE, typed=True) def curve(self) -> EllipticCurve: """Retrieves the elliptic curve associated with this `Koblitz` instance. @@ -34,7 +35,7 @@ def curve(self) -> EllipticCurve: """ return get_curve(self.curve_name) - @lru_cache(maxsize=1024, typed=True) + @lru_cache(maxsize=LRU_CACHE_MAXSIZE, typed=True) def encode( self, message: str, alphabet_size: int = 2**8, lengthy=False ) -> Union[Tuple[Tuple[Point, int]], Tuple[Point, int]]: @@ -108,7 +109,7 @@ def encode( return tuple(encoded_messages) - @lru_cache(maxsize=1024, typed=True) + @lru_cache(maxsize=LRU_CACHE_MAXSIZE, typed=True) def decode( self, encoded: Union[Point, tuple[Tuple[Point, int]]], @@ -200,7 +201,7 @@ class DigitalSignature: curve_name: str = "secp192k1" @property - @lru_cache(maxsize=1024, typed=True) + @lru_cache(maxsize=LRU_CACHE_MAXSIZE, typed=True) def curve(self) -> EllipticCurve: """Retrieves the elliptic curve associated with this `DigitalSignature` instance. @@ -214,7 +215,7 @@ def curve(self) -> EllipticCurve: return get_curve(self.curve_name) @property - @lru_cache(maxsize=1024, typed=True) + @lru_cache(maxsize=LRU_CACHE_MAXSIZE, typed=True) def public_key(self) -> Point: """Computes and returns the public key corresponding to the private key. @@ -230,7 +231,7 @@ def public_key(self) -> Point: """ return self.curve.multiply_point(self.private_key, self.curve.G) - @lru_cache(maxsize=1024, typed=True) + @lru_cache(maxsize=LRU_CACHE_MAXSIZE, typed=True) def generate_signature(self, message_hash: int) -> Tuple[int, int]: """Generates an ECDSA signature for a given message hash using the private key. @@ -261,7 +262,7 @@ def generate_signature(self, message_hash: int) -> Tuple[int, int]: ) % self.curve.n return r, s - @lru_cache(maxsize=1024, typed=True) + @lru_cache(maxsize=LRU_CACHE_MAXSIZE, typed=True) def verify_signature( self, public_key: Point, message_hash: int, r: int, s: int ) -> bool: diff --git a/src/ecutils/core.py b/src/ecutils/core.py index deb8660..b000b08 100755 --- a/src/ecutils/core.py +++ b/src/ecutils/core.py @@ -2,6 +2,8 @@ from functools import lru_cache from typing import Optional +from ecutils.settings import LRU_CACHE_MAXSIZE + @dataclass(frozen=True) class Point: @@ -36,7 +38,7 @@ class EllipticCurveOperations: use_projective_coordinates: bool = True - @lru_cache(maxsize=1024, typed=True) + @lru_cache(maxsize=LRU_CACHE_MAXSIZE, typed=True) def add_points(self, p1: Point, p2: Point) -> Point: """Add two points on an elliptic curve. @@ -81,7 +83,7 @@ def add_points(self, p1: Point, p2: Point) -> Point: y_3 = (s * (p1.x - x_3) - p1.y) % self.p return Point(x_3, y_3) - @lru_cache(maxsize=1024, typed=True) + @lru_cache(maxsize=LRU_CACHE_MAXSIZE, typed=True) def double_point(self, p: Point) -> Point: """Double a point on an elliptic curve.""" if p.x is None or p.y is None: @@ -94,16 +96,17 @@ def double_point(self, p: Point) -> Point: n = (3 * p.x**2 + self.a) % self.p d = (2 * p.y) % self.p - try: - inv = pow(d, -1, self.p) - except ValueError: - return Point() # Point at infinity + # try: + # inv = pow(d, -1, self.p) + # except ValueError: + # return Point() # Point at infinity + inv = pow(d, -1, self.p) s = (n * inv) % self.p x_3 = (s**2 - p.x - p.x) % self.p y_3 = (s * (p.x - x_3) - p.y) % self.p return Point(x_3, y_3) - @lru_cache(maxsize=1024, typed=True) + @lru_cache(maxsize=LRU_CACHE_MAXSIZE, typed=True) def multiply_point(self, k: int, p: Point) -> Point: """Multiply a point on an elliptic curve by an integer scalar. @@ -154,7 +157,7 @@ def multiply_point(self, k: int, p: Point) -> Point: r = self.add_points(r, p) return r - @lru_cache(maxsize=1024, typed=True) + @lru_cache(maxsize=LRU_CACHE_MAXSIZE, typed=True) def jacobian_add_points( self, p1: JacobianPoint, p2: JacobianPoint ) -> JacobianPoint: @@ -187,7 +190,7 @@ def jacobian_add_points( return JacobianPoint(x, y, z) - @lru_cache(maxsize=1024, typed=True) + @lru_cache(maxsize=LRU_CACHE_MAXSIZE, typed=True) def jacobian_double_point(self, p: JacobianPoint) -> JacobianPoint: """Double a point on an elliptic curve using Jacobian coordinates.""" if p.x is None or p.y is None: @@ -206,7 +209,7 @@ def jacobian_double_point(self, p: JacobianPoint) -> JacobianPoint: return JacobianPoint(nx, ny, nz) - @lru_cache(maxsize=1024, typed=True) + @lru_cache(maxsize=LRU_CACHE_MAXSIZE, typed=True) def jacobian_multiply_point(self, k: int, p: JacobianPoint) -> JacobianPoint: """Multiply a point on an elliptic curve by an integer scalar using repeated addition.""" if k == 0 or p.x is None or p.y is None: @@ -222,7 +225,7 @@ def jacobian_multiply_point(self, k: int, p: JacobianPoint) -> JacobianPoint: return result @staticmethod - @lru_cache(maxsize=1024, typed=True) + @lru_cache(maxsize=LRU_CACHE_MAXSIZE, typed=True) def to_jacobian(point: Point) -> JacobianPoint: """Converts a point from affine coordinates to Jacobian coordinates. @@ -236,7 +239,7 @@ def to_jacobian(point: Point) -> JacobianPoint: return JacobianPoint() return JacobianPoint(point.x, point.y, 1) - @lru_cache(maxsize=1024, typed=True) + @lru_cache(maxsize=LRU_CACHE_MAXSIZE, typed=True) def to_affine(self, point: JacobianPoint) -> Point: """Converts a point from Jacobian coordinates to affine coordinates. @@ -251,7 +254,7 @@ def to_affine(self, point: JacobianPoint) -> Point: inv_z = pow(point.z, -1, self.p) return Point((point.x * inv_z**2) % self.p, (point.y * inv_z**3) % self.p) - @lru_cache(maxsize=1024, typed=True) + @lru_cache(maxsize=LRU_CACHE_MAXSIZE, typed=True) def is_point_on_curve(self, p: Point) -> bool: """Check if a point lies on the elliptic curve. diff --git a/src/ecutils/curves.py b/src/ecutils/curves.py index 74b2fb6..8d41d94 100644 --- a/src/ecutils/curves.py +++ b/src/ecutils/curves.py @@ -1,6 +1,7 @@ from functools import lru_cache from ecutils.core import EllipticCurve, Point +from ecutils.settings import LRU_CACHE_MAXSIZE secp192k1 = EllipticCurve( p=0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFEE37, @@ -101,7 +102,7 @@ ) -@lru_cache(maxsize=1024, typed=True) +@lru_cache(maxsize=LRU_CACHE_MAXSIZE, typed=True) def get(name) -> EllipticCurve: """Retrieve an EllipticCurve instance by its standard name. diff --git a/src/ecutils/protocols.py b/src/ecutils/protocols.py index 5292425..d63649b 100644 --- a/src/ecutils/protocols.py +++ b/src/ecutils/protocols.py @@ -3,6 +3,7 @@ from ecutils.core import EllipticCurve, Point from ecutils.curves import get as get_curve +from ecutils.settings import LRU_CACHE_MAXSIZE @dataclass(frozen=True) @@ -18,7 +19,7 @@ class DiffieHellman: curve_name: str = "secp192k1" @property - @lru_cache(maxsize=1024, typed=True) + @lru_cache(maxsize=LRU_CACHE_MAXSIZE, typed=True) def curve(self) -> EllipticCurve: """Retrieves the elliptic curve associated with this `DiffieHellman` instance. @@ -32,7 +33,7 @@ def curve(self) -> EllipticCurve: return get_curve(self.curve_name) @property - @lru_cache(maxsize=1024, typed=True) + @lru_cache(maxsize=LRU_CACHE_MAXSIZE, typed=True) def public_key(self) -> Point: """Computes and returns the public key corresponding to the private key. @@ -48,7 +49,7 @@ def public_key(self) -> Point: """ return self.curve.multiply_point(self.private_key, self.curve.G) - @lru_cache(maxsize=1024, typed=True) + @lru_cache(maxsize=LRU_CACHE_MAXSIZE, typed=True) def compute_shared_secret(self, other_public_key: Point) -> Point: """Computes the shared secret using the private key and the other party's public key. @@ -75,7 +76,7 @@ class MasseyOmura: curve_name: str = "secp192k1" @property - @lru_cache(maxsize=1024, typed=True) + @lru_cache(maxsize=LRU_CACHE_MAXSIZE, typed=True) def curve(self) -> EllipticCurve: """Retrieves the elliptic curve associated with this `MasseyOmura` instance. @@ -89,7 +90,7 @@ def curve(self) -> EllipticCurve: return get_curve(self.curve_name) @property - @lru_cache(maxsize=1024, typed=True) + @lru_cache(maxsize=LRU_CACHE_MAXSIZE, typed=True) def public_key(self) -> Point: """Computes and returns the public key corresponding to the private key. @@ -105,19 +106,19 @@ def public_key(self) -> Point: """ return self.curve.multiply_point(self.private_key, self.curve.G) - @lru_cache(maxsize=1024, typed=True) + @lru_cache(maxsize=LRU_CACHE_MAXSIZE, typed=True) def first_encryption_step(self, message: Point) -> Point: """Encrypts the message with the sender's private key.""" return self.curve.multiply_point(self.private_key, message) - @lru_cache(maxsize=1024, typed=True) + @lru_cache(maxsize=LRU_CACHE_MAXSIZE, typed=True) def second_encryption_step(self, received_encrypted_message: Point) -> Point: """Applies the receiver's private key on the received encrypted message.""" return self.first_encryption_step(received_encrypted_message) - @lru_cache(maxsize=1024, typed=True) + @lru_cache(maxsize=LRU_CACHE_MAXSIZE, typed=True) def partial_decryption_step(self, encrypted_message: Point) -> Point: """Partial decryption using the inverse of the sender's private key.""" diff --git a/src/ecutils/settings.py b/src/ecutils/settings.py new file mode 100644 index 0000000..32fcfe4 --- /dev/null +++ b/src/ecutils/settings.py @@ -0,0 +1,4 @@ +import os + +# Default value for LRU cache maxsize +LRU_CACHE_MAXSIZE = os.environ.get("LRU_CACHE_MAXSIZE", 1024) diff --git a/src/ecutils/utils.py b/src/ecutils/utils.py index 18fde53..e2c8ab4 100644 --- a/src/ecutils/utils.py +++ b/src/ecutils/utils.py @@ -1,8 +1,10 @@ import hashlib from functools import lru_cache +from ecutils.settings import LRU_CACHE_MAXSIZE -@lru_cache(maxsize=1024, typed=True) + +@lru_cache(maxsize=LRU_CACHE_MAXSIZE, typed=True) def calculate_file_hash(file_name: str, block_size: int = 16384) -> int: """Calculates the SHA-256 hash of a file efficiently. diff --git a/tests/test_elliptic_curve_operations.py b/tests/test_elliptic_curve_operations.py index 5f82d89..770330b 100644 --- a/tests/test_elliptic_curve_operations.py +++ b/tests/test_elliptic_curve_operations.py @@ -9,7 +9,10 @@ class TestEllipticCurveOperations(unittest.TestCase): def setUp(self): """Set up an elliptic curve environment for testing.""" + self.curve = secp192k1 + self.curve.__class__.use_projective_coordinates = True + self.point1 = Point( x=0xF091CF6331B1747684F5D2549CD1D4B3A8BED93B94F93CB6, y=0xFD7AF42E1E7565A02E6268661C5E42E603DA2D98A18F2ED5, @@ -25,6 +28,16 @@ def test_point_addition(self): x=0x3CD61E370D02CA0687C0B5F7EBF6D0373F4DD0CCCCB7CC2D, y=0x2C4BEFD9B02F301EB4014504F0533AA7EB19E9EA56441F78, ) + + self.curve.__class__.use_projective_coordinates = False + + calculated_sum = self.curve.add_points(self.point1, self.point2) + self.assertEqual( + calculated_sum, expected_sum, "Point addition result is incorrect." + ) + + self.curve.__class__.use_projective_coordinates = True + calculated_sum = self.curve.add_points(self.point1, self.point2) self.assertEqual( calculated_sum, expected_sum, "Point addition result is incorrect." @@ -36,11 +49,34 @@ def test_point_doubling(self): x=0xEA525DD5A1353762A14E9E78B9063316D1F2D5E792F87862, y=0xA936D583530982690C445427CDF2C5B0BB1C88749247B02E, ) + + self.curve.__class__.use_projective_coordinates = False + calculated_double = self.curve.add_points(self.point1, self.point1) self.assertEqual( calculated_double, expected_double, "Point doubling result is incorrect." ) + self.curve.__class__.use_projective_coordinates = True + + calculated_double = self.curve.add_points(self.point1, self.point1) + self.assertEqual( + calculated_double, expected_double, "Point doubling result is incorrect." + ) + + calculated_double = self.curve.jacobian_double_point(Point()) + self.assertEqual( + self.curve.to_affine(calculated_double), + Point(), + "Point doubling result is incorrect.", + ) + + def test_invalid_point_doubling(self): + """Test doubling invalid points not on the curve.""" + off_curve_point = Point(x=200, y=119) + with self.assertRaises(ValueError): + self.curve.double_point(off_curve_point) + def test_scalar_multiplication(self): """Test the scalar multiplication of a point on the curve.""" scalar = 2 @@ -48,6 +84,30 @@ def test_scalar_multiplication(self): x=0xEA525DD5A1353762A14E9E78B9063316D1F2D5E792F87862, y=0xA936D583530982690C445427CDF2C5B0BB1C88749247B02E, ) + + self.curve.__class__.use_projective_coordinates = False + + calculated_product = self.curve.multiply_point(scalar, self.point1) + self.assertEqual( + calculated_product, + expected_product, + "Scalar multiplication result is incorrect.", + ) + + calculated_product = self.curve.multiply_point( + 0xEA525DD5A1353762A14E9E78B9063316D1F2D5E792F87862, self.point1 + ) + self.assertEqual( + calculated_product, + Point( + x=5095008632516147798595855149669871701227161828659032863660, + y=4326825067835634121700785249151086742283636342358962787033, + ), + "Scalar multiplication result is incorrect.", + ) + + self.curve.__class__.use_projective_coordinates = True + calculated_product = self.curve.multiply_point(scalar, self.point1) self.assertEqual( calculated_product, @@ -61,6 +121,10 @@ def test_point_on_curve(self): self.curve.is_point_on_curve(self.point1), "The point should be on the curve.", ) + self.assertTrue( + self.curve.is_point_on_curve(self.curve.to_jacobian(self.point1)), + "The point should be on the curve.", + ) off_curve_point = Point(x=200, y=119) self.assertFalse( self.curve.is_point_on_curve(off_curve_point), @@ -94,6 +158,15 @@ def test_addition_with_identity(self): "Adding the identity element should return the original point.", ) + calculated_sum = self.curve.jacobian_add_points( + self.curve.to_jacobian(self.point1), identity + ) + self.assertEqual( + self.curve.to_affine(calculated_sum), + self.point1, + "Point doubling result is incorrect.", + ) + def test_invalid_scalar_multiplication(self): """Test scalar multiplication with invalid scalar or point.""" with self.assertRaises(ValueError, msg="Test multiplying by scalar 0"): @@ -113,6 +186,25 @@ def test_invalid_scalar_multiplication(self): def test_addition_of_inverses_leading_to_infinity(self): """Test adding a point on the curve to its inverse, which should lead to the point at infinity.""" + + self.curve.__class__.use_projective_coordinates = False + + inverse_point = Point( + x=self.point1.x, + y=(-self.point1.y) % self.curve.p, # Calculating the modular inverse for y + ) + identity_element = Point() # Point at infinity representation with None values + + # Adding a point to its negation will result in the point at infinity + calculated_sum = self.curve.add_points(self.point1, inverse_point) + self.assertEqual( + calculated_sum, + identity_element, + "Adding a point to its negation should give the point at infinity.", + ) + + self.curve.__class__.use_projective_coordinates = True + inverse_point = Point( x=self.point1.x, y=(-self.point1.y) % self.curve.p, # Calculating the modular inverse for y @@ -142,6 +234,7 @@ def test_point_doubling_to_infinity(self): n=4, # The order of the base point G. h=0, # The cofactor (not relevant in this test case). ) + curve.__class__.use_projective_coordinates = True # Create a point with a y-coordinate of zero (located at the curve's x-axis). point_with_y_zero = Point(x=0, y=0) @@ -178,6 +271,35 @@ def test_multiply_point_at_infinity(self): n=4, # The order of the base point G. h=0, # The cofactor. ) + curve.__class__.use_projective_coordinates = True + + # The point at infinity, represented as a point with no coordinates. + point_at_infinity = Point() + + # The expected result of multiplying the point at infinity by any scalar. + expected_result_at_infinity = Point() + + # Multiply the point at infinity by a scalar (here, scalar = 3). + result = curve.multiply_point(3, point_at_infinity) + + # Assert that the multiplication result is the point at infinity. + # This confirms the mathematical property of the point at infinity on elliptic curves. + self.assertEqual( + result, + expected_result_at_infinity, + "Multiplying the point at infinity by any scalar should remain the point at infinity.", + ) + + # Reinitialize the elliptic curve with the same parameters as before. + curve = EllipticCurve( + p=13, # The prime number defining the finite field. + a=1, # The 'a' coefficient of the elliptic curve equation. + b=0, # The 'b' coefficient of the elliptic curve equation. + G=Point(x=2, y=1), # The generator point for the curve group. + n=4, # The order of the base point G. + h=0, # The cofactor. + ) + curve.__class__.use_projective_coordinates = False # The point at infinity, represented as a point with no coordinates. point_at_infinity = Point() diff --git a/tests/test_koblitz.py b/tests/test_koblitz.py index ba0822e..eae4a06 100644 --- a/tests/test_koblitz.py +++ b/tests/test_koblitz.py @@ -11,11 +11,37 @@ def setUp(self): self.encoder = Koblitz(curve_name="secp192k1") self.decoder = Koblitz(curve_name="secp192k1") - def test_encode_decode(self): + def test_encode_decode_unicode(self): """Validate that encoding and then decoding retrieves the original message.""" message = "Hello, EC!" - encoded_point, j = self.encoder.encode(message) - decoded_message = self.decoder.decode(encoded_point, j) + encoded_point, j = self.encoder.encode(message, alphabet_size=2**16) + decoded_message = self.decoder.decode(encoded_point, j, alphabet_size=2**16) self.assertEqual( message, decoded_message, "Decoded message should match the original." ) + + def test_encode_decode_ascii(self): + """Validate that encoding and then decoding retrieves the original message.""" + message = "Hello, EC!" + encoded_point, j = self.encoder.encode(message, alphabet_size=2**8) + decoded_message = self.decoder.decode(encoded_point, j, alphabet_size=2**8) + self.assertEqual( + message, decoded_message, "Decoded message should match the original." + ) + + def test_encode_lengthy_message(self): + """Test encoding and decoding a lengthy message using parallel processing.""" + self.encoder = Koblitz(curve_name="secp521r1") + self.decoder = Koblitz(curve_name="secp521r1") + lengthy_message = "Hello, Elliptic Curve Cryptography! " * 10 + encoded_messages = self.encoder.encode( + lengthy_message, alphabet_size=2**8, lengthy=True + ) + decoded_message = self.decoder.decode( + encoded_messages, alphabet_size=2**8, lengthy=True + ) + self.assertEqual( + lengthy_message, + decoded_message, + "Decoded message should match the original lengthy message.", + ) diff --git a/tests/test_massey_omura.py b/tests/test_massey_omura.py index 2872de7..eb1a5ca 100644 --- a/tests/test_massey_omura.py +++ b/tests/test_massey_omura.py @@ -40,3 +40,25 @@ def test_encryption_decryption(self): fully_decrypted_message, "Decrypted message should match the original one.", ) + + def test_point_multiplication(self): + """Validate the point multiplication with the private key.""" + private_key = 123456 # Define a test private key. + mo = MasseyOmura(private_key) # Initialize MasseyOmura instance. + + # Perform point multiplication using private key and generator point. + public_key = mo.public_key + expected_point = mo.curve.multiply_point(private_key, mo.curve.G) + + # Check if the resulting point is on the curve. + self.assertTrue( + mo.curve.is_point_on_curve(expected_point), + "The calculated public key should lie on the curve.", + ) + + # Validate that the public_key computed using `multiply_point` matches what we derived directly. + self.assertEqual( + public_key, + expected_point, + "The public key calculated does not match the expected point from multiplication.", + ) diff --git a/tests/test_utils.py b/tests/test_utils.py new file mode 100644 index 0000000..71a5772 --- /dev/null +++ b/tests/test_utils.py @@ -0,0 +1,56 @@ +import hashlib +import os +import tempfile +import unittest + +from ecutils.utils import calculate_file_hash + + +class TestFileHashing(unittest.TestCase): + + def generate_expected_hash(self, file_path, block_size=16384): + """Computes the expected SHA-256 hash using hashlib.""" + hash_sha256 = hashlib.sha256() + with open(file_path, "rb") as f: + for block in iter(lambda: f.read(block_size), b""): + hash_sha256.update(block) + return int(hash_sha256.hexdigest(), 16) + + def test_calculate_file_hash(self): + """Test the file hashing function with a temporary file.""" + # Create a temporary file + with tempfile.NamedTemporaryFile(delete=False) as temp_file: + file_path = temp_file.name + # Write some data to the file + temp_file.write(b"Sample data for hashing") + + try: + # Calculate the hash using the function being tested + calculated_hash = calculate_file_hash(file_path) + + # Calculate the expected hash manually + expected_hash = self.generate_expected_hash(file_path) + + # Compare the hashes + self.assertEqual( + calculated_hash, expected_hash, "The file hashes do not match!" + ) + finally: + # Clean up the temporary file + if os.path.exists(file_path): + os.remove(file_path) + + def test_file_not_found_error(self): + """Test the handling of FileNotFoundError in calculate_file_hash.""" + non_existent_file = "non_existent_file.txt" + + # Ensure the file does not exist + if os.path.exists(non_existent_file): + os.remove(non_existent_file) + + # Expecting a FileNotFoundError when trying to hash a non-existent file + with self.assertRaises(FileNotFoundError) as context: + calculate_file_hash(non_existent_file) + + # Check if the raised error message contains the correct file information + self.assertIn(f"File not found: {non_existent_file}", str(context.exception)) From 024939f2c4c6b072db46e9a84c534384cfa8e120 Mon Sep 17 00:00:00 2001 From: Isak Ruas Date: Thu, 3 Oct 2024 22:56:33 -0300 Subject: [PATCH 2/2] feature: update dependencies and cache config --- .github/workflows/pypi.yml | 5 ++++- CHANGELOG.md | 5 +++++ SECURITY.md | 1 + src/ecutils/__init__.py | 2 +- 4 files changed, 11 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pypi.yml b/.github/workflows/pypi.yml index 58ef647..071afb7 100644 --- a/.github/workflows/pypi.yml +++ b/.github/workflows/pypi.yml @@ -7,9 +7,12 @@ on: pull_request: branches: - master + types: + - closed jobs: build: + if: github.event.pull_request.merged == true runs-on: ubuntu-latest steps: @@ -39,4 +42,4 @@ jobs: permissions: id-token: write - contents: read + contents: read \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index a4aa70e..83d47f8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,11 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [v1.1.2] - 2024-10-03 + +### Changed +- Updated test and documentation dependencies, expanded test coverage for the code, and added the ability to configure LRU cache size via an environment variable. + ## [v1.1.1] - 2024-09-30 ### Security diff --git a/SECURITY.md b/SECURITY.md index 7fbe3d7..ef4fcf1 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -6,6 +6,7 @@ Use this section to tell people about which versions of your project are current | Version | Supported | | ------- | ------------------ | +| 1.1.2 | :white_check_mark: | | 1.1.1 | :white_check_mark: | | 1.1.0 | :white_check_mark: | | 1.0.0 | :white_check_mark: | diff --git a/src/ecutils/__init__.py b/src/ecutils/__init__.py index a82b376..72f26f5 100755 --- a/src/ecutils/__init__.py +++ b/src/ecutils/__init__.py @@ -1 +1 @@ -__version__ = "1.1.1" +__version__ = "1.1.2"