aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--pyecsca/ec/coordinates.py23
-rw-r--r--pyecsca/ec/formula.py26
-rw-r--r--pyecsca/ec/op.py17
-rw-r--r--pyecsca/misc/utils.py10
-rw-r--r--test/ec/test_configuration.py13
-rw-r--r--test/ec/test_formula.py6
-rw-r--r--test/ec/test_model.py13
-rw-r--r--test/ec/test_params.py6
-rw-r--r--test/ec/test_point.py12
9 files changed, 120 insertions, 6 deletions
diff --git a/pyecsca/ec/coordinates.py b/pyecsca/ec/coordinates.py
index 9b63c75..fe55d42 100644
--- a/pyecsca/ec/coordinates.py
+++ b/pyecsca/ec/coordinates.py
@@ -1,5 +1,6 @@
"""Provides a coordinate model class."""
from ast import parse, Module
+from astunparse import unparse
from importlib_resources.abc import Traversable
from importlib_resources import as_file
from typing import List, Any, MutableMapping
@@ -17,6 +18,7 @@ from .formula import (
ScalingEFDFormula,
NegationEFDFormula,
)
+from ..misc.utils import pexec
@public
@@ -52,6 +54,23 @@ class CoordinateModel:
def __repr__(self):
return f'{self.__class__.__name__}("{self.name}" on {self.curve_model.name})'
+ def __getstate__(self):
+ state = self.__dict__.copy()
+ state["satisfying"] = list(map(unparse, state["satisfying"]))
+ state["toaffine"] = list(map(unparse, state["toaffine"]))
+ state["tosystem"] = list(map(unparse, state["tosystem"]))
+ state["assumptions"] = list(map(unparse, state["assumptions"]))
+ state["neutral"] = list(map(unparse, state["neutral"]))
+ return state
+
+ def __setstate__(self, state):
+ state["satisfying"] = list(map(pexec, state["satisfying"]))
+ state["toaffine"] = list(map(pexec, state["toaffine"]))
+ state["tosystem"] = list(map(pexec, state["tosystem"]))
+ state["assumptions"] = list(map(pexec, state["assumptions"]))
+ state["neutral"] = list(map(pexec, state["neutral"]))
+ self.__dict__.update(state)
+
@public
class AffineCoordinateModel(CoordinateModel):
@@ -115,7 +134,9 @@ class EFDCoordinateModel(CoordinateModel):
"negation": NegationEFDFormula,
}
cls = formula_types.get(formula_type, EFDFormula)
- self.formulas[fpath.stem] = cls(fpath, fpath.with_suffix(".op3"), fpath.stem, self)
+ self.formulas[fpath.stem] = cls(
+ fpath, fpath.with_suffix(".op3"), fpath.stem, self
+ )
def __read_coordinates_file(self, file_path: Traversable):
with file_path.open("rb") as f:
diff --git a/pyecsca/ec/formula.py b/pyecsca/ec/formula.py
index ed3afb5..91e551b 100644
--- a/pyecsca/ec/formula.py
+++ b/pyecsca/ec/formula.py
@@ -18,6 +18,7 @@ from .error import UnsatisfiedAssumptionError, raise_unsatisified_assumption
from .mod import Mod, SymbolicMod
from .op import CodeOp, OpType
from ..misc.cfg import getconfig
+from ..misc.utils import peval
@public
@@ -31,7 +32,9 @@ class OpResult:
def __init__(self, name: str, value: Mod, op: OpType, *parents: Any):
if len(parents) != op.num_inputs:
- raise ValueError(f"Wrong number of parents ({len(parents)}) to OpResult: {op} ({op.num_inputs}).")
+ raise ValueError(
+ f"Wrong number of parents ({len(parents)}) to OpResult: {op} ({op.num_inputs})."
+ )
self.parents = tuple(parents)
self.name = name
self.value = value
@@ -156,7 +159,9 @@ class Formula(ABC):
# Validate assumptions and compute formula parameters.
# TODO: Should this also validate coordinate assumptions and compute their parameters?
is_symbolic = any(isinstance(x, SymbolicMod) for x in params.values())
- for assumption, assumption_string in zip(self.assumptions, self.assumptions_str):
+ for assumption, assumption_string in zip(
+ self.assumptions, self.assumptions_str
+ ):
lhs, rhs = assumption_string.split(" == ")
if lhs in params:
# Handle an assumption check on value of input points.
@@ -275,6 +280,15 @@ class Formula(ABC):
def __repr__(self):
return f"{self.__class__.__name__}({self.name} for {self.coordinate_model})"
+ def __getstate__(self):
+ state = self.__dict__.copy()
+ state["assumptions"] = list(map(unparse, state["assumptions"]))
+ return state
+
+ def __setstate__(self, state):
+ state["assumptions"] = list(map(peval, state["assumptions"]))
+ self.__dict__.update(state)
+
@property
@abstractmethod
def input_index(self):
@@ -340,7 +354,13 @@ class Formula(ABC):
class EFDFormula(Formula):
"""Formula from the [EFD]_."""
- def __init__(self, meta_path: Traversable, op3_path: Traversable, name: str, coordinate_model: Any):
+ def __init__(
+ self,
+ meta_path: Traversable,
+ op3_path: Traversable,
+ name: str,
+ coordinate_model: Any,
+ ):
self.name = name
self.coordinate_model = coordinate_model
self.meta = {}
diff --git a/pyecsca/ec/op.py b/pyecsca/ec/op.py
index 8841f2c..2a730e1 100644
--- a/pyecsca/ec/op.py
+++ b/pyecsca/ec/op.py
@@ -15,11 +15,13 @@ from ast import (
operator as ast_operator,
unaryop as ast_unaryop,
USub,
+ parse,
)
from enum import Enum
from types import CodeType
from typing import FrozenSet, cast, Any, Optional, Union, Tuple
+from astunparse import unparse
from public import public
from .mod import Mod
@@ -131,12 +133,23 @@ class CodeOp:
@property
def parents(self) -> Tuple[Union[str, int]]:
if self.operator in (OpType.Inv, OpType.Neg):
- return self.right, # type: ignore
+ return (self.right,) # type: ignore
elif self.operator in (OpType.Sqr, OpType.Id):
- return self.left, # type: ignore
+ return (self.left,) # type: ignore
else:
return self.left, self.right # type: ignore
+ def __getstate__(self):
+ state = self.__dict__.copy()
+ state["code"] = unparse(state["code"])
+ del state["compiled"]
+ return state
+
+ def __setstate__(self, state):
+ state["code"] = parse(state["code"], mode="exec")
+ state["compiled"] = compile(state["code"], "", mode="exec")
+ self.__dict__.update(state)
+
def __str__(self):
return f"{self.result} = {self.left if self.left is not None else ''}{self.operator.op_str}{self.right if self.right is not None else ''}"
diff --git a/pyecsca/misc/utils.py b/pyecsca/misc/utils.py
new file mode 100644
index 0000000..1e9e9a0
--- /dev/null
+++ b/pyecsca/misc/utils.py
@@ -0,0 +1,10 @@
+"""Just some utilities I promise."""
+from ast import parse
+
+
+def pexec(s):
+ return parse(s, mode="exec")
+
+
+def peval(s):
+ return parse(s, mode="eval")
diff --git a/test/ec/test_configuration.py b/test/ec/test_configuration.py
index bdd7d68..54cc53a 100644
--- a/test/ec/test_configuration.py
+++ b/test/ec/test_configuration.py
@@ -1,3 +1,5 @@
+import pickle
+
import pytest
from pyecsca.ec.configuration import (
@@ -57,3 +59,14 @@ def test_one(base_independents):
assert len(configs) == 1
configs = list(all_configurations(model=model, scalarmult=scalarmult, **base_independents))
assert len(configs) == 1
+
+
+def test_pickle(base_independents):
+ model = ShortWeierstrassModel()
+ coords = model.coordinates["projective"]
+ scalarmult = {"cls": LTRMultiplier, "add": coords.formulas["add-1998-cmo"], "dbl": coords.formulas["dbl-1998-cmo"],
+ "scl": None, "always": True, "complete": False, "short_circuit": True,
+ "accumulation_order": AccumulationOrder.PeqRP}
+ configs = list(all_configurations(model=model, coords=coords, scalarmult=scalarmult, **base_independents))
+ config = configs[0]
+ assert config == pickle.loads(pickle.dumps(config))
diff --git a/test/ec/test_formula.py b/test/ec/test_formula.py
index 420e0b3..bb3d123 100644
--- a/test/ec/test_formula.py
+++ b/test/ec/test_formula.py
@@ -1,3 +1,5 @@
+import pickle
+
import pytest
from sympy import FF, symbols
@@ -120,3 +122,7 @@ def test_symbolic(secp128r1, dbl):
inner_var, k(getattr(secp128r1.generator, inner_var).x)
)
assert Mod(int(symbolic_val), p) == Mod(generator_val, p)
+
+
+def test_pickle(add, dbl):
+ assert add == pickle.loads(pickle.dumps(add))
diff --git a/test/ec/test_model.py b/test/ec/test_model.py
index d5c5afe..e6bc176 100644
--- a/test/ec/test_model.py
+++ b/test/ec/test_model.py
@@ -1,3 +1,5 @@
+import pickle
+
from pyecsca.ec.model import (
ShortWeierstrassModel,
MontgomeryModel,
@@ -11,3 +13,14 @@ def test_load():
assert len(MontgomeryModel().coordinates) > 0
assert len(EdwardsModel().coordinates) > 0
assert len(TwistedEdwardsModel().coordinates) > 0
+
+
+def test_pickle():
+ sw = ShortWeierstrassModel()
+ m = MontgomeryModel()
+ e = EdwardsModel()
+ te = TwistedEdwardsModel()
+ assert sw == pickle.loads(pickle.dumps(sw))
+ assert m == pickle.loads(pickle.dumps(MontgomeryModel()))
+ assert e == pickle.loads(pickle.dumps(EdwardsModel()))
+ assert te == pickle.loads(pickle.dumps(TwistedEdwardsModel()))
diff --git a/test/ec/test_params.py b/test/ec/test_params.py
index ceaef48..2163684 100644
--- a/test/ec/test_params.py
+++ b/test/ec/test_params.py
@@ -1,3 +1,5 @@
+import pickle
+
from importlib_resources import files, as_file
import pytest
@@ -142,3 +144,7 @@ def test_custom_params():
assert params is not None
res = params.curve.affine_double(generator.to_affine())
assert res is not None
+
+
+def test_pickle(secp128r1):
+ assert secp128r1 == pickle.loads(pickle.dumps(secp128r1))
diff --git a/test/ec/test_point.py b/test/ec/test_point.py
index 7a0440c..9513840 100644
--- a/test/ec/test_point.py
+++ b/test/ec/test_point.py
@@ -1,3 +1,4 @@
+import pickle
from contextlib import nullcontext as does_not_raise
from pyecsca.ec.coordinates import AffineCoordinateModel
from pyecsca.ec.params import get_params
@@ -174,3 +175,14 @@ def test_iter(secp128r1, secp128r1_coords):
assert len(InfinityPoint(secp128r1_coords)) == 0
assert len(tuple(InfinityPoint(secp128r1_coords))) == 0
+
+
+def test_pickle(secp128r1, secp128r1_coords):
+ pt = Point(
+ secp128r1_coords,
+ X=Mod(0x4, secp128r1.curve.prime),
+ Y=Mod(0x6, secp128r1.curve.prime),
+ Z=Mod(2, secp128r1.curve.prime),
+ )
+ pickle.dumps(secp128r1_coords)
+ assert pt == pickle.loads(pickle.dumps(pt))