diff --git a/libs/algebra/include/nil/crypto3/algebra/fields/field_order.hpp b/libs/algebra/include/nil/crypto3/algebra/fields/field_order.hpp index bdeba15ba..1e457aaec 100644 --- a/libs/algebra/include/nil/crypto3/algebra/fields/field_order.hpp +++ b/libs/algebra/include/nil/crypto3/algebra/fields/field_order.hpp @@ -31,13 +31,19 @@ namespace nil::crypto3::algebra::fields { + /** Return the characteristic of FieldType. Extension fields have the same characteristic as their prime field. */ + template + boost::multiprecision::cpp_int field_characteristic() { + return boost::multiprecision::cpp_int(FieldType::modulus.backend().to_cpp_int()); + } + /** * Return the number of elements in FieldType. Crypto3 field types describe extensions with an arity equal to their * degree over the prime field, so a field of characteristic p and arity d has p^d elements. */ template boost::multiprecision::cpp_int field_order() { - const boost::multiprecision::cpp_int characteristic(FieldType::modulus.backend().to_cpp_int()); + const boost::multiprecision::cpp_int characteristic = field_characteristic(); boost::multiprecision::cpp_int order = 1; for (std::size_t i = 0; i < FieldType::arity; ++i) { order *= characteristic; diff --git a/libs/algebra/test/field_order.cpp b/libs/algebra/test/field_order.cpp index 03fdd314c..ca7bd3798 100644 --- a/libs/algebra/test/field_order.cpp +++ b/libs/algebra/test/field_order.cpp @@ -46,6 +46,8 @@ BOOST_AUTO_TEST_CASE(prime_field_order_equals_its_characteristic) { const boost::multiprecision::cpp_int expected( "21888242871839275222246405745257275088696311157297823662689037894645226208583"); + BOOST_CHECK(fields::field_characteristic() == expected); + BOOST_CHECK(fields::field_characteristic() == expected); BOOST_CHECK(fields::field_order() == expected); } diff --git a/libs/mac/include/nil/crypto3/mac/poly1305.hpp b/libs/mac/include/nil/crypto3/mac/poly1305.hpp index e64e348d3..d64f5873f 100644 --- a/libs/mac/include/nil/crypto3/mac/poly1305.hpp +++ b/libs/mac/include/nil/crypto3/mac/poly1305.hpp @@ -149,10 +149,8 @@ namespace nil { std::copy(key.begin() + r_bytes.size(), key.end(), s_bytes.begin()); clamp_poly1305_r(r_bytes); - return { - load_little_endian_integer(r_bytes.cbegin(), r_bytes.size()), - load_little_endian_integer(s_bytes.cbegin(), s_bytes.size()) - }; + return {load_little_endian_integer(r_bytes.cbegin(), r_bytes.size()), + load_little_endian_integer(s_bytes.cbegin(), s_bytes.size())}; } template @@ -338,15 +336,13 @@ namespace nil { const std::uint64_t t0 = load_little_endian_64(key.data()); const std::uint64_t t1 = load_little_endian_64(key.data() + 8); - key_schedule_type schedule = { - t0 & 0xffc0fffffff, - ((t0 >> 44) | (t1 << 20)) & 0xfffffc0ffff, - (t1 >> 24) & 0x00ffffffc0f, - 0, - 0, - load_little_endian_64(key.data() + 16), - load_little_endian_64(key.data() + 24) - }; + key_schedule_type schedule = {t0 & 0xffc0fffffff, + ((t0 >> 44) | (t1 << 20)) & 0xfffffc0ffff, + (t1 >> 24) & 0x00ffffffc0f, + 0, + 0, + load_little_endian_64(key.data() + 16), + load_little_endian_64(key.data() + 24)}; schedule.s1 = schedule.r1 * (5 << 2); schedule.s2 = schedule.r2 * (5 << 2); @@ -371,7 +367,7 @@ namespace nil { #else typedef poly1305_reference_backend poly1305_default_backend; #endif - } // namespace detail + } // namespace detail template struct basic_poly1305 { @@ -447,8 +443,8 @@ namespace nil { key_schedule_type schedule; }; - } // namespace mac - } // namespace crypto3 -} // namespace nil + } // namespace mac + } // namespace crypto3 +} // namespace nil #endif // CRYPTO3_MAC_POLY1305_HPP diff --git a/libs/math/include/nil/crypto3/math/matrix/compressed.hpp b/libs/math/include/nil/crypto3/math/matrix/compressed.hpp index 6d3044b6c..09058013d 100644 --- a/libs/math/include/nil/crypto3/math/matrix/compressed.hpp +++ b/libs/math/include/nil/crypto3/math/matrix/compressed.hpp @@ -38,9 +38,9 @@ namespace nil::crypto3::math { source.find_element(row, column); destination(row, column) = *source.find_element(row, column); } - void assign_if_stored(const matrix &source, std::size_t source_row, - std::size_t source_column, matrix &destination, - std::size_t destination_row, std::size_t destination_column) { + void assign_if_stored(const matrix &source, std::size_t source_row, std::size_t source_column, + matrix &destination, std::size_t destination_row, + std::size_t destination_column) { if (auto element = source.backend().find_element(source_row, source_column)) { destination(destination_row, destination_column) = *element; } diff --git a/libs/math/include/nil/crypto3/math/polynomial/distinct_degree_factorization.hpp b/libs/math/include/nil/crypto3/math/polynomial/distinct_degree_factorization.hpp new file mode 100644 index 000000000..3cc737d08 --- /dev/null +++ b/libs/math/include/nil/crypto3/math/polynomial/distinct_degree_factorization.hpp @@ -0,0 +1,153 @@ +//---------------------------------------------------------------------------// +// Copyright (c) 2026 +// +// MIT License +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//---------------------------------------------------------------------------// + +#ifndef CRYPTO3_MATH_DISTINCT_DEGREE_FACTORIZATION_HPP +#define CRYPTO3_MATH_DISTINCT_DEGREE_FACTORIZATION_HPP + +#include +#include +#include +#include + +#include +#include +#include + +namespace nil::crypto3::math { + + /** + * Split a square-free polynomial into products of irreducible factors of equal degree using the classical + * distinct-degree algorithm. This implementation is intended as a correctness reference for faster blocked + * algorithms. + * + * Over a finite field with Q elements, X^(Q^d) - X is the product of all monic irreducible polynomials whose + * degrees divide d. The algorithm visits d = 1, 2, ... and removes the factors found at earlier degrees. Therefore + * + * gcd(remaining, X^(Q^d) - X) + * + * contains exactly the remaining irreducible factors of degree d. Frobenius powers are computed modulo the + * original monic input. This is also valid modulo every factor subsequently removed from that input and avoids + * rebuilding the Frobenius precomputation after each split. + * + * For example, let F = L1 * L2 * Q1 * Q2 * C, where L1 and L2 are irreducible linears, Q1 and Q2 are + * irreducible quadratics, and C is an irreducible cubic. At d = 1, the GCD extracts L1 * L2. At d = 2, + * X^(Q^2) - X contains factors of degrees one and two, but the linears have already been removed, so the GCD + * extracts Q1 * Q2. The remaining C is then reported with degree three. The algorithm therefore returns three + * groups: (L1 * L2, 1), (Q1 * Q2, 2), and (C, 3). + * + * The input is normalized to monic form and its original leading coefficient is preserved in the result. Zero + * and constant inputs return that coefficient and no factors. Nonconstant input must be square-free; this is + * checked before the decomposition begins. + * + * Factors are emitted in increasing irreducible-factor degree. After each factor is appended to the result, + * factor_callback may request an early stop. A stopped result includes that factor and has complete set to false. + * + * @throws std::invalid_argument if a nonconstant input is not square-free. + * @pre input is a nonempty coefficient polynomial. + */ + template + requires requires(FactorCallback &callback, + const distinct_degree_factor &factor) { + { callback(factor) } -> std::same_as; + } + distinct_degree_factorization_result + distinct_degree_factorization_reference(const typename Backend::polynomial_type &input, + polynomial_arithmetic::polynomial_context &arithmetic_context, + FactorCallback &&factor_callback) { + using polynomial_type = typename Backend::polynomial_type; + using value_type = typename polynomial_type::value_type; + using result_type = distinct_degree_factorization_result; + + result_type result; + polynomial_type monic_input(input); + condense(monic_input); + result.leading_coefficient = monic_input.back(); + if (monic_input.size() == 1) { + return result; + } + make_monic(monic_input, monic_input); + + polynomial_type input_derivative; + derivative(input_derivative, monic_input); + polynomial_type repeated_factor; + gcd(repeated_factor, monic_input, input_derivative, arithmetic_context); + if (repeated_factor.size() > 1) { + throw std::invalid_argument("distinct-degree factorization requires a square-free polynomial"); + } + + const polynomial_type x = {value_type {}, value_type::one()}; + polynomial_type frobenius_power(x); + polynomial_frobenius_context frobenius_context(monic_input, arithmetic_context); + polynomial_type remaining(std::move(monic_input)); + + std::size_t irreducible_factor_degree = 1; + // Every factor of degree below the current degree has already been removed. If remaining has degree less + // than twice the current degree, it can contain at most one irreducible factor and is emitted after the loop. + while (irreducible_factor_degree <= (remaining.size() - 1) / 2) { + frobenius_map(frobenius_power, frobenius_power, frobenius_context, arithmetic_context); + + polynomial_type frobenius_difference; + subtraction(frobenius_difference, frobenius_power, x); + polynomial_type factor; + gcd(factor, remaining, frobenius_difference, arithmetic_context); + if (factor.size() > 1) { + result.factors.push_back({std::move(factor), irreducible_factor_degree}); + if (factor_callback(result.factors.back()) == factorization_control::stop_factorization) { + result.complete = false; + return result; + } + + polynomial_type quotient; + detail::factorization_exact_quotient(quotient, remaining, result.factors.back().polynomial, + arithmetic_context); + remaining = std::move(quotient); + } + ++irreducible_factor_degree; + } + + if (remaining.size() > 1) { + const std::size_t remaining_degree = remaining.size() - 1; + result.factors.push_back({std::move(remaining), remaining_degree}); + if (factor_callback(result.factors.back()) == factorization_control::stop_factorization) { + result.complete = false; + } + } + + return result; + } + + /** Compute the complete reference distinct-degree factorization without a staged callback. */ + template + distinct_degree_factorization_result distinct_degree_factorization_reference( + const typename Backend::polynomial_type &input, + polynomial_arithmetic::polynomial_context &arithmetic_context) { + using factor_type = distinct_degree_factor; + return distinct_degree_factorization_reference(input, arithmetic_context, [](const factor_type &) { + return factorization_control::continue_factorization; + }); + } + +} // namespace nil::crypto3::math + +#endif // CRYPTO3_MATH_DISTINCT_DEGREE_FACTORIZATION_HPP diff --git a/libs/math/include/nil/crypto3/math/polynomial/polynomial_factorization.hpp b/libs/math/include/nil/crypto3/math/polynomial/polynomial_factorization.hpp new file mode 100644 index 000000000..74a212ec3 --- /dev/null +++ b/libs/math/include/nil/crypto3/math/polynomial/polynomial_factorization.hpp @@ -0,0 +1,121 @@ +//---------------------------------------------------------------------------// +// Copyright (c) 2026 +// +// MIT License +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//---------------------------------------------------------------------------// + +#ifndef CRYPTO3_MATH_POLYNOMIAL_FACTORIZATION_HPP +#define CRYPTO3_MATH_POLYNOMIAL_FACTORIZATION_HPP + +#include +#include + +#include +#include + +namespace nil::crypto3::math { + + /** The action requested by a callback after a factorization stage produces a factor. */ + enum class factorization_control { continue_factorization, stop_factorization }; + + /** A polynomial factor together with its positive multiplicity in the input polynomial. */ + template + struct polynomial_factor { + Polynomial polynomial; + std::size_t multiplicity = 1; + + bool operator==(const polynomial_factor &) const = default; + }; + + /** + * A collected polynomial factorization. A complete result satisfies + * + * input = leading_coefficient * product(factor.polynomial ^ factor.multiplicity). + * + * Nonconstant factors are stored in monic canonical form. When a staged callback requests an early stop, factors + * contains the produced prefix, including the factor that caused the stop, and complete is false. + */ + template + struct polynomial_factorization_result { + using polynomial_type = Polynomial; + using value_type = typename polynomial_type::value_type; + using factor_type = polynomial_factor; + + value_type leading_coefficient {}; + std::vector factors; + bool complete = true; + + bool operator==(const polynomial_factorization_result &) const = default; + }; + + /** + * One group produced by distinct-degree factorization. polynomial is the product of all irreducible input factors + * whose degree equals irreducible_factor_degree; it need not itself be irreducible. + */ + template + struct distinct_degree_factor { + Polynomial polynomial; + std::size_t irreducible_factor_degree = 1; + + bool operator==(const distinct_degree_factor &) const = default; + }; + + /** + * A collected distinct-degree factorization. A complete result satisfies + * + * input = leading_coefficient * product(factor.polynomial). + * + * Each stored polynomial is monic and square-free, and contains all input factors having the associated + * irreducible_factor_degree. A stopped result contains the produced prefix, including the factor that caused the + * stop, and has complete set to false. + */ + template + struct distinct_degree_factorization_result { + using polynomial_type = Polynomial; + using value_type = typename polynomial_type::value_type; + using factor_type = distinct_degree_factor; + + value_type leading_coefficient {}; + std::vector factors; + bool complete = true; + + bool operator==(const distinct_degree_factorization_result &) const = default; + }; + + namespace detail { + + template + void factorization_exact_quotient(typename Backend::polynomial_type &output, + const typename Backend::polynomial_type ÷nd, + const typename Backend::polynomial_type &divisor, + polynomial_arithmetic::polynomial_context &arithmetic_context) { + const std::size_t quotient_coefficient_count = + dividend.size() >= divisor.size() ? dividend.size() - divisor.size() + 1 : 1; + polynomial_divisor_context divisor_context(divisor, quotient_coefficient_count, + arithmetic_context); + exact_division(output, dividend, divisor_context, arithmetic_context); + } + + } // namespace detail + +} // namespace nil::crypto3::math + +#endif // CRYPTO3_MATH_POLYNOMIAL_FACTORIZATION_HPP diff --git a/libs/math/include/nil/crypto3/math/polynomial/square_free_factorization.hpp b/libs/math/include/nil/crypto3/math/polynomial/square_free_factorization.hpp new file mode 100644 index 000000000..72f8c45b2 --- /dev/null +++ b/libs/math/include/nil/crypto3/math/polynomial/square_free_factorization.hpp @@ -0,0 +1,157 @@ +//---------------------------------------------------------------------------// +// Copyright (c) 2026 +// +// MIT License +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//---------------------------------------------------------------------------// + +#ifndef CRYPTO3_MATH_SQUARE_FREE_FACTORIZATION_HPP +#define CRYPTO3_MATH_SQUARE_FREE_FACTORIZATION_HPP + +#include +#include +#include +#include + +#include + +#include +#include + +namespace nil::crypto3::math { + /** + * Separate a polynomial into monic square-free factors with distinct multiplicities using Yun's decomposition. + * For a nonconstant input, the returned leading coefficient is the input's original leading coefficient. By + * convention, the zero polynomial returns scalar zero and no factors; a nonzero constant returns its sole + * coefficient and no factors. + * + * Write a nonconstant monic input as F = product(P_j^m_j), where the P_j are distinct irreducible factors. Each + * summand of the product-rule expansion of F' differentiates one factor, but every summand remains divisible by + * P_j^(m_j-1) for every j. Under the characteristic restriction below, G = gcd(F, F') therefore contains exactly + * m_j-1 copies of each P_j. Consequently W = F/G contains one copy of every distinct factor. + * In formulas + * F = \prod_j P_j^m_j(x) + * F'= \{\prod_{k}P_k^{m_k-1}\}\, \sum_j m_j P'_j(x) \prod_{i\neq j} P_i(x) + * G = gcd(F,F') = \prod_{k}P_k^{m_k-1} + * W = F/G = \prod_k P_k(x) + * + * The algorithm then loops over factor multiplicities i = 1, 2, 3, ... and stops when W = 1. At the start of the + * iteration for multiplicity i, W contains one copy of each factor whose original multiplicity is at least i, and G + * contains its remaining copies. Set Y = gcd(W, G): Y retains the factors whose multiplicity is greater than i, so + * Z = W/Y contains exactly the factors of multiplicity i. Emit Z when it is nonconstant, replace W with Y and G + * with G/Y, and continue with the next multiplicity. + * In formulas, at multiplicity i, + * W_i = \prod_{m_j >= i} P_j, + * G_i = \prod_{m_j >= i} P_j^(m_j-i), + * Y_i = gcd(W_i, G_i) = \prod_{m_j > i} P_j, + * Z_i = W_i/Y_i = \prod_{m_j = i} P_j. + * Thus Y_i equals W_i only when no factor has multiplicity exactly i; in that case Z_i = 1 and nothing is emitted. + * + * For example, for the non-square-free input F = A * B^2 * C^3, initially W = A * B * C and G = B * C^2. The + * first iteration emits A and leaves W = B * C, G = C; the second emits B and leaves W = C, G = 1; the third emits + * C. + * + * This initial implementation requires the coefficient-field characteristic to be greater than degree(F). Under + * that restriction, differentiation cannot erase a nonconstant p-th-power component, so the recurrence accounts + * for every factor without extracting polynomial p-th roots. Later support for small characteristic can remove + * this restriction. + * + * Factors are emitted in increasing multiplicity. After each nonconstant factor is appended to the result, + * factor_callback may request an early stop. A stopped result includes that factor and has complete set to false. + * + * @throws std::invalid_argument if the coefficient-field characteristic is not greater than the input degree. + */ + template + requires requires(FactorCallback &callback, + const polynomial_factor &factor) { + { callback(factor) } -> std::same_as; + } + polynomial_factorization_result + square_free_factorization(const typename Backend::polynomial_type &input, + polynomial_arithmetic::polynomial_context &arithmetic_context, + FactorCallback &&factor_callback) { + using polynomial_type = typename Backend::polynomial_type; + using value_type = typename polynomial_type::value_type; + using field_type = typename value_type::field_type; + using result_type = polynomial_factorization_result; + + result_type result; + polynomial_type monic_input(input); + condense(monic_input); + result.leading_coefficient = monic_input.back(); + + if (monic_input.size() == 1) { + return result; + } + + const std::size_t input_degree = monic_input.size() - 1; + if (algebra::fields::field_characteristic() <= input_degree) { + throw std::invalid_argument( + "square-free factorization requires characteristic greater than the polynomial degree"); + } + make_monic(monic_input, monic_input); + + polynomial_type input_derivative; + derivative(input_derivative, monic_input); + + polynomial_type repeated_part; + gcd(repeated_part, monic_input, input_derivative, arithmetic_context); + + polynomial_type square_free_part; + detail::factorization_exact_quotient(square_free_part, monic_input, repeated_part, arithmetic_context); + + std::size_t multiplicity = 1; + while (square_free_part.size() > 1) { + polynomial_type next_square_free_part; + gcd(next_square_free_part, square_free_part, repeated_part, arithmetic_context); + + polynomial_type factor; + detail::factorization_exact_quotient(factor, square_free_part, next_square_free_part, arithmetic_context); + if (factor.size() > 1) { + result.factors.push_back({std::move(factor), multiplicity}); + if (factor_callback(result.factors.back()) == factorization_control::stop_factorization) { + result.complete = false; + return result; + } + } + + detail::factorization_exact_quotient(repeated_part, repeated_part, next_square_free_part, + arithmetic_context); + square_free_part = std::move(next_square_free_part); + ++multiplicity; + } + + return result; + } + + /** Compute the complete square-free factorization without a staged callback. */ + template + polynomial_factorization_result + square_free_factorization(const typename Backend::polynomial_type &input, + polynomial_arithmetic::polynomial_context &arithmetic_context) { + using factor_type = polynomial_factor; + return square_free_factorization(input, arithmetic_context, [](const factor_type &) { + return factorization_control::continue_factorization; + }); + } + +} // namespace nil::crypto3::math + +#endif // CRYPTO3_MATH_SQUARE_FREE_FACTORIZATION_HPP diff --git a/libs/math/test/CMakeLists.txt b/libs/math/test/CMakeLists.txt index 6161e790d..fc36457a8 100644 --- a/libs/math/test/CMakeLists.txt +++ b/libs/math/test/CMakeLists.txt @@ -40,6 +40,7 @@ endmacro() set(TESTS_NAMES "batch_inverse" + "distinct_degree_factorization" "evaluation_domain" "geometric_sequence_domain" "integer_sqrt" @@ -52,6 +53,7 @@ set(TESTS_NAMES "polynomial_exponentiation" "polynomial_composition" "polynomial_frobenius" + "square_free_factorization" "power_series" "polynomial" "polynomial_view" diff --git a/libs/math/test/distinct_degree_factorization.cpp b/libs/math/test/distinct_degree_factorization.cpp new file mode 100644 index 000000000..69c7f0483 --- /dev/null +++ b/libs/math/test/distinct_degree_factorization.cpp @@ -0,0 +1,189 @@ +//---------------------------------------------------------------------------// +// Copyright (c) 2026 +// +// MIT License +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//---------------------------------------------------------------------------// + +#define BOOST_TEST_MODULE distinct_degree_factorization_test + +#include + +#include + +#include +#include + +#include +#include + +namespace { + namespace math = nil::crypto3::math; + namespace polynomial_arithmetic = math::polynomial_arithmetic; + namespace fields = nil::crypto3::algebra::fields; + + using fq_field_type = fields::alt_bn128_base_field<254>; + using fq_value_type = fq_field_type::value_type; + using fq12_field_type = fields::fp12_2over3over2>; + using fq12_value_type = fq12_field_type::value_type; + + fq12_value_type fq12_value(std::size_t first_coordinate) { + fq12_value_type value = fq12_value_type::zero(); + for (std::size_t i = 0; i < fq12_field_type::arity; ++i) { + value.coordinate(i) = fq_value_type(first_coordinate + i); + } + return value; + } + + template + typename Backend::polynomial_type multiply(Backend &backend, const typename Backend::polynomial_type &left, + const typename Backend::polynomial_type &right) { + typename Backend::polynomial_type result; + backend.multiply(result, left, right); + return result; + } + + template + typename Backend::polynomial_type + reconstruct(Backend &backend, + const math::distinct_degree_factorization_result &result) { + typename Backend::polynomial_type reconstructed = {result.leading_coefficient}; + for (const auto &factor : result.factors) { + reconstructed = multiply(backend, reconstructed, factor.polynomial); + } + return reconstructed; + } +} // namespace + +BOOST_AUTO_TEST_SUITE(distinct_degree_factorization_test_suite) + +BOOST_AUTO_TEST_CASE(reference_factorization_separates_known_irreducible_degrees) { + using backend_type = polynomial_arithmetic::schoolbook_backend; + using polynomial_type = typename backend_type::polynomial_type; + + backend_type backend; + const polynomial_type linear_one = {fq_value_type(1), fq_value_type::one()}; + const polynomial_type linear_two = {fq_value_type(2), fq_value_type::one()}; + const polynomial_type degree_one_factors = multiply(backend, linear_one, linear_two); + + // Three is a multiplicative generator of BN254 Fq, hence it is neither a square nor a cube. Therefore X^2 - 3 + // and X^3 - 3 are irreducible over this field. + const polynomial_type degree_two_factor = {-fq_value_type(3), fq_value_type::zero(), fq_value_type::one()}; + const polynomial_type degree_three_factor = {-fq_value_type(3), fq_value_type::zero(), fq_value_type::zero(), + fq_value_type::one()}; + const fq_value_type leading_coefficient(11); + + polynomial_type input = multiply(backend, degree_one_factors, degree_two_factor); + input = multiply(backend, input, degree_three_factor); + for (fq_value_type &coefficient : input) { + coefficient *= leading_coefficient; + } + + polynomial_arithmetic::polynomial_context arithmetic_context; + const auto result = math::distinct_degree_factorization_reference(input, arithmetic_context); + + BOOST_CHECK(result.complete); + BOOST_CHECK(result.leading_coefficient == leading_coefficient); + BOOST_REQUIRE_EQUAL(result.factors.size(), 3); + BOOST_CHECK(result.factors[0] == math::distinct_degree_factor({degree_one_factors, 1})); + BOOST_CHECK(result.factors[1] == math::distinct_degree_factor({degree_two_factor, 2})); + BOOST_CHECK(result.factors[2] == math::distinct_degree_factor({degree_three_factor, 3})); + BOOST_CHECK(reconstruct(backend, result) == input); +} + +BOOST_AUTO_TEST_CASE(staged_factorization_stops_after_the_reported_degree) { + using backend_type = polynomial_arithmetic::schoolbook_backend; + using polynomial_type = typename backend_type::polynomial_type; + + backend_type backend; + const polynomial_type degree_one_factor = {fq_value_type(1), fq_value_type::one()}; + const polynomial_type degree_two_factor = {-fq_value_type(3), fq_value_type::zero(), fq_value_type::one()}; + const polynomial_type degree_three_factor = {-fq_value_type(3), fq_value_type::zero(), fq_value_type::zero(), + fq_value_type::one()}; + polynomial_type input = multiply(backend, degree_one_factor, degree_two_factor); + input = multiply(backend, input, degree_three_factor); + + std::size_t callback_count = 0; + polynomial_arithmetic::polynomial_context arithmetic_context; + const auto result = math::distinct_degree_factorization_reference( + input, arithmetic_context, [&](const math::distinct_degree_factor &factor) { + ++callback_count; + return factor.irreducible_factor_degree == 2 ? math::factorization_control::stop_factorization : + math::factorization_control::continue_factorization; + }); + + BOOST_CHECK(!result.complete); + BOOST_CHECK_EQUAL(callback_count, 2); + BOOST_REQUIRE_EQUAL(result.factors.size(), 2); + BOOST_CHECK_EQUAL(result.factors[0].irreducible_factor_degree, 1); + BOOST_CHECK_EQUAL(result.factors[1].irreducible_factor_degree, 2); + BOOST_CHECK(result.factors[1].polynomial == degree_two_factor); +} + +BOOST_AUTO_TEST_CASE(reference_factorization_rejects_a_repeated_factor) { + using backend_type = polynomial_arithmetic::schoolbook_backend; + using polynomial_type = typename backend_type::polynomial_type; + + backend_type backend; + const polynomial_type factor = {fq_value_type(1), fq_value_type::one()}; + const polynomial_type input = multiply(backend, factor, factor); + polynomial_arithmetic::polynomial_context arithmetic_context; + + BOOST_CHECK_THROW(math::distinct_degree_factorization_reference(input, arithmetic_context), + std::invalid_argument); +} + +BOOST_AUTO_TEST_CASE(zero_and_constant_polynomials_have_no_distinct_degree_factors) { + using backend_type = polynomial_arithmetic::schoolbook_backend; + using polynomial_type = typename backend_type::polynomial_type; + + polynomial_arithmetic::polynomial_context arithmetic_context; + const auto zero = math::distinct_degree_factorization_reference( + polynomial_type {fq_value_type::zero()}, arithmetic_context); + BOOST_CHECK(zero.complete); + BOOST_CHECK(zero.leading_coefficient == fq_value_type::zero()); + BOOST_CHECK(zero.factors.empty()); + + const auto constant = math::distinct_degree_factorization_reference( + polynomial_type {fq_value_type(13)}, arithmetic_context); + BOOST_CHECK(constant.complete); + BOOST_CHECK(constant.leading_coefficient == fq_value_type(13)); + BOOST_CHECK(constant.factors.empty()); +} + +BOOST_AUTO_TEST_CASE(reference_factorization_supports_native_extension_field_coefficients) { + using backend_type = polynomial_arithmetic::schoolbook_backend; + using polynomial_type = typename backend_type::polynomial_type; + + backend_type backend; + const polynomial_type first_linear_factor = {fq12_value(1), fq12_value_type::one()}; + const polynomial_type second_linear_factor = {fq12_value(20), fq12_value_type::one()}; + const polynomial_type input = multiply(backend, first_linear_factor, second_linear_factor); + + polynomial_arithmetic::polynomial_context arithmetic_context; + const auto result = math::distinct_degree_factorization_reference(input, arithmetic_context); + + BOOST_CHECK(result.complete); + BOOST_REQUIRE_EQUAL(result.factors.size(), 1); + BOOST_CHECK_EQUAL(result.factors.front().irreducible_factor_degree, 1); + BOOST_CHECK(result.factors.front().polynomial == input); +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/libs/math/test/square_free_factorization.cpp b/libs/math/test/square_free_factorization.cpp new file mode 100644 index 000000000..0c0efbb86 --- /dev/null +++ b/libs/math/test/square_free_factorization.cpp @@ -0,0 +1,240 @@ +//---------------------------------------------------------------------------// +// Copyright (c) 2026 +// +// MIT License +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +//---------------------------------------------------------------------------// + +#define BOOST_TEST_MODULE square_free_factorization_test + +#include + +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace { + namespace math = nil::crypto3::math; + namespace polynomial_arithmetic = math::polynomial_arithmetic; + namespace fields = nil::crypto3::algebra::fields; + + using fq_field_type = fields::alt_bn128_base_field<254>; + using fq_value_type = fq_field_type::value_type; + using fq12_field_type = fields::fp12_2over3over2>; + using fq12_value_type = fq12_field_type::value_type; + + using namespace boost::multiprecision::literals; + + class test_prime_field_7 : public fields::field<4> { + public: + using policy_type = fields::field<4>; + using integral_type = policy_type::integral_type; + + constexpr static std::size_t value_bits = modulus_bits; + constexpr static std::size_t arity = 1; + constexpr static integral_type modulus = 0x7_cppui_modular4; + constexpr static integral_type group_order_minus_one_half = (modulus - 1u) / 2; + constexpr static const modular_params_type modulus_params = modulus.backend(); + + using modular_type = boost::multiprecision::number>>; + using value_type = fields::detail::element_fp>; + }; + + fq12_value_type fq12_value(std::size_t first_coordinate) { + fq12_value_type value = fq12_value_type::zero(); + for (std::size_t i = 0; i < fq12_field_type::arity; ++i) { + value.coordinate(i) = fq_value_type(first_coordinate + i); + } + return value; + } + + template + typename Backend::polynomial_type multiply(Backend &backend, const typename Backend::polynomial_type &left, + const typename Backend::polynomial_type &right) { + typename Backend::polynomial_type result; + backend.multiply(result, left, right); + return result; + } + + template + typename Backend::polynomial_type power(Backend &backend, const typename Backend::polynomial_type &base, + std::size_t exponent) { + using polynomial_type = typename Backend::polynomial_type; + using value_type = typename polynomial_type::value_type; + + polynomial_type result = {value_type::one()}; + for (std::size_t i = 0; i < exponent; ++i) { + result = multiply(backend, result, base); + } + return result; + } + + template + typename Backend::polynomial_type + reconstruct(Backend &backend, + const math::polynomial_factorization_result &result) { + using polynomial_type = typename Backend::polynomial_type; + + polynomial_type reconstructed = {result.leading_coefficient}; + for (const auto &factor : result.factors) { + for (std::size_t i = 0; i < factor.multiplicity; ++i) { + reconstructed = multiply(backend, reconstructed, factor.polynomial); + } + } + return reconstructed; + } +} // namespace + +BOOST_AUTO_TEST_SUITE(square_free_factorization_test_suite) + +BOOST_AUTO_TEST_CASE(fq_factorization_preserves_the_leading_coefficient_and_multiplicities) { + using backend_type = polynomial_arithmetic::schoolbook_backend; + using polynomial_type = typename backend_type::polynomial_type; + + backend_type backend; + const polynomial_type multiplicity_one = {fq_value_type(4), fq_value_type::one()}; + const polynomial_type multiplicity_two = {fq_value_type::one(), fq_value_type::one()}; + const polynomial_type multiplicity_three = {fq_value_type(2), fq_value_type::zero(), fq_value_type::one()}; + + polynomial_type input = multiply(backend, multiplicity_one, power(backend, multiplicity_two, 2)); + input = multiply(backend, input, power(backend, multiplicity_three, 3)); + math::scalar_multiplication(input, input, fq_value_type(7)); + + polynomial_arithmetic::polynomial_context arithmetic_context; + const auto result = math::square_free_factorization(input, arithmetic_context); + + BOOST_CHECK(result.complete); + BOOST_CHECK(result.leading_coefficient == fq_value_type(7)); + BOOST_REQUIRE_EQUAL(result.factors.size(), 3); + BOOST_CHECK(result.factors[0] == math::polynomial_factor({multiplicity_one, 1})); + BOOST_CHECK(result.factors[1] == math::polynomial_factor({multiplicity_two, 2})); + BOOST_CHECK(result.factors[2] == math::polynomial_factor({multiplicity_three, 3})); + BOOST_CHECK(reconstruct(backend, result) == input); +} + +BOOST_AUTO_TEST_CASE(staged_factorization_stops_after_the_reported_factor) { + using backend_type = polynomial_arithmetic::schoolbook_backend; + using polynomial_type = typename backend_type::polynomial_type; + + backend_type backend; + const polynomial_type multiplicity_one = {fq_value_type(4), fq_value_type::one()}; + const polynomial_type multiplicity_two = {fq_value_type::one(), fq_value_type::one()}; + const polynomial_type input = multiply(backend, multiplicity_one, power(backend, multiplicity_two, 2)); + + std::size_t callback_count = 0; + polynomial_arithmetic::polynomial_context arithmetic_context; + const auto result = math::square_free_factorization( + input, arithmetic_context, [&](const math::polynomial_factor &factor) { + ++callback_count; + BOOST_CHECK(factor.polynomial == multiplicity_one); + BOOST_CHECK_EQUAL(factor.multiplicity, 1); + return math::factorization_control::stop_factorization; + }); + + BOOST_CHECK(!result.complete); + BOOST_CHECK_EQUAL(callback_count, 1); + BOOST_REQUIRE_EQUAL(result.factors.size(), 1); + BOOST_CHECK(result.factors.front().polynomial == multiplicity_one); + BOOST_CHECK_EQUAL(result.factors.front().multiplicity, 1); +} + +BOOST_AUTO_TEST_CASE(zero_and_constant_polynomials_are_represented_by_the_leading_coefficient) { + using backend_type = polynomial_arithmetic::schoolbook_backend; + using polynomial_type = typename backend_type::polynomial_type; + + polynomial_arithmetic::polynomial_context arithmetic_context; + const auto zero = + math::square_free_factorization(polynomial_type {fq_value_type::zero()}, arithmetic_context); + BOOST_CHECK(zero.complete); + BOOST_CHECK(zero.leading_coefficient == fq_value_type::zero()); + BOOST_CHECK(zero.factors.empty()); + + const auto constant = + math::square_free_factorization(polynomial_type {fq_value_type(13)}, arithmetic_context); + BOOST_CHECK(constant.complete); + BOOST_CHECK(constant.leading_coefficient == fq_value_type(13)); + BOOST_CHECK(constant.factors.empty()); +} + +BOOST_AUTO_TEST_CASE(fq12_factorization_uses_native_extension_field_coefficients) { + using backend_type = polynomial_arithmetic::schoolbook_backend; + using polynomial_type = typename backend_type::polynomial_type; + + backend_type backend; + const polynomial_type multiplicity_two = {fq12_value(1), fq12_value_type::one()}; + const polynomial_type multiplicity_three = {fq12_value(20), fq12_value_type::one()}; + const fq12_value_type leading_coefficient = fq12_value(40); + + polynomial_type input = + multiply(backend, power(backend, multiplicity_two, 2), power(backend, multiplicity_three, 3)); + math::scalar_multiplication(input, input, leading_coefficient); + + polynomial_arithmetic::polynomial_context arithmetic_context; + const auto result = math::square_free_factorization(input, arithmetic_context); + + BOOST_CHECK(result.complete); + BOOST_CHECK(result.leading_coefficient == leading_coefficient); + BOOST_REQUIRE_EQUAL(result.factors.size(), 2); + BOOST_CHECK(result.factors[0] == math::polynomial_factor({multiplicity_two, 2})); + BOOST_CHECK(result.factors[1] == math::polynomial_factor({multiplicity_three, 3})); + BOOST_CHECK(reconstruct(backend, result) == input); + + using mixed_radix_backend = polynomial_arithmetic::mixed_radix_backend; + polynomial_arithmetic::polynomial_context_options options; + // Force fast division so the test exercises mixed-radix arithmetic inside the factorization algorithm. + options.basecase_divisor_coefficient_cutoff = 0; + options.basecase_quotient_coefficient_cutoff = 0; + polynomial_arithmetic::polynomial_context mixed_radix_context(mixed_radix_backend(18), + options); + const auto mixed_radix_result = math::square_free_factorization(input, mixed_radix_context); + BOOST_CHECK(mixed_radix_result == result); +} + +BOOST_AUTO_TEST_CASE(characteristic_must_be_greater_than_the_polynomial_degree) { + using value_type = test_prime_field_7::value_type; + using backend_type = polynomial_arithmetic::schoolbook_backend; + using polynomial_type = typename backend_type::polynomial_type; + + polynomial_arithmetic::polynomial_context arithmetic_context; + polynomial_type degree_six(7, value_type::zero()); + degree_six[6] = value_type::one(); + const auto supported = math::square_free_factorization(degree_six, arithmetic_context); + BOOST_REQUIRE_EQUAL(supported.factors.size(), 1); + BOOST_CHECK(supported.factors.front().polynomial == polynomial_type({value_type::zero(), value_type::one()})); + BOOST_CHECK_EQUAL(supported.factors.front().multiplicity, 6); + + polynomial_type degree_seven(8, value_type::zero()); + degree_seven[0] = value_type::one(); + degree_seven[7] = value_type::one(); + BOOST_CHECK_THROW(math::square_free_factorization(degree_seven, arithmetic_context), + std::invalid_argument); +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/libs/modes/include/nil/crypto3/modes/aead/chacha20poly1305.hpp b/libs/modes/include/nil/crypto3/modes/aead/chacha20poly1305.hpp index 71d5ad0aa..3ebbc59a0 100644 --- a/libs/modes/include/nil/crypto3/modes/aead/chacha20poly1305.hpp +++ b/libs/modes/include/nil/crypto3/modes/aead/chacha20poly1305.hpp @@ -185,15 +185,15 @@ namespace nil { return compute(data, poly1305_key); } }; - } // namespace aead - } // namespace modes + } // namespace aead + } // namespace modes namespace stream { namespace modes { typedef ::nil::crypto3::modes::aead::chacha20poly1305 chacha20poly1305; - } // namespace modes - } // namespace stream - } // namespace crypto3 -} // namespace nil + } // namespace modes + } // namespace stream + } // namespace crypto3 +} // namespace nil #endif // CRYPTO3_MODE_AEAD_CHACHA20_POLY1305_HPP