diff options
| -rw-r--r-- | pyecsca/ec/coordinates.py | 23 | ||||
| -rw-r--r-- | pyecsca/ec/formula.py | 26 | ||||
| -rw-r--r-- | pyecsca/ec/op.py | 17 | ||||
| -rw-r--r-- | pyecsca/misc/utils.py | 10 | ||||
| -rw-r--r-- | test/ec/test_configuration.py | 13 | ||||
| -rw-r--r-- | test/ec/test_formula.py | 6 | ||||
| -rw-r--r-- | test/ec/test_model.py | 13 | ||||
| -rw-r--r-- | test/ec/test_params.py | 6 | ||||
| -rw-r--r-- | test/ec/test_point.py | 12 |
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)) |
