Skip to content

Commit 4a90d9a

Browse files
committed
add test coverage
1 parent 93f17aa commit 4a90d9a

13 files changed

+208
-22
lines changed

CHANGELOG.md

+11-4
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,15 @@
44
### Added
55
- Support for python3.13
66
- `uv` for package management
7-
- Typing for all modules in this package
7+
- Typing for all non C extension modules in this package and a corresponding `py.typed` marker
88
- Github Actions for testing and publishing
99
- Pre-commit hooks to enforce formatting and type checks
10+
- Functions in `fastedcsa.keys` specific to public and private keys:
11+
- `export_private_key`
12+
- `export_public_key`
13+
- `import_private_key`
14+
- `import_public_key`
15+
- `__self__` to `fastecdsa.curve.Curve`
1016

1117
### Changed
1218
- `fastecdsa.encodng.KeyEncoder` interface:
@@ -15,13 +21,14 @@
1521
- Decoders always take `bytes` for key data
1622
- Private key methods do not consume or return public key data in their signatures
1723
- Private key encoding _does_ take a curve parameter as some encoders do encode the public key as part of the encoded private key (in this case the public key is computed using the private key and the curve)
18-
-
1924

2025
### Removed
2126
- Support for python3.7
2227
- Support for python3.8
23-
- `int` tuples are no longer allowed to be passed as `Q` in `ecdsa.verify`. `Q` must be of type `point.Point` now.
24-
- `keys.export_key` and `keys.import_key`. Superseded by functions specific to public and private keys.
28+
- `int` tuples are no longer allowed to be passed as `Q` in `fastecdsa.ecdsa.verify`. `Q` must be of type `point.Point` now.
29+
- `fastecdsa.keys.export_key` and `fastecdsa.keys.import_key`. Superseded by functions specific to public and private keys.
30+
- Default curve parameter from `fastecdsa.point.Point` constructor
31+
- `__unicode__` from `fastecdsa.point.Point`
2532

2633
## [2.3.2]
2734
### Added

MANIFEST.in

+1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
include LICENSE
22
include README.rst
3+
include fastecdsa/py.typed
34
include src/_ecdsa.h
45
include src/curve.h
56
include src/curveMath.h

fastecdsa/curve.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -64,9 +64,12 @@ def __init__(
6464
if oid is not None:
6565
self._oid_lookup[oid] = self
6666

67-
def __repr__(self) -> str:
67+
def __str__(self) -> str:
6868
return self.name
6969

70+
def __repr__(self) -> str:
71+
return self.__str__()
72+
7073
@classmethod
7174
def get_curve_by_oid(cls, oid: bytes) -> Optional[Curve]:
7275
r"""Get a curve via its object identifier.

fastecdsa/ecdsa.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ def verify(
127127
def _hex_digest(msg: SignableMessage, hashfunc: HashFunction, prehashed: bool) -> str:
128128
if prehashed:
129129
if not isinstance(msg, (bytes, bytearray)):
130-
raise ValueError(f"Prehashed message must be bytes, got {type(msg)}")
130+
raise TypeError(f"Prehashed message must be bytes, got {type(msg)}")
131131
return hexlify(msg).decode()
132132
else:
133133
return hashfunc(msg_bytes(msg)).hexdigest()

fastecdsa/keys.py

+5-5
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
from hashlib import sha256
21
from os import urandom
3-
from typing import Optional, Tuple
2+
from typing import Any, Callable, Optional, Tuple
43

5-
from .curve import Curve, P256
4+
from .curve import Curve
65
from .ecdsa import verify
76
from .encoding import KeyEncoder
87
from .point import Point
8+
from .typing import EcdsaSignature
99
from .util import mod_sqrt, msg_bytes
1010

1111

@@ -29,7 +29,7 @@ def gen_keypair(curve: Curve) -> Tuple[int, Point]:
2929
return private_key, public_key
3030

3131

32-
def gen_private_key(curve: Curve, randfunc=urandom) -> int:
32+
def gen_private_key(curve: Curve, randfunc: Callable[[Any], bytes] = urandom) -> int:
3333
"""Generate a private key to sign data with.
3434
3535
The private key :math:`d` is an integer generated via a cryptographically secure random number
@@ -82,7 +82,7 @@ def get_public_key(d: int, curve: Curve) -> Point:
8282

8383

8484
def get_public_keys_from_sig(
85-
sig: Tuple[int, int], msg, curve: Curve = P256, hashfunc=sha256
85+
sig: EcdsaSignature, msg, curve: Curve, hashfunc: Callable
8686
) -> Tuple[Point, Point]:
8787
"""Recover the public keys that can verify a signature / message pair.
8888

fastecdsa/point.py

+2-5
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from __future__ import annotations
22

33
from fastecdsa import curvemath # type: ignore
4-
from .curve import Curve, P256
4+
from .curve import Curve
55

66

77
class CurveMismatchError(Exception):
@@ -20,7 +20,7 @@ class Point:
2020
| curve (:class:`Curve`): The curve that the point lies on.
2121
"""
2222

23-
def __init__(self, x: int, y: int, curve: Curve = P256):
23+
def __init__(self, x: int, y: int, curve: Curve):
2424
r"""Initialize a point on an elliptic curve.
2525
2626
The x and y parameters must satisfy the equation :math:`y^2 \equiv x^3 + ax + b \pmod{p}`,
@@ -54,9 +54,6 @@ def __str__(self) -> str:
5454
else:
5555
return f"X: 0x{self.x:x}\nY: 0x{self.y:x}\n(On curve <{self.curve}>)"
5656

57-
def __unicode__(self) -> str:
58-
return self.__str__()
59-
6057
def __repr__(self) -> str:
6158
return self.__str__()
6259

fastecdsa/py.typed

Whitespace-only changes.

pyproject.toml

+12-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "fastecdsa"
3-
version = "3.0.0a0"
3+
version = "3.0.0a1"
44
description = "Fast elliptic curve digital signatures"
55
readme = "README.rst"
66
requires-python = ">=3.9"
@@ -35,6 +35,16 @@ benchmark = "fastecdsa.benchmark:run"
3535
requires = ["setuptools>=75.6.0"]
3636
build-backend = "setuptools.build_meta"
3737

38+
[tool.coverage.run]
39+
omit = [
40+
"fastecdsa/benchmark.py",
41+
"fastecdsa/encoding/__init__.py"
42+
]
43+
44+
[tool.pytest.ini_options]
45+
pythonpath = ["."]
46+
addopts = "--cov=fastecdsa --cov-report=term-missing --cov-fail-under=95"
47+
3848
[tool.setuptools]
3949
include-package-data = true
4050

@@ -50,4 +60,5 @@ dev = [
5060
"setuptools>=75.6.0",
5161
"sphinx-rtd-theme>=3.0.2",
5262
"sphinx>=7.4.7",
63+
"pytest-cov>=6.0.0",
5364
]

tests/encoding/test_asn1.py

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
from unittest import TestCase
2+
3+
from fastecdsa.curve import Curve
4+
from fastecdsa.encoding.asn1 import asn1_oid
5+
6+
7+
class TestAsn1(TestCase):
8+
def test_asn1_oid(self):
9+
expected = b""
10+
11+
curve = Curve("", 0, 0, 0, 0, 0, 0)
12+
actual = asn1_oid(curve)
13+
14+
self.assertEqual(expected, actual)

tests/test_curve.py

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
from unittest import TestCase
2+
3+
from fastecdsa.curve import Curve
4+
5+
6+
class TestCurve(TestCase):
7+
def test_repr(self):
8+
expected = "Test Curve"
9+
10+
curve = Curve(expected, 0, 0, 0, 0, 0, 0)
11+
actual = str(curve)
12+
13+
self.assertEqual(expected, actual)

tests/test_p256_ecdsa.py

+12-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
from unittest import TestCase
33

44
from fastecdsa.curve import P256
5-
from fastecdsa.ecdsa import sign, verify
5+
from fastecdsa.ecdsa import EcdsaError, sign, verify
66
from fastecdsa.point import Point
77

88

@@ -97,3 +97,14 @@ def test_ecdsa_P256_verify(self):
9797
0x7D1FF961980F961BDAA3233B6209F4013317D3E3F9E1493592DBEAA1AF2BC367,
9898
)
9999
self.assertFalse(verify(sig, msg, Q, curve=P256, hashfunc=sha256))
100+
101+
def test_ecdsa_P256_invalid_Q(self):
102+
Q = P256.G
103+
Q.x = 0
104+
105+
with self.assertRaises(EcdsaError):
106+
verify((1, 1), "", Q)
107+
108+
def test_ecdsa_P256_invalid_prehashed_msg_type(self):
109+
with self.assertRaises(TypeError):
110+
sign("this is a str type", 0x10001, prehashed=True)

tests/test_point.py

+43-3
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,50 @@
11
from unittest import TestCase
22

3-
from fastecdsa.curve import W25519
4-
from fastecdsa.point import Point
3+
from fastecdsa.curve import P256, W25519
4+
from fastecdsa.point import CurveMismatchError, Point
55

66

7-
class TestTypeValidation(TestCase):
7+
class TestPoint(TestCase):
8+
def test_init_invalid_coordinates_point(self):
9+
with self.assertRaises(ValueError):
10+
Point(0, 1, P256)
11+
12+
def test_add_different_curves(self):
13+
with self.assertRaises(CurveMismatchError):
14+
P256.G + W25519.G
15+
16+
def test_str_reprs(self):
17+
expected = (
18+
"X: 0x6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296\n"
19+
"Y: 0x4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5\n"
20+
"(On curve <P256>)"
21+
)
22+
23+
self.assertEqual(expected, str(P256.G))
24+
self.assertEqual(expected, repr(P256.G))
25+
26+
self.assertEqual("<POINT AT INFINITY>", str(Point._identity_element()))
27+
self.assertEqual("<POINT AT INFINITY>", repr(Point._identity_element()))
28+
29+
def test_eq(self):
30+
self.assertTrue(P256.G == P256.G)
31+
self.assertFalse(P256.G == W25519.G)
32+
33+
with self.assertRaises(TypeError):
34+
P256.G == 2
35+
36+
def test_sub(self):
37+
value = P256.G - P256.G
38+
39+
self.assertTrue(value._is_identity())
40+
41+
def test_neg(self):
42+
value = Point._identity_element()
43+
44+
self.assertTrue((-value)._is_identity())
45+
46+
47+
class TestPointTypeValidation(TestCase):
848
def test_type_validation_add(self):
949
with self.assertRaises(TypeError):
1050
_ = Point._identity_element() + 2 # type: ignore

0 commit comments

Comments
 (0)