From b0aef526f5bf8dd923c802d75a289b30ac243288 Mon Sep 17 00:00:00 2001 From: javierverbel Date: Tue, 16 Jan 2024 11:24:31 +0400 Subject: [PATCH 1/2] Feat:minors algorithm for the MREstimator --- .../MREstimator/MRAlgorithms/__init__.py | 1 + .../MREstimator/MRAlgorithms/minors.py | 169 ++++++++++++++++++ .../MREstimator/MRAlgorithms/mr_helper.py | 62 +++++++ .../MREstimator/mr_estimator.py | 13 ++ 4 files changed, 245 insertions(+) create mode 100644 cryptographic_estimators/MREstimator/MRAlgorithms/minors.py create mode 100644 cryptographic_estimators/MREstimator/MRAlgorithms/mr_helper.py diff --git a/cryptographic_estimators/MREstimator/MRAlgorithms/__init__.py b/cryptographic_estimators/MREstimator/MRAlgorithms/__init__.py index 63500690..b895e8c0 100644 --- a/cryptographic_estimators/MREstimator/MRAlgorithms/__init__.py +++ b/cryptographic_estimators/MREstimator/MRAlgorithms/__init__.py @@ -1,3 +1,4 @@ from .support_minors import SupportMinors from .kernel_search import KernelSearch from .big_k import BigK +from .minors import Minors \ No newline at end of file diff --git a/cryptographic_estimators/MREstimator/MRAlgorithms/minors.py b/cryptographic_estimators/MREstimator/MRAlgorithms/minors.py new file mode 100644 index 00000000..a3827b2b --- /dev/null +++ b/cryptographic_estimators/MREstimator/MRAlgorithms/minors.py @@ -0,0 +1,169 @@ +# **************************************************************************** +# Copyright 2023 Technology Innovation Institute +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# **************************************************************************** + +from ...MREstimator.mr_algorithm import MRAlgorithm +from ...MREstimator.mr_problem import MRProblem +from ...base_algorithm import optimal_parameter +from math import log2, ceil +from math import comb as binomial +from .mr_helper import minors_polynomial +from ..mr_constants import MR_NUMBER_OF_KERNEL_VECTORS_TO_GUESS, MR_NUMBER_OF_COEFFICIENTS_TO_GUESS + + +class Minors(MRAlgorithm): + r""" + Construct an instance of Minors estimator + + + INPUT: + + - ``problem`` -- an instance of the MRProblem class + - ``w`` -- linear algebra constant (default: 3) + - ``theta`` -- exponent of the conversion factor (default: 2.81) + + EXAMPLES:: + + sage: from cryptographic_estimators.MREstimator.MRAlgorithms.minors import Minors + sage: from cryptographic_estimators.MREstimator.mr_problem import MRProblem + sage: E = Minors(MRProblem(q=7, m=9, n=10, k=15, r=4)) + sage: E + Minors estimator for the MinRank problem with (q, m, n, k, r) = (7, 9, 10, 15, 4) + """ + + def __init__(self, problem: MRProblem, **kwargs): + + super(Minors, self).__init__(problem, **kwargs) + + q, m, n, k, r = self.problem.get_parameters() + self.set_parameter_ranges('a', 0, min(n - r, ceil(k / m))) + self.set_parameter_ranges('lv', 0, r) + self._name = "Minors" + + @optimal_parameter + def a(self): + """ + Return the optimal `a`, i.e. no. of vectors to guess in the kernel of the low-rank matrix + + EXAMPLES:: + + sage: from cryptographic_estimators.MREstimator.MRAlgorithms.minors import Minors + sage: from cryptographic_estimators.MREstimator.mr_problem import MRProblem + sage: ME = Minors(MRProblem(q=7, m=9, n=10, k=15, r=4)) + sage: ME.a() + 2 + + TESTS:: + + sage: from cryptographic_estimators.MREstimator.MRAlgorithms.minors import Minors + sage: from cryptographic_estimators.MREstimator.mr_problem import MRProblem + sage: ME = Minors(MRProblem(q=16, m=15, n=15, k=78, r=6)) + sage: ME.a() + 5 + """ + return self._get_optimal_parameter(MR_NUMBER_OF_KERNEL_VECTORS_TO_GUESS) + + @optimal_parameter + def lv(self): + """ + Return the optimal `lv`, i.e. no. of entries to guess in the solution + + EXAMPLES:: + + sage: from cryptographic_estimators.MREstimator.MRAlgorithms.minors import Minors + sage: from cryptographic_estimators.MREstimator.mr_problem import MRProblem + sage: ME = Minors(MRProblem(q=7, m=9, n=10, k=15, r=4)) + sage: ME.lv() + 0 + + TESTS:: + + sage: from cryptographic_estimators.MREstimator.MRAlgorithms.minors import Minors + sage: from cryptographic_estimators.MREstimator.mr_problem import MRProblem + sage: ME = Minors(MRProblem(q=16, m=15, n=15, k=78, r=6)) + sage: ME.lv() + 0 + """ + return self._get_optimal_parameter(MR_NUMBER_OF_COEFFICIENTS_TO_GUESS) + + def _ME_time_complexity_helper_(self, m, n_reduced, k_reduced, r): + time = 0 + poly = minors_polynomial(m, n_reduced, k_reduced, r) + D = poly.degree() + w = self._w + if k_reduced > 0: + time = w * log2(binomial(k_reduced + D, D)) + return time + + def _ME_memory_complexity_helper_(self, m, n_reduced, k_reduced, r): + memory = 0 + poly = minors_polynomial(m, n_reduced, k_reduced, r) + D = poly.degree() + if k_reduced > 0: + memory = 2 * log2(binomial(k_reduced + D, D)) + return memory + + def _compute_time_complexity(self, parameters: dict): + """ + Return the time complexity of the algorithm for a given set of parameters + + INPUT: + + - ``parameters`` -- dictionary including the parameters + + TESTS:: + + sage: from cryptographic_estimators.MREstimator.MRAlgorithms.minors import Minors + sage: from cryptographic_estimators.MREstimator.mr_problem import MRProblem + sage: ME = Minors(MRProblem(q=16, m=15, n=15, k=78, r=6)) + sage: ME.time_complexity() + 143.1769522683363 + """ + a = parameters[MR_NUMBER_OF_KERNEL_VECTORS_TO_GUESS] + lv = parameters[MR_NUMBER_OF_COEFFICIENTS_TO_GUESS] + q, m, _, k, r = self.problem.get_parameters() + _, _, n_reduced, k_reduced, _ = self.get_problem_parameters_reduced(a, lv) + time = self.hybridization_factor(a, lv) + time_complexity = self._ME_time_complexity_helper_(m, n_reduced, k_reduced, r) + reduction_cost = self.cost_reduction(a) + time += max(time_complexity, reduction_cost) + if abs(time_complexity - reduction_cost) < 0: + time += 1 + return time + + def _compute_memory_complexity(self, parameters: dict): + """ + Return the memory complexity of the algorithm for a given set of parameters + + INPUT: + + - ``parameters`` -- dictionary including the parameters + + TESTS:: + + sage: from cryptographic_estimators.MREstimator.MRAlgorithms.minors import Minors + sage: from cryptographic_estimators.MREstimator.mr_problem import MRProblem + sage: ME = Minors(MRProblem(q=16, m=15, n=15, k=78, r=6)) + sage: ME.memory_complexity() + 14.784634845557521 + """ + + a = parameters[MR_NUMBER_OF_KERNEL_VECTORS_TO_GUESS] + lv = parameters[MR_NUMBER_OF_COEFFICIENTS_TO_GUESS] + q, m, n, k, r = self.problem.get_parameters() + _, _, n_reduced, k_reduced, _ = self.get_problem_parameters_reduced(a, lv) + memory = self._ME_memory_complexity_helper_(m, n_reduced, k_reduced, r) + return memory diff --git a/cryptographic_estimators/MREstimator/MRAlgorithms/mr_helper.py b/cryptographic_estimators/MREstimator/MRAlgorithms/mr_helper.py new file mode 100644 index 00000000..c352cc8f --- /dev/null +++ b/cryptographic_estimators/MREstimator/MRAlgorithms/mr_helper.py @@ -0,0 +1,62 @@ +# **************************************************************************** +# Copyright 2023 Technology Innovation Institute +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# **************************************************************************** + + +from sage.all import QQ +from sage.rings.power_series_ring import PowerSeriesRing +from itertools import product +from math import comb as binomial +from sage.matrix.special import zero_matrix +from math import log2 + +def _binomial_mult(n, m, i, j, l): + return binomial(m - i, l) * binomial(n - j, l) + +def entry_i_j_of_A(n, m, t, i, j): + limit = max(m - i, n - j) + return sum([_binomial_mult(n, m, i, j, l) * t ** l for l in range(limit + 1)]) + +def matrix_A(m, n, r, PR): + A = zero_matrix(PR, r, r) + t = PR.gen() + square_r = range(1, r + 1) + for i, j in product(square_r, square_r): + entry_i_j = entry_i_j_of_A(n, m, t, i, j) + A.add_to_entry(i-1, j-1, entry_i_j) + return A + +def deteterminant_of_A(m, n, r, t): + A = matrix_A(m, n, r, t) + return A.determinant() + +def minors_series(m, n, k, r): + PR = PowerSeriesRing(QQ, 't', default_prec=max(n, m) + 2) + t = PR.gen() + exp = (m - r) * (n - r) - (k + 1) + series = (1 - t) ** exp * deteterminant_of_A(m, n, r, PR)/(t ** binomial(r, 2)) + return series + +def minors_polynomial(m, n_reduced, k_reduced, r): + poly = 0 + series = minors_series(m, n_reduced, k_reduced, r) + t = series.parent().gen() + for D in range(series.degree()): + poly += series[D] * t ** D + if series[D + 1] <= 0: + break + return poly + diff --git a/cryptographic_estimators/MREstimator/mr_estimator.py b/cryptographic_estimators/MREstimator/mr_estimator.py index 5a49a95a..91772c4f 100644 --- a/cryptographic_estimators/MREstimator/mr_estimator.py +++ b/cryptographic_estimators/MREstimator/mr_estimator.py @@ -76,6 +76,7 @@ def table(self, show_quantum_complexity=0, show_tilde_o_time=0, | SupportMinors | 38.8 | 13.0 | {'a': 1, 'lv': 0, 'b': 1, 'nprime': 8, 'variant': 'block_wiedemann'} | | KernelSearch | 33.2 | 11.5 | {'a': 1, 'lv': 0} | | BigK | 132.5 | 13.1 | {'a': 0, 'lv': 0} | + | Minors | 37.2 | 1.6 | {'a': 2, 'lv': 0} | +---------------+-------+--------+----------------------------------------------------------------------+ TESTS: @@ -91,8 +92,10 @@ def table(self, show_quantum_complexity=0, show_tilde_o_time=0, | SupportMinors | 144.0 | 11.8 | {'a': 5, 'lv': 0, 'b': 1, 'nprime': 8, 'variant': 'block_wiedemann'} | | KernelSearch | 147.7 | 14.3 | {'a': 4, 'lv': 3} | | BigK | 154.7 | 13.8 | {'a': 5, 'lv': 3} | + | Minors | 143.2 | 14.8 | {'a': 5, 'lv': 0} | +---------------+-------+--------+----------------------------------------------------------------------+ + sage: MRE = MREstimator(q=16, m=16, n=16, k=142, r=4) sage: MRE.table(show_all_parameters=1) +---------------+---------------------------------------------------------------------------------------+ @@ -103,8 +106,10 @@ def table(self, show_quantum_complexity=0, show_tilde_o_time=0, | SupportMinors | 165.9 | 18.0 | {'a': 8, 'lv': 0, 'b': 2, 'nprime': 8, 'variant': 'block_wiedemann'} | | KernelSearch | 159.4 | 13.8 | {'a': 8, 'lv': 0} | | BigK | 230.8 | 17.2 | {'a': 0, 'lv': 0} | + | Minors | 162.5 | 33.0 | {'a': 7, 'lv': 0} | +---------------+-------+--------+----------------------------------------------------------------------+ + sage: MRE = MREstimator(q=16, m=19, n=19, k=109, r=8) sage: MRE.table(show_all_parameters=1) +---------------+----------------------------------------------------------------------------------------+ @@ -115,8 +120,10 @@ def table(self, show_quantum_complexity=0, show_tilde_o_time=0, | SupportMinors | 209.6 | 23.5 | {'a': 5, 'lv': 0, 'b': 2, 'nprime': 14, 'variant': 'block_wiedemann'} | | KernelSearch | 207.4 | 14.9 | {'a': 5, 'lv': 0} | | BigK | 431.1 | 17.4 | {'a': 0, 'lv': 0} | + | Minors | 211.5 | 55.0 | {'a': 4, 'lv': 0} | +---------------+-------+--------+-----------------------------------------------------------------------+ + sage: MRE = MREstimator(q=16, m=19, n=19, k=167, r=6) sage: MRE.table(show_all_parameters=1) +---------------+----------------------------------------------------------------------------------------+ @@ -127,8 +134,10 @@ def table(self, show_quantum_complexity=0, show_tilde_o_time=0, | SupportMinors | 236.3 | 20.9 | {'a': 8, 'lv': 0, 'b': 2, 'nprime': 11, 'variant': 'block_wiedemann'} | | KernelSearch | 231.7 | 14.6 | {'a': 8, 'lv': 0} | | BigK | 351.8 | 17.9 | {'a': 0, 'lv': 0} | + | Minors | 237.6 | 45.7 | {'a': 7, 'lv': 0} | +---------------+-------+--------+-----------------------------------------------------------------------+ + sage: MRE = MREstimator(q=16, m=21, n=21, k=189, r=7) sage: MRE.table(show_all_parameters=1) +---------------+----------------------------------------------------------------------------------------+ @@ -139,8 +148,10 @@ def table(self, show_quantum_complexity=0, show_tilde_o_time=0, | SupportMinors | 274.5 | 51.0 | {'a': 6, 'lv': 0, 'b': 8, 'nprime': 15, 'variant': 'block_wiedemann'} | | KernelSearch | 269.2 | 15.5 | {'a': 8, 'lv': 0} | | BigK | 452.6 | 18.4 | {'a': 0, 'lv': 0} | + | Minors | 278.7 | 2.0 | {'a': 9, 'lv': 0} | +---------------+-------+--------+-----------------------------------------------------------------------+ + sage: MRE = MREstimator(q=16, m=22, n=22, k=254, r=6) sage: MRE.table(show_all_parameters=1) +---------------+-----------------------------------------------------------------------------------------+ @@ -151,8 +162,10 @@ def table(self, show_quantum_complexity=0, show_tilde_o_time=0, | SupportMinors | 301.2 | 17.6 | {'a': 11, 'lv': 0, 'b': 1, 'nprime': 11, 'variant': 'block_wiedemann'} | | KernelSearch | 302.8 | 14.5 | {'a': 11, 'lv': 0} | | BigK | 425.4 | 18.9 | {'a': 0, 'lv': 0} | + | Minors | 307.1 | 60.1 | {'a': 9, 'lv': 0} | +---------------+-------+--------+------------------------------------------------------------------------+ + """ super(MREstimator, self).table(show_quantum_complexity=show_quantum_complexity, show_tilde_o_time=show_tilde_o_time, From 3487007b60d7c3ac92f419ca523c35880aa3d626 Mon Sep 17 00:00:00 2001 From: javierverbel Date: Mon, 22 Jan 2024 12:36:28 +0400 Subject: [PATCH 2/2] Refactor: merge helper functions minors MR algorithm --- .../MREstimator/MRAlgorithms/minors.py | 26 ++++++++----------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/cryptographic_estimators/MREstimator/MRAlgorithms/minors.py b/cryptographic_estimators/MREstimator/MRAlgorithms/minors.py index a3827b2b..ed471b7c 100644 --- a/cryptographic_estimators/MREstimator/MRAlgorithms/minors.py +++ b/cryptographic_estimators/MREstimator/MRAlgorithms/minors.py @@ -25,7 +25,7 @@ class Minors(MRAlgorithm): - r""" + """ Construct an instance of Minors estimator @@ -99,22 +99,18 @@ def lv(self): """ return self._get_optimal_parameter(MR_NUMBER_OF_COEFFICIENTS_TO_GUESS) - def _ME_time_complexity_helper_(self, m, n_reduced, k_reduced, r): - time = 0 + def _ME_time_memory_complexity_helper_(self, m: int, n_reduced: int, k_reduced: int, r: int, time_mem: str): + out = 0 poly = minors_polynomial(m, n_reduced, k_reduced, r) D = poly.degree() - w = self._w if k_reduced > 0: - time = w * log2(binomial(k_reduced + D, D)) - return time + if time_mem == "time": + w = self._w + out = w * log2(binomial(k_reduced + D, D)) + elif time_mem == "memory": + out = 2 * log2(binomial(k_reduced + D, D)) + return out - def _ME_memory_complexity_helper_(self, m, n_reduced, k_reduced, r): - memory = 0 - poly = minors_polynomial(m, n_reduced, k_reduced, r) - D = poly.degree() - if k_reduced > 0: - memory = 2 * log2(binomial(k_reduced + D, D)) - return memory def _compute_time_complexity(self, parameters: dict): """ @@ -137,7 +133,7 @@ def _compute_time_complexity(self, parameters: dict): q, m, _, k, r = self.problem.get_parameters() _, _, n_reduced, k_reduced, _ = self.get_problem_parameters_reduced(a, lv) time = self.hybridization_factor(a, lv) - time_complexity = self._ME_time_complexity_helper_(m, n_reduced, k_reduced, r) + time_complexity = self._ME_time_memory_complexity_helper_(m, n_reduced, k_reduced, r, "time") reduction_cost = self.cost_reduction(a) time += max(time_complexity, reduction_cost) if abs(time_complexity - reduction_cost) < 0: @@ -165,5 +161,5 @@ def _compute_memory_complexity(self, parameters: dict): lv = parameters[MR_NUMBER_OF_COEFFICIENTS_TO_GUESS] q, m, n, k, r = self.problem.get_parameters() _, _, n_reduced, k_reduced, _ = self.get_problem_parameters_reduced(a, lv) - memory = self._ME_memory_complexity_helper_(m, n_reduced, k_reduced, r) + memory = self._ME_time_memory_complexity_helper_(m, n_reduced, k_reduced, r, "memory") return memory