aboutsummaryrefslogtreecommitdiffhomepage
path: root/pyecsca/codegen/render.py
diff options
context:
space:
mode:
authorJ08nY2025-10-02 20:17:34 +0200
committerJ08nY2025-10-02 20:17:34 +0200
commitd22476cf8135282214e596ed47e2746c7f403735 (patch)
treebb6f54a6b0a232a90a53559b835492a47777ff0a /pyecsca/codegen/render.py
parentcfef5c9aa4f727027731fac56b384e7e46ae078b (diff)
downloadpyecsca-codegen-d22476cf8135282214e596ed47e2746c7f403735.tar.gz
pyecsca-codegen-d22476cf8135282214e596ed47e2746c7f403735.tar.zst
pyecsca-codegen-d22476cf8135282214e596ed47e2746c7f403735.zip
More docs.
Diffstat (limited to 'pyecsca/codegen/render.py')
-rw-r--r--pyecsca/codegen/render.py341
1 files changed, 272 insertions, 69 deletions
diff --git a/pyecsca/codegen/render.py b/pyecsca/codegen/render.py
index b1c1477..9bc01b1 100644
--- a/pyecsca/codegen/render.py
+++ b/pyecsca/codegen/render.py
@@ -1,4 +1,5 @@
"""Rendering and building of implementation configurations."""
+
import os
import shutil
import subprocess
@@ -10,7 +11,13 @@ from typing import Optional, List, Set, Mapping, MutableMapping, Any, Tuple
from jinja2 import Environment, PackageLoader
from importlib_resources import files
from public import public
-from pyecsca.ec.configuration import HashType, RandomMod, Reduction, Multiplication, Squaring
+from pyecsca.ec.configuration import (
+ HashType,
+ RandomMod,
+ Reduction,
+ Multiplication,
+ Squaring,
+)
from pyecsca.ec.coordinates import CoordinateModel
from pyecsca.ec.formula import Formula
from pyecsca.ec.model import CurveModel
@@ -30,44 +37,64 @@ from pyecsca.ec.mult import (
BGMWMultiplier,
CombMultiplier,
AccumulationOrder,
- ProcessingDirection, WindowBoothMultiplier
+ ProcessingDirection,
+ WindowBoothMultiplier,
)
from pyecsca.ec.op import OpType, CodeOp
from pyecsca.codegen.common import Platform, DeviceConfiguration
-env = Environment(
- loader=PackageLoader("pyecsca.codegen")
-)
+env = Environment(loader=PackageLoader("pyecsca.codegen"))
+# Make some Python functions available in the templates.
env.globals["isinstance"] = isinstance
env.globals["bin"] = bin
env.globals["AccumulationOrder"] = AccumulationOrder
env.globals["ProcessingDirection"] = ProcessingDirection
-def render_op(op: OpType, result: str, left: str, right: str, mod: str, red: str) -> Optional[str]:
+def render_op(
+ op: OpType, result: str, left: str, right: str, mod: str, red: str
+) -> Optional[str]:
"""
Render an operation `op` (add, sub, neg, ...) into a C string.
The operation will use `left` (optional) and `right` operands, be over `mod` modulus,
use the `red` reduction context and leave the result in `result`. All of these need to
be valid C variable names.
+
+ :param op: The operation type.
+ :param result: The variable name to store the result in.
+ :param left: The left operand variable name (if any).
+ :param right: The right operand variable name (if any).
+ :param mod: The modulus variable name.
+ :param red: The reduction context variable name.
+ :return: The rendered C source code as a string, or None if the operation is not supported.
"""
if op == OpType.Add:
- return "bn_red_add(&{}, &{}, &{}, &{}, &{});".format(left, right, mod, red, result)
+ return "bn_red_add(&{}, &{}, &{}, &{}, &{});".format(
+ left, right, mod, red, result
+ )
elif op == OpType.Sub:
- return "bn_red_sub(&{}, &{}, &{}, &{}, &{});".format(left, right, mod, red, result)
+ return "bn_red_sub(&{}, &{}, &{}, &{}, &{});".format(
+ left, right, mod, red, result
+ )
elif op == OpType.Neg:
return "bn_red_neg(&{}, &{}, &{}, &{});".format(right, mod, red, result)
elif op == OpType.Mult:
- return "bn_red_mul(&{}, &{}, &{}, &{}, &{});".format(left, right, mod, red, result)
+ return "bn_red_mul(&{}, &{}, &{}, &{}, &{});".format(
+ left, right, mod, red, result
+ )
elif op == OpType.Div or op == OpType.Inv:
- return "bn_red_div(&{}, &{}, &{}, &{}, &{});".format(left, right, mod, red, result)
+ return "bn_red_div(&{}, &{}, &{}, &{}, &{});".format(
+ left, right, mod, red, result
+ )
elif op == OpType.Sqr:
return "bn_red_sqr(&{}, &{}, &{}, &{});".format(left, mod, red, result)
elif op == OpType.Pow:
- return "bn_red_pow(&{}, &{}, &{}, &{}, &{});".format(left, right, mod, red, result)
+ return "bn_red_pow(&{}, &{}, &{}, &{}, &{});".format(
+ left, right, mod, red, result
+ )
elif op == OpType.Id:
return "bn_copy(&{}, &{});".format(left, result)
else:
@@ -84,29 +111,57 @@ def render_defs(model: CurveModel, coords: CoordinateModel) -> str:
These change depending on the number (and name) of curve parameters and number
(and name) of point coordinates.
+
+ :param model: The curve model to render.
+ :param coords: The coordinate model to render.
+ :return: The rendered C source code as a string.
"""
- return env.get_template("defs.h").render(params=model.parameter_names,
- variables=coords.variables)
+ return env.get_template("defs.h").render(
+ params=model.parameter_names, variables=coords.variables
+ )
def render_curve_impl(model: CurveModel) -> str:
"""
- Render the defs.h file with basic curve operations.
+ Render the curve.c file with basic curve operations.
These change depending on the number (and name) of curve parameters.
+
+ :param model: The curve model to render.
+ :return: The rendered C source code as a string.
"""
return env.get_template("curve.c").render(params=model.parameter_names)
-def transform_ops(ops: List[CodeOp], parameters: List[str], outputs: Set[str],
- renames: Mapping[str, str] = None) -> MutableMapping[Any, Any]:
+def transform_ops(
+ ops: List[CodeOp],
+ parameters: List[str],
+ outputs: Set[str],
+ renames: Mapping[str, str] = None,
+) -> MutableMapping[Any, Any]:
"""
Transform a list of CodeOps, parameters, outputs and renames into a mapping
that will be used by the ops template macros to render the ops.
This tracks allocations and frees, also creates a mapping of constants
to variable names.
+
+ Useful when you have a list of operations and want to render them,
+ such as when executing a formula or coordinate transformation.
+
+ :param ops: The list of operations to transform.
+ :param parameters: The list of parameter names that are inputs to the operations.
+ :param outputs: The set of output names that should not be freed.
+ :param renames: Optional mapping of variable names to renamed versions.
+ :return: A mapping with keys:
+ - allocations: List of variable names that need to be allocated.
+ - initializations: Mapping of variable names to (constant, encode) tuples for initialization.
+ - const_mapping: Mapping of (constant, encode) tuples to variable names.
+ - operations: List of (op, result, left, right) tuples for the operations
+ - frees: List of variable names that need to be freed.
+ - returns: Mapping of output variable names to renamed versions.
"""
+
def rename(name: str):
if renames is not None and name not in outputs:
return renames.get(name, name)
@@ -161,13 +216,26 @@ def transform_ops(ops: List[CodeOp], parameters: List[str], outputs: Set[str],
if r_from in outputs:
returns[r_from] = r_to
- return dict(allocations=allocations,
- initializations=initializations,
- const_mapping=const_mapping, operations=mapped,
- frees=frees, returns=returns)
+ return dict(
+ allocations=allocations,
+ initializations=initializations,
+ const_mapping=const_mapping,
+ operations=mapped,
+ frees=frees,
+ returns=returns,
+ )
-def render_coords_impl(coords: CoordinateModel, accumulation_order: Optional[AccumulationOrder]) -> str:
+def render_coords_impl(
+ coords: CoordinateModel, accumulation_order: Optional[AccumulationOrder]
+) -> str:
+ """
+ Render the point.c file with coordinate operations for a given curve model.
+
+ :param coords: The coordinate model containing variables and satisfying equations.
+ :param accumulation_order: The accumulation order for scalar multiplication, if any.
+ :return: Rendered C source code as a string.
+ """
ops = []
for s in coords.satisfying:
try:
@@ -179,24 +247,45 @@ def render_coords_impl(coords: CoordinateModel, accumulation_order: Optional[Acc
renames[variable] = "point->{}".format(variable)
for param in coords.curve_model.parameter_names:
renames[param] = "curve->{}".format(param)
- namespace = transform_ops(ops, coords.curve_model.parameter_names,
- coords.curve_model.coordinate_names, renames)
+ namespace = transform_ops(
+ ops,
+ coords.curve_model.parameter_names,
+ coords.curve_model.coordinate_names,
+ renames,
+ )
returns = namespace["returns"]
namespace["returns"] = {}
frees = namespace["frees"]
namespace["frees"] = {}
- return env.get_template("point.c").render(variables=coords.variables, **namespace,
- to_affine_rets=returns, to_affine_frees=frees,
- accumulation_order=accumulation_order)
+ return env.get_template("point.c").render(
+ variables=coords.variables,
+ **namespace,
+ to_affine_rets=returns,
+ to_affine_frees=frees,
+ accumulation_order=accumulation_order,
+ )
def render_formulas_impl(formulas: Set[Formula]) -> str:
+ """
+ Render the formulas.c file with formula dispatch functions.
+
+ :param formulas: The set of formulas to render.
+ :return: The rendered C source code as a string.
+ """
names = {formula.shortname for formula in formulas}
return env.get_template("formulas.c").render(names=names)
def render_formula_impl(formula: Formula, short_circuit: bool = False) -> str:
+ """
+ Render a single formula into its own C file.
+
+ :param formula: The formula to render.
+ :param short_circuit: Whether to short-circuit on zero inputs.
+ :return: The rendered C source code as a string.
+ """
template = env.get_template(f"formula_{formula.shortname}.c")
inputs = ["one", "other", "diff"]
outputs = ["out_one", "out_other"]
@@ -211,51 +300,115 @@ def render_formula_impl(formula: Formula, short_circuit: bool = False) -> str:
var = output[0]
num = int(output[1:]) - formula.output_index
renames[output] = "{}->{}".format(outputs[num], var)
- namespace = transform_ops(formula.code, formula.coordinate_model.curve_model.parameter_names,
- formula.outputs, renames)
+ namespace = transform_ops(
+ formula.code,
+ formula.coordinate_model.curve_model.parameter_names,
+ formula.outputs,
+ renames,
+ )
namespace["short_circuit"] = short_circuit
namespace["formula"] = formula
return template.render(namespace)
def render_scalarmult_impl(scalarmult: ScalarMultiplier) -> str:
- return env.get_template("mult.c").render(scalarmult=scalarmult, LTRMultiplier=LTRMultiplier,
- RTLMultiplier=RTLMultiplier,
- CoronMultiplier=CoronMultiplier,
- LadderMultiplier=LadderMultiplier,
- SimpleLadderMultiplier=SimpleLadderMultiplier,
- DifferentialLadderMultiplier=DifferentialLadderMultiplier,
- BinaryNAFMultiplier=BinaryNAFMultiplier,
- WindowNAFMultiplier=WindowNAFMultiplier,
- WindowBoothMultiplier=WindowBoothMultiplier,
- SlidingWindowMultiplier=SlidingWindowMultiplier,
- FixedWindowLTRMultiplier=FixedWindowLTRMultiplier,
- FullPrecompMultiplier=FullPrecompMultiplier,
- BGMWMultiplier=BGMWMultiplier,
- CombMultiplier=CombMultiplier)
+ """
+ Render the mult.c file with scalar multiplication implementation.
+
+ :param scalarmult: The scalar multiplication algorithm to render.
+ :return: The rendered C source code as a string.
+ """
+ return env.get_template("mult.c").render(
+ scalarmult=scalarmult,
+ LTRMultiplier=LTRMultiplier,
+ RTLMultiplier=RTLMultiplier,
+ CoronMultiplier=CoronMultiplier,
+ LadderMultiplier=LadderMultiplier,
+ SimpleLadderMultiplier=SimpleLadderMultiplier,
+ DifferentialLadderMultiplier=DifferentialLadderMultiplier,
+ BinaryNAFMultiplier=BinaryNAFMultiplier,
+ WindowNAFMultiplier=WindowNAFMultiplier,
+ WindowBoothMultiplier=WindowBoothMultiplier,
+ SlidingWindowMultiplier=SlidingWindowMultiplier,
+ FixedWindowLTRMultiplier=FixedWindowLTRMultiplier,
+ FullPrecompMultiplier=FullPrecompMultiplier,
+ BGMWMultiplier=BGMWMultiplier,
+ CombMultiplier=CombMultiplier,
+ )
def render_action() -> str:
+ """
+ Render the action.c file with the action macros.
+
+ :return: The rendered C source code as a string.
+ """
return env.get_template("action.c").render()
def render_rand() -> str:
+ """
+ Render the rand.c file with the random number generation code.
+
+ :return: The rendered C source code as a string.
+ """
return env.get_template("rand.c").render()
-def render_main(model: CurveModel, coords: CoordinateModel, keygen: bool, ecdh: bool,
- ecdsa: bool) -> str:
- return env.get_template("main.c").render(model=model, coords=coords,
- curve_variables=coords.variables,
- curve_parameters=model.parameter_names,
- keygen=keygen, ecdh=ecdh, ecdsa=ecdsa)
+def render_main(
+ model: CurveModel, coords: CoordinateModel, keygen: bool, ecdh: bool, ecdsa: bool
+) -> str:
+ """
+ Render the main.c file with the main function and high-level operations.
+
+ :param model: The curve model.
+ :param coords: The coordinate model.
+ :param keygen: Whether to include key generation.
+ :param ecdh: Whether to include ECDH.
+ :param ecdsa: Whether to include ECDSA.
+ :return: The rendered C source code as a string.
+ """
+ return env.get_template("main.c").render(
+ model=model,
+ coords=coords,
+ curve_variables=coords.variables,
+ curve_parameters=model.parameter_names,
+ keygen=keygen,
+ ecdh=ecdh,
+ ecdsa=ecdsa,
+ )
+
+def render_makefile(
+ platform: Platform,
+ hash_type: HashType,
+ mod_rand: RandomMod,
+ reduction: Reduction,
+ mul: Multiplication,
+ sqr: Squaring,
+ defines: Optional[MutableMapping[str, Any]],
+) -> str:
+ """
+ Render the Makefile with the given configuration options.
-def render_makefile(platform: Platform, hash_type: HashType, mod_rand: RandomMod,
- reduction: Reduction, mul: Multiplication, sqr: Squaring, defines: Optional[MutableMapping[str, Any]]) -> str:
- return env.get_template("Makefile").render(platform=str(platform), hash_type=str(hash_type),
- mod_rand=str(mod_rand), reduction=str(reduction),
- mul=str(mul), sqr=str(sqr), defines=defines)
+ :param platform: The target platform.
+ :param hash_type: The hash type to use.
+ :param mod_rand: The random sampling method.
+ :param reduction: The modular reduction method.
+ :param mul: The multiplication method.
+ :param sqr: The squaring method.
+ :param defines: Optional mapping of additional defines to include.
+ :return: The rendered Makefile as a string.
+ """
+ return env.get_template("Makefile").render(
+ platform=str(platform),
+ hash_type=str(hash_type),
+ mod_rand=str(mod_rand),
+ reduction=str(reduction),
+ mul=str(mul),
+ sqr=str(sqr),
+ defines=defines,
+ )
def save_render(dir: str, fname: str, rendered: str):
@@ -266,40 +419,89 @@ def save_render(dir: str, fname: str, rendered: str):
@public
def render(config: DeviceConfiguration) -> Tuple[str, str, str]:
"""
- Render the `config` into a temporary directory.
+ Render the `config`dispatching into a temporary directory.
:param config: The configuration to render.
:return: The temporary directory, the elf-file name, the hex-file name.
"""
temp = tempfile.mkdtemp()
- symlinks = ["asn1", "bn", "hal", "hash", "prng", "simpleserial", "tommath", "fat.h",
- "rand.h", "point.h", "curve.h", "mult.h", "formulas.h", "action.h", "Makefile.inc"]
+ symlinks = [
+ "asn1",
+ "bn",
+ "hal",
+ "hash",
+ "prng",
+ "simpleserial",
+ "tommath",
+ "fat.h",
+ "rand.h",
+ "point.h",
+ "curve.h",
+ "mult.h",
+ "formulas.h",
+ "action.h",
+ "Makefile.inc",
+ ]
for sym in symlinks:
os.symlink(files("pyecsca.codegen").joinpath(sym), path.join(temp, sym))
gen_dir = path.join(temp, "gen")
makedirs(gen_dir, exist_ok=True)
- save_render(temp, "Makefile",
- render_makefile(config.platform, config.hash_type, config.mod_rand, config.red, config.mult, config.sqr, config.defines))
- save_render(temp, "main.c",
- render_main(config.model, config.coords, config.keygen, config.ecdh, config.ecdsa))
+ save_render(
+ temp,
+ "Makefile",
+ render_makefile(
+ config.platform,
+ config.hash_type,
+ config.mod_rand,
+ config.red,
+ config.mult,
+ config.sqr,
+ config.defines,
+ ),
+ )
+ save_render(
+ temp,
+ "main.c",
+ render_main(
+ config.model, config.coords, config.keygen, config.ecdh, config.ecdsa
+ ),
+ )
save_render(gen_dir, "defs.h", render_defs(config.model, config.coords))
- save_render(gen_dir, "point.c", render_coords_impl(config.coords, getattr(config.scalarmult, "accumulation_order", None)))
+ save_render(
+ gen_dir,
+ "point.c",
+ render_coords_impl(
+ config.coords, getattr(config.scalarmult, "accumulation_order", None)
+ ),
+ )
save_render(gen_dir, "formulas.c", render_formulas_impl(config.formulas))
for formula in config.formulas:
- save_render(gen_dir, f"formula_{formula.shortname}.c",
- render_formula_impl(formula, config.scalarmult.short_circuit))
+ save_render(
+ gen_dir,
+ f"formula_{formula.shortname}.c",
+ render_formula_impl(formula, config.scalarmult.short_circuit),
+ )
save_render(gen_dir, "action.c", render_action())
save_render(gen_dir, "rand.c", render_rand())
save_render(gen_dir, "curve.c", render_curve_impl(config.model))
save_render(gen_dir, "mult.c", render_scalarmult_impl(config.scalarmult))
- return temp, "pyecsca-codegen-{}.elf".format(
- str(config.platform)), "pyecsca-codegen-{}.hex".format(str(config.platform))
+ return (
+ temp,
+ "pyecsca-codegen-{}.elf".format(str(config.platform)),
+ "pyecsca-codegen-{}.hex".format(str(config.platform)),
+ )
@public
-def build(dir: str, elf_file: str, hex_file: str, outdir: str, strip: bool = False,
- remove: bool = True) -> subprocess.CompletedProcess:
+def build(
+ dir: str,
+ elf_file: str,
+ hex_file: str,
+ outdir: str,
+ strip: bool = False,
+ remove: bool = True,
+) -> subprocess.CompletedProcess:
"""
Build a rendered configuration.
@@ -327,8 +529,9 @@ def build(dir: str, elf_file: str, hex_file: str, outdir: str, strip: bool = Fal
@public
-def render_and_build(config: DeviceConfiguration, outdir: str, strip: bool = False,
- remove: bool = True) -> Tuple[str, str, str, subprocess.CompletedProcess]:
+def render_and_build(
+ config: DeviceConfiguration, outdir: str, strip: bool = False, remove: bool = True
+) -> Tuple[str, str, str, subprocess.CompletedProcess]:
"""
Render and build a `config` in one go.