Files
2025-08-27 09:58:26 +00:00

192 lines
7.1 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# -*- coding: utf-8 -*-
"""
High-level LWE interface
"""
from functools import partial
from sage.all import oo
from .lwe_primal import primal_usvp, primal_bdd, primal_hybrid
from .lwe_bkw import coded_bkw
from .lwe_guess import exhaustive_search, mitm, distinguish, guess_composition # noqa
from .lwe_dual import dual
from .lwe_dual import matzov as dual_hybrid
from .gb import arora_gb # noqa
from .lwe_parameters import LWEParameters as Parameters # noqa
from .conf import (
red_cost_model as red_cost_model_default,
red_shape_model as red_shape_model_default,
)
from .util import batch_estimate, f_name
from .reduction import RC
from .io import Logging
class Estimate:
def rough(self, params, jobs=1, catch_exceptions=True, quiet=False):
"""
This function makes the following (non-default) somewhat routine assumptions to evaluate the cost of lattice
reduction, and to provide comparable numbers with most of the literature:
- The GSA holds.
- The Core-SVP model holds.
Provided numbers are notably not directly comparable with the rest of our API, when using the default cost
models.
This function furthermore assumes the following heuristics:
- The primal hybrid attack only applies to sparse secrets.
- The dual hybrid MITM attack only applies to sparse secrets.
- Arora-GB only applies to bounded noise with at least `n^2` samples.
- BKW is not competitive.
:param params: LWE parameters.
:param jobs: Use multiple threads in parallel.
:param catch_exceptions: When an estimate fails, just print a warning.
:param quiet: suppress printing
EXAMPLE ::
>>> from estimator import *
>>> _ = LWE.estimate.rough(schemes.Kyber512)
usvp :: rop: ≈2^118.6, red: ≈2^118.6, δ: 1.003941, β: 406, d: 998, tag: usvp
dual_hybrid :: rop: ≈2^115.5, red: ≈2^115.3, guess: ≈2^112.3, β: 395, p: 5, ζ: 0, t: 40, β': 395...
>>> _ = LWE.estimate.rough(schemes.Kyber512, quiet=True)
"""
params = params.normalize()
algorithms = {}
algorithms["usvp"] = partial(primal_usvp, red_cost_model=RC.ADPS16, red_shape_model="gsa")
algorithms["dual_hybrid"] = partial(dual_hybrid, red_cost_model=RC.ADPS16)
if params.m > params.n**2 and params.Xe.is_bounded:
if params.Xs.is_sparse:
algorithms["arora-gb"] = guess_composition(arora_gb.cost_bounded)
else:
algorithms["arora-gb"] = arora_gb.cost_bounded
res_raw = batch_estimate(
params, algorithms.values(), log_level=1, jobs=jobs, catch_exceptions=catch_exceptions
)
res_raw = res_raw[params]
res = {
algorithm: v
for algorithm, attack in algorithms.items()
for k, v in res_raw.items()
if f_name(attack) == k
}
for algorithm in algorithms:
if algorithm not in res:
continue
result = res[algorithm]
if result["rop"] != oo:
Logging.print("estimator", int(quiet), f"{algorithm:20s} :: {result!r}")
return res
def __call__(
self,
params,
red_cost_model=red_cost_model_default,
red_shape_model=red_shape_model_default,
deny_list=tuple(),
add_list=tuple(),
jobs=1,
catch_exceptions=True,
quiet=False,
):
"""
Run all estimates, based on the default cost and shape models for lattice reduction.
:param params: LWE parameters.
:param red_cost_model: How to cost lattice reduction.
:param red_shape_model: How to model the shape of a reduced basis (applies to primal attacks)
:param deny_list: skip these algorithms
:param add_list: add these ``(name, function)`` pairs to the list of algorithms to estimate.a
:param jobs: Use multiple threads in parallel.
:param catch_exceptions: When an estimate fails, just print a warning.
:param quiet: suppress printing
EXAMPLE ::
>>> from estimator import *
>>> _ = LWE.estimate(schemes.Kyber512)
bkw :: rop: ≈2^178.8, m: ≈2^166.8, mem: ≈2^167.8, b: 14, t1: 0, t2: 16, ℓ: 13, #cod: 448...
usvp :: rop: ≈2^143.8, red: ≈2^143.8, δ: 1.003941, β: 406, d: 998, tag: usvp
bdd :: rop: ≈2^140.2, red: ≈2^139.1, svp: ≈2^139.3, β: 389, η: 422, d: 1005, tag: bdd
dual :: rop: ≈2^149.9, mem: ≈2^97.1, m: 512, β: 424, d: 1024, ↻: 1, tag: dual
dual_hybrid :: rop: ≈2^139.7, red: ≈2^139.5, guess: ≈2^135.9, β: 387, p: 5, ζ: 0, t: 50, β': 391...
>>> _ = LWE.estimate(schemes.Kyber512, quiet=True)
"""
params = params.normalize()
algorithms = {}
algorithms["arora-gb"] = guess_composition(arora_gb)
algorithms["bkw"] = coded_bkw
algorithms["usvp"] = partial(
primal_usvp, red_cost_model=red_cost_model, red_shape_model=red_shape_model
)
algorithms["bdd"] = partial(
primal_bdd, red_cost_model=red_cost_model, red_shape_model=red_shape_model
)
algorithms["bdd_hybrid"] = partial(
primal_hybrid,
mitm=False,
babai=False,
red_cost_model=red_cost_model,
red_shape_model=red_shape_model,
)
# we ignore the case of mitm=True babai=False for now, due to it being overly-optimistic
algorithms["bdd_mitm_hybrid"] = partial(
primal_hybrid,
mitm=True,
babai=True,
red_cost_model=red_cost_model,
red_shape_model=red_shape_model,
)
algorithms["dual"] = partial(dual, red_cost_model=red_cost_model)
algorithms["dual_hybrid"] = partial(dual_hybrid, red_cost_model=red_cost_model)
algorithms = {k: v for k, v in algorithms.items() if k not in deny_list}
algorithms.update(add_list)
res_raw = batch_estimate(
params, algorithms.values(), log_level=1, jobs=jobs, catch_exceptions=catch_exceptions
)
res_raw = res_raw[params]
res = {
algorithm: v
for algorithm, attack in algorithms.items()
for k, v in res_raw.items()
if f_name(attack) == k
}
for algorithm in algorithms:
if algorithm not in res:
continue
result = res[algorithm]
if result["rop"] == oo:
continue
if algorithm == "bdd_hybrid" and res["bdd"]["rop"] <= result["rop"]:
continue
if algorithm == "bdd_mitm_hybrid" and res["bdd_hybrid"]["rop"] <= result["rop"]:
continue
if algorithm == "dual_mitm_hybrid" and res["dual_hybrid"]["rop"] < result["rop"]:
continue
Logging.print("estimator", int(quiet), f"{algorithm:20s} :: {result!r}")
return res
estimate = Estimate()