diff options
| author | Andrej Bátora | 2023-12-14 22:40:05 +0100 |
|---|---|---|
| committer | GitHub | 2023-12-14 22:40:05 +0100 |
| commit | 2b7bda7b57f0f66102cf92526ceee78f11ea29d4 (patch) | |
| tree | 92df965a2bc4afb5ea3d38ce69703cc4c39ae7d4 | |
| parent | c69aebde34d3bdf8a1ff789b3aa172040b59f1d7 (diff) | |
| parent | cee7e6b3196739b9ceaf12da3a11d5f5486b16bb (diff) | |
| download | pyecsca-2b7bda7b57f0f66102cf92526ceee78f11ea29d4.tar.gz pyecsca-2b7bda7b57f0f66102cf92526ceee78f11ea29d4.tar.zst pyecsca-2b7bda7b57f0f66102cf92526ceee78f11ea29d4.zip | |
Merge branch 'J08nY:master' into CPA_correlations_tracking
25 files changed, 2593 insertions, 721 deletions
@@ -1,14 +1,3 @@ -EC_TESTS = test.ec.test_context test.ec.test_configuration test.ec.test_curve test.ec.test_formula \ -test.ec.test_params test.ec.test_key_agreement test.ec.test_key_generation test.ec.test_mod test.ec.test_model \ -test.ec.test_mult test.ec.test_naf test.ec.test_op test.ec.test_point test.ec.test_signature test.ec.test_transformations test.ec.test_regress \ -test.ec.test_divpoly - -SCA_TESTS = test.sca.test_align test.sca.test_combine test.sca.test_edit test.sca.test_filter test.sca.test_match test.sca.test_process \ -test.sca.test_sampling test.sca.test_target test.sca.test_test test.sca.test_trace test.sca.test_traceset test.sca.test_plot test.sca.test_rpa \ -test.sca.test_zvp test.sca.test_stacked_combine test.sca.test_leakage_models - -TESTS = ${EC_TESTS} ${SCA_TESTS} - PERF_SCRIPTS = test.ec.perf_mod test.ec.perf_formula test.ec.perf_mult test.sca.perf_combine test.sca.perf_zvp test: @@ -49,7 +38,7 @@ perf-plots: python test/plots/plot_perf.py -d .perf doc-coverage: - interrogate -vv -nmps -e pyecsca/ec/std/.github/ -f 55 pyecsca + interrogate -c pyproject.toml -vv -nmps -f 55 pyecsca docs: $(MAKE) -C docs apidoc diff --git a/docs/references.rst b/docs/references.rst index de3beae..675a13a 100644 --- a/docs/references.rst +++ b/docs/references.rst @@ -16,3 +16,5 @@ References .. [CO2002] Jean-Sébastien Coron. Resistance against Differential Power Analysis for Elliptic Curve Cryptosystems, https://link.springer.com/chapter/10.1007/3-540-48059-5_25 .. [DJB02] D.J. Bernstein: Pippenger's Exponentiation Algorithm, https://cr.yp.to/papers/pippenger.pdf .. [SG14] Shay Gueron & Vlad Krasnov. Fast prime field elliptic-curve cryptography with 256-bit primes, https://link.springer.com/article/10.1007/s13389-014-0090-x +.. [B51] Andrew D. Booth. A signed binary multiplication technique. +.. [M61] O. L. Macsorley. High-speed arithmetic in binary computers. diff --git a/pyecsca/ec/coordinates.py b/pyecsca/ec/coordinates.py index 49452c7..8692809 100644 --- a/pyecsca/ec/coordinates.py +++ b/pyecsca/ec/coordinates.py @@ -7,8 +7,8 @@ from typing import List, Any, MutableMapping from public import public -from .formula import ( - Formula, +from .formula import Formula +from .formula.efd import ( EFDFormula, AdditionEFDFormula, DoublingEFDFormula, @@ -55,7 +55,9 @@ class CoordinateModel: return f"{self.curve_model.shortname}/{self.name}" def __repr__(self): - return f"{self.__class__.__name__}(\"{self.name}\", curve_model={self.curve_model})" + return ( + f'{self.__class__.__name__}("{self.name}", curve_model={self.curve_model})' + ) def __getstate__(self): state = self.__dict__.copy() diff --git a/pyecsca/ec/formula/__init__.py b/pyecsca/ec/formula/__init__.py new file mode 100644 index 0000000..b6efd8a --- /dev/null +++ b/pyecsca/ec/formula/__init__.py @@ -0,0 +1,4 @@ +"""""" + +from .base import * +from .efd import * diff --git a/pyecsca/ec/formula.py b/pyecsca/ec/formula/base.py index fff3210..733a82d 100644 --- a/pyecsca/ec/formula.py +++ b/pyecsca/ec/formula/base.py @@ -1,24 +1,22 @@ -"""Provides an abstract base class of a formula along with concrete instantiations.""" +"""Provides an abstract base class of a formula.""" from abc import ABC, abstractmethod -from ast import parse, Expression +from ast import Expression from functools import cached_property from astunparse import unparse -from itertools import product from typing import List, Set, Any, ClassVar, MutableMapping, Tuple, Union, Dict -from importlib_resources.abc import Traversable from public import public from sympy import FF, symbols, Poly, Rational, simplify -from ..misc.cache import sympify -from .context import ResultAction -from . import context -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 +from ..context import ResultAction +from .. import context +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 +from ...misc.cache import sympify @public @@ -235,7 +233,7 @@ class Formula(ABC): :param params: Parameters of the curve. :return: The resulting point(s). """ - from .point import Point + from ..point import Point self.__validate_params(field, params) self.__validate_points(field, points, params) @@ -351,93 +349,6 @@ class Formula(ABC): ) -class EFDFormula(Formula): - """Formula from the [EFD]_.""" - - def __init__( - self, - meta_path: Traversable, - op3_path: Traversable, - name: str, - coordinate_model: Any, - ): - self.name = name - self.coordinate_model = coordinate_model - self.meta = {} - self.parameters = [] - self.assumptions = [] - self.code = [] - self.unified = False - self.__read_meta_file(meta_path) - self.__read_op3_file(op3_path) - - def __read_meta_file(self, path: Traversable): - with path.open("rb") as f: - line = f.readline().decode("ascii").rstrip() - while line: - if line.startswith("source"): - self.meta["source"] = line[7:] - elif line.startswith("parameter"): - self.parameters.append(line[10:]) - elif line.startswith("assume"): - self.assumptions.append( - parse( - line[7:].replace("=", "==").replace("^", "**"), mode="eval" - ) - ) - elif line.startswith("unified"): - self.unified = True - line = f.readline().decode("ascii").rstrip() - - def __read_op3_file(self, path: Traversable): - with path.open("rb") as f: - for line in f.readlines(): - code_module = parse( - line.decode("ascii").replace("^", "**"), str(path), mode="exec" - ) - self.code.append(CodeOp(code_module)) - - def __str__(self): - return f"{self.coordinate_model!s}/{self.name}" - - @cached_property - def input_index(self): - return 1 - - @cached_property - def output_index(self): - return max(self.num_inputs + 1, 3) - - @cached_property - def inputs(self): - return { - var + str(i) - for var, i in product( - self.coordinate_model.variables, range(1, 1 + self.num_inputs) - ) - } - - @cached_property - def outputs(self): - return { - var + str(i) - for var, i in product( - self.coordinate_model.variables, - range(self.output_index, self.output_index + self.num_outputs), - ) - } - - def __eq__(self, other): - if not isinstance(other, EFDFormula): - return False - return ( - self.name == other.name and self.coordinate_model == other.coordinate_model - ) - - def __hash__(self): - return hash((self.coordinate_model, self.name)) - - @public class AdditionFormula(Formula, ABC): """Formula that adds two points.""" @@ -448,11 +359,6 @@ class AdditionFormula(Formula, ABC): @public -class AdditionEFDFormula(AdditionFormula, EFDFormula): - pass - - -@public class DoublingFormula(Formula, ABC): """Formula that doubles a point.""" @@ -462,11 +368,6 @@ class DoublingFormula(Formula, ABC): @public -class DoublingEFDFormula(DoublingFormula, EFDFormula): - pass - - -@public class TriplingFormula(Formula, ABC): """Formula that triples a point.""" @@ -476,11 +377,6 @@ class TriplingFormula(Formula, ABC): @public -class TriplingEFDFormula(TriplingFormula, EFDFormula): - pass - - -@public class NegationFormula(Formula, ABC): """Formula that negates a point.""" @@ -490,11 +386,6 @@ class NegationFormula(Formula, ABC): @public -class NegationEFDFormula(NegationFormula, EFDFormula): - pass - - -@public class ScalingFormula(Formula, ABC): """Formula that somehow scales the point (to a given representative of a projective class).""" @@ -504,11 +395,6 @@ class ScalingFormula(Formula, ABC): @public -class ScalingEFDFormula(ScalingFormula, EFDFormula): - pass - - -@public class DifferentialAdditionFormula(Formula, ABC): """ Differential addition formula that adds two points with a known difference. @@ -522,11 +408,6 @@ class DifferentialAdditionFormula(Formula, ABC): @public -class DifferentialAdditionEFDFormula(DifferentialAdditionFormula, EFDFormula): - pass - - -@public class LadderFormula(Formula, ABC): """ Ladder formula for simultaneous addition of two points and doubling of the one of them, with a known difference. @@ -539,8 +420,3 @@ class LadderFormula(Formula, ABC): shortname = "ladd" num_inputs = 3 num_outputs = 2 - - -@public -class LadderEFDFormula(LadderFormula, EFDFormula): - pass diff --git a/pyecsca/ec/formula/efd.py b/pyecsca/ec/formula/efd.py new file mode 100644 index 0000000..0156fb3 --- /dev/null +++ b/pyecsca/ec/formula/efd.py @@ -0,0 +1,142 @@ +"""""" +from functools import cached_property +from itertools import product + +from public import public + +from importlib_resources.abc import Traversable +from typing import Any +from .base import ( + Formula, + CodeOp, + AdditionFormula, + DoublingFormula, + TriplingFormula, + NegationFormula, + ScalingFormula, + DifferentialAdditionFormula, + LadderFormula, +) +from ast import parse + + +class EFDFormula(Formula): + """Formula from the [EFD]_.""" + + def __init__( + self, + meta_path: Traversable, + op3_path: Traversable, + name: str, + coordinate_model: Any, + ): + self.name = name + self.coordinate_model = coordinate_model + self.meta = {} + self.parameters = [] + self.assumptions = [] + self.code = [] + self.unified = False + self.__read_meta_file(meta_path) + self.__read_op3_file(op3_path) + + def __read_meta_file(self, path: Traversable): + with path.open("rb") as f: + line = f.readline().decode("ascii").rstrip() + while line: + if line.startswith("source"): + self.meta["source"] = line[7:] + elif line.startswith("parameter"): + self.parameters.append(line[10:]) + elif line.startswith("assume"): + self.assumptions.append( + parse( + line[7:].replace("=", "==").replace("^", "**"), mode="eval" + ) + ) + elif line.startswith("unified"): + self.unified = True + line = f.readline().decode("ascii").rstrip() + + def __read_op3_file(self, path: Traversable): + with path.open("rb") as f: + for line in f.readlines(): + code_module = parse( + line.decode("ascii").replace("^", "**"), str(path), mode="exec" + ) + self.code.append(CodeOp(code_module)) + + def __str__(self): + return f"{self.coordinate_model!s}/{self.name}" + + @cached_property + def input_index(self): + return 1 + + @cached_property + def output_index(self): + return max(self.num_inputs + 1, 3) + + @cached_property + def inputs(self): + return { + var + str(i) + for var, i in product( + self.coordinate_model.variables, range(1, 1 + self.num_inputs) + ) + } + + @cached_property + def outputs(self): + return { + var + str(i) + for var, i in product( + self.coordinate_model.variables, + range(self.output_index, self.output_index + self.num_outputs), + ) + } + + def __eq__(self, other): + if not isinstance(other, EFDFormula): + return False + return ( + self.name == other.name and self.coordinate_model == other.coordinate_model + ) + + def __hash__(self): + return hash((self.coordinate_model, self.name)) + + +@public +class AdditionEFDFormula(AdditionFormula, EFDFormula): + pass + + +@public +class DoublingEFDFormula(DoublingFormula, EFDFormula): + pass + + +@public +class TriplingEFDFormula(TriplingFormula, EFDFormula): + pass + + +@public +class NegationEFDFormula(NegationFormula, EFDFormula): + pass + + +@public +class ScalingEFDFormula(ScalingFormula, EFDFormula): + pass + + +@public +class DifferentialAdditionEFDFormula(DifferentialAdditionFormula, EFDFormula): + pass + + +@public +class LadderEFDFormula(LadderFormula, EFDFormula): + pass diff --git a/pyecsca/ec/formula/expand.py b/pyecsca/ec/formula/expand.py new file mode 100644 index 0000000..3265131 --- /dev/null +++ b/pyecsca/ec/formula/expand.py @@ -0,0 +1,51 @@ +from typing import List, Callable, Any +from public import public + +from . import Formula +from .efd import EFDFormula +from .fliparoo import recursive_fliparoo +from .graph import ModifiedEFDFormula +from .metrics import ivs_norm +from .partitions import reduce_all_adds, expand_all_muls, expand_all_nopower2_muls +from .switch_sign import generate_switched_formulas + + +def reduce_with_similarity(formulas: List[EFDFormula], norm): + efd = list(filter(lambda x: not isinstance(x, ModifiedEFDFormula), formulas)) + reduced_efd = efd + similarities = list(map(norm, efd)) + for formula in formulas: + n = norm(formula) + if n in similarities: + continue + similarities.append(n) + reduced_efd.append(formula) + return reduced_efd + + +@public +def expand_formula_list( + formulas: List[EFDFormula], norm: Callable[[Formula], Any] = ivs_norm +) -> List[EFDFormula]: + extended = reduce_with_similarity(formulas, norm) + + fliparood: List[EFDFormula] = sum(list(map(recursive_fliparoo, extended)), []) + extended.extend(fliparood) + extended = reduce_with_similarity(extended, norm) + + switch_signs: List[EFDFormula] = sum( + [list(generate_switched_formulas(f)) for f in extended], [] + ) + extended.extend(switch_signs) + extended = reduce_with_similarity(extended, norm) + + extended.extend(list(map(reduce_all_adds, extended))) + extended = reduce_with_similarity(extended, norm) + + extended.extend(list(map(expand_all_muls, extended))) + extended = reduce_with_similarity(extended, norm) + + extended.extend(list(map(expand_all_nopower2_muls, extended))) + extended = reduce_with_similarity(extended, norm) + + return extended diff --git a/pyecsca/ec/formula/fliparoo.py b/pyecsca/ec/formula/fliparoo.py new file mode 100644 index 0000000..c8d77ac --- /dev/null +++ b/pyecsca/ec/formula/fliparoo.py @@ -0,0 +1,378 @@ +from typing import Iterator, List, Tuple, Type, Optional +from ..op import OpType +from .graph import EFDFormulaGraph, Node, CodeOpNode, CodeOp, parse +from .efd import EFDFormula +from random import randint + + +class Fliparoo: + """ + Fliparoo is a chain of nodes N1->N2->...->Nk in EFDFormulaGraph for k>=2 such that: + - All Ni are * or All Ni are +/- + - For every Ni, except for Nk, the only outgoing node is Ni+1 + - Neither of N1,...,Nk-1 is an output node + """ + + nodes: List[CodeOpNode] + graph: EFDFormulaGraph + operator: Optional[OpType] + + def __init__(self, chain: List[CodeOpNode], graph: EFDFormulaGraph): + self.verify_chain(chain) + self.nodes = chain + self.graph = graph + self.operator = None + + def verify_chain(self, nodes: List[CodeOpNode]): + for i, node in enumerate(nodes[:-1]): + if node.outgoing_nodes != [nodes[i + 1]]: + raise BadFliparoo + if node.output_node: + raise BadFliparoo + + @property + def first(self): + return self.nodes[0] + + @property + def last(self): + return self.nodes[-1] + + def __len__(self): + return len(self.nodes) + + def __repr__(self): + return "->".join(map(lambda x: x.__repr__(), self.nodes)) + + def previous(self, node: CodeOpNode) -> Optional[CodeOpNode]: + i = self.nodes.index(node) + if i == 0: + return None + return self.nodes[i - 1] + + def __getitem__(self, i: int): + return self.nodes[i] + + def __iter__(self): + return iter(self.nodes) + + def __eq__(self, other): + return self.graph == other.graph and self.nodes == other.nodes + + def deepcopy(self): + ngraph = self.graph.deepcopy() + nchain = [mirror_node(node, self.graph, ngraph) for node in self.nodes] + return self.__class__(nchain, ngraph) + + def input_nodes(self) -> List[Node]: + input_nodes: List[Node] = [] + for node in self: + input_nodes.extend( + filter(lambda x: x not in self.nodes, node.incoming_nodes) + ) + return input_nodes + + def slice(self, i: int, j: int): + return self.__class__(self.nodes[i:j], self.graph) + + +class MulFliparoo(Fliparoo): + def __init__(self, chain: List[CodeOpNode], graph: EFDFormulaGraph): + super().__init__(chain, graph) + operations = set(node.op.operator for node in self.nodes) + if len(operations) != 1 or list(operations)[0] != OpType.Mult: + raise BadFliparoo + self.operator = OpType.Mult + + +class AddSubFliparoo(Fliparoo): + def __init__(self, chain: List[CodeOpNode], graph: EFDFormulaGraph): + super().__init__(chain, graph) + operations = set(node.op.operator for node in self.nodes) + if not operations.issubset([OpType.Add, OpType.Sub]): + raise BadFliparoo + + +class AddFliparoo(Fliparoo): + def __init__(self, chain: List[CodeOpNode], graph: EFDFormulaGraph): + super().__init__(chain, graph) + operations = set(node.op.operator for node in self.nodes) + if len(operations) != 1 or list(operations)[0] != OpType.Add: + raise BadFliparoo + self.operator = OpType.Add + + +class BadFliparoo(Exception): + pass + + +def find_fliparoos( + graph: EFDFormulaGraph, fliparoo_type: Optional[Type[Fliparoo]] = None +) -> List[Fliparoo]: + """Finds a list of Fliparoos in a graph""" + fliparoos: List[Fliparoo] = [] + for ilong_path in graph.find_all_paths(): + long_path = ilong_path[1:] # get rid of the input variables + fliparoo = largest_fliparoo(long_path, graph, fliparoo_type) # type: ignore + if fliparoo and fliparoo not in fliparoos: + fliparoos.append(fliparoo) + + # remove duplicities and fliparoos in inclusion + fliparoos = sorted(fliparoos, key=len, reverse=True) + longest_fliparoos: List[Fliparoo] = [] + for fliparoo in fliparoos: + if not is_subfliparoo(fliparoo, longest_fliparoos): + longest_fliparoos.append(fliparoo) + return longest_fliparoos + + +def is_subfliparoo(fliparoo: Fliparoo, longest_fliparoos: List[Fliparoo]) -> bool: + """Returns true if fliparoo is a part of any fliparoo in longest_fliparoos""" + for other_fliparoo in longest_fliparoos: + l1, l2 = len(fliparoo), len(other_fliparoo) + for i in range(l2 - l1): + if other_fliparoo.slice(i, i + l1) == fliparoo: + return True + return False + + +def largest_fliparoo( + chain: List[CodeOpNode], + graph: EFDFormulaGraph, + fliparoo_type: Optional[Type[Fliparoo]] = None, +) -> Optional[Fliparoo]: + """Finds the largest fliparoo in a list of Nodes""" + for i in range(len(chain) - 1): + for j in range(len(chain) - 1, i, -1): + subchain = chain[i : j + 1] + if fliparoo_type: + try: + fliparoo_type(subchain, graph) + except BadFliparoo: + continue + try: + return MulFliparoo(subchain, graph) + except BadFliparoo: + pass + try: + return AddSubFliparoo(subchain, graph) + except BadFliparoo: + pass + return None + + +class SignedNode: + + """ + Represents a summand in an expression X1-X2+X3+X4-X5... + Used for creating +/- Fliparoos + """ + + node: CodeOpNode + sign: int + + def __init__(self, node: CodeOpNode): + self.node = node + self.sign = 1 + + def __repr__(self): + s = {1: "+", -1: "-"}[self.sign] + return f"{s}{self.node.__repr__()}" + + +class SignedSubGraph: + """Subgraph of an EFDFormula graph with signed nodes""" + + def __init__(self, nodes: List[SignedNode], graph: EFDFormulaGraph): + self.nodes = nodes + self.supergraph = graph + + def add_node(self, node: SignedNode): + self.nodes.append(node) + + def remove_node(self, node: SignedNode): + self.nodes.remove(node) + + def change_signs(self): + for node in self.nodes: + node.sign *= -1 + + def __getitem__(self, i): + return self.nodes[i] + + @property + def components(self): + return len(self.nodes) + + def deepcopy(self): + sgraph = self.supergraph.deepcopy() + return SignedSubGraph( + [mirror_node(n, self.supergraph, sgraph) for n in self.nodes], sgraph + ) + + +def mirror_node(node, graph, graph_copy): + """Finds the corresponding node in a copy of the graph""" + if isinstance(node, SignedNode): + ns = SignedNode(graph_copy.nodes[graph.node_index(node.node)]) + ns.sign = node.sign + return ns + if isinstance(node, Node): + return graph_copy.nodes[graph.node_index(node)] + raise NotImplementedError + + +class DummyNode(Node): + def __repr__(self): + return "Dummy node" + + def label(self): + pass + + def result(self): + pass + + +def generate_fliparood_formulas( + formula: EFDFormula, rename: bool = True +) -> Iterator[EFDFormula]: + graph = EFDFormulaGraph(formula, rename) + fliparoos = find_fliparoos(graph) + for fliparoo in fliparoos: + for flip_graph in generate_fliparood_graphs(fliparoo): + yield flip_graph.to_EFDFormula() + + +def generate_fliparood_graphs(fliparoo: Fliparoo) -> Iterator[EFDFormulaGraph]: + fliparoo = fliparoo.deepcopy() + last_str = fliparoo.last.result + disconnect_fliparoo_outputs(fliparoo) + + signed_subgraph = extract_fliparoo_signed_inputs(fliparoo) + + # Starting with a single list of unconnected signed nodes + signed_subgraphs = [signed_subgraph] + for _ in range(signed_subgraph.components - 1): + subgraphs_updated = [] + for signed_subgraph in signed_subgraphs: + extended_subgraphs = combine_all_pairs_signed_nodes( + signed_subgraph, fliparoo + ) + subgraphs_updated.extend(extended_subgraphs) + signed_subgraphs = subgraphs_updated + + for signed_subgraph in signed_subgraphs: + graph = signed_subgraph.supergraph + assert signed_subgraph.components == 1 + final_signed_node = signed_subgraph.nodes[0] + if final_signed_node.sign != 1: + continue + final_node: CodeOpNode = final_signed_node.node + + opstr = f"{last_str} = {final_node.op.left}{final_node.optype.op_str}{final_node.op.right}" + final_node.op = CodeOp(parse(opstr)) + reconnect_fliparoo_outputs(graph, final_node) + graph.update() + yield graph + + +def extract_fliparoo_signed_inputs(fliparoo: Fliparoo) -> SignedSubGraph: + graph = fliparoo.graph + signed_inputs = SignedSubGraph([], graph) + for node in fliparoo: + prev = fliparoo.previous(node) + left, right = map(SignedNode, node.incoming_nodes) + if right.node != prev: + right.sign = -1 if node.is_sub else 1 + signed_inputs.add_node(right) + if left.node != prev: + if node.is_sub and right.node == prev: + signed_inputs.change_signs() + signed_inputs.add_node(left) + if prev: + graph.remove_node(prev) + graph.remove_node(fliparoo.last) + return signed_inputs + + +def disconnect_fliparoo_outputs(fliparoo: Fliparoo): + # remember positions of the last node with a DummyNode placeholder + dummy = DummyNode() + fliparoo.graph.add_node(dummy) + fliparoo.last.reconnect_outgoing_nodes(dummy) + + +def reconnect_fliparoo_outputs(graph: EFDFormulaGraph, last_node: Node): + dummy = next(filter(lambda x: isinstance(x, DummyNode), graph.nodes)) + dummy.reconnect_outgoing_nodes(last_node) + graph.remove_node(dummy) + assert not list(filter(lambda x: isinstance(x, DummyNode), graph.nodes)) + + +def combine_all_pairs_signed_nodes( + signed_subgraph: SignedSubGraph, fliparoo: Fliparoo +) -> List[SignedSubGraph]: + signed_subgraphs = [] + n_components = signed_subgraph.components + for i in range(n_components): + for j in range(i + 1, n_components): + csigned_subgraph = signed_subgraph.deepcopy() + v, w = csigned_subgraph[i], csigned_subgraph[j] + combine_signed_nodes(csigned_subgraph, v, w, fliparoo) + signed_subgraphs.append(csigned_subgraph) + return signed_subgraphs + + +def combine_signed_nodes( + subgraph: SignedSubGraph, + left_signed_node: SignedNode, + right_signed_node: SignedNode, + fliparoo: Fliparoo, +): + left_node, right_node = left_signed_node.node, right_signed_node.node + sign = 1 + operator = OpType.Mult + if isinstance(fliparoo, AddSubFliparoo): + s0, s1 = left_signed_node.sign, right_signed_node.sign + if s0 == 1: + operator = OpType.Add if s1 == 1 else OpType.Sub + + if s0 == -1 and s1 == 1: + operator = OpType.Sub + left_node, right_node = right_node, left_node + + # propagate the sign + if s0 == -1 and s1 == -1: + operator = OpType.Add + sign = -1 + + new_node = CodeOpNode.from_str( + f"Fliparoo{id(left_node)}_{id(operator)}_{id(sign)}_{id(right_node)}", left_node.result, operator, right_node.result + ) + new_node.incoming_nodes = [left_node, right_node] + left_node.outgoing_nodes.append(new_node) + right_node.outgoing_nodes.append(new_node) + subgraph.supergraph.add_node(new_node) + new_node = SignedNode(new_node) + new_node.sign = sign + subgraph.remove_node(left_signed_node) + subgraph.remove_node(right_signed_node) + subgraph.add_node(new_node) + + +def recursive_fliparoo(formula, depth=2): + all_fliparoos = {0: [formula]} + counter = 0 + while depth > counter: + prev_level = all_fliparoos[counter] + fliparoo_level = [] + for flipparood_formula in prev_level: + rename = not counter # rename ivs before the first fliparoo + for newly_fliparood in generate_fliparood_formulas( + flipparood_formula, rename + ): + fliparoo_level.append(newly_fliparood) + counter += 1 + all_fliparoos[counter] = fliparoo_level + + return sum(all_fliparoos.values(), []) diff --git a/pyecsca/ec/formula/graph.py b/pyecsca/ec/formula/graph.py new file mode 100644 index 0000000..1473858 --- /dev/null +++ b/pyecsca/ec/formula/graph.py @@ -0,0 +1,436 @@ +from .efd import ( + EFDFormula, + DoublingEFDFormula, + AdditionEFDFormula, + LadderEFDFormula, + DifferentialAdditionEFDFormula, +) +from ..op import CodeOp, OpType +import matplotlib.pyplot as plt +import networkx as nx +from ast import parse +from typing import Dict, List, Tuple, Set, Optional, MutableMapping, Any +from copy import deepcopy +from abc import ABC, abstractmethod + + +class Node(ABC): + def __init__(self): + self.incoming_nodes = [] + self.outgoing_nodes = [] + self.output_node = False + self.input_node = False + + @property + @abstractmethod + def label(self) -> str: + pass + + @property + @abstractmethod + def result(self) -> str: + pass + + @property + def is_sub(self) -> bool: + return False + + @property + def is_mul(self) -> bool: + return False + + @property + def is_add(self) -> bool: + return False + + @property + def is_id(self) -> bool: + return False + + @property + def is_sqr(self) -> bool: + return False + + @property + def is_pow(self) -> bool: + return False + + @property + def is_inv(self) -> bool: + return False + + @property + def is_div(self) -> bool: + return False + + @property + def is_neg(self) -> bool: + return False + + @abstractmethod + def __repr__(self) -> str: + pass + + def reconnect_outgoing_nodes(self, destination): + destination.output_node = self.output_node + for out in self.outgoing_nodes: + out.incoming_nodes = [ + n if n != self else destination for n in out.incoming_nodes + ] + destination.outgoing_nodes.append(out) + + +class ConstantNode(Node): + color = "#b41f44" + + def __init__(self, i: int): + super().__init__() + self.value = i + + @property + def label(self) -> str: + return str(self.value) + + @property + def result(self) -> str: + return str(self.value) + + def __repr__(self) -> str: + return f"Node({self.value})" + + +class CodeOpNode(Node): + color = "#1f78b4" + + def __init__(self, op: CodeOp): + super().__init__() + self.op = op + assert self.op.operator in [ + OpType.Sub, + OpType.Add, + OpType.Id, + OpType.Sqr, + OpType.Mult, + OpType.Div, + OpType.Pow, + OpType.Inv, + OpType.Neg, + ], self.op.operator + + @classmethod + def from_str(cls, result: str, left, operator, right): + opstr = f"{result} = {left if left is not None else ''}{operator.op_str}{right if right is not None else ''}" + return cls(CodeOp(parse(opstr.replace("^", "**")))) + + @property + def label(self) -> str: + return f"{self.op.result}:{self.op.operator.op_str}" + + @property + def result(self) -> str: + return str(self.op.result) + + @property + def optype(self) -> OpType: + return self.op.operator + + @property + def is_sub(self) -> bool: + return self.optype == OpType.Sub + + @property + def is_mul(self) -> bool: + return self.optype == OpType.Mult + + @property + def is_add(self) -> bool: + return self.optype == OpType.Add + + @property + def is_id(self) -> bool: + return self.optype == OpType.Id + + @property + def is_sqr(self) -> bool: + return self.optype == OpType.Sqr + + @property + def is_pow(self) -> bool: + return self.optype == OpType.Pow + + @property + def is_inv(self) -> bool: + return self.optype == OpType.Inv + + @property + def is_div(self) -> bool: + return self.optype == OpType.Div + + @property + def is_neg(self) -> bool: + return self.optype == OpType.Neg + + def __repr__(self) -> str: + return f"Node({self.op})" + + +class InputNode(Node): + color = "#b41f44" + + def __init__(self, input: str): + super().__init__() + self.input = input + self.input_node = True + + @property + def label(self) -> str: + return self.input + + @property + def result(self) -> str: + return self.input + + def __repr__(self) -> str: + return f"Node({self.input})" + + +def formula_input_variables(formula: EFDFormula) -> List[str]: + return ( + list(formula.inputs) + + formula.parameters + + formula.coordinate_model.curve_model.parameter_names + ) + + +# temporary solution +class ModifiedEFDFormula(EFDFormula): + def __eq__(self, other): + if not isinstance(other, ModifiedEFDFormula): + return False + return ( + self.name == other.name and self.coordinate_model == other.coordinate_model and self.code == other.code + ) + + +class ModifiedDoublingEFDFormula(DoublingEFDFormula, ModifiedEFDFormula): + pass + + +class ModifiedAdditionEFDFormula(AdditionEFDFormula, ModifiedEFDFormula): + pass + + +class ModifiedDifferentialAdditionEFDFormula( + DifferentialAdditionEFDFormula, ModifiedEFDFormula +): + pass + + +class ModifiedLadderEFDFormula(LadderEFDFormula, ModifiedEFDFormula): + pass + + +class EFDFormulaGraph: + nodes: List[Node] + input_nodes: MutableMapping[str, InputNode] + output_names: Set[str] + roots: List[Node] + coordinate_model: Any + + def __init__(self, formula: EFDFormula, rename=True): + self._formula = formula # TODO remove, its here only for to_EFDFormula + self.coordinate_model = formula.coordinate_model + self.output_names = formula.outputs + self.input_nodes = {v: InputNode(v) for v in formula_input_variables(formula)} + self.roots = list(self.input_nodes.values()) + self.nodes = self.roots.copy() + discovered_nodes: Dict[str, Node] = self.input_nodes.copy() # type: ignore + constants: Dict[int, Node] = {} + for op in formula.code: + code_node = CodeOpNode(op) + for side in (op.left, op.right): + if side is None: + continue + if isinstance(side, int): + if side in constants: + parent_node = constants[side] + else: + parent_node = ConstantNode(side) + self.nodes.append(parent_node) + self.roots.append(parent_node) + else: + parent_node = discovered_nodes[side] + parent_node.outgoing_nodes.append(code_node) + code_node.incoming_nodes.append(parent_node) + self.nodes.append(code_node) + discovered_nodes[op.result] = code_node + # flag output nodes + for output_name in self.output_names: + discovered_nodes[output_name].output_node = True + + # go through the nodes and make sure that every node is root or has parents + for node in self.nodes: + if not node.incoming_nodes and node not in self.roots: + self.roots.append(node) + if rename: + self.reindex() + + def node_index(self, node: Node) -> int: + return self.nodes.index(node) + + def deepcopy(self): + return deepcopy(self) + + def to_EFDFormula(self) -> ModifiedEFDFormula: + # TODO rewrite + new_graph = deepcopy(self) + new_formula = new_graph._formula + new_formula.code = list( + map( + lambda x: x.op, # type: ignore + filter(lambda n: n not in new_graph.roots, new_graph.nodes), + ) + ) + casting = { + AdditionEFDFormula: ModifiedAdditionEFDFormula, + DoublingEFDFormula: ModifiedDoublingEFDFormula, + DifferentialAdditionEFDFormula: ModifiedDifferentialAdditionEFDFormula, + LadderEFDFormula: ModifiedLadderEFDFormula, + } + if new_formula.__class__ not in set(casting.values()): + new_formula.__class__ = casting[new_formula.__class__] + return new_formula # type: ignore + + def networkx_graph(self) -> nx.DiGraph: + graph = nx.DiGraph() + vertices = list(range(len(self.nodes))) + graph.add_nodes_from(vertices) + stack = self.roots.copy() + while stack: + node = stack.pop() + for out in node.outgoing_nodes: + stack.append(out) + graph.add_edge(self.node_index(node), self.node_index(out)) + return graph + + def levels(self) -> List[List[Node]]: + stack = self.roots.copy() + levels = [(n, 0) for n in stack] + level_counter = 1 + while stack: + tmp_stack = [] + while stack: + node = stack.pop() + levels.append((node, level_counter)) + for out in node.outgoing_nodes: + tmp_stack.append(out) + stack = tmp_stack + level_counter += 1 + # separate into lists + + level_lists: List[List[Node]] = [[] for _ in range(level_counter)] + discovered = [] + for node, l in reversed(levels): + if node not in discovered: + level_lists[l].append(node) + discovered.append(node) + return level_lists + + def output_nodes(self) -> List[Node]: + return list(filter(lambda x: x.output_node, self.nodes)) + + def planar_positions(self) -> Dict[int, Tuple[float, float]]: + positions = {} + for i, level in enumerate(self.levels()): + for j, node in enumerate(level): + positions[self.node_index(node)] = ( + 0.1 * float(i**2) + 3 * float(j), + -6 * float(i), + ) + return positions + + def draw(self, filename: Optional[str] = None, figsize: Tuple[int, int] = (12, 12)): + gnx = self.networkx_graph() + pos = nx.rescale_layout_dict(self.planar_positions()) + plt.figure(figsize=figsize) + colors = [self.nodes[n].color for n in gnx.nodes] + labels = {n: self.nodes[n].label for n in gnx.nodes} + nx.draw(gnx, pos, node_color=colors, node_size=2000, labels=labels) + if filename: + plt.savefig(filename) + plt.close() + else: + plt.show() + + def find_all_paths(self) -> List[List[Node]]: + gnx = self.networkx_graph() + index_paths = [] + for u in self.roots: + iu = self.node_index(u) + for v in self.output_nodes(): + iv = self.node_index(v) + index_paths.extend(nx.all_simple_paths(gnx, iu, iv)) + paths = [] + for p in index_paths: + paths.append([self.nodes[v] for v in p]) + return paths + + def reorder(self): + self.nodes = sum(self.levels(), []) + + def remove_node(self, node): + self.nodes.remove(node) + if node in self.roots: + self.roots.remove(node) + for in_node in node.incoming_nodes: + in_node.outgoing_nodes = list( + filter(lambda x: x != node, in_node.outgoing_nodes) + ) + for out_node in node.outgoing_nodes: + out_node.incoming_nodes = list( + filter(lambda x: x != node, out_node.incoming_nodes) + ) + + def add_node(self, node): + if isinstance(node, ConstantNode): + self.roots.append(node) + self.nodes.append(node) + + def reindex(self): + results: Dict[str, str] = {} + counter = 0 + for node in self.nodes: + if not isinstance(node, CodeOpNode): + continue + op = node.op + result, left, operator, right = ( + op.result, + op.left, + op.operator.op_str, + op.right, + ) + if left in results: + left = results[left] + if right in results: + right = results[right] + if not node.output_node: + new_result = f"iv{counter}" + counter += 1 + else: + new_result = result + opstr = f"{new_result} = {left if left is not None else ''}{operator}{right if right is not None else ''}" + results[result] = new_result + node.op = CodeOp(parse(opstr.replace("^", "**"))) + + def update(self): + self.reorder() + self.reindex() + + def print(self): + for node in self.nodes: + print(node) + + +def rename_ivs(formula: EFDFormula): + graph = EFDFormulaGraph(formula) + return graph.to_EFDFormula() diff --git a/pyecsca/ec/formula/metrics.py b/pyecsca/ec/formula/metrics.py new file mode 100644 index 0000000..a0e42eb --- /dev/null +++ b/pyecsca/ec/formula/metrics.py @@ -0,0 +1,100 @@ +from public import public +from ...sca.re.zvp import unroll_formula +from .base import Formula +import warnings +from typing import Dict +from operator import itemgetter, attrgetter +from ..curve import EllipticCurve +from ..context import DefaultContext, local + + +@public +def formula_ivs(formula: Formula): + one_unroll = unroll_formula(formula) + one_results = {} + for name, value in one_unroll: + if name in formula.outputs: + one_results[name] = value + one_polys = set(map(itemgetter(1), one_unroll)) + return one_polys, set(one_results.values()) + + +@public +def ivs_norm(one: Formula): + return formula_ivs(one)[0] + + +@public +def formula_similarity(one: Formula, other: Formula) -> Dict[str, float]: + if one.coordinate_model != other.coordinate_model: + warnings.warn("Mismatched coordinate model.") + + one_polys, one_result_polys = formula_ivs(one) + other_polys, other_result_polys = formula_ivs(other) + return { + "output": len(one_result_polys.intersection(other_result_polys)) + / max(len(one_result_polys), len(other_result_polys)), + "ivs": len(one_polys.intersection(other_polys)) + / max(len(one_polys), len(other_polys)), + } + + +@public +def formula_similarity_abs(one: Formula, other: Formula) -> Dict[str, float]: + if one.coordinate_model != other.coordinate_model: + warnings.warn("Mismatched coordinate model.") + + one_polys, one_result_polys = formula_ivs(one) + other_polys, other_result_polys = formula_ivs(other) + + one_polys = set([f if f.LC() > 0 else -f for f in one_polys]) + other_polys = set([f if f.LC() > 0 else -f for f in other_polys]) + + one_result_polys = set([f if f.LC() > 0 else -f for f in one_result_polys]) + other_result_polys = set([f if f.LC() > 0 else -f for f in other_result_polys]) + return { + "output": len(one_result_polys.intersection(other_result_polys)) + / max(len(one_result_polys), len(other_result_polys)), + "ivs": len(one_polys.intersection(other_polys)) + / max(len(one_polys), len(other_polys)), + } + + +@public +def formula_similarity_fuzz( + one: Formula, other: Formula, curve: EllipticCurve, samples: int = 1000 +) -> Dict[str, float]: + if one.coordinate_model != other.coordinate_model: + raise ValueError("Mismatched coordinate model.") + + output_matches = 0.0 + iv_matches = 0.0 + for _ in range(samples): + Paff = curve.affine_random() + Qaff = curve.affine_random() + Raff = curve.affine_add(Paff, Qaff) + P = Paff.to_model(one.coordinate_model, curve) + Q = Qaff.to_model(one.coordinate_model, curve) + R = Raff.to_model(one.coordinate_model, curve) + inputs = (P, Q, R)[: one.num_inputs] + with local(DefaultContext()) as ctx: + res_one = one(curve.prime, *inputs, **curve.parameters) + action_one = ctx.actions.get_by_index([0]) + ivs_one = set( + map(attrgetter("value"), sum(action_one[0].intermediates.values(), [])) + ) + with local(DefaultContext()) as ctx: + res_other = other(curve.prime, *inputs, **curve.parameters) + action_other = ctx.actions.get_by_index([0]) + ivs_other = set( + map(attrgetter("value"), sum(action_other[0].intermediates.values(), [])) + ) + iv_matches += len(ivs_one.intersection(ivs_other)) / max( + len(ivs_one), len(ivs_other) + ) + one_coords = set(res_one) + other_coords = set(res_other) + output_matches += len(one_coords.intersection(other_coords)) / max( + len(one_coords), len(other_coords) + ) + return {"output": output_matches / samples, "ivs": iv_matches / samples} diff --git a/pyecsca/ec/formula/partitions.py b/pyecsca/ec/formula/partitions.py new file mode 100644 index 0000000..9ea108c --- /dev/null +++ b/pyecsca/ec/formula/partitions.py @@ -0,0 +1,378 @@ +from typing import List, Any, Generator +from ast import parse +from ..op import OpType, CodeOp +from .graph import ( + EFDFormulaGraph, + CodeOpNode, + ConstantNode, + Node, +) +from .fliparoo import find_fliparoos, AddFliparoo, MulFliparoo +from copy import deepcopy +from .efd import EFDFormula + + +def reduce_all_adds(formula: EFDFormula, rename=True) -> EFDFormula: + graph = EFDFormulaGraph(formula, rename=rename) + add_fliparoos = find_single_input_add_fliparoos(graph) + for add_fliparoo in add_fliparoos: + reduce_add_fliparoo(add_fliparoo, copy=False) + reduce_all_XplusX(graph) + mul_fliparoos = find_constant_mul_fliparoos(graph) + for mul_fliparoo in mul_fliparoos: + reduce_mul_fliparoo(mul_fliparoo, copy=False) + return graph.to_EFDFormula() + + +def expand_all_muls(formula: EFDFormula, rename=True) -> EFDFormula: + graph = EFDFormulaGraph(formula, rename) + enodes = find_expansion_nodes(graph) + for enode in enodes: + expand_mul(graph, enode, copy=False) + return graph.to_EFDFormula() + + +def expand_all_nopower2_muls(formula: EFDFormula, rename=True) -> EFDFormula: + graph = EFDFormulaGraph(formula, rename) + enodes = find_expansion_nodes(graph, nopower2=True) + for enode in enodes: + expand_mul(graph, enode, copy=False) + return graph.to_EFDFormula() + + +def find_single_input_add_fliparoos(graph: EFDFormulaGraph) -> List[AddFliparoo]: + fliparoos = find_fliparoos(graph, AddFliparoo) + single_input_fliparoos = [] + for fliparoo in fliparoos: + found = False + for i in range(len(fliparoo), 1, -1): + subfliparoo = fliparoo.slice(0, i) + if len(set(subfliparoo.input_nodes())) == 1: + found = True + break + if found: + s = subfliparoo.slice(0, i) + single_input_fliparoos.append(s) + return single_input_fliparoos + + +def find_constant_mul_fliparoos(graph: EFDFormulaGraph) -> List[MulFliparoo]: + fliparoos = find_fliparoos(graph, MulFliparoo) + constant_mul_fliparoo = [] + for fliparoo in fliparoos: + found = False + for i in range(len(fliparoo), 1, -1): + subfliparoo = fliparoo.slice(0, i) + nonconstant_inputs = list( + filter( + lambda x: not isinstance(x, ConstantNode), subfliparoo.input_nodes() + ) + ) + if len(nonconstant_inputs) != 1: + continue + inode = nonconstant_inputs[0] + if inode not in fliparoo.first.incoming_nodes: + continue + if not sum( + 1 + for node in fliparoo.first.incoming_nodes + if isinstance(node, ConstantNode) + ): + continue + found = True + break + if found: + s = subfliparoo.slice(0, i) + constant_mul_fliparoo.append(s) + return constant_mul_fliparoo + + +def find_expansion_nodes(graph: EFDFormulaGraph, nopower2=False) -> List[Node]: + expansion_nodes: List[Node] = [] + for node in graph.nodes: + if not isinstance(node, CodeOpNode) or not node.is_mul: + continue + for par in node.incoming_nodes: + if isinstance(par, ConstantNode): + if nopower2 and is_power_of_2(par.value): + continue + expansion_nodes.append(node) + break + return expansion_nodes + + +def is_power_of_2(n: int) -> bool: + while n > 1: + if n & 1 == 1: + return False + n >>= 1 + return True + + +def reduce_all_XplusX(graph: EFDFormulaGraph): + adds = find_all_XplusX(graph) + for node in adds: + reduce_XplusX(graph, node) + graph.update() + + +def find_all_XplusX(graph) -> List[CodeOpNode]: + adds = [] + for node in graph.nodes: + if not isinstance(node, CodeOpNode) or not node.is_add: + continue + if node.incoming_nodes[0] == node.incoming_nodes[1]: + adds.append(node) + return adds + + +def reduce_XplusX(graph: EFDFormulaGraph, node: CodeOpNode): + inode = node.incoming_nodes[0] + const_node = ConstantNode(2) + node.incoming_nodes[1] = const_node + const_node.outgoing_nodes = [node] + graph.add_node(const_node) + inode.outgoing_nodes = list(filter(lambda x: x != node, inode.outgoing_nodes)) + inode.outgoing_nodes.append(node) + opstr = f"{node.result} = {inode.result}{OpType.Mult.op_str}{const_node.value}" + node.op = CodeOp(parse(opstr)) + + +def reduce_mul_fliparoo(fliparoo: MulFliparoo, copy=True): + if copy: + fliparoo = fliparoo.deepcopy() + + first, last = fliparoo.first, fliparoo.last + inode = next( + filter(lambda x: not isinstance(x, ConstantNode), first.incoming_nodes) + ) + const_nodes: List[ConstantNode] = [node for node in fliparoo.input_nodes() if isinstance(node, ConstantNode)] + sum_const_node = ConstantNode(sum(v.value for v in const_nodes)) + fliparoo.graph.add_node(sum_const_node) + + inode.outgoing_nodes = [n if n != first else last for n in inode.outgoing_nodes] + last.incoming_nodes = [inode, sum_const_node] + sum_const_node.outgoing_nodes = [last] + + opstr = f"{last.result} = {inode.result}{OpType.Mult.op_str}{sum_const_node.value}" + last.op = CodeOp(parse(opstr)) + + for node in fliparoo: + if node == last: + continue + fliparoo.graph.remove_node(node) + + for node in const_nodes: + if not node.outgoing_nodes: + fliparoo.graph.remove_node(node) + + fliparoo.graph.update() + + return fliparoo.graph + + +def reduce_add_fliparoo(fliparoo: AddFliparoo, copy=True): + if copy: + fliparoo = fliparoo.deepcopy() + first, last = fliparoo.first, fliparoo.last + par = first.incoming_nodes[0] + const_node = ConstantNode(len(fliparoo) + 1) + fliparoo.graph.add_node(const_node) + mul_node = CodeOpNode.from_str( + last.result, const_node.result, OpType.Mult, par.result + ) + fliparoo.graph.add_node(mul_node) + mul_node.incoming_nodes = [const_node, par] + par.outgoing_nodes.append(mul_node) + const_node.outgoing_nodes.append(mul_node) + mul_node.output_node = last.output_node + last.reconnect_outgoing_nodes(mul_node) + for node in fliparoo: + fliparoo.graph.remove_node(node) + + fliparoo.graph.update() + + return fliparoo.graph + + +def expand_mul(graph: EFDFormulaGraph, node: Node, copy=True) -> EFDFormulaGraph: + if copy: + i = graph.node_index(node) + graph = deepcopy(graph) + node = graph.nodes[i] + + const_par = next(filter(lambda x: isinstance(x, ConstantNode), node.incoming_nodes)) + par = next(filter(lambda x: not isinstance(x, ConstantNode), node.incoming_nodes)) + initial_node = CodeOpNode.from_str(node.result, par.result, OpType.Add, par.result) + graph.add_node(initial_node) + initial_node.incoming_nodes = [par, par] + par.outgoing_nodes.extend([initial_node, initial_node]) + prev_node = initial_node + for _ in range(const_par.value - 2): + anode = CodeOpNode.from_str( + node.result, prev_node.result, OpType.Add, par.result + ) + anode.incoming_nodes = [prev_node, par] + par.outgoing_nodes.append(anode) + graph.add_node(anode) + prev_node.outgoing_nodes = [anode] + prev_node = anode + + prev_node.output_node = node.output_node + node.reconnect_outgoing_nodes(prev_node) + graph.remove_node(node) + graph.remove_node(const_par) + graph.update() + + return graph + + +class Partition: + value: int + parts: List["Partition"] + + def __init__(self, n: int): + self.value = n + self.parts = [] + + @property + def is_final(self): + return not self.parts + + def __repr__(self): + if self.is_final: + return f"({self.value})" + l, r = self.parts + return f"({l.__repr__()},{r.__repr__()})" + + def __add__(self, other): + a = Partition(self.value + other.value) + a.parts = [self, other] + return a + + def __eq__(self, other): + if self.value != other.value: + return False + if self.is_final or other.is_final: + return self.is_final == other.is_final + l, r = self.parts + lo, ro = other.parts + return (l == lo and r == ro) or (l == ro and r == lo) + + +def compute_partitions(n: int) -> List[Partition]: + partitions = [Partition(n)] + for d in range(1, n // 2 + 1): + n_d = n - d + for partition_dp in compute_partitions(d): + for partition_n_dp in compute_partitions(n_d): + partitions.append(partition_dp + partition_n_dp) + # remove duplicates + result = [] + for p in partitions: + if p not in result: + result.append(p) + return result + + +def generate_partitioned_formulas(formula: EFDFormula, rename=True): + graph = EFDFormulaGraph(formula, rename) + enodes = find_expansion_nodes(graph) + for enode in enodes: + for part_graph in generate_all_node_partitions(graph, enode): + yield part_graph.to_EFDFormula() + + +def generate_all_node_partitions( + original_graph: EFDFormulaGraph, node: Node +) -> Generator[EFDFormulaGraph, Any, None]: + const_par = next(filter(lambda x: isinstance(x, ConstantNode), node.incoming_nodes)) + const_par_value = const_par.value + + par = next(filter(lambda x: not isinstance(x, ConstantNode), node.incoming_nodes)) + i, ic, ip = ( + original_graph.node_index(node), + original_graph.node_index(const_par), + original_graph.node_index(par), + ) + + for partition in compute_partitions(const_par_value): + if partition.is_final: + continue + + # copy + graph = deepcopy(original_graph) + node, const_par, par = graph.nodes[i], graph.nodes[ic], graph.nodes[ip] + graph.remove_node(const_par) + lresult, rresult = f"{node.result}L", f"{node.result}R" + empty_left_node = CodeOpNode.from_str(lresult, "PART", OpType.Add, "PART") + empty_right_node = CodeOpNode.from_str(rresult, "PART", OpType.Add, "PART") + addition_node = CodeOpNode.from_str(node.result, lresult, OpType.Add, rresult) + graph.add_node(empty_left_node) + graph.add_node(empty_right_node) + graph.add_node(addition_node) + addition_node.outgoing_nodes = node.outgoing_nodes + addition_node.output_node = node.output_node + addition_node.incoming_nodes = [empty_left_node, empty_right_node] + empty_left_node.outgoing_nodes = [addition_node] + empty_right_node.outgoing_nodes = [addition_node] + + left, right = partition.parts + partition_node(graph, empty_left_node, left, par) + partition_node(graph, empty_right_node, right, par) + + graph.remove_node(node) + graph.update() + yield graph + + +def partition_node( + graph: EFDFormulaGraph, node: CodeOpNode, partition: Partition, source_node: Node +): + if partition.is_final and partition.value == 1: + # source node will take the role of node + # note: node has precisely one output node, since it was created during partitions + assert len(node.outgoing_nodes) == 1 + child = node.outgoing_nodes[0] + source_node.outgoing_nodes.append(child) + + left, right = child.incoming_nodes[0].result, child.incoming_nodes[1].result + if child.incoming_nodes[0] == node: + left = source_node.result + child.incoming_nodes[0] = source_node + else: + right = source_node.result + child.incoming_nodes[1] = source_node + opstr = f"{child.result} = {left}{child.optype.op_str}{right}" + child.op = CodeOp(parse(opstr)) + graph.remove_node(node) + return + + if partition.is_final: + source_node.outgoing_nodes.append(node) + const_node = ConstantNode(partition.value) + graph.add_node(const_node) + opstr = ( + f"{node.result} = {source_node.result}{OpType.Mult.op_str}{partition.value}" + ) + node.op = CodeOp(parse(opstr)) + node.incoming_nodes = [source_node, const_node] + const_node.outgoing_nodes = [node] + return + + lresult, rresult = f"{node.result}L", f"{node.result}R" + empty_left_node = CodeOpNode.from_str(lresult, "PART", OpType.Add, "PART") + empty_right_node = CodeOpNode.from_str(rresult, "PART", OpType.Add, "PART") + + opstr = f"{node.result} = {lresult}{OpType.Add.op_str}{rresult}" + node.op = CodeOp(parse(opstr)) + graph.add_node(empty_left_node) + graph.add_node(empty_right_node) + + node.incoming_nodes = [empty_left_node, empty_right_node] + empty_left_node.outgoing_nodes = [node] + empty_right_node.outgoing_nodes = [node] + + left, right = partition.parts + partition_node(graph, empty_left_node, left, source_node) + partition_node(graph, empty_right_node, right, source_node) diff --git a/pyecsca/ec/formula/switch_sign.py b/pyecsca/ec/formula/switch_sign.py new file mode 100644 index 0000000..1acef5b --- /dev/null +++ b/pyecsca/ec/formula/switch_sign.py @@ -0,0 +1,131 @@ +from typing import Dict, Iterator, List, Any +from ast import parse +from ..op import OpType, CodeOp +from .graph import EFDFormulaGraph, ConstantNode, Node, CodeOpNode +from itertools import chain, combinations +from .efd import EFDFormula +from ..point import Point +from ..mod import Mod + + +def generate_switched_formulas( + formula: EFDFormula, rename=True +) -> Iterator[EFDFormula]: + graph = EFDFormulaGraph(formula, rename) + for node_combination in subnode_lists(graph): + try: + yield switch_sign(graph, node_combination).to_EFDFormula() + except BadSignSwitch: + continue + + +def subnode_lists(graph: EFDFormulaGraph): + return powerlist(filter(lambda x: x not in graph.roots and x.is_sub, graph.nodes)) + + +def switch_sign(graph: EFDFormulaGraph, node_combination) -> EFDFormulaGraph: + nodes_i = [graph.node_index(node) for node in node_combination] + graph = graph.deepcopy() + node_combination = set(graph.nodes[node_i] for node_i in nodes_i) + output_signs = {out: 1 for out in graph.output_names} + + queue = [] + for node in node_combination: + change_sides(node) + if node.output_node: + output_signs[node.result] = -1 + queue.extend([(out, node.result) for out in node.outgoing_nodes]) + + while queue: + node, variable = queue.pop() + queue = switch_sign_propagate(node, variable, output_signs) + queue + + sign_test(output_signs, graph.coordinate_model) + return graph + + +def sign_test(output_signs: Dict[str, int], coordinate_model: Any): + scale = coordinate_model.formulas.get("z", None) + if scale is None: + scale = coordinate_model.formulas.get("scale", None) + p = 7 + out_inds = set(map(lambda x: "".join([o for o in x if o.isdigit()]), output_signs)) + for ind in out_inds: + point_dict = {} + for out, sign in output_signs.items(): + if not out.endswith(ind): + continue + out_var = out[:out.index(ind)] + if not out_var.isalpha(): + continue + point_dict[out_var] = Mod(sign, p) + point = Point(coordinate_model, **point_dict) + try: + apoint = point.to_affine() + except NotImplementedError: + apoint = scale(p, point)[0] + if set(apoint.coords.values()) != set([Mod(1, p)]): + raise BadSignSwitch + + +class BadSignSwitch(Exception): + pass + + +def switch_sign_propagate( + node: CodeOpNode, variable: str, output_signs: Dict[str, int] +): + if node.is_add: + if variable == node.incoming_nodes[1].result: + node.op = change_operator(node.op, OpType.Sub) + return [] + change_sides(node) + node.op = change_operator(node.op, OpType.Sub) + return [] + if node.is_id or node.is_neg: + output_signs[node.result] *= -1 + return [(child, node.result) for child in node.outgoing_nodes] + + if node.is_sqr: + return [] + if node.is_sub: + if node.incoming_nodes[0].result == variable: + node.op = change_operator(node.op, OpType.Add) + if node.output_node: + output_signs[node.result] *= -1 + return [(child, node.result) for child in node.outgoing_nodes] + node.op = change_operator(node.op, OpType.Add) + return [] + if node.is_pow: + exponent = next( + filter(lambda n: isinstance(n, ConstantNode), node.incoming_nodes) + ) + if exponent.value % 2 == 0: + return [] + if node.output_node: + output_signs[node.result] *= -1 + assert node.is_mul or node.is_pow or node.is_inv or node.is_div + return [(child, node.result) for child in node.outgoing_nodes] + + +def change_operator(op, new_operator): + result, left, right = op.result, op.left, op.right + opstr = f"{result} = {left if left is not None else ''}{new_operator.op_str}{right if right is not None else ''}" + return CodeOp(parse(opstr.replace("^", "**"))) + + +def change_sides(node): + op = node.op + result, left, operator, right = op.result, op.left, op.operator.op_str, op.right + left, right = right, left + opstr = f"{result} = {left if left is not None else ''}{operator}{right if right is not None else ''}" + node.op = CodeOp(parse(opstr.replace("^", "**"))) + node.incoming_nodes[1], node.incoming_nodes[0] = ( + node.incoming_nodes[0], + node.incoming_nodes[1], + ) + + +def powerlist(iterable: Iterator) -> List: + s = list(iterable) + return list(chain.from_iterable(combinations(s, r) for r in range(len(s) + 1))) diff --git a/pyecsca/ec/mult/window.py b/pyecsca/ec/mult/window.py index f85a58a..d025cc1 100644 --- a/pyecsca/ec/mult/window.py +++ b/pyecsca/ec/mult/window.py @@ -4,15 +4,22 @@ from typing import Optional, MutableMapping from public import public from ..params import DomainParameters -from .base import ScalarMultiplier, AccumulationOrder, ScalarMultiplicationAction, PrecomputationAction, \ - ProcessingDirection, AccumulatorMultiplier +from .base import ( + ScalarMultiplier, + AccumulationOrder, + ScalarMultiplicationAction, + PrecomputationAction, + ProcessingDirection, + AccumulatorMultiplier, +) from ..formula import ( AdditionFormula, DoublingFormula, ScalingFormula, + NegationFormula, ) from ..point import Point -from ..scalar import convert_base, sliding_window_rtl, sliding_window_ltr +from ..scalar import convert_base, sliding_window_rtl, sliding_window_ltr, booth_window @public @@ -34,17 +41,21 @@ class SlidingWindowMultiplier(AccumulatorMultiplier, ScalarMultiplier): _points: MutableMapping[int, Point] def __init__( - self, - add: AdditionFormula, - dbl: DoublingFormula, - width: int, - scl: Optional[ScalingFormula] = None, - recoding_direction: ProcessingDirection = ProcessingDirection.LTR, - accumulation_order: AccumulationOrder = AccumulationOrder.PeqPR, - short_circuit: bool = True, + self, + add: AdditionFormula, + dbl: DoublingFormula, + width: int, + scl: Optional[ScalingFormula] = None, + recoding_direction: ProcessingDirection = ProcessingDirection.LTR, + accumulation_order: AccumulationOrder = AccumulationOrder.PeqPR, + short_circuit: bool = True, ): super().__init__( - short_circuit=short_circuit, accumulation_order=accumulation_order, add=add, dbl=dbl, scl=scl + short_circuit=short_circuit, + accumulation_order=accumulation_order, + add=add, + dbl=dbl, + scl=scl, ) self.width = width self.recoding_direction = recoding_direction @@ -55,7 +66,13 @@ class SlidingWindowMultiplier(AccumulatorMultiplier, ScalarMultiplier): def __eq__(self, other): if not isinstance(other, SlidingWindowMultiplier): return False - return self.formulas == other.formulas and self.short_circuit == other.short_circuit and self.width == other.width and self.recoding_direction == other.recoding_direction and self.accumulation_order == other.accumulation_order + return ( + self.formulas == other.formulas + and self.short_circuit == other.short_circuit + and self.width == other.width + and self.recoding_direction == other.recoding_direction + and self.accumulation_order == other.accumulation_order + ) def __repr__(self): return f"{self.__class__.__name__}({', '.join(map(str, self.formulas.values()))}, short_circuit={self.short_circuit}, width={self.width}, recoding_direction={self.recoding_direction.name}, accumulation_order={self.accumulation_order.name})" @@ -112,16 +129,20 @@ class FixedWindowLTRMultiplier(AccumulatorMultiplier, ScalarMultiplier): _points: MutableMapping[int, Point] def __init__( - self, - add: AdditionFormula, - dbl: DoublingFormula, - m: int, - scl: Optional[ScalingFormula] = None, - accumulation_order: AccumulationOrder = AccumulationOrder.PeqPR, - short_circuit: bool = True, + self, + add: AdditionFormula, + dbl: DoublingFormula, + m: int, + scl: Optional[ScalingFormula] = None, + accumulation_order: AccumulationOrder = AccumulationOrder.PeqPR, + short_circuit: bool = True, ): super().__init__( - short_circuit=short_circuit, accumulation_order=accumulation_order, add=add, dbl=dbl, scl=scl + short_circuit=short_circuit, + accumulation_order=accumulation_order, + add=add, + dbl=dbl, + scl=scl, ) if m < 2: raise ValueError("Invalid base.") @@ -134,7 +155,12 @@ class FixedWindowLTRMultiplier(AccumulatorMultiplier, ScalarMultiplier): def __eq__(self, other): if not isinstance(other, FixedWindowLTRMultiplier): return False - return self.formulas == other.formulas and self.short_circuit == other.short_circuit and self.m == other.m and self.accumulation_order == other.accumulation_order + return ( + self.formulas == other.formulas + and self.short_circuit == other.short_circuit + and self.m == other.m + and self.accumulation_order == other.accumulation_order + ) def __repr__(self): return f"{self.__class__.__name__}({', '.join(map(str, self.formulas.values()))}, short_circuit={self.short_circuit}, m={self.m}, accumulation_order={self.accumulation_order.name})" @@ -180,3 +206,103 @@ class FixedWindowLTRMultiplier(AccumulatorMultiplier, ScalarMultiplier): if "scl" in self.formulas: q = self._scl(q) return action.exit(q) + + +@public +class WindowBoothMultiplier(AccumulatorMultiplier, ScalarMultiplier): + """ + + :param short_circuit: Whether the use of formulas will be guarded by short-circuit on inputs + of the point at infinity. + :param width: The width of the window. + :param accumulation_order: The order of accumulation of points. + :param precompute_negation: Whether to precompute the negation of the precomputed points as well. + It is computed on the fly otherwise. + """ + + requires = {AdditionFormula, DoublingFormula, NegationFormula} + optionals = {ScalingFormula} + _points: MutableMapping[int, Point] + _points_neg: MutableMapping[int, Point] + precompute_negation: bool = False + """Whether to precompute the negation of the precomputed points as well.""" + width: int + """The width of the window.""" + + def __init__( + self, + add: AdditionFormula, + dbl: DoublingFormula, + neg: NegationFormula, + width: int, + scl: Optional[ScalingFormula] = None, + accumulation_order: AccumulationOrder = AccumulationOrder.PeqPR, + precompute_negation: bool = False, + short_circuit: bool = True, + ): + super().__init__( + short_circuit=short_circuit, + accumulation_order=accumulation_order, + add=add, + dbl=dbl, + neg=neg, + scl=scl, + ) + self.width = width + self.precompute_negation = precompute_negation + + def __hash__(self): + return id(self) + + def __eq__(self, other): + if not isinstance(other, WindowBoothMultiplier): + return False + return ( + self.formulas == other.formulas + and self.short_circuit == other.short_circuit + and self.width == other.width + and self.precompute_negation == other.precompute_negation + and self.accumulation_order == other.accumulation_order + ) + + def __repr__(self): + return f"{self.__class__.__name__}({', '.join(map(str, self.formulas.values()))}, short_circuit={self.short_circuit}, width={self.width}, precompute_negation={self.precompute_negation}, accumulation_order={self.accumulation_order.name})" + + def init(self, params: DomainParameters, point: Point): + with PrecomputationAction(params, point): + super().init(params, point) + double_point = self._dbl(point) + self._points = {1: point, 2: double_point} + if self.precompute_negation: + self._points_neg = {1: self._neg(point), 2: self._neg(double_point)} + current_point = double_point + for i in range(3, 2 ** (self.width - 1) + 1): + current_point = self._add(current_point, point) + self._points[i] = current_point + if self.precompute_negation: + self._points_neg[i] = self._neg(current_point) + + def multiply(self, scalar: int) -> Point: + if not self._initialized: + raise ValueError("ScalarMultiplier not initialized.") + with ScalarMultiplicationAction(self._point, scalar) as action: + if scalar == 0: + return action.exit(copy(self._params.curve.neutral)) + scalar_booth = booth_window( + scalar, self.width, self._params.order.bit_length() + ) + q = copy(self._params.curve.neutral) + for val in scalar_booth: + for _ in range(self.width): + q = self._dbl(q) + if val > 0: + q = self._accumulate(q, self._points[val]) + elif val < 0: + if self.precompute_negation: + neg = self._points_neg[-val] + else: + neg = self._neg(self._points[-val]) + q = self._accumulate(q, neg) + if "scl" in self.formulas: + q = self._scl(q) + return action.exit(q) diff --git a/pyecsca/ec/point.py b/pyecsca/ec/point.py index 280d746..bdbba5e 100644 --- a/pyecsca/ec/point.py +++ b/pyecsca/ec/point.py @@ -140,7 +140,7 @@ class Point: if randomized: lmbd = Mod.random(curve.prime) for var, value in result.items(): - result[var] = value * lmbd**coordinate_model.homogweights[var] + result[var] = value * (lmbd**coordinate_model.homogweights[var]) return action.exit(Point(coordinate_model, **result)) def equals_affine(self, other: "Point") -> bool: diff --git a/pyecsca/ec/scalar.py b/pyecsca/ec/scalar.py index 3fadc00..af5a6ab 100644 --- a/pyecsca/ec/scalar.py +++ b/pyecsca/ec/scalar.py @@ -1,5 +1,5 @@ """Provides functions for computing various scalar representations (like NAF, or different bases).""" -from typing import List +from typing import List, Tuple, Literal from itertools import dropwhile from public import public @@ -11,7 +11,7 @@ def convert_base(i: int, base: int) -> List[int]: :param i: The scalar. :param base: The base. - :return: The resulting digit list. + :return: The resulting digit list (little-endian). """ if i == 0: return [0] @@ -131,3 +131,62 @@ def naf(k: int) -> List[int]: :return: The NAF. """ return wnaf(k, 2) + + +@public +def booth(k: int) -> List[int]: + """ + Original Booth binary recoding, from [B51]_. + + :param k: The scalar to recode. + :return: The recoded list of digits (0, 1, -1), little-endian. + """ + res = [] + for i in range(k.bit_length()): + a_i = (k >> i) & 1 + b_i = (k >> (i + 1)) & 1 + res.append(a_i - b_i) + res.insert(0, -(k & 1)) + return res + + +@public +def booth_word(digit: int, w: int) -> int: + """ + Modified Booth recoding, from [M61]_ and BoringSSL NIST impl. + + Needs `w+1` bits of scalar in digit, but the one bit is overlapping (window size is `w`). + + :param digit: + :param w: + :return: + """ + if digit.bit_length() > (w + 1): + raise ValueError("Invalid digit, cannot be larger than w + 1 bits.") + s = ~((digit >> w) - 1) + d = (1 << (w + 1)) - digit - 1 + d = (d & s) | (digit & ~s) + d = (d >> 1) + (d & 1) + + return -d if s else d + + +@public +def booth_window(k: int, w: int, blen: int) -> List[int]: + """ + Recode a whole scalar using Booth recoding as in BoringSSL. + + :param k: The scalar. + :param w: The window size. + :param blen: The bit-length of the group. + :return: The big-endian recoding + """ + mask = (1 << (w + 1)) - 1 + res = [] + for i in range(blen + (w - (blen % w) - 1), -1, -w): + if i >= w: + d = (k >> (i - w)) & mask + else: + d = (k << (w - i)) & mask + res.append(booth_word(d, w)) + return res diff --git a/pyecsca/sca/re/rpa.py b/pyecsca/sca/re/rpa.py index db88dae..b1661e6 100644 --- a/pyecsca/sca/re/rpa.py +++ b/pyecsca/sca/re/rpa.py @@ -34,8 +34,11 @@ class MultipleContext(Context): """Context that traces the multiples of points computed.""" base: Optional[Point] + """The base point that all the multiples are counted from.""" points: MutableMapping[Point, int] + """The mapping of points to the multiples they represent (e.g., base -> 1).""" parents: MutableMapping[Point, List[Point]] + """The mapping of points to their parent they were computed from.""" inside: bool def __init__(self): diff --git a/pyecsca/sca/re/structural.py b/pyecsca/sca/re/structural.py index c79e604..f3b0d32 100644 --- a/pyecsca/sca/re/structural.py +++ b/pyecsca/sca/re/structural.py @@ -1,77 +1 @@ """""" -import warnings -from typing import Dict -from public import public - -from ...ec.curve import EllipticCurve -from ...ec.formula import Formula -from ...ec.context import DefaultContext, local -from .zvp import unroll_formula -from operator import itemgetter, attrgetter - - -@public -def formula_similarity(one: Formula, other: Formula) -> Dict[str, float]: - if one.coordinate_model != other.coordinate_model: - warnings.warn("Mismatched coordinate model.") - - one_unroll = unroll_formula(one) - other_unroll = unroll_formula(other) - one_results = {} - for name, value in one_unroll: - if name in one.outputs: - one_results[name] = value - other_results = {} - for name, value in other_unroll: - if name in other.outputs: - other_results[name] = value - one_result_polys = set(one_results.values()) - other_result_polys = set(other_results.values()) - one_polys = set(map(itemgetter(1), one_unroll)) - other_polys = set(map(itemgetter(1), other_unroll)) - return { - "output": len(one_result_polys.intersection(other_result_polys)) - / max(len(one_result_polys), len(other_result_polys)), - "ivs": len(one_polys.intersection(other_polys)) - / max(len(one_polys), len(other_polys)), - } - - -@public -def formula_similarity_fuzz( - one: Formula, other: Formula, curve: EllipticCurve, samples: int = 1000 -) -> Dict[str, float]: - if one.coordinate_model != other.coordinate_model: - warnings.warn("Mismatched coordinate model.") - - output_matches = 0.0 - iv_matches = 0.0 - for _ in range(samples): - Paff = curve.affine_random() - Qaff = curve.affine_random() - Raff = curve.affine_add(Paff, Qaff) - P = Paff.to_model(one.coordinate_model, curve) - Q = Qaff.to_model(one.coordinate_model, curve) - R = Raff.to_model(one.coordinate_model, curve) - inputs = (P, Q, R)[: one.num_inputs] - with local(DefaultContext()) as ctx: - res_one = one(curve.prime, *inputs, **curve.parameters) - action_one = ctx.actions.get_by_index([0]) - ivs_one = set( - map(attrgetter("value"), sum(action_one[0].intermediates.values(), [])) - ) - with local(DefaultContext()) as ctx: - res_other = other(curve.prime, *inputs, **curve.parameters) - action_other = ctx.actions.get_by_index([0]) - ivs_other = set( - map(attrgetter("value"), sum(action_other[0].intermediates.values(), [])) - ) - iv_matches += len(ivs_one.intersection(ivs_other)) / max( - len(ivs_one), len(ivs_other) - ) - one_coords = set(res_one) - other_coords = set(res_other) - output_matches += len(one_coords.intersection(other_coords)) / max( - len(one_coords), len(other_coords) - ) - return {"output": output_matches / samples, "ivs": iv_matches / samples} diff --git a/pyecsca/sca/target/emulator.py b/pyecsca/sca/target/leakage.py index bca27cf..8a8acd6 100644 --- a/pyecsca/sca/target/emulator.py +++ b/pyecsca/sca/target/leakage.py @@ -18,7 +18,7 @@ import numpy as np @public -class EmulatorTarget(Target): +class LeakageTarget(Target): model: CurveModel coords: CoordinateModel @@ -48,7 +48,7 @@ class EmulatorTarget(Target): context.actions.walk(callback) return Trace(np.array(temp_trace)) - def emulate_scalar_mult_traces(self, num_of_traces: int, scalar: int) -> Tuple[list[Point], list[Trace]]: + def simulate_scalar_mult_traces(self, num_of_traces: int, scalar: int) -> Tuple[list[Point], list[Trace]]: if self.params is None: raise ValueError("Missing DomainParameters") points = [self.params.curve.affine_random().to_model(self.coords, self.params.curve) for _ in range(num_of_traces)] @@ -58,7 +58,7 @@ class EmulatorTarget(Target): traces.append(trace) return points, traces - def emulate_ecdh_traces(self, num_of_traces: int) -> Tuple[list[Point], list[Trace]]: + def simulate_ecdh_traces(self, num_of_traces: int) -> Tuple[list[Point], list[Trace]]: if self.params is None: raise ValueError("Missing DomainParameters") other_pubs = [self.params.curve.affine_random().to_model(self.coords, self.params.curve) for _ in range(num_of_traces)] diff --git a/pyproject.toml b/pyproject.toml index 52b03a1..48ad351 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -85,3 +85,6 @@ filterwarnings = [ [tool.mypy] plugins = "numpy.typing.mypy_plugin" + +[tool.interrogate] +exclude = ["pyecsca/ec/formula/fliparoo.py", "pyecsca/ec/formula/graph.py", "pyecsca/ec/formula/partitions.py", "pyecsca/ec/formula/switch_sign.py", "pyecsca/ec/std/.github/"] diff --git a/test/ec/test_configuration.py b/test/ec/test_configuration.py index 54cc53a..15c9471 100644 --- a/test/ec/test_configuration.py +++ b/test/ec/test_configuration.py @@ -33,7 +33,7 @@ def test_weierstrass_projective(base_independents): coords = model.coordinates["projective"] configs = list(all_configurations(model=model, coords=coords, **base_independents)) assert len(set(map(lambda cfg: cfg.scalarmult, configs))) == len(configs) - assert len(configs) == 11360 + assert len(configs) == 12640 def test_mult_class(base_independents): diff --git a/test/ec/test_formula.py b/test/ec/test_formula.py index bb3d123..3f8d45c 100644 --- a/test/ec/test_formula.py +++ b/test/ec/test_formula.py @@ -1,13 +1,39 @@ import pickle +from operator import itemgetter +from typing import Tuple import pytest from sympy import FF, symbols - +from importlib_resources import files, as_file +import test.data.formulas +from pyecsca.ec.formula.expand import expand_formula_list +from pyecsca.ec.formula.fliparoo import generate_fliparood_formulas +from pyecsca.ec.formula.graph import rename_ivs +from pyecsca.ec.formula.metrics import ( + formula_similarity, + formula_similarity_abs, + formula_similarity_fuzz, +) +from pyecsca.ec.formula.partitions import ( + reduce_all_adds, + expand_all_muls, + expand_all_nopower2_muls, + generate_partitioned_formulas, +) +from pyecsca.ec.formula.switch_sign import generate_switched_formulas from pyecsca.ec.mod import SymbolicMod, Mod from pyecsca.misc.cfg import TemporaryConfig from pyecsca.ec.error import UnsatisfiedAssumptionError -from pyecsca.ec.params import get_params +from pyecsca.ec.params import get_params, DomainParameters from pyecsca.ec.point import Point +from pyecsca.ec.model import ShortWeierstrassModel, MontgomeryModel, TwistedEdwardsModel +from pyecsca.ec.formula.efd import ( + AdditionEFDFormula, + DoublingEFDFormula, + LadderEFDFormula, + EFDFormula, +) +from pyecsca.ec.formula import AdditionFormula, DoublingFormula, LadderFormula @pytest.fixture() @@ -62,39 +88,27 @@ def test_num_ops(add): def test_assumptions(secp128r1, mdbl): - res = mdbl( - secp128r1.curve.prime, - secp128r1.generator, - **secp128r1.curve.parameters - ) + res = mdbl(secp128r1.curve.prime, secp128r1.generator, **secp128r1.curve.parameters) assert res is not None - coords = { - name: value * 5 for name, value in secp128r1.generator.coords.items() - } + coords = {name: value * 5 for name, value in secp128r1.generator.coords.items()} other = Point(secp128r1.generator.coordinate_model, **coords) with pytest.raises(UnsatisfiedAssumptionError): - mdbl( - secp128r1.curve.prime, other, **secp128r1.curve.parameters - ) + mdbl(secp128r1.curve.prime, other, **secp128r1.curve.parameters) with TemporaryConfig() as cfg: cfg.ec.unsatisfied_formula_assumption_action = "ignore" - pt = mdbl( - secp128r1.curve.prime, other, **secp128r1.curve.parameters - ) + pt = mdbl(secp128r1.curve.prime, other, **secp128r1.curve.parameters) assert pt is not None def test_parameters(): jac_secp128r1 = get_params("secg", "secp128r1", "jacobian") - jac_dbl = jac_secp128r1.curve.coordinate_model.formulas[ - "dbl-1998-hnm" - ] + jac_dbl = jac_secp128r1.curve.coordinate_model.formulas["dbl-1998-hnm"] res = jac_dbl( jac_secp128r1.curve.prime, jac_secp128r1.generator, - **jac_secp128r1.curve.parameters + **jac_secp128r1.curve.parameters, ) assert res is not None @@ -111,9 +125,7 @@ def test_symbolic(secp128r1, dbl): coords, **{key: SymbolicMod(symbols(key), p) for key in coords.variables} ) symbolic_double = dbl(p, symbolic_point, **sympy_params)[0] - generator_double = dbl( - p, secp128r1.generator, **secp128r1.curve.parameters - )[0] + generator_double = dbl(p, secp128r1.generator, **secp128r1.curve.parameters)[0] for outer_var in coords.variables: symbolic_val = getattr(symbolic_double, outer_var).x generator_val = getattr(generator_double, outer_var).x @@ -126,3 +138,371 @@ def test_symbolic(secp128r1, dbl): def test_pickle(add, dbl): assert add == pickle.loads(pickle.dumps(add)) + + +def test_formula_similarity(secp128r1): + add_bl = secp128r1.curve.coordinate_model.formulas["add-2007-bl"] + add_rcb = secp128r1.curve.coordinate_model.formulas["add-2015-rcb"] + out = formula_similarity(add_bl, add_rcb) + assert out["output"] == 0 + assert out["ivs"] < 0.5 + out_abs = formula_similarity_abs(add_bl, add_rcb) + assert out_abs["output"] == 0 + assert out_abs["ivs"] < 0.5 + out_fuzz = formula_similarity_fuzz(add_bl, add_rcb, secp128r1.curve, samples=100) + assert out_fuzz["output"] == 0 + assert out_fuzz["ivs"] < 0.5 + out_same = formula_similarity(add_bl, add_bl) + assert out_same["output"] == 1 + assert out_same["ivs"] == 1 + + +LIBRARY_FORMULAS = [ + [ + "add-bc-r1rv76-jac", + ShortWeierstrassModel, + "jacobian", + ("secg", "secp128r1"), + AdditionEFDFormula, + ], + [ + "add-bc-r1rv76-mod", + ShortWeierstrassModel, + "modified", + ("secg", "secp128r1"), + AdditionEFDFormula, + ], + [ + "dbl-bc-r1rv76-jac", + ShortWeierstrassModel, + "jacobian", + ("secg", "secp128r1"), + DoublingEFDFormula, + ], + [ + "dbl-bc-r1rv76-mod", + ShortWeierstrassModel, + "modified", + ("secg", "secp128r1"), + DoublingEFDFormula, + ], + [ + "dbl-bc-r1rv76-x25519", + MontgomeryModel, + "xz", + ("other", "Curve25519"), + DoublingEFDFormula, + ], + [ + "ladd-bc-r1rv76-x25519", + MontgomeryModel, + "xz", + ("other", "Curve25519"), + LadderEFDFormula, + ], + [ + "dbl-boringssl-p224", + ShortWeierstrassModel, + "jacobian-3", + ("secg", "secp224r1"), + DoublingEFDFormula, + ], + [ + "add-boringssl-p224", + ShortWeierstrassModel, + "jacobian-3", + ("secg", "secp224r1"), + AdditionEFDFormula, + ], + [ + "add-libressl-v382", + ShortWeierstrassModel, + "jacobian", + ("secg", "secp128r1"), + AdditionEFDFormula, + ], + [ + "dbl-libressl-v382", + ShortWeierstrassModel, + "jacobian", + ("secg", "secp128r1"), + DoublingEFDFormula, + ], + [ + "dbl-secp256k1-v040", + ShortWeierstrassModel, + "jacobian", + ("secg", "secp256k1"), + DoublingEFDFormula, + ], + [ + "add-openssl-z256", + ShortWeierstrassModel, + "jacobian-3", + ("secg", "secp256r1"), + AdditionEFDFormula, + ], + [ + "add-openssl-z256a", + ShortWeierstrassModel, + "jacobian-3", + ("secg", "secp256r1"), + AdditionEFDFormula, + ], + [ + "ladd-openssl-x25519", + MontgomeryModel, + "xz", + ("other", "Curve25519"), + LadderEFDFormula, + ], + [ + "ladd-hacl-x25519", + MontgomeryModel, + "xz", + ("other", "Curve25519"), + LadderEFDFormula, + ], + [ + "dbl-hacl-x25519", + MontgomeryModel, + "xz", + ("other", "Curve25519"), + DoublingEFDFormula, + ], + [ + "dbl-sunec-v21", + ShortWeierstrassModel, + "projective-3", + ("secg", "secp256r1"), + DoublingEFDFormula, + ], + [ + "add-sunec-v21", + ShortWeierstrassModel, + "projective-3", + ("secg", "secp256r1"), + AdditionEFDFormula, + ], + [ + "add-sunec-v21-ed25519", + TwistedEdwardsModel, + "extended", + ("other", "Ed25519"), + AdditionEFDFormula, + ], + [ + "dbl-sunec-v21-ed25519", + TwistedEdwardsModel, + "extended", + ("other", "Ed25519"), + DoublingEFDFormula, + ], + [ + "ladd-rfc7748", + MontgomeryModel, + "xz", + ("other", "Curve25519"), + LadderEFDFormula, + ], + [ + "add-bearssl-v06", + ShortWeierstrassModel, + "jacobian", + ("secg", "secp256r1"), + AdditionEFDFormula, + ], + [ + "dbl-bearssl-v06", + ShortWeierstrassModel, + "jacobian", + ("secg", "secp256r1"), + DoublingEFDFormula, + ], + [ + "add-libgcrypt-v1102", + ShortWeierstrassModel, + "jacobian", + ("secg", "secp256r1"), + AdditionEFDFormula, + ], + [ + "dbl-libgcrypt-v1102", + ShortWeierstrassModel, + "jacobian", + ("secg", "secp256r1"), + DoublingEFDFormula, + ], + [ + "ladd-go-1214", + MontgomeryModel, + "xz", + ("other", "Curve25519"), + LadderEFDFormula, + ], + [ + "add-gecc-322", + ShortWeierstrassModel, + "jacobian-3", + ("secg", "secp256r1"), + AdditionEFDFormula, + ], + [ + "dbl-gecc-321", + ShortWeierstrassModel, + "jacobian-3", + ("secg", "secp256r1"), + DoublingEFDFormula, + ], + [ + "ladd-boringssl-x25519", + MontgomeryModel, + "xz", + ("other", "Curve25519"), + LadderEFDFormula, + ], + [ + "dbl-ipp-x25519", + MontgomeryModel, + "xz", + ("other", "Curve25519"), + DoublingEFDFormula, + ], + [ + "ladd-botan-x25519", + MontgomeryModel, + "xz", + ("other", "Curve25519"), + LadderEFDFormula, + ], +] + + +@pytest.fixture(params=LIBRARY_FORMULAS, ids=list(map(itemgetter(0), LIBRARY_FORMULAS))) +def library_formula_params(request) -> Tuple[EFDFormula, DomainParameters]: + name, model, coords_name, param_spec, formula_type = request.param + model = model() + coordinate_model = model.coordinates[coords_name] + with as_file(files(test.data.formulas).joinpath(name)) as meta_path, as_file( + files(test.data.formulas).joinpath(name + ".op3") + ) as op3_path: + formula = formula_type(meta_path, op3_path, name, coordinate_model) + params = get_params(*param_spec, coords_name) + return formula, params + + +def test_formula_graph(library_formula_params): + formula, params = library_formula_params + do_test_formula(rename_ivs(formula), params) + + +def test_switch_sign(library_formula_params): + formula, params = library_formula_params + for switch_formula in generate_switched_formulas(formula): + do_test_formula(switch_formula, params) + + +def test_fliparood_formula(library_formula_params): + formula, params = library_formula_params + for fliparood in generate_fliparood_formulas(formula): + do_test_formula(fliparood, params) + + +def test_partition_formula_single(library_formula_params): + formula, params = library_formula_params + try: + next(iter(generate_partitioned_formulas(formula))) + except StopIteration: + pass + + +@pytest.mark.slow +def test_partition_formula(library_formula_params): + formula, params = library_formula_params + for partitioned in generate_partitioned_formulas(formula): + do_test_formula(partitioned, params) + + +def test_reductions(library_formula_params): + formula, params = library_formula_params + do_test_formula(reduce_all_adds(formula), params) + + +def test_expansions(library_formula_params): + formula, params = library_formula_params + do_test_formula(expand_all_muls(formula), params) + do_test_formula(expand_all_nopower2_muls(formula), params) + + +def do_test_formula(formula, params): + coordinate_model = formula.coordinate_model + scale = coordinate_model.formulas.get("z", None) + if scale is None: + scale = coordinate_model.formulas.get("scale", None) + + formula_type = formula.__class__ + for _ in range(10): + Paff = params.curve.affine_random() + P2aff = params.curve.affine_double(Paff) + Qaff = params.curve.affine_random() + Q2aff = params.curve.affine_double(Qaff) + Raff = params.curve.affine_add(Paff, Qaff) + R2aff = params.curve.affine_double(Raff) + QRaff = params.curve.affine_add(Qaff, Raff) + P = Paff.to_model(coordinate_model, params.curve) + P2 = P2aff.to_model(coordinate_model, params.curve) + Q = Qaff.to_model(coordinate_model, params.curve) + Q2 = Q2aff.to_model(coordinate_model, params.curve) + R = Raff.to_model(coordinate_model, params.curve) + R2 = R2aff.to_model(coordinate_model, params.curve) # noqa + QR = QRaff.to_model(coordinate_model, params.curve) + inputs = (P, Q, R)[: formula.num_inputs] + res = formula(params.curve.prime, *inputs, **params.curve.parameters) + if issubclass(formula_type, AdditionFormula): + try: + assert res[0].to_affine() == Raff + except NotImplementedError: + assert ( + scale(params.curve.prime, res[0], **params.curve.parameters)[0] == R + ) + elif issubclass(formula_type, DoublingFormula): + try: + assert res[0].to_affine() == P2aff + except NotImplementedError: + assert ( + scale(params.curve.prime, res[0], **params.curve.parameters)[0] + == P2 + ) + elif issubclass(formula_type, LadderFormula): + try: + assert res[0].to_affine() == Q2aff + assert res[1].to_affine() == QRaff + except NotImplementedError: + # print(scale(params.curve.prime, res[0], **params.curve.parameters)[0]) + # print(scale(params.curve.prime, res[1], **params.curve.parameters)[0]) + # print(P) + # print(Q) + # print(R) + # print(P2) + # print(Q2) + # print(R2) + # print(QR) + # print("------------------------------------") + assert ( + scale(params.curve.prime, res[1], **params.curve.parameters)[0] + == QR + ) + assert ( + scale(params.curve.prime, res[0], **params.curve.parameters)[0] + == Q2 + ) + + +def test_formula_correctness(library_formula_params): + formula, params = library_formula_params + do_test_formula(formula, params) + + +def test_formula_expand(add): + res = expand_formula_list([add]) + assert len(res) > 1 diff --git a/test/ec/test_mult.py b/test/ec/test_mult.py index 20e1c95..d5e3146 100644 --- a/test/ec/test_mult.py +++ b/test/ec/test_mult.py @@ -20,6 +20,7 @@ from pyecsca.ec.mult import ( SlidingWindowMultiplier, BGMWMultiplier, CombMultiplier, + WindowBoothMultiplier, ) from pyecsca.ec.mult.fixed import FullPrecompMultiplier from pyecsca.ec.point import InfinityPoint, Point @@ -36,12 +37,9 @@ def assert_pt_equality(one: Point, other: Point, scale): assert one.equals(other) -def do_basic_test( - mult_class, params, base, add, dbl, scale, neg=None, **kwargs -): +def do_basic_test(mult_class, params, base, add, dbl, scale, neg=None, **kwargs): mult = mult_class( - *get_formulas(params.curve.coordinate_model, add, dbl, neg, scale), - **kwargs + *get_formulas(params.curve.coordinate_model, add, dbl, neg, scale), **kwargs ) mult.init(params, base) res = mult.multiply(314) @@ -59,26 +57,28 @@ def do_basic_test( return res -@pytest.mark.parametrize("add,dbl,scale", - [ - ("add-1998-cmo-2", "dbl-1998-cmo-2", "z"), - ("add-2015-rcb", "dbl-2015-rcb", None), - ("add-1998-cmo-2", "dbl-1998-cmo-2", None), - ]) +@pytest.mark.parametrize( + "add,dbl,scale", + [ + ("add-1998-cmo-2", "dbl-1998-cmo-2", "z"), + ("add-2015-rcb", "dbl-2015-rcb", None), + ("add-1998-cmo-2", "dbl-1998-cmo-2", None), + ], +) def test_rtl(secp128r1, add, dbl, scale): do_basic_test(RTLMultiplier, secp128r1, secp128r1.generator, add, dbl, scale) -@pytest.mark.parametrize("add,dbl,scale", - [ - ("add-1998-cmo-2", "dbl-1998-cmo-2", "z"), - ("add-2015-rcb", "dbl-2015-rcb", None), - ("add-1998-cmo-2", "dbl-1998-cmo-2", None), - ]) +@pytest.mark.parametrize( + "add,dbl,scale", + [ + ("add-1998-cmo-2", "dbl-1998-cmo-2", "z"), + ("add-2015-rcb", "dbl-2015-rcb", None), + ("add-1998-cmo-2", "dbl-1998-cmo-2", None), + ], +) def test_ltr(secp128r1, add, dbl, scale): - a = do_basic_test( - LTRMultiplier, secp128r1, secp128r1.generator, add, dbl, scale - ) + a = do_basic_test(LTRMultiplier, secp128r1, secp128r1.generator, add, dbl, scale) b = do_basic_test( LTRMultiplier, secp128r1, secp128r1.generator, add, dbl, scale, always=True ) @@ -100,22 +100,35 @@ def test_ltr(secp128r1, add, dbl, scale): assert_pt_equality(c, d, scale) -@pytest.mark.parametrize("add,dbl,scale", - [ - ("add-1998-cmo-2", "dbl-1998-cmo-2", "z"), - ("add-2015-rcb", "dbl-2015-rcb", None), - ("add-1998-cmo-2", "dbl-1998-cmo-2", None), - ]) +@pytest.mark.parametrize( + "add,dbl,scale", + [ + ("add-1998-cmo-2", "dbl-1998-cmo-2", "z"), + ("add-2015-rcb", "dbl-2015-rcb", None), + ("add-1998-cmo-2", "dbl-1998-cmo-2", None), + ], +) def test_doubleandadd(secp128r1, add, dbl, scale): a = do_basic_test( DoubleAndAddMultiplier, secp128r1, secp128r1.generator, add, dbl, scale ) b = do_basic_test( - DoubleAndAddMultiplier, secp128r1, secp128r1.generator, add, dbl, scale, direction=ProcessingDirection.RTL + DoubleAndAddMultiplier, + secp128r1, + secp128r1.generator, + add, + dbl, + scale, + direction=ProcessingDirection.RTL, ) c = do_basic_test( - DoubleAndAddMultiplier, secp128r1, secp128r1.generator, add, dbl, scale, - accumulation_order=AccumulationOrder.PeqPR + DoubleAndAddMultiplier, + secp128r1, + secp128r1.generator, + add, + dbl, + scale, + accumulation_order=AccumulationOrder.PeqPR, ) d = do_basic_test( DoubleAndAddMultiplier, @@ -132,13 +145,14 @@ def test_doubleandadd(secp128r1, add, dbl, scale): assert_pt_equality(c, d, scale) -@pytest.mark.parametrize("add,dbl,scale", - [ - ("add-1998-cmo-2", "dbl-1998-cmo-2", "z"), - ("add-2015-rcb", "dbl-2015-rcb", None), - ("add-1998-cmo-2", "dbl-1998-cmo-2", None), - ] - ) +@pytest.mark.parametrize( + "add,dbl,scale", + [ + ("add-1998-cmo-2", "dbl-1998-cmo-2", "z"), + ("add-2015-rcb", "dbl-2015-rcb", None), + ("add-1998-cmo-2", "dbl-1998-cmo-2", None), + ], +) def test_coron(secp128r1, add, dbl, scale): do_basic_test(CoronMultiplier, secp128r1, secp128r1.generator, add, dbl, scale) @@ -164,27 +178,31 @@ def test_ladder(curve25519): assert_pt_equality(a, b, True) -@pytest.mark.parametrize("add,dbl,scale", - [ - ("add-1998-cmo-2", "dbl-1998-cmo-2", "z"), - ("add-2015-rcb", "dbl-2015-rcb", None), - ("add-1998-cmo-2", "dbl-1998-cmo-2", None), - ]) +@pytest.mark.parametrize( + "add,dbl,scale", + [ + ("add-1998-cmo-2", "dbl-1998-cmo-2", "z"), + ("add-2015-rcb", "dbl-2015-rcb", None), + ("add-1998-cmo-2", "dbl-1998-cmo-2", None), + ], +) def test_simple_ladder(secp128r1, add, dbl, scale): do_basic_test( SimpleLadderMultiplier, secp128r1, secp128r1.generator, add, dbl, scale ) -@pytest.mark.parametrize("num,complete", - [ - (15, True), - (15, False), - (2355498743, True), - (2355498743, False), - (325385790209017329644351321912443757746, True), - (325385790209017329644351321912443757746, False), - ]) +@pytest.mark.parametrize( + "num,complete", + [ + (15, True), + (15, False), + (2355498743, True), + (2355498743, False), + (325385790209017329644351321912443757746, True), + (325385790209017329644351321912443757746, False), + ], +) def test_ladder_differential(curve25519, num, complete): ladder = LadderMultiplier( curve25519.curve.coordinate_model.formulas["ladd-1987-m"], @@ -206,27 +224,31 @@ def test_ladder_differential(curve25519, num, complete): assert InfinityPoint(curve25519.curve.coordinate_model) == differential.multiply(0) -@pytest.mark.parametrize("add,dbl,neg,scale", - [ - ("add-1998-cmo-2", "dbl-1998-cmo-2", "neg", "z"), - ("add-2015-rcb", "dbl-2015-rcb", "neg", None), - ("add-1998-cmo-2", "dbl-1998-cmo-2", "neg", None), - ]) +@pytest.mark.parametrize( + "add,dbl,neg,scale", + [ + ("add-1998-cmo-2", "dbl-1998-cmo-2", "neg", "z"), + ("add-2015-rcb", "dbl-2015-rcb", "neg", None), + ("add-1998-cmo-2", "dbl-1998-cmo-2", "neg", None), + ], +) def test_binary_naf(secp128r1, add, dbl, neg, scale): do_basic_test( BinaryNAFMultiplier, secp128r1, secp128r1.generator, add, dbl, scale, neg ) -@pytest.mark.parametrize("add,dbl,neg,width,scale", - [ - ("add-1998-cmo-2", "dbl-1998-cmo-2", "neg", 3, "z"), - ("add-1998-cmo-2", "dbl-1998-cmo-2", "neg", 3, None), - ("add-2015-rcb", "dbl-2015-rcb", "neg", 3, None), - ("add-1998-cmo-2", "dbl-1998-cmo-2", "neg", 5, "z"), - ("add-1998-cmo-2", "dbl-1998-cmo-2", "neg", 5, None), - ("add-2015-rcb", "dbl-2015-rcb", "neg", 5, None), - ]) +@pytest.mark.parametrize( + "add,dbl,neg,width,scale", + [ + ("add-1998-cmo-2", "dbl-1998-cmo-2", "neg", 3, "z"), + ("add-1998-cmo-2", "dbl-1998-cmo-2", "neg", 3, None), + ("add-2015-rcb", "dbl-2015-rcb", "neg", 3, None), + ("add-1998-cmo-2", "dbl-1998-cmo-2", "neg", 5, "z"), + ("add-1998-cmo-2", "dbl-1998-cmo-2", "neg", 5, None), + ("add-2015-rcb", "dbl-2015-rcb", "neg", 5, None), + ], +) def test_window_naf(secp128r1, add, dbl, neg, width, scale): formulas = get_formulas(secp128r1.curve.coordinate_model, add, dbl, neg, scale) mult = WindowNAFMultiplier(*formulas[:3], width, *formulas[3:]) @@ -247,12 +269,14 @@ def test_window_naf(secp128r1, add, dbl, neg, width, scale): assert_pt_equality(res_precompute, res, scale) -@pytest.mark.parametrize("add,dbl,width,scale", - [ - ("add-1998-cmo-2", "dbl-1998-cmo-2", 5, "z"), - ("add-2015-rcb", "dbl-2015-rcb", 5, None), - ("add-1998-cmo-2", "dbl-1998-cmo-2", 5, None), - ]) +@pytest.mark.parametrize( + "add,dbl,width,scale", + [ + ("add-1998-cmo-2", "dbl-1998-cmo-2", 5, "z"), + ("add-2015-rcb", "dbl-2015-rcb", 5, None), + ("add-1998-cmo-2", "dbl-1998-cmo-2", 5, None), + ], +) def test_fixed_window(secp128r1, add, dbl, width, scale): formulas = get_formulas(secp128r1.curve.coordinate_model, add, dbl, scale) mult = FixedWindowLTRMultiplier(*formulas[:2], width, *formulas[2:]) @@ -266,6 +290,27 @@ def test_fixed_window(secp128r1, add, dbl, width, scale): assert InfinityPoint(secp128r1.curve.coordinate_model) == mult.multiply(0) +@pytest.mark.parametrize( + "add,dbl,neg,width,scale", + [ + ("add-1998-cmo-2", "dbl-1998-cmo-2", "neg", 5, "z"), + ("add-2015-rcb", "dbl-2015-rcb", "neg", 5, None), + ("add-1998-cmo-2", "dbl-1998-cmo-2", "neg", 5, None), + ], +) +def test_booth_window(secp128r1, add, dbl, neg, width, scale): + formulas = get_formulas(secp128r1.curve.coordinate_model, add, dbl, neg, scale) + mult = WindowBoothMultiplier(*formulas[:3], width, *formulas[3:]) + mult.init(secp128r1, secp128r1.generator) + res = mult.multiply(157 * 789) + other = mult.multiply(157) + mult.init(secp128r1, other) + other = mult.multiply(789) + assert_pt_equality(res, other, scale) + mult.init(secp128r1, secp128r1.generator) + assert InfinityPoint(secp128r1.curve.coordinate_model) == mult.multiply(0) + + @pytest.fixture(params=["add-1998-cmo-2", "add-2015-rcb"]) def add(secp128r1, request): return secp128r1.curve.coordinate_model.formulas[request.param] @@ -276,53 +321,137 @@ def dbl(secp128r1, request): return secp128r1.curve.coordinate_model.formulas[request.param] -@pytest.mark.parametrize("num", [10, 2355498743, 325385790209017329644351321912443757746]) +@pytest.mark.parametrize( + "num", [10, 2355498743, 325385790209017329644351321912443757746] +) def test_basic_multipliers(secp128r1, num, add, dbl): neg = secp128r1.curve.coordinate_model.formulas["neg"] scale = secp128r1.curve.coordinate_model.formulas["z"] - ltr_options = {"always": (True, False), - "complete": (True, False), - "accumulation_order": tuple(AccumulationOrder)} - ltrs = [LTRMultiplier(add, dbl, scale, **dict(zip(ltr_options.keys(), combination))) for combination in product(*ltr_options.values())] + ltr_options = { + "always": (True, False), + "complete": (True, False), + "accumulation_order": tuple(AccumulationOrder), + } + ltrs = [ + LTRMultiplier(add, dbl, scale, **dict(zip(ltr_options.keys(), combination))) + for combination in product(*ltr_options.values()) + ] rtl_options = ltr_options - rtls = [RTLMultiplier(add, dbl, scale, **dict(zip(rtl_options.keys(), combination))) for combination in product(*rtl_options.values())] - bnaf_options = {"direction": tuple(ProcessingDirection), - "accumulation_order": tuple(AccumulationOrder)} - bnafs = [BinaryNAFMultiplier(add, dbl, neg, scale, **dict(zip(bnaf_options.keys(), combination))) for combination in product(*bnaf_options.values())] - wnaf_options = {"precompute_negation": (True, False), - "width": (3, 5), - "accumulation_order": tuple(AccumulationOrder)} - wnafs = [WindowNAFMultiplier(add, dbl, neg, scl=scale, **dict(zip(wnaf_options.keys(), combination))) for combination in product(*wnaf_options.values())] + rtls = [ + RTLMultiplier(add, dbl, scale, **dict(zip(rtl_options.keys(), combination))) + for combination in product(*rtl_options.values()) + ] + bnaf_options = { + "direction": tuple(ProcessingDirection), + "accumulation_order": tuple(AccumulationOrder), + } + bnafs = [ + BinaryNAFMultiplier( + add, dbl, neg, scale, **dict(zip(bnaf_options.keys(), combination)) + ) + for combination in product(*bnaf_options.values()) + ] + wnaf_options = { + "precompute_negation": (True, False), + "width": (3, 5), + "accumulation_order": tuple(AccumulationOrder), + } + wnafs = [ + WindowNAFMultiplier( + add, dbl, neg, scl=scale, **dict(zip(wnaf_options.keys(), combination)) + ) + for combination in product(*wnaf_options.values()) + ] + booth_options = { + "precompute_negation": (True, False), + "width": (3, 5), + "accumulation_order": tuple(AccumulationOrder), + } + booths = [ + WindowBoothMultiplier( + add, dbl, neg, scl=scale, **dict(zip(booth_options.keys(), combination)) + ) + for combination in product(*booth_options.values()) + ] ladder_options = {"complete": (True, False)} - ladders = [SimpleLadderMultiplier(add, dbl, scale, **dict(zip(ladder_options.keys(), combination))) for combination in product(*ladder_options.values())] - fixed_options = {"m": (5, 8), - "accumulation_order": tuple(AccumulationOrder)} - fixeds = [FixedWindowLTRMultiplier(add, dbl, scl=scale, **dict(zip(fixed_options.keys(), combination))) for combination in product(*fixed_options.values())] - sliding_options = {"width": (3, 5), - "recoding_direction": tuple(ProcessingDirection), - "accumulation_order": tuple(AccumulationOrder)} - slides = [SlidingWindowMultiplier(add, dbl, scl=scale, **dict(zip(sliding_options.keys(), combination))) for combination in product(*sliding_options.values())] - precomp_options = {"always": (True, False), - "complete": (True, False), - "direction": tuple(ProcessingDirection), - "accumulation_order": tuple(AccumulationOrder)} - precomps = [FullPrecompMultiplier(add, dbl, scl=scale, **dict(zip(precomp_options.keys(), combination))) for combination in product(*precomp_options.values())] - bgmw_options = {"width": (3, 5), - "direction": tuple(ProcessingDirection), - "accumulation_order": tuple(AccumulationOrder)} - bgmws = [BGMWMultiplier(add, dbl, scl=scale, **dict(zip(bgmw_options.keys(), combination))) for combination in product(*bgmw_options.values())] - comb_options = {"width": (2, 3, 5), - "accumulation_order": tuple(AccumulationOrder)} - combs = [CombMultiplier(add, dbl, scl=scale, **dict(zip(comb_options.keys(), combination))) for combination in product(*comb_options.values())] + ladders = [ + SimpleLadderMultiplier( + add, dbl, scale, **dict(zip(ladder_options.keys(), combination)) + ) + for combination in product(*ladder_options.values()) + ] + fixed_options = {"m": (5, 8), "accumulation_order": tuple(AccumulationOrder)} + fixeds = [ + FixedWindowLTRMultiplier( + add, dbl, scl=scale, **dict(zip(fixed_options.keys(), combination)) + ) + for combination in product(*fixed_options.values()) + ] + sliding_options = { + "width": (3, 5), + "recoding_direction": tuple(ProcessingDirection), + "accumulation_order": tuple(AccumulationOrder), + } + slides = [ + SlidingWindowMultiplier( + add, dbl, scl=scale, **dict(zip(sliding_options.keys(), combination)) + ) + for combination in product(*sliding_options.values()) + ] + precomp_options = { + "always": (True, False), + "complete": (True, False), + "direction": tuple(ProcessingDirection), + "accumulation_order": tuple(AccumulationOrder), + } + precomps = [ + FullPrecompMultiplier( + add, dbl, scl=scale, **dict(zip(precomp_options.keys(), combination)) + ) + for combination in product(*precomp_options.values()) + ] + bgmw_options = { + "width": (3, 5), + "direction": tuple(ProcessingDirection), + "accumulation_order": tuple(AccumulationOrder), + } + bgmws = [ + BGMWMultiplier( + add, dbl, scl=scale, **dict(zip(bgmw_options.keys(), combination)) + ) + for combination in product(*bgmw_options.values()) + ] + comb_options = {"width": (2, 3, 5), "accumulation_order": tuple(AccumulationOrder)} + combs = [ + CombMultiplier( + add, dbl, scl=scale, **dict(zip(comb_options.keys(), combination)) + ) + for combination in product(*comb_options.values()) + ] - mults: Sequence[ScalarMultiplier] = ltrs + rtls + bnafs + wnafs + [CoronMultiplier(add, dbl, scale)] + ladders + fixeds + slides + precomps + bgmws + combs + mults: Sequence[ScalarMultiplier] = ( + ltrs + + rtls + + bnafs + + wnafs + + booths + + [CoronMultiplier(add, dbl, scale)] + + ladders + + fixeds + + slides + + precomps + + bgmws + + combs + ) results = [] for mult in mults: mult.init(secp128r1, secp128r1.generator) res = mult.multiply(num) if results: - assert res == results[-1], f"Points not equal {res} != {results[-1]} for mult = {mult}" + assert ( + res == results[-1] + ), f"Points not equal {res} != {results[-1]} for mult = {mult}" results.append(res) diff --git a/test/ec/test_scalar.py b/test/ec/test_scalar.py index 493690a..9c6cb60 100644 --- a/test/ec/test_scalar.py +++ b/test/ec/test_scalar.py @@ -1,4 +1,13 @@ -from pyecsca.ec.scalar import convert_base, sliding_window_ltr, sliding_window_rtl, wnaf, naf +from pyecsca.ec.scalar import ( + convert_base, + sliding_window_ltr, + sliding_window_rtl, + wnaf, + naf, + booth, + booth_word, + booth_window, +) def test_convert(): @@ -14,3 +23,18 @@ def test_sliding_window(): def test_nafs(): i = 0b1100110101001101011011 assert naf(i) == wnaf(i, 2) + + +def test_booth(): + assert booth(0b101) == [-1, 1, -1, 1] + for i in range(2**6): + bw = booth_word(i, 5) + # verified with BoringSSL recoding impl. (ec_GFp_nistp_recode_scalar_bits) + if i <= 31: + assert bw == (i + 1) // 2 + else: + assert bw == -((64 - i) // 2) + s = 0x12345678123456781234567812345678123456781234567812345678 + bw = booth_window(s, 5, 224) + # verified with BoringSSL ec_GFp_nistp224_point_mul + assert bw == [1, 4, 13, 3, -10, 15, 0, 9, 3, 9, -10, -12, -8, 2, 9, -6, 5, 13, -2, 1, -14, 7, -15, 11, 8, -16, 5, -14, -12, 11, -6, -4, 1, 4, 13, 3, -10, 15, 0, 9, 3, 9, -10, -12, -8] diff --git a/test/sca/test_rpa.py b/test/sca/test_rpa.py index f09a1a1..39c281d 100644 --- a/test/sca/test_rpa.py +++ b/test/sca/test_rpa.py @@ -12,12 +12,24 @@ from pyecsca.ec.mult import ( RTLMultiplier, BinaryNAFMultiplier, WindowNAFMultiplier, - SimpleLadderMultiplier, AccumulationOrder, ProcessingDirection, SlidingWindowMultiplier, FixedWindowLTRMultiplier, - FullPrecompMultiplier, BGMWMultiplier, CombMultiplier, + SimpleLadderMultiplier, + AccumulationOrder, + ProcessingDirection, + SlidingWindowMultiplier, + FixedWindowLTRMultiplier, + FullPrecompMultiplier, + BGMWMultiplier, + CombMultiplier, + WindowBoothMultiplier, ) from pyecsca.ec.params import DomainParameters from pyecsca.ec.point import Point -from pyecsca.sca.re.rpa import MultipleContext, rpa_point_0y, rpa_point_x0, rpa_distinguish +from pyecsca.sca.re.rpa import ( + MultipleContext, + rpa_point_0y, + rpa_point_x0, + rpa_distinguish, +) @pytest.fixture() @@ -47,18 +59,18 @@ def neg(coords): @pytest.fixture() def rpa_params(model, coords): - p = 0x85d265945a4f5681 - a = Mod(0x7fc57b4110698bc0, p) - b = Mod(0x37113ea591b04527, p) - gx = Mod(0x80d2d78fddb97597, p) - gy = Mod(0x5586d818b7910930, p) + p = 0x85D265945A4F5681 + a = Mod(0x7FC57B4110698BC0, p) + b = Mod(0x37113EA591B04527, p) + gx = Mod(0x80D2D78FDDB97597, p) + gy = Mod(0x5586D818B7910930, p) # (0x4880bcf620852a54, 0) RPA point # (0, 0x6bed3155c9ada064) RPA point infty = Point(coords, X=Mod(0, p), Y=Mod(1, p), Z=Mod(0, p)) g = Point(coords, X=gx, Y=gy, Z=Mod(1, p)) curve = EllipticCurve(model, coords, p, infty, dict(a=a, b=b)) - return DomainParameters(curve, g, 0x85d265932d90785c, 1) + return DomainParameters(curve, g, 0x85D265932D90785C, 1) def test_x0_point(rpa_params): @@ -80,25 +92,63 @@ def test_distinguish(secp128r1, add, dbl, neg): RTLMultiplier(add, dbl, None, False, AccumulationOrder.PeqPR, True), RTLMultiplier(add, dbl, None, True, AccumulationOrder.PeqPR, False), SimpleLadderMultiplier(add, dbl, None, True, True), - BinaryNAFMultiplier(add, dbl, neg, None, ProcessingDirection.LTR, AccumulationOrder.PeqPR, True), - WindowNAFMultiplier(add, dbl, neg, 3, None, AccumulationOrder.PeqPR, True, True), - WindowNAFMultiplier(add, dbl, neg, 4, None, AccumulationOrder.PeqPR, True, True), + BinaryNAFMultiplier( + add, dbl, neg, None, ProcessingDirection.LTR, AccumulationOrder.PeqPR, True + ), + WindowNAFMultiplier( + add, dbl, neg, 3, None, AccumulationOrder.PeqPR, True, True + ), + WindowNAFMultiplier( + add, dbl, neg, 4, None, AccumulationOrder.PeqPR, True, True + ), + WindowBoothMultiplier( + add, dbl, neg, 4, None, AccumulationOrder.PeqPR, True, True + ), # WindowNAFMultiplier(add, dbl, neg, 4, None, AccumulationOrder.PeqPR, False, True), - SlidingWindowMultiplier(add, dbl, 3, None, ProcessingDirection.LTR, AccumulationOrder.PeqPR, True), - SlidingWindowMultiplier(add, dbl, 5, None, ProcessingDirection.LTR, AccumulationOrder.PeqPR, True), + SlidingWindowMultiplier( + add, dbl, 3, None, ProcessingDirection.LTR, AccumulationOrder.PeqPR, True + ), + SlidingWindowMultiplier( + add, dbl, 5, None, ProcessingDirection.LTR, AccumulationOrder.PeqPR, True + ), FixedWindowLTRMultiplier(add, dbl, 4, None, AccumulationOrder.PeqPR, True), FixedWindowLTRMultiplier(add, dbl, 5, None, AccumulationOrder.PeqPR, True), - FullPrecompMultiplier(add, dbl, None, True, ProcessingDirection.LTR, AccumulationOrder.PeqPR, True, True), - FullPrecompMultiplier(add, dbl, None, False, ProcessingDirection.LTR, AccumulationOrder.PeqPR, True, True), + FullPrecompMultiplier( + add, + dbl, + None, + True, + ProcessingDirection.LTR, + AccumulationOrder.PeqPR, + True, + True, + ), + FullPrecompMultiplier( + add, + dbl, + None, + False, + ProcessingDirection.LTR, + AccumulationOrder.PeqPR, + True, + True, + ), # FullPrecompMultiplier(add, dbl, None, False, ProcessingDirection.RTL, AccumulationOrder.PeqPR, True, True), - BGMWMultiplier(add, dbl, 3, None, ProcessingDirection.LTR, AccumulationOrder.PeqPR, True), - BGMWMultiplier(add, dbl, 5, None, ProcessingDirection.LTR, AccumulationOrder.PeqPR, True), + BGMWMultiplier( + add, dbl, 3, None, ProcessingDirection.LTR, AccumulationOrder.PeqPR, True + ), + BGMWMultiplier( + add, dbl, 5, None, ProcessingDirection.LTR, AccumulationOrder.PeqPR, True + ), CombMultiplier(add, dbl, 3, None, AccumulationOrder.PeqPR, True), - CombMultiplier(add, dbl, 5, None, AccumulationOrder.PeqPR, True) + CombMultiplier(add, dbl, 5, None, AccumulationOrder.PeqPR, True), ] for real_mult in multipliers: + def simulated_oracle(scalar, affine_point): - point = affine_point.to_model(secp128r1.curve.coordinate_model, secp128r1.curve) + point = affine_point.to_model( + secp128r1.curve.coordinate_model, secp128r1.curve + ) with local(MultipleContext()) as ctx: real_mult.init(secp128r1, point) real_mult.multiply(scalar) diff --git a/test/sca/test_structural.py b/test/sca/test_structural.py deleted file mode 100644 index 970e4fc..0000000 --- a/test/sca/test_structural.py +++ /dev/null @@ -1,315 +0,0 @@ -import pytest -from importlib_resources import files, as_file -import test.data.formulas -from pyecsca.ec.formula import ( - AdditionEFDFormula, - LadderEFDFormula, - DoublingEFDFormula, - AdditionFormula, - DoublingFormula, - LadderFormula, -) -from pyecsca.ec.model import ShortWeierstrassModel, MontgomeryModel, TwistedEdwardsModel -from pyecsca.ec.params import get_params -from pyecsca.sca.re.structural import formula_similarity - - -def test_formula_similarity(secp128r1): - add_bl = secp128r1.curve.coordinate_model.formulas["add-2007-bl"] - add_rcb = secp128r1.curve.coordinate_model.formulas["add-2015-rcb"] - out = formula_similarity(add_bl, add_rcb) - assert out["output"] == 0 - assert out["ivs"] < 0.5 - out_same = formula_similarity(add_bl, add_bl) - assert out_same["output"] == 1 - assert out_same["ivs"] == 1 - - -@pytest.mark.parametrize( - "name,model,coords,param_spec,formula_type", - [ - [ - "add-bc-r1rv76-jac", - ShortWeierstrassModel, - "jacobian", - ("secg", "secp128r1"), - AdditionEFDFormula, - ], - [ - "add-bc-r1rv76-mod", - ShortWeierstrassModel, - "modified", - ("secg", "secp128r1"), - AdditionEFDFormula, - ], - [ - "dbl-bc-r1rv76-jac", - ShortWeierstrassModel, - "jacobian", - ("secg", "secp128r1"), - DoublingEFDFormula, - ], - [ - "dbl-bc-r1rv76-mod", - ShortWeierstrassModel, - "modified", - ("secg", "secp128r1"), - DoublingEFDFormula, - ], - [ - "dbl-bc-r1rv76-x25519", - MontgomeryModel, - "xz", - ("other", "Curve25519"), - DoublingEFDFormula, - ], - [ - "ladd-bc-r1rv76-x25519", - MontgomeryModel, - "xz", - ("other", "Curve25519"), - LadderEFDFormula, - ], - [ - "dbl-boringssl-p224", - ShortWeierstrassModel, - "jacobian-3", - ("secg", "secp224r1"), - DoublingEFDFormula, - ], - [ - "add-boringssl-p224", - ShortWeierstrassModel, - "jacobian-3", - ("secg", "secp224r1"), - AdditionEFDFormula, - ], - [ - "add-libressl-v382", - ShortWeierstrassModel, - "jacobian", - ("secg", "secp128r1"), - AdditionEFDFormula, - ], - [ - "dbl-libressl-v382", - ShortWeierstrassModel, - "jacobian", - ("secg", "secp128r1"), - DoublingEFDFormula, - ], - [ - "dbl-secp256k1-v040", - ShortWeierstrassModel, - "jacobian", - ("secg", "secp256k1"), - DoublingEFDFormula, - ], - [ - "add-openssl-z256", - ShortWeierstrassModel, - "jacobian-3", - ("secg", "secp256r1"), - AdditionEFDFormula, - ], - [ - "add-openssl-z256a", - ShortWeierstrassModel, - "jacobian-3", - ("secg", "secp256r1"), - AdditionEFDFormula, - ], - [ - "ladd-openssl-x25519", - MontgomeryModel, - "xz", - ("other", "Curve25519"), - LadderEFDFormula, - ], - [ - "ladd-hacl-x25519", - MontgomeryModel, - "xz", - ("other", "Curve25519"), - LadderEFDFormula, - ], - [ - "dbl-hacl-x25519", - MontgomeryModel, - "xz", - ("other", "Curve25519"), - DoublingEFDFormula, - ], - [ - "dbl-sunec-v21", - ShortWeierstrassModel, - "projective-3", - ("secg", "secp256r1"), - DoublingEFDFormula, - ], - [ - "add-sunec-v21", - ShortWeierstrassModel, - "projective-3", - ("secg", "secp256r1"), - AdditionEFDFormula, - ], - [ - "add-sunec-v21-ed25519", - TwistedEdwardsModel, - "extended", - ("other", "Ed25519"), - AdditionEFDFormula, - ], - [ - "dbl-sunec-v21-ed25519", - TwistedEdwardsModel, - "extended", - ("other", "Ed25519"), - DoublingEFDFormula, - ], - [ - "ladd-rfc7748", - MontgomeryModel, - "xz", - ("other", "Curve25519"), - LadderEFDFormula, - ], - [ - "add-bearssl-v06", - ShortWeierstrassModel, - "jacobian", - ("secg", "secp256r1"), - AdditionEFDFormula, - ], - [ - "dbl-bearssl-v06", - ShortWeierstrassModel, - "jacobian", - ("secg", "secp256r1"), - DoublingEFDFormula, - ], - [ - "add-libgcrypt-v1102", - ShortWeierstrassModel, - "jacobian", - ("secg", "secp256r1"), - AdditionEFDFormula, - ], - [ - "dbl-libgcrypt-v1102", - ShortWeierstrassModel, - "jacobian", - ("secg", "secp256r1"), - DoublingEFDFormula, - ], - [ - "ladd-go-1214", - MontgomeryModel, - "xz", - ("other", "Curve25519"), - LadderEFDFormula, - ], - [ - "add-gecc-322", - ShortWeierstrassModel, - "jacobian-3", - ("secg", "secp256r1"), - AdditionEFDFormula, - ], - [ - "dbl-gecc-321", - ShortWeierstrassModel, - "jacobian-3", - ("secg", "secp256r1"), - DoublingEFDFormula, - ], - [ - "ladd-boringssl-x25519", - MontgomeryModel, - "xz", - ("other", "Curve25519"), - LadderEFDFormula, - ], - [ - "dbl-ipp-x25519", - MontgomeryModel, - "xz", - ("other", "Curve25519"), - DoublingEFDFormula, - ], - [ - "ladd-botan-x25519", - MontgomeryModel, - "xz", - ("other", "Curve25519"), - LadderEFDFormula, - ], - ], -) -def test_formula_correctness(name, model, coords, param_spec, formula_type): - model = model() - coordinate_model = model.coordinates[coords] - with as_file(files(test.data.formulas).joinpath(name)) as meta_path, as_file( - files(test.data.formulas).joinpath(name + ".op3") - ) as op3_path: - formula = formula_type(meta_path, op3_path, name, coordinate_model) - params = get_params(*param_spec, coords) - scale = coordinate_model.formulas.get("z", None) - if scale is None: - scale = coordinate_model.formulas.get("scale", None) - for _ in range(10): - Paff = params.curve.affine_random() - P2aff = params.curve.affine_double(Paff) - Qaff = params.curve.affine_random() - Q2aff = params.curve.affine_double(Qaff) - Raff = params.curve.affine_add(Paff, Qaff) - R2aff = params.curve.affine_double(Raff) - QRaff = params.curve.affine_add(Qaff, Raff) - P = Paff.to_model(coordinate_model, params.curve) - P2 = P2aff.to_model(coordinate_model, params.curve) - Q = Qaff.to_model(coordinate_model, params.curve) - Q2 = Q2aff.to_model(coordinate_model, params.curve) - R = Raff.to_model(coordinate_model, params.curve) - R2 = R2aff.to_model(coordinate_model, params.curve) # noqa - QR = QRaff.to_model(coordinate_model, params.curve) - inputs = (P, Q, R)[: formula.num_inputs] - res = formula(params.curve.prime, *inputs, **params.curve.parameters) - if issubclass(formula_type, AdditionFormula): - try: - assert res[0].to_affine() == Raff - except NotImplementedError: - assert ( - scale(params.curve.prime, res[0], **params.curve.parameters)[0] == R - ) - elif issubclass(formula_type, DoublingFormula): - try: - assert res[0].to_affine() == P2aff - except NotImplementedError: - assert ( - scale(params.curve.prime, res[0], **params.curve.parameters)[0] - == P2 - ) - elif issubclass(formula_type, LadderFormula): - try: - assert res[0].to_affine() == Q2aff - assert res[1].to_affine() == QRaff - except NotImplementedError: - # print(scale(params.curve.prime, res[0], **params.curve.parameters)[0]) - # print(scale(params.curve.prime, res[1], **params.curve.parameters)[0]) - # print(P) - # print(Q) - # print(R) - # print(P2) - # print(Q2) - # print(R2) - # print(QR) - # print("------------------------------------") - assert ( - scale(params.curve.prime, res[1], **params.curve.parameters)[0] - == QR - ) - assert ( - scale(params.curve.prime, res[0], **params.curve.parameters)[0] - == Q2 - ) |
