Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -218,20 +218,12 @@ namespace nil {
}

using boost::multiprecision::cpp_int;
static const cpp_int order_minus_one = field_order<field_type>() - 1;
static const auto parameters = []() {
cpp_int odd_part = order_minus_one;
std::size_t power_of_two = 0;
while ((odd_part & 1) == 0) {
odd_part >>= 1;
++power_of_two;
}
return std::pair<cpp_int, std::size_t>(odd_part, power_of_two);
}();
static const auto parameters = field_multiplicative_group_decomposition<field_type>();
static const cpp_int order_minus_one = parameters.odd_order << parameters.two_adicity;

const cpp_int &odd_part = parameters.first;
const std::size_t power_of_two = parameters.second;
if (power_of_two == 1) {
const cpp_int &odd_order = parameters.odd_order;
const std::size_t two_adicity = parameters.two_adicity;
if (two_adicity == 1) {
return pow((order_minus_one + 2) >> 2);
}

Expand All @@ -241,17 +233,17 @@ namespace nil {
const element_fp12_2over3over2 value(underlying_type::zero(),
underlying_type(base_type(candidate)));
if (value.pow(order_minus_one >> 1) == -one()) {
return value.pow(parameters.first);
return value.pow(parameters.odd_order);
}
}
assert(false && "failed to find an Fp12 quadratic non-residue");
return zero();
}();

element_fp12_2over3over2 c = non_residue_to_odd_part;
element_fp12_2over3over2 t = pow(odd_part);
element_fp12_2over3over2 root = pow((odd_part + 1) >> 1);
std::size_t remaining_power = power_of_two;
element_fp12_2over3over2 t = pow(odd_order);
element_fp12_2over3over2 root = pow((odd_order + 1) >> 1);
std::size_t remaining_power = two_adicity;

while (t != one()) {
element_fp12_2over3over2 t_squared = t;
Expand Down
48 changes: 48 additions & 0 deletions libs/algebra/include/nil/crypto3/algebra/fields/field_order.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,34 @@
#define CRYPTO3_ALGEBRA_FIELDS_FIELD_ORDER_HPP

#include <cstddef>
#include <stdexcept>
#include <utility>

#include <boost/multiprecision/cpp_int.hpp>

#include <nil/crypto3/algebra/type_traits.hpp>

namespace nil::crypto3::algebra::fields {

struct multiplicative_group_decomposition {
boost::multiprecision::cpp_int odd_order;
std::size_t two_adicity = 0;
};

/** Decompose a positive multiplicative-group order into odd_order * 2^two_adicity. */
inline multiplicative_group_decomposition
decompose_multiplicative_group_order(boost::multiprecision::cpp_int group_order) {
if (group_order <= 0) {
throw std::invalid_argument("a multiplicative-group order must be positive");
}
std::size_t two_adicity = 0;
while ((group_order & 1) == 0) {
group_order >>= 1;
++two_adicity;
}
return {std::move(group_order), two_adicity};
}

template<typename FieldOrValue, bool = FieldValue<FieldOrValue>>
struct field_type {
using type = FieldOrValue;
Expand Down Expand Up @@ -68,6 +89,33 @@ namespace nil::crypto3::algebra::fields {
return order;
}

/** Return the number of elements in a positive-degree extension of FieldType. */
template<typename FieldType>
boost::multiprecision::cpp_int extension_field_order(std::size_t extension_degree) {
if (extension_degree == 0) {
throw std::invalid_argument("a field extension must have positive degree");
}
const boost::multiprecision::cpp_int base_field_order = field_order<FieldType>();
boost::multiprecision::cpp_int order = 1;
for (std::size_t i = 0; i < extension_degree; ++i) {
order *= base_field_order;
}
return order;
}

/** Decompose the multiplicative-group order of FieldType into odd_order * 2^two_adicity. */
template<typename FieldType>
multiplicative_group_decomposition field_multiplicative_group_decomposition() {
return decompose_multiplicative_group_order(field_order<FieldType>() - 1);
}

/** Decompose the multiplicative-group order of a positive-degree extension of FieldType. */
template<typename FieldType>
multiplicative_group_decomposition
extension_field_multiplicative_group_decomposition(std::size_t extension_degree) {
return decompose_multiplicative_group_order(extension_field_order<FieldType>(extension_degree) - 1);
}

} // namespace nil::crypto3::algebra::fields

#endif // CRYPTO3_ALGEBRA_FIELDS_FIELD_ORDER_HPP
30 changes: 30 additions & 0 deletions libs/algebra/test/field_order.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -71,4 +71,34 @@ BOOST_AUTO_TEST_CASE(extension_field_order_is_characteristic_to_the_extension_de
BOOST_CHECK(fields::field_characteristic<fq12_field_type::value_type>() == characteristic);
}

BOOST_AUTO_TEST_CASE(runtime_extension_field_order_is_base_field_order_to_the_requested_degree) {
const boost::multiprecision::cpp_int fq_order = fields::field_order<fq_field_type>();
const boost::multiprecision::cpp_int fq12_order = fields::field_order<fq12_field_type>();

BOOST_CHECK(fields::extension_field_order<fq_field_type>(1) == fq_order);
BOOST_CHECK(fields::extension_field_order<fq_field_type>(2) == fq_order * fq_order);
BOOST_CHECK(fields::extension_field_order<fq12_field_type>(2) == fq12_order * fq12_order);
}

BOOST_AUTO_TEST_CASE(runtime_extension_field_order_rejects_degree_zero) {
BOOST_CHECK_THROW(fields::extension_field_order<fq_field_type>(0), std::invalid_argument);
}

BOOST_AUTO_TEST_CASE(multiplicative_group_order_decomposition_separates_the_two_primary_part) {
const auto fq12_decomposition = fields::field_multiplicative_group_decomposition<fq12_field_type>();
BOOST_CHECK((fq12_decomposition.odd_order & 1) == 1);
BOOST_CHECK((fq12_decomposition.odd_order << fq12_decomposition.two_adicity) ==
fields::field_order<fq12_field_type>() - 1);

const auto quadratic_decomposition = fields::extension_field_multiplicative_group_decomposition<fq_field_type>(2);
BOOST_CHECK((quadratic_decomposition.odd_order & 1) == 1);
BOOST_CHECK((quadratic_decomposition.odd_order << quadratic_decomposition.two_adicity) ==
fields::extension_field_order<fq_field_type>(2) - 1);
}

BOOST_AUTO_TEST_CASE(multiplicative_group_order_decomposition_rejects_nonpositive_orders) {
BOOST_CHECK_THROW(fields::decompose_multiplicative_group_order(0), std::invalid_argument);
BOOST_CHECK_THROW(fields::decompose_multiplicative_group_order(-1), std::invalid_argument);
}

BOOST_AUTO_TEST_SUITE_END()
18 changes: 6 additions & 12 deletions libs/algebra/test/fp12.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -219,12 +219,9 @@ BOOST_AUTO_TEST_CASE(bn254_subgroup_algorithms_match_direct_exponentiation) {
using value_type = typename field_type::value_type;
using boost::multiprecision::cpp_int;

cpp_int odd_order = fields::field_order<field_type>() - 1;
std::size_t two_adicity = 0;
while ((odd_order & 1) == 0) {
odd_order >>= 1;
++two_adicity;
}
const auto order_decomposition = fields::field_multiplicative_group_decomposition<field_type>();
const cpp_int &odd_order = order_decomposition.odd_order;
const std::size_t two_adicity = order_decomposition.two_adicity;
boost::random::mt19937 rng(0x2a11);

for (std::size_t i = 0; i < 2; ++i) {
Expand Down Expand Up @@ -274,12 +271,9 @@ BOOST_AUTO_TEST_CASE(generic_subgroup_algorithms_match_direct_formulas) {
using value_type = typename field_type::value_type;
using boost::multiprecision::cpp_int;

cpp_int odd_order = fields::field_order<field_type>() - 1;
std::size_t two_adicity = 0;
while ((odd_order & 1) == 0) {
odd_order >>= 1;
++two_adicity;
}
const auto order_decomposition = fields::field_multiplicative_group_decomposition<field_type>();
const cpp_int &odd_order = order_decomposition.odd_order;
const std::size_t two_adicity = order_decomposition.two_adicity;
boost::random::mt19937 rng(0x3812);
const value_type x = random_fp12<field_type>(rng);
BOOST_CHECK_EQUAL(fields::two_primary_component(x, odd_order, two_adicity), x.pow(odd_order));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,12 +90,7 @@ namespace nil::crypto3::math {
"Cantor-Zassenhaus splitting for characteristic two is not implemented");
}

const boost::multiprecision::cpp_int field_order = algebra::fields::field_order<field_type>();
exponent_ = 1;
for (std::size_t i = 0; i < irreducible_factor_degree_; ++i) {
exponent_ *= field_order;
}
exponent_ = (exponent_ - 1) / 2;
exponent_ = (algebra::fields::extension_field_order<field_type>(irreducible_factor_degree_) - 1) / 2;
}

std::size_t irreducible_factor_degree() const {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
//---------------------------------------------------------------------------//
// 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_RATIONAL_RECONSTRUCTION_HPP
#define CRYPTO3_MATH_POLYNOMIAL_RATIONAL_RECONSTRUCTION_HPP

#include <cstddef>
#include <memory>
#include <stdexcept>
#include <utility>

#include <nil/crypto3/math/polynomial/half_gcd.hpp>

namespace nil::crypto3::math {

/**
* Reconstruct polynomials numerator P and denominator Q such that
*
* P = residue * Q mod modulus,
*
* with degree(P) at most maximum_numerator_degree and degree(Q) at most
* maximum_denominator_degree. The denominator is normalized to monic form, and the numerator is scaled by the
* same field element. Outputs are replaced only when reconstruction succeeds and may alias either input, but must
* be distinct from each other.
*
* Starting with consecutive Euclidean remainders R0 = modulus and R1 = residue, write
*
* Ri = Ui * modulus + Qi * residue.
*
* Here Ui is the coefficient of modulus and Qi is the coefficient of residue. Initially U0 = 1, Q0 = 0 and
* U1 = 0, Q1 = 1. The Ui terms vanish modulo modulus, so the algorithm stores only Q0 and Q1.
*
* A Euclidean quotient q replaces the pairs by
*
* (R0, R1) <- (R1, R0 - q * R1),
* (Q0, Q1) <- (Q1, Q0 - q * Q1).
*
* Repeat this step while R1 is nonzero and degree(R1) exceeds maximum_numerator_degree. The loop normally stops
* before the Euclidean sequence reaches zero. At that point R1 is the first remainder within the numerator bound,
* so R1 is a valid numerator and Q1 is its denominator. Reconstruction fails if Q1 exceeds the denominator bound.
*
* The strict condition
*
* maximum_numerator_degree + maximum_denominator_degree < degree(modulus)
*
* ensures that at most one rational function can satisfy the requested bounds.
*
* @return true on success; false if the Euclidean candidate does not satisfy the denominator bound.
* @throws std::invalid_argument if the outputs are the same object; an input is empty or noncanonical; modulus is
* constant; residue is not reduced modulo modulus; or the degree bounds do not satisfy the strict
* uniqueness condition.
*/
template<detail::SupportsDivrem Backend>
bool rational_reconstruct(typename Backend::polynomial_type &numerator,
typename Backend::polynomial_type &denominator,
const typename Backend::polynomial_type &residue,
const typename Backend::polynomial_type &modulus,
std::size_t maximum_numerator_degree,
std::size_t maximum_denominator_degree,
polynomial_arithmetic::polynomial_context<Backend> &arithmetic_context) {
using polynomial_type = typename Backend::polynomial_type;
using value_type = typename polynomial_type::value_type;

if (std::addressof(numerator) == std::addressof(denominator)) {
throw std::invalid_argument("rational-reconstruction outputs must be distinct objects");
}
const auto is_canonical = [](const polynomial_type &polynomial) {
return !polynomial.empty() &&
(polynomial.size() == 1 || polynomial[polynomial.size() - 1] != value_type {});
};
if (!is_canonical(residue) || !is_canonical(modulus)) {
throw std::invalid_argument("rational reconstruction requires canonical nonempty inputs");
}
if (modulus.size() == 1) {
throw std::invalid_argument("rational reconstruction requires a nonconstant modulus");
}

const std::size_t modulus_degree = modulus.size() - 1;
if (residue.size() > modulus_degree) {
throw std::invalid_argument("rational reconstruction requires a reduced residue");
}
// Express the strict sum bound without a potentially overflowing addition.
if (maximum_numerator_degree >= modulus_degree ||
maximum_denominator_degree >= modulus_degree - maximum_numerator_degree) {
throw std::invalid_argument("rational-reconstruction degree bounds do not ensure uniqueness");
}

polynomial_type previous_remainder(modulus);
polynomial_type current_remainder(residue);
polynomial_type previous_denominator = {value_type {}};
polynomial_type current_denominator = {value_type::one()};
polynomial_type quotient;
polynomial_type next_remainder;
polynomial_type quotient_times_denominator;
polynomial_type next_denominator;

while (!is_zero(current_remainder) && current_remainder.size() - 1 > maximum_numerator_degree) {
detail::gcd_divrem_step(quotient, next_remainder, previous_remainder, current_remainder,
arithmetic_context);
arithmetic_context.multiply(quotient_times_denominator, quotient, current_denominator);
subtraction(next_denominator, previous_denominator, quotient_times_denominator);

previous_remainder = std::move(current_remainder);
current_remainder = std::move(next_remainder);
previous_denominator = std::move(current_denominator);
current_denominator = std::move(next_denominator);
}

if (is_zero(current_denominator) || current_denominator.size() - 1 > maximum_denominator_degree) {
return false;
}

const value_type denominator_leading_coefficient = current_denominator[current_denominator.size() - 1];
if (denominator_leading_coefficient != value_type::one()) {
const value_type normalization = denominator_leading_coefficient.inversed();
scalar_multiplication(current_remainder, current_remainder, normalization);
scalar_multiplication(current_denominator, current_denominator, normalization);
}

numerator = std::move(current_remainder);
denominator = std::move(current_denominator);
return true;
}

} // namespace nil::crypto3::math

#endif // CRYPTO3_MATH_POLYNOMIAL_RATIONAL_RECONSTRUCTION_HPP
Loading
Loading