aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile5
-rw-r--r--README.md22
-rw-r--r--docs/conf.py3
-rw-r--r--docs/index.rst2
-rw-r--r--pyecsca/ec/coordinates.py10
-rw-r--r--pyecsca/ec/curve.py50
-rw-r--r--pyecsca/ec/mult.py30
-rw-r--r--pyecsca/ec/op.py9
-rw-r--r--pyecsca/ec/point.py6
m---------pyecsca/ec/std0
-rw-r--r--setup.py2
11 files changed, 119 insertions, 20 deletions
diff --git a/Makefile b/Makefile
index 125f807..4c2d6c4 100644
--- a/Makefile
+++ b/Makefile
@@ -22,8 +22,11 @@ typecheck:
codestyle:
flake8 --ignore=E501,F405,F403,F401,E126 pyecsca
+doc-coverage:
+ interrogate -vv -nmps pyecsca
+
docs:
$(MAKE) -C docs apidoc
$(MAKE) -C docs html
-.PHONY: test test-plots test-all typecheck codestyle docs \ No newline at end of file
+.PHONY: test test-plots test-all typecheck codestyle doc-coverage docs \ No newline at end of file
diff --git a/README.md b/README.md
index accb6e7..4179742 100644
--- a/README.md
+++ b/README.md
@@ -8,11 +8,24 @@ For more info, see the [![docs](https://img.shields.io/badge/docs-neuromancer.sk
## Functionality
-*pyecsca* aims to fill a gap in SCA tooling for Elliptic Curve Cryptography, it focuses on
+**pyecsca** aims to fill a gap in SCA tooling for Elliptic Curve Cryptography, it focuses on
black-box implementations of ECC and presents a way to extract implementation information
-about a black-box implementation of ECC through side-channels. It is in an alpha stage of development
-and thus currently only provides basic trace processing capabilities (in the [*pyecsca.sca*](pyecsca/sca) package)
-and ECC simulation in the [*pyecsca.ec*](pyecsca/ec) package.
+about a black-box implementation of ECC through side-channels. The main goal of **pyecsca**
+is to be able to reverse engineer the curve model, coordinate system, addition formulas, scalar
+multiplier and even finite-field implementation details.
+
+It is currently in an alpha stage of development and thus only provides:
+ - Enumeration of millions of possible ECC implementation configurations (see [notebook/configuration_space](https://neuromancer.sk/pyecsca/notebook/configuration_space.html))
+ - Simulation and execution tracing of key generation, ECDH and ECDSA (see [notebook/simulation](https://neuromancer.sk/pyecsca/notebook/simulation.html))
+ - Synthesis of C implementations of ECC for embedded devices, given any implementation configuration (see [notebook/codegen](https://neuromancer.sk/pyecsca/notebook/codegen.html))
+ - Trace acquisition using PicoScope/ChipWhisperer oscilloscopes (see [notebook/measurement](https://neuromancer.sk/pyecsca/notebook/measurement.html))
+ - Trace processing capabilities, e.g. signal-processing, filtering, averaging, cutting, aligning ([pyecsca.sca](https://neuromancer.sk/pyecsca/api/pyecsca.sca.html))
+ - Trace visualization using holoviews and datashader (see [notebook/visualization](https://neuromancer.sk/pyecsca/notebook/visualization.html))
+
+**pyecsca** consists of three packages:
+ - the core: https://github.com/J08nY/pyecsca
+ - the codegen package: https://github.com/J08nY/pyecsca-codegen
+ - the notebook package: https://github.com/J08nY/pyecsca-notebook
## Requirements
@@ -28,6 +41,7 @@ and ECC simulation in the [*pyecsca.ec*](pyecsca/ec) package.
- [datashader](https://datashader.org)
- [matplotlib](https://matplotlib.org/)
- [xarray](https://xarray.pydata.org/en/stable/)
+ - [astunparse](https://astunparse.readthedocs.io/)
- **Optionally**:
- **Oscilloscope support:**
- [picosdk](https://github.com/picotech/picosdk-python-wrappers/)
diff --git a/docs/conf.py b/docs/conf.py
index 7f4489b..5ceee32 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -206,7 +206,8 @@ autodoc_default_options = {
"members": True,
"undoc-members": True,
"inherited-members": True,
- "show-inheritance": True
+ "show-inheritance": True,
+ "member-order": "bysource"
}
nbsphinx_allow_errors = True
diff --git a/docs/index.rst b/docs/index.rst
index 6052cf5..1ae054f 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -73,6 +73,7 @@ Requirements
- datashader_
- matplotlib_
- xarray_
+ - astunparse_
- **Optionally**:
- **Oscilloscope support:**
@@ -150,6 +151,7 @@ this support is very appreciated.
.. _bokeh: https://bokeh.org
.. _datashader: https://datashader.org
.. _xarray: https://xarray.pydata.org/en/stable/
+.. _astunparse: https://astunparse.readthedocs.io/
.. _picosdk: https://github.com/picotech/picosdk-python-wrappers/
.. _picoscope: https://github.com/colinoflynn/pico-python
.. _chipwhisperer: https://github.com/newaetech/chipwhisperer
diff --git a/pyecsca/ec/coordinates.py b/pyecsca/ec/coordinates.py
index 48a8f76..6e001a9 100644
--- a/pyecsca/ec/coordinates.py
+++ b/pyecsca/ec/coordinates.py
@@ -14,14 +14,24 @@ from .formula import (Formula, EFDFormula, AdditionEFDFormula, DoublingEFDFormul
class CoordinateModel(object):
"""A coordinate system for a particular model(form) of an elliptic curve."""
name: str
+ """Name of the coordinate model"""
full_name: str
+ """Full name."""
curve_model: Any
+ """The curve model."""
variables: List[str]
+ """Variables that the coordinate model uses."""
satisfying: List[Module]
+ """Relationship between the coordinate system and affine coordinates."""
parameters: List[str]
+ """Coordinate system parameters."""
assumptions: List[Module]
+ """Assumptions that need to hold for the curve to use this coordinate system,
+ also used to compute the values of the coordinate system parameters."""
neutral: List[Module]
+ """Coordinates of the neutral point in the coordinate system, might contain expressions of parameters."""
formulas: MutableMapping[str, Formula]
+ """Formulas available on the coordinate system."""
def __repr__(self):
return f"{self.__class__.__name__}(\"{self.name}\" on {self.curve_model.name})"
diff --git a/pyecsca/ec/curve.py b/pyecsca/ec/curve.py
index 44358d0..7351583 100644
--- a/pyecsca/ec/curve.py
+++ b/pyecsca/ec/curve.py
@@ -14,10 +14,15 @@ from .point import Point, InfinityPoint
class EllipticCurve(object):
"""An elliptic curve."""
model: CurveModel
+ """The model of the curve."""
coordinate_model: CoordinateModel
+ """The coordinate system of the curve."""
prime: int
+ """The prime specifying the base prime field of the curve."""
parameters: MutableMapping[str, Mod]
+ """The values of the parameters defining the curve, these cover the curve model and coordinate system parameters."""
neutral: Point
+ """The neutral point on the curve."""
def __init__(self, model: CurveModel, coordinate_model: CoordinateModel,
prime: int, neutral: Point, parameters: MutableMapping[str, Union[Mod, int]]):
@@ -59,6 +64,10 @@ class EllipticCurve(object):
"""
Add two affine points using the affine addition formula.
Handles the case of point at infinity gracefully.
+
+ :param one: One point.
+ :param other: Another point.
+ :return: The addition of the two points.
"""
if isinstance(one, InfinityPoint):
return other
@@ -72,6 +81,9 @@ class EllipticCurve(object):
"""
Double an affine point using the affine doubling formula.
Handles the case of point at infinity gracefully.
+
+ :param one: A point.
+ :return: The doubling of the point.
"""
if isinstance(one, InfinityPoint):
return one
@@ -81,6 +93,9 @@ class EllipticCurve(object):
"""
Negate an affine point using the affine negation formula.
Handles the case of point at infinity gracefully.
+
+ :param one: A point.
+ :return: The negation of the point.
"""
if isinstance(one, InfinityPoint):
return one
@@ -90,6 +105,10 @@ class EllipticCurve(object):
"""
Multiply an affine point by a scalar using the affine doubling and addition formulas.
Handles the case of point at infinity gracefully.
+
+ :param point: The point to multiply.
+ :param scalar: The scalar to use.
+ :return: The scalar multiplication of `point`.
"""
if isinstance(point, InfinityPoint):
return point
@@ -110,6 +129,7 @@ class EllipticCurve(object):
def affine_neutral(self) -> Optional[Point]:
"""
Get the neutral point in affine form, if it has one, otherwise `None`.
+
:return: The affine neutral point or `None`.
"""
if not self.neutral_is_affine:
@@ -129,11 +149,20 @@ class EllipticCurve(object):
return bool(self.model.base_neutral)
def is_neutral(self, point: Point) -> bool:
- """Check whether the point is the neutral point."""
+ """Check whether the point is the neutral point.
+
+ :param point: The point to test.
+ :return: Whether it is the neutral point.
+ """
return self.neutral == point
def is_on_curve(self, point: Point) -> bool:
- """Check whether the point is on the curve."""
+ """
+ Check whether the point is on the curve.
+
+ :param point: The point to test.
+ :return: Whether it is on the curve.
+ """
if point.coordinate_model.curve_model != self.model:
return False
if self.is_neutral(point):
@@ -142,12 +171,25 @@ class EllipticCurve(object):
return eval(compile(self.model.equation, "", mode="eval"), loc)
def to_affine(self) -> "EllipticCurve":
- """Convert this curve into the affine coordinate model, if possible."""
+ """
+ Convert this curve into the affine coordinate model, if possible.
+
+ :return: The transformed elliptic curve.
+ """
coord_model = AffineCoordinateModel(self.model)
return EllipticCurve(self.model, coord_model, self.prime, self.neutral.to_affine(), self.parameters) # type: ignore[arg-type]
def decode_point(self, encoded: bytes) -> Point:
- """Decode a point encoded as a sequence of bytes (ANSI X9.62)."""
+ """
+ Decode a point encoded as a sequence of bytes (ANSI X9.62). This decoding is the same as ANSI X9.63 for
+ the affine coordinate system and for others it only implements the uncompressed variant.
+
+ .. warning::
+ The point is not validated to be on the curve (if the uncompressed encoding is used).
+
+ :param encoded: The encoded representation of a point.
+ :return: The decoded point.
+ """
if encoded[0] == 0x00 and len(encoded) == 1:
return InfinityPoint(self.coordinate_model)
coord_len = (self.prime.bit_length() + 7) // 8
diff --git a/pyecsca/ec/mult.py b/pyecsca/ec/mult.py
index 7a8e23e..5804eef 100644
--- a/pyecsca/ec/mult.py
+++ b/pyecsca/ec/mult.py
@@ -27,6 +27,7 @@ class ScalarMultiplicationAction(ResultAction):
return f"{self.__class__.__name__}({self.point}, {self.scalar})"
+@public
class ScalarMultiplier(ABC):
"""
A scalar multiplication algorithm.
@@ -36,7 +37,9 @@ class ScalarMultiplier(ABC):
:param formulas: Formulas this instance will use.
"""
requires: ClassVar[Set[Type]] # Type[Formula] but mypy has a false positive
+ """The set of formulas that the multiplier requires."""
optionals: ClassVar[Set[Type]] # Type[Formula] but mypy has a false positive
+ """The optional set of formulas that the multiplier can use."""
short_circuit: bool
formulas: Mapping[str, Formula]
_params: DomainParameters
@@ -99,7 +102,15 @@ class ScalarMultiplier(ABC):
return self.formulas["neg"](point, **self._params.curve.parameters)[0]
def init(self, params: DomainParameters, point: Point):
- """Initialize the scalar multiplier with params and a point."""
+ """
+ Initialize the scalar multiplier with params and a point.
+
+ .. warning::
+ The point is not verified to be on the curve represented in the domain parameters.
+
+ :param params: The domain parameters to initialize the multiplier with.
+ :param point: The point to initialize the multiplier with.
+ """
coord_model = set(self.formulas.values()).pop().coordinate_model
if params.curve.coordinate_model != coord_model or point.coordinate_model != coord_model:
raise ValueError
@@ -109,14 +120,22 @@ class ScalarMultiplier(ABC):
@abstractmethod
def multiply(self, scalar: int) -> Point:
- """Multiply the point with the scalar."""
+ """
+ Multiply the point with the scalar.
+
+ .. note::
+ The multiplier needs to be initialized by a call to the :py:meth:`.init` method.
+
+ :param scalar: The scalar to use.
+ :return: The resulting multiple.
+ """
...
@public
class LTRMultiplier(ScalarMultiplier):
"""
- Classic double and add scalar multiplication algorithm, that scans the scalar left-to-right (msb to lsb)
+ Classic double and add scalar multiplication algorithm, that scans the scalar left-to-right (msb to lsb).
The `always` parameter determines whether the double and add always method is used.
"""
@@ -161,7 +180,7 @@ class LTRMultiplier(ScalarMultiplier):
@public
class RTLMultiplier(ScalarMultiplier):
"""
- Classic double and add scalar multiplication algorithm, that scans the scalar right-to-left (lsb to msb)
+ Classic double and add scalar multiplication algorithm, that scans the scalar right-to-left (lsb to msb).
The `always` parameter determines whether the double and add always method is used.
"""
@@ -195,6 +214,7 @@ class RTLMultiplier(ScalarMultiplier):
return action.exit(r)
+@public
class CoronMultiplier(ScalarMultiplier):
"""
Coron's double and add resistant against SPA, from:
@@ -407,7 +427,7 @@ class WindowNAFMultiplier(ScalarMultiplier):
self._points_neg = {}
current_point = point
double_point = self._dbl(point)
- for i in range(0, 2**(self.width - 2)):
+ for i in range(0, 2 ** (self.width - 2)):
self._points[2 * i + 1] = current_point
if self.precompute_negation:
self._points_neg[2 * i + 1] = self._neg(current_point)
diff --git a/pyecsca/ec/op.py b/pyecsca/ec/op.py
index d72716d..ee23e51 100644
--- a/pyecsca/ec/op.py
+++ b/pyecsca/ec/op.py
@@ -6,12 +6,12 @@ from typing import FrozenSet, cast, Any, Optional, Union
from public import public
-from .context import ResultAction
from .mod import Mod
@public
class OpType(Enum):
+ """A type of binary and unary operators."""
Add = (2, "+")
Sub = (2, "-")
Neg = (1, "-")
@@ -29,12 +29,19 @@ class OpType(Enum):
@public
class CodeOp(object):
+ """An operation that can be executed."""
result: str
+ """The result variable of the operation (e.g. the `r` in `r = 2*a`)."""
parameters: FrozenSet[str]
+ """The parameters used in the operation (e.g. `a`, `b`)."""
variables: FrozenSet[str]
+ """The variables used in the operation (e.g. `X1`, `Z2`)."""
code: Module
+ """The code of the operation."""
operator: OpType
+ """The operator type that executes in the operation."""
compiled: CodeType
+ """The compiled code of the operation."""
def __init__(self, code: Module):
self.code = code
diff --git a/pyecsca/ec/point.py b/pyecsca/ec/point.py
index 19c2483..62e4b05 100644
--- a/pyecsca/ec/point.py
+++ b/pyecsca/ec/point.py
@@ -4,7 +4,7 @@ from typing import Mapping, Any
from public import public
from .context import ResultAction
-from .coordinates import AffineCoordinateModel, CoordinateModel
+from .coordinates import AffineCoordinateModel, CoordinateModel, EFDCoordinateModel
from .mod import Mod, Undefined
from .op import CodeOp
@@ -97,11 +97,11 @@ class Point(object):
result[var] = locls[var]
elif var == "X":
result[var] = self.coords["x"]
- if coordinate_model.name == "inverted":
+ if isinstance(coordinate_model, EFDCoordinateModel) and coordinate_model.name == "inverted":
result[var] = result[var].inverse()
elif var == "Y":
result[var] = self.coords["y"]
- if coordinate_model.name == "inverted":
+ if isinstance(coordinate_model, EFDCoordinateModel) and coordinate_model.name == "inverted":
result[var] = result[var].inverse()
elif var.startswith("Z"):
result[var] = Mod(1, curve.prime)
diff --git a/pyecsca/ec/std b/pyecsca/ec/std
-Subproject 4af0d222a5de43f650efbb639cca6de6bb8cfe6
+Subproject 73397a4d61733da53e87415826f3133cf3ff8a0
diff --git a/setup.py b/setup.py
index 54c2cd1..9d0a2c3 100644
--- a/setup.py
+++ b/setup.py
@@ -47,7 +47,7 @@ setup(
"chipwhisperer": ["chipwhisperer"],
"smartcard": ["pyscard"],
"gmp": ["gmpy2"],
- "dev": ["mypy", "flake8"],
+ "dev": ["mypy", "flake8", "interrogate"],
"test": ["nose2", "parameterized", "green", "coverage"]
}
)