From 3ae72758b065b369275643dded4926bacbf8b62b Mon Sep 17 00:00:00 2001 From: Brent Carmer Date: Mon, 24 Aug 2026 08:33:00 -0700 Subject: [PATCH 1/6] initial step - create field concept --- .../detail/element/fp12_2over3over2.hpp | 91 +++++++++++++++++++ .../algebra/fields/detail/element/fp2.hpp | 3 + .../fields/detail/element/fp6_3over2.hpp | 8 ++ .../crypto3/algebra/fields/field_order.hpp | 23 ++++- .../nil/crypto3/algebra/type_traits.hpp | 14 +++ libs/algebra/test/field_order.cpp | 4 + libs/algebra/test/fp12.cpp | 36 ++++++++ libs/algebra/test/type_traits.cpp | 15 ++- 8 files changed, 188 insertions(+), 6 deletions(-) diff --git a/libs/algebra/include/nil/crypto3/algebra/fields/detail/element/fp12_2over3over2.hpp b/libs/algebra/include/nil/crypto3/algebra/fields/detail/element/fp12_2over3over2.hpp index 775758687..d406a4c7a 100644 --- a/libs/algebra/include/nil/crypto3/algebra/fields/detail/element/fp12_2over3over2.hpp +++ b/libs/algebra/include/nil/crypto3/algebra/fields/detail/element/fp12_2over3over2.hpp @@ -27,8 +27,11 @@ #ifndef CRYPTO3_ALGEBRA_FIELDS_ELEMENT_FP12_2OVER3OVER2_HPP #define CRYPTO3_ALGEBRA_FIELDS_ELEMENT_FP12_2OVER3OVER2_HPP +#include + #include #include +#include namespace nil { namespace crypto3 { @@ -53,6 +56,20 @@ namespace nil { constexpr element_fp12_2over3over2() = default; + constexpr element_fp12_2over3over2(const underlying_type &in_data) : + data({in_data, underlying_type::zero()}) { + } + + constexpr element_fp12_2over3over2( + const typename underlying_type::underlying_type &in_data) : + element_fp12_2over3over2(underlying_type(in_data)) { + } + + constexpr element_fp12_2over3over2( + const typename underlying_type::underlying_type::underlying_type &in_data) : + element_fp12_2over3over2(underlying_type(in_data)) { + } + constexpr element_fp12_2over3over2(const underlying_type &in_data0, const underlying_type &in_data1) : data({in_data0, in_data1}) { @@ -192,6 +209,80 @@ namespace nil { return element_fp12_2over3over2(c0, c1); } + bool is_square() const { + return is_zero() || pow((field_order() - 1) >> 1) == one(); + } + + element_fp12_2over3over2 sqrt_known_square() const { + if (is_zero() || is_one()) { + return *this; + } + + using boost::multiprecision::cpp_int; + static const cpp_int order_minus_one = field_order() - 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(odd_part, power_of_two); + }(); + + const cpp_int &odd_part = parameters.first; + const std::size_t power_of_two = parameters.second; + if (power_of_two == 1) { + return pow((order_minus_one + 2) >> 2); + } + + static const element_fp12_2over3over2 non_residue_to_odd_part = []() { + using base_type = typename underlying_type::underlying_type::underlying_type; + for (unsigned candidate = 1; candidate < 256; ++candidate) { + 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); + } + } + 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; + + while (t != one()) { + element_fp12_2over3over2 t_squared = t; + std::size_t i = 0; + for (i = 1; i < remaining_power; ++i) { + t_squared = t_squared.squared(); + if (t_squared == one()) { + break; + } + } + assert(i < remaining_power && "Fp12 Tonelli-Shanks step failed"); + + const element_fp12_2over3over2 b = + c.pow(cpp_int(1) << (remaining_power - i - 1)); + const element_fp12_2over3over2 b_squared = b.squared(); + root *= b; + t *= b_squared; + c = b_squared; + remaining_power = i; + } + + assert(root.squared() == *this); + return root; + } + + element_fp12_2over3over2 sqrt() const { + assert(is_square() && "sqrt() called on a non-square Fp12 element"); + return sqrt_known_square(); + } + /** @brief Frobenius map: exponentiation by a degree of field characteristic. * For \f$a \in F_{p^k}\f$ this returns \f$a^{p^{pwr}}\f$. */ template diff --git a/libs/algebra/include/nil/crypto3/algebra/fields/detail/element/fp2.hpp b/libs/algebra/include/nil/crypto3/algebra/fields/detail/element/fp2.hpp index 7b8dc3e1e..3cf713485 100644 --- a/libs/algebra/include/nil/crypto3/algebra/fields/detail/element/fp2.hpp +++ b/libs/algebra/include/nil/crypto3/algebra/fields/detail/element/fp2.hpp @@ -71,6 +71,9 @@ namespace nil { constexpr element_fp2(const data_type &in_data) : data({in_data[0], in_data[1]}) { } + constexpr element_fp2(const underlying_type &in_data) : data({in_data, underlying_type::zero()}) { + } + constexpr element_fp2(const underlying_type &in_data0, const underlying_type &in_data1) : data({in_data0, in_data1}) { } diff --git a/libs/algebra/include/nil/crypto3/algebra/fields/detail/element/fp6_3over2.hpp b/libs/algebra/include/nil/crypto3/algebra/fields/detail/element/fp6_3over2.hpp index 747440b1a..5253af687 100644 --- a/libs/algebra/include/nil/crypto3/algebra/fields/detail/element/fp6_3over2.hpp +++ b/libs/algebra/include/nil/crypto3/algebra/fields/detail/element/fp6_3over2.hpp @@ -53,6 +53,14 @@ namespace nil { constexpr element_fp6_3over2() = default; + constexpr element_fp6_3over2(const underlying_type &in_data) : + data({in_data, underlying_type::zero(), underlying_type::zero()}) { + } + + constexpr element_fp6_3over2(const typename underlying_type::underlying_type &in_data) : + element_fp6_3over2(underlying_type(in_data)) { + } + constexpr element_fp6_3over2(const underlying_type &in_data0, const underlying_type &in_data1, const underlying_type &in_data2) : 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 1e457aaec..d1d1e5707 100644 --- a/libs/algebra/include/nil/crypto3/algebra/fields/field_order.hpp +++ b/libs/algebra/include/nil/crypto3/algebra/fields/field_order.hpp @@ -29,12 +29,28 @@ #include +#include + namespace nil::crypto3::algebra::fields { + template> + struct field_type { + using type = FieldOrValue; + }; + + template + struct field_type { + using type = typename FieldValueType::field_type; + }; + + template + using field_type_t = typename field_type::type; + /** 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()); + using field_type = field_type_t; + return boost::multiprecision::cpp_int(field_type::modulus.backend().to_cpp_int()); } /** @@ -43,9 +59,10 @@ namespace nil::crypto3::algebra::fields { */ template boost::multiprecision::cpp_int field_order() { - const boost::multiprecision::cpp_int characteristic = field_characteristic(); + using field_type = field_type_t; + const boost::multiprecision::cpp_int characteristic = field_characteristic(); boost::multiprecision::cpp_int order = 1; - for (std::size_t i = 0; i < FieldType::arity; ++i) { + for (std::size_t i = 0; i < field_type::arity; ++i) { order *= characteristic; } return order; diff --git a/libs/algebra/include/nil/crypto3/algebra/type_traits.hpp b/libs/algebra/include/nil/crypto3/algebra/type_traits.hpp index bafa98236..75dfcdc4e 100644 --- a/libs/algebra/include/nil/crypto3/algebra/type_traits.hpp +++ b/libs/algebra/include/nil/crypto3/algebra/type_traits.hpp @@ -35,6 +35,7 @@ #include #include #include +#include namespace nil { namespace crypto3 { @@ -164,6 +165,19 @@ namespace nil { static const bool value = is_field_element::value && has_type_underlying_type::value; }; + template + concept FieldValue = requires(const T &a, const T &b, const boost::multiprecision::cpp_int &exponent) { + typename T::field_type; + requires std::same_as; + { T::zero() } -> std::convertible_to; + { T::one() } -> std::convertible_to; + { a + b } -> std::same_as; + { a - b } -> std::same_as; + { a * b } -> std::same_as; + { a.inversed() } -> std::same_as; + { a.pow(exponent) } -> std::same_as; + }; + template concept FieldElementWithCoordinates = is_field_element::value && requires(T &value, const T &const_value, std::size_t index) { diff --git a/libs/algebra/test/field_order.cpp b/libs/algebra/test/field_order.cpp index ca7bd3798..43ac8efbe 100644 --- a/libs/algebra/test/field_order.cpp +++ b/libs/algebra/test/field_order.cpp @@ -30,6 +30,7 @@ #include #include #include +#include #include namespace { @@ -37,6 +38,7 @@ namespace { using fq_field_type = fields::alt_bn128_base_field<254>; using fq2_field_type = fields::fp2; + using fq6_field_type = fields::fp6_3over2; using fq12_field_type = fields::fp12_2over3over2; } // namespace @@ -59,7 +61,9 @@ BOOST_AUTO_TEST_CASE(extension_field_order_is_characteristic_to_the_extension_de } BOOST_CHECK(fields::field_order() == characteristic * characteristic); + BOOST_CHECK(fields::field_order() == fields::field_order()); BOOST_CHECK(fields::field_order() == expected_fq12_order); + BOOST_CHECK(fields::field_characteristic() == characteristic); } BOOST_AUTO_TEST_SUITE_END() diff --git a/libs/algebra/test/fp12.cpp b/libs/algebra/test/fp12.cpp index ba404909e..53a2cb98f 100644 --- a/libs/algebra/test/fp12.cpp +++ b/libs/algebra/test/fp12.cpp @@ -151,6 +151,42 @@ namespace { BOOST_AUTO_TEST_SUITE(fp12_tests) +BOOST_AUTO_TEST_CASE_TEMPLATE(tower_values_embed_into_fp12, Fp12Field, fp12_field_types) { + using fp12_value_type = typename Fp12Field::value_type; + using fp6_value_type = typename fp12_value_type::underlying_type; + using fp2_value_type = typename fp6_value_type::underlying_type; + using fp_value_type = typename fp2_value_type::underlying_type; + + const fp_value_type fp(7); + const fp2_value_type fp2(fp); + const fp6_value_type fp6(fp2); + + BOOST_CHECK_EQUAL(fp2, fp2_value_type(fp, fp_value_type::zero())); + BOOST_CHECK_EQUAL(fp6, fp6_value_type(fp2, fp2_value_type::zero(), fp2_value_type::zero())); + BOOST_CHECK_EQUAL(fp12_value_type(fp), fp12_value_type(fp6)); + BOOST_CHECK_EQUAL(fp12_value_type(fp2), fp12_value_type(fp6)); + BOOST_CHECK_EQUAL(fp12_value_type(fp6), fp12_value_type(fp6, fp6_value_type::zero())); + BOOST_CHECK_EQUAL(fp * fp12_value_type::one(), fp12_value_type(fp)); + BOOST_CHECK_EQUAL(fp12_value_type::one() * fp, fp12_value_type(fp)); +} + +BOOST_AUTO_TEST_CASE_TEMPLATE(square_roots_round_trip, Fp12Field, fp12_field_types) { + using fp12_value_type = typename Fp12Field::value_type; + boost::random::mt19937 rng(0x5a127); + + BOOST_CHECK(fp12_value_type::zero().is_square()); + BOOST_CHECK_EQUAL(fp12_value_type::zero().sqrt(), fp12_value_type::zero()); + BOOST_CHECK(fp12_value_type::one().is_square()); + BOOST_CHECK_EQUAL(fp12_value_type::one().sqrt(), fp12_value_type::one()); + + for (std::size_t i = 0; i < 2; ++i) { + const fp12_value_type square = random_fp12(rng).squared(); + BOOST_CHECK(square.is_square()); + BOOST_CHECK_EQUAL(square.sqrt_known_square().squared(), square); + BOOST_CHECK_EQUAL(square.sqrt().squared(), square); + } +} + BOOST_AUTO_TEST_CASE_TEMPLATE(coordinates_follow_tower_storage_order, Fp12Field, fp12_field_types) { using fp12_value_type = typename Fp12Field::value_type; using base_value_type = typename Fp12Field::base_field_type::value_type; diff --git a/libs/algebra/test/type_traits.cpp b/libs/algebra/test/type_traits.cpp index 5b02b8021..12a2011ee 100644 --- a/libs/algebra/test/type_traits.cpp +++ b/libs/algebra/test/type_traits.cpp @@ -165,6 +165,15 @@ BOOST_AUTO_TEST_CASE(mnt_type_traits) { BOOST_AUTO_TEST_CASE(alt_bn128_type_traits) { test_pairing_friendly_curve_types>(); + using base_field_type = curves::alt_bn128<254>::base_field_type; + using fp2_field_type = fields::fp2; + using fp6_field_type = fields::fp6_3over2; + using fp12_field_type = fields::fp12_2over3over2; + static_assert(FieldValue); + static_assert(FieldValue); + static_assert(FieldValue); + static_assert(FieldValue); + static_assert(!FieldValue); } BOOST_AUTO_TEST_CASE(jubjub_type_traits) { @@ -219,19 +228,19 @@ BOOST_AUTO_TEST_CASE(test_extended_fields_sqrt_trait) { BOOST_ASSERT(FIELD_HAS_SQRT(curves::alt_bn128_254::scalar_field_type)); BOOST_ASSERT(FIELD_HAS_SQRT(curves::alt_bn128_254::template g1_type<>::field_type)); BOOST_ASSERT(FIELD_HAS_SQRT(curves::alt_bn128_254::template g2_type<>::field_type)); - BOOST_ASSERT(!FIELD_HAS_SQRT(curves::alt_bn128_254::gt_type)); + BOOST_ASSERT(FIELD_HAS_SQRT(curves::alt_bn128_254::gt_type)); BOOST_ASSERT(FIELD_HAS_SQRT(curves::bls12_381::base_field_type)); BOOST_ASSERT(FIELD_HAS_SQRT(curves::bls12_381::scalar_field_type)); BOOST_ASSERT(FIELD_HAS_SQRT(curves::bls12_381::template g1_type<>::field_type)); BOOST_ASSERT(FIELD_HAS_SQRT(curves::bls12_381::template g2_type<>::field_type)); - BOOST_ASSERT(!FIELD_HAS_SQRT(curves::bls12_381::gt_type)); + BOOST_ASSERT(FIELD_HAS_SQRT(curves::bls12_381::gt_type)); BOOST_ASSERT(FIELD_HAS_SQRT(curves::bls12_377::base_field_type)); BOOST_ASSERT(FIELD_HAS_SQRT(curves::bls12_377::scalar_field_type)); BOOST_ASSERT(FIELD_HAS_SQRT(curves::bls12_377::template g1_type<>::field_type)); BOOST_ASSERT(FIELD_HAS_SQRT(curves::bls12_377::template g2_type<>::field_type)); - BOOST_ASSERT(!FIELD_HAS_SQRT(curves::bls12_377::gt_type)); + BOOST_ASSERT(FIELD_HAS_SQRT(curves::bls12_377::gt_type)); BOOST_ASSERT(FIELD_HAS_SQRT(curves::mnt4_298::base_field_type)); BOOST_ASSERT(FIELD_HAS_SQRT(curves::mnt4_298::scalar_field_type)); From c5bfc76fdae565aa0b2037b7bf95305ff6fc06eb Mon Sep 17 00:00:00 2001 From: Brent Carmer Date: Mon, 24 Aug 2026 09:37:38 -0700 Subject: [PATCH 2/6] port field algorithms from aadp --- .../fields/detail/field_algorithms.hpp | 49 +++++ .../field_algorithms/alt_bn128_fp12.hpp | 208 ++++++++++++++++++ .../algebra/fields/field_algorithms.hpp | 71 ++++++ libs/algebra/test/fp12.cpp | 63 ++++++ 4 files changed, 391 insertions(+) create mode 100644 libs/algebra/include/nil/crypto3/algebra/fields/detail/field_algorithms.hpp create mode 100644 libs/algebra/include/nil/crypto3/algebra/fields/detail/field_algorithms/alt_bn128_fp12.hpp create mode 100644 libs/algebra/include/nil/crypto3/algebra/fields/field_algorithms.hpp diff --git a/libs/algebra/include/nil/crypto3/algebra/fields/detail/field_algorithms.hpp b/libs/algebra/include/nil/crypto3/algebra/fields/detail/field_algorithms.hpp new file mode 100644 index 000000000..f7f61ad3d --- /dev/null +++ b/libs/algebra/include/nil/crypto3/algebra/fields/detail/field_algorithms.hpp @@ -0,0 +1,49 @@ +//---------------------------------------------------------------------------// +// Copyright (c) 2026 +// +// MIT License +//---------------------------------------------------------------------------// + +#ifndef CRYPTO3_ALGEBRA_FIELDS_DETAIL_FIELD_ALGORITHMS_HPP +#define CRYPTO3_ALGEBRA_FIELDS_DETAIL_FIELD_ALGORITHMS_HPP + +#include +#include +#include + +#include + +#include + +namespace nil::crypto3::algebra::fields::detail { + + // Customization point used by the public free functions in field_algorithms.hpp. + // This primary template provides formulas that work for any FieldValue. A field + // may specialize this class in a separate detail header when its tower structure + // permits a faster implementation. Keeping dispatch here gives callers one stable + // public API while keeping field-specific types and algorithms private. + template + struct field_algorithms { + static Value two_primary_component(const Value &x, + const boost::multiprecision::cpp_int &odd_order, + std::size_t) { + return x.pow(odd_order); + } + + static Value odd_subgroup_sqrt(const Value &x, const boost::multiprecision::cpp_int &odd_order) { + assert((odd_order & 1) != 0); + return x.is_zero() || x.is_one() ? x : x.pow((odd_order + 1) >> 1); + } + + static Value sqrt_known_square(const Value &x) { + return x.sqrt(); + } + + static Value primitive_two_power_root_of_unity(std::size_t) { + throw std::invalid_argument("primitive root discovery is not available for this field"); + } + }; + +} // namespace nil::crypto3::algebra::fields::detail + +#endif // CRYPTO3_ALGEBRA_FIELDS_DETAIL_FIELD_ALGORITHMS_HPP diff --git a/libs/algebra/include/nil/crypto3/algebra/fields/detail/field_algorithms/alt_bn128_fp12.hpp b/libs/algebra/include/nil/crypto3/algebra/fields/detail/field_algorithms/alt_bn128_fp12.hpp new file mode 100644 index 000000000..a5252ec32 --- /dev/null +++ b/libs/algebra/include/nil/crypto3/algebra/fields/detail/field_algorithms/alt_bn128_fp12.hpp @@ -0,0 +1,208 @@ +//---------------------------------------------------------------------------// +// Copyright (c) 2026 +// +// MIT License +//---------------------------------------------------------------------------// + +#ifndef CRYPTO3_ALGEBRA_FIELDS_DETAIL_ALT_BN128_FP12_ALGORITHMS_HPP +#define CRYPTO3_ALGEBRA_FIELDS_DETAIL_ALT_BN128_FP12_ALGORITHMS_HPP + +#include +#include + +#include + +#include +#include +#include +#include +#include +#include + +namespace nil::crypto3::algebra::fields::detail { + + using alt_bn128_254_base_field = ::nil::crypto3::algebra::fields::alt_bn128_base_field<254>; + using alt_bn128_254_fp2_field = ::nil::crypto3::algebra::fields::fp2; + using alt_bn128_254_fp6_field = ::nil::crypto3::algebra::fields::fp6_3over2; + using alt_bn128_254_fp12_field = ::nil::crypto3::algebra::fields::fp12_2over3over2; + using alt_bn128_254_fp_value = typename alt_bn128_254_base_field::value_type; + using alt_bn128_254_fp2_value = typename alt_bn128_254_fp2_field::value_type; + using alt_bn128_254_fp6_value = typename alt_bn128_254_fp6_field::value_type; + using alt_bn128_254_fp12_value = typename alt_bn128_254_fp12_field::value_type; + + struct alt_bn128_fp4_subfield_value { + alt_bn128_254_fp2_value c0; + alt_bn128_254_fp2_value c1; + + static const alt_bn128_254_fp2_value &non_residue() { + static const alt_bn128_254_fp2_value value(alt_bn128_254_fp_value(9), alt_bn128_254_fp_value(1)); + return value; + } + + alt_bn128_fp4_subfield_value squared() const { + const alt_bn128_254_fp2_value aa = c0.squared(); + const alt_bn128_254_fp2_value bb = c1.squared(); + const alt_bn128_254_fp2_value ab = c0 * c1; + return {aa + non_residue() * bb, ab + ab}; + } + }; + + inline alt_bn128_fp4_subfield_value operator*(const alt_bn128_fp4_subfield_value &a, + const alt_bn128_fp4_subfield_value &b) { + const alt_bn128_254_fp2_value a0b0 = a.c0 * b.c0; + const alt_bn128_254_fp2_value a1b1 = a.c1 * b.c1; + return {a0b0 + alt_bn128_fp4_subfield_value::non_residue() * a1b1, + (a.c0 + a.c1) * (b.c0 + b.c1) - a0b0 - a1b1}; + } + + inline alt_bn128_fp4_subfield_value pow(alt_bn128_fp4_subfield_value x, + boost::multiprecision::cpp_int exponent) { + alt_bn128_fp4_subfield_value result {alt_bn128_254_fp2_value::one(), alt_bn128_254_fp2_value::zero()}; + while (exponent != 0) { + if (boost::multiprecision::bit_test(exponent, 0)) { + result = result * x; + } + exponent >>= 1; + if (exponent != 0) { + x = x.squared(); + } + } + return result; + } + + inline alt_bn128_254_fp12_value fp12_from_fp4_subfield(const alt_bn128_fp4_subfield_value &x) { + return alt_bn128_254_fp12_value( + alt_bn128_254_fp6_value(x.c0, alt_bn128_254_fp2_value::zero(), alt_bn128_254_fp2_value::zero()), + alt_bn128_254_fp6_value(alt_bn128_254_fp2_value::zero(), x.c1, alt_bn128_254_fp2_value::zero())); + } + + inline alt_bn128_fp4_subfield_value fp12_to_fp4_subfield(const alt_bn128_254_fp12_value &x) { + assert(x.data[0].data[1].is_zero()); + assert(x.data[0].data[2].is_zero()); + assert(x.data[1].data[0].is_zero()); + assert(x.data[1].data[2].is_zero()); + return {x.data[0].data[0], x.data[1].data[1]}; + } + + inline alt_bn128_254_fp6_value fp12_to_fp6_subfield(const alt_bn128_254_fp12_value &x) { + assert(x.data[1].is_zero()); + return x.data[0]; + } + + inline alt_bn128_254_fp12_value fp12_norm_one_sqrt_frobenius(const alt_bn128_254_fp12_value &x) { + static const boost::multiprecision::cpp_int exponent = + (field_order() - 1) >> 2; + const alt_bn128_254_fp12_value y = x.pow(exponent); + return x * y * y.Frobenius_map(2) * y.Frobenius_map(4); + } + + inline alt_bn128_254_fp6_value fp6_fourth_root_odd_frobenius(const alt_bn128_254_fp6_value &x) { + static const boost::multiprecision::cpp_int q = field_order(); + static const boost::multiprecision::cpp_int a = (q - 1) >> 4; + static const boost::multiprecision::cpp_int b = (boost::multiprecision::cpp_int(3) * a - 1) >> 2; + + assert((q - 1) % 16 == 0); + assert((boost::multiprecision::cpp_int(3) * a - 1) % 4 == 0); + + const alt_bn128_254_fp6_value y = x.pow(b); + const alt_bn128_254_fp6_value z = x.pow(a); + const alt_bn128_254_fp6_value z4 = z.squared().squared(); + const alt_bn128_254_fp6_value z8 = z4.squared(); + return y * y.Frobenius_map(2) * y.Frobenius_map(4) * z4.Frobenius_map(2) * x * z8; + } + + inline alt_bn128_254_fp12_value alt_bn128_fp12_two_primary_component( + const alt_bn128_254_fp12_value &x, + const boost::multiprecision::cpp_int &odd_order, + std::size_t two_adicity) { + static const boost::multiprecision::cpp_int expected_odd_order = + (field_order() - 1) >> 5; + if (two_adicity != 5 || odd_order != expected_odd_order) { + return x.pow(odd_order); + } + + static const boost::multiprecision::cpp_int fp4_odd_order = []() { + const boost::multiprecision::cpp_int p = field_characteristic(); + boost::multiprecision::cpp_int order = p * p * p * p; + order -= 1; + order >>= 5; + return order; + }(); + const alt_bn128_254_fp12_value norm = x * x.Frobenius_map(4) * x.Frobenius_map(8); + return fp12_from_fp4_subfield(pow(fp12_to_fp4_subfield(norm), fp4_odd_order)); + } + + inline alt_bn128_254_fp12_value alt_bn128_fp12_odd_subgroup_sqrt( + const alt_bn128_254_fp12_value &x, + const boost::multiprecision::cpp_int &odd_order) { + if (x.is_zero() || x.is_one()) { + return x; + } + + static const boost::multiprecision::cpp_int expected_odd_order = + (field_order() - 1) >> 5; + if (odd_order != expected_odd_order) { + assert((odd_order & 1) != 0); + return x.pow((odd_order + 1) >> 1); + } + + const alt_bn128_254_fp6_value norm = fp12_to_fp6_subfield(x * x.Frobenius_map(6)); + const alt_bn128_254_fp6_value fourth_root = fp6_fourth_root_odd_frobenius(norm); + const alt_bn128_254_fp6_value fp6_component = fourth_root.squared(); + const alt_bn128_254_fp12_value norm_one = x * alt_bn128_254_fp12_value(fp6_component.inversed()); + const alt_bn128_254_fp12_value result = + alt_bn128_254_fp12_value(fourth_root) * fp12_norm_one_sqrt_frobenius(norm_one); + assert(result.squared() == x); + return result; + } + + inline alt_bn128_254_fp12_value alt_bn128_fp12_primitive_root(std::size_t two_adicity) { + if (two_adicity != 5) { + throw std::invalid_argument("BN254 Fp12 primitive root discovery supports two-adicity 5"); + } + + static const alt_bn128_254_fp12_value root = []() { + const boost::multiprecision::cpp_int exponent = + (field_order() - 1) >> 5; + for (unsigned candidate = 2; candidate < 64; ++candidate) { + const alt_bn128_254_fp12_value value( + alt_bn128_254_fp6_value::zero(), alt_bn128_254_fp6_value(alt_bn128_254_fp_value(candidate))); + const alt_bn128_254_fp12_value possible_root = value.pow(exponent); + if (!possible_root.is_one() && possible_root.pow(16) != alt_bn128_254_fp12_value::one() && + possible_root.pow(32) == alt_bn128_254_fp12_value::one()) { + return possible_root; + } + } + assert(false && "failed to find a primitive 32nd root of unity"); + return alt_bn128_254_fp12_value::one(); + }(); + return root; + } + + template<> + struct field_algorithms { + static alt_bn128_254_fp12_value two_primary_component( + const alt_bn128_254_fp12_value &x, + const boost::multiprecision::cpp_int &odd_order, + std::size_t two_adicity) { + return alt_bn128_fp12_two_primary_component(x, odd_order, two_adicity); + } + + static alt_bn128_254_fp12_value odd_subgroup_sqrt( + const alt_bn128_254_fp12_value &x, + const boost::multiprecision::cpp_int &odd_order) { + return alt_bn128_fp12_odd_subgroup_sqrt(x, odd_order); + } + + static alt_bn128_254_fp12_value sqrt_known_square(const alt_bn128_254_fp12_value &x) { + return x.sqrt_known_square(); + } + + static alt_bn128_254_fp12_value primitive_two_power_root_of_unity(std::size_t two_adicity) { + return alt_bn128_fp12_primitive_root(two_adicity); + } + }; + +} // namespace nil::crypto3::algebra::fields::detail + +#endif // CRYPTO3_ALGEBRA_FIELDS_DETAIL_ALT_BN128_FP12_ALGORITHMS_HPP diff --git a/libs/algebra/include/nil/crypto3/algebra/fields/field_algorithms.hpp b/libs/algebra/include/nil/crypto3/algebra/fields/field_algorithms.hpp new file mode 100644 index 000000000..33ac1d4c4 --- /dev/null +++ b/libs/algebra/include/nil/crypto3/algebra/fields/field_algorithms.hpp @@ -0,0 +1,71 @@ +//---------------------------------------------------------------------------// +// Copyright (c) 2026 +// +// MIT License +//---------------------------------------------------------------------------// + +#ifndef CRYPTO3_ALGEBRA_FIELDS_FIELD_ALGORITHMS_HPP +#define CRYPTO3_ALGEBRA_FIELDS_FIELD_ALGORITHMS_HPP + +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +namespace nil::crypto3::algebra::fields { + + template + Value two_primary_component(const Value &x, + const boost::multiprecision::cpp_int &odd_order, + std::size_t two_adicity) { + return detail::field_algorithms::two_primary_component(x, odd_order, two_adicity); + } + + template + Value odd_subgroup_sqrt(const Value &x, const boost::multiprecision::cpp_int &odd_order) { + return detail::field_algorithms::odd_subgroup_sqrt(x, odd_order); + } + + template + Value sqrt_known_square(const Value &x) { + return detail::field_algorithms::sqrt_known_square(x); + } + + template + Value primitive_two_power_root_of_unity(std::size_t two_adicity) { + return detail::field_algorithms::primitive_two_power_root_of_unity(two_adicity); + } + + template + std::vector roots_of_unity(const Value &primitive_root, std::size_t two_adicity) { + if (two_adicity >= std::numeric_limits::digits) { + throw std::invalid_argument("two-adicity is too large"); + } + + const std::size_t count = std::size_t(1) << two_adicity; + assert(primitive_root.pow(count) == Value::one()); + assert(two_adicity == 0 || primitive_root.pow(count >> 1) != Value::one()); + + std::vector roots(count); + roots[0] = Value::one(); + for (std::size_t i = 1; i < count; ++i) { + roots[i] = roots[i - 1] * primitive_root; + } + return roots; + } + + template + std::vector roots_of_unity(std::size_t two_adicity) { + return roots_of_unity(primitive_two_power_root_of_unity(two_adicity), two_adicity); + } + +} // namespace nil::crypto3::algebra::fields + +#endif // CRYPTO3_ALGEBRA_FIELDS_FIELD_ALGORITHMS_HPP diff --git a/libs/algebra/test/fp12.cpp b/libs/algebra/test/fp12.cpp index 53a2cb98f..ce878e604 100644 --- a/libs/algebra/test/fp12.cpp +++ b/libs/algebra/test/fp12.cpp @@ -34,6 +34,7 @@ #include #include +#include #include namespace { @@ -187,6 +188,68 @@ BOOST_AUTO_TEST_CASE_TEMPLATE(square_roots_round_trip, Fp12Field, fp12_field_typ } } +BOOST_AUTO_TEST_CASE(bn254_subgroup_algorithms_match_direct_exponentiation) { + using field_type = alt_bn128_254_fp12; + using value_type = typename field_type::value_type; + using boost::multiprecision::cpp_int; + + cpp_int odd_order = fields::field_order() - 1; + std::size_t two_adicity = 0; + while ((odd_order & 1) == 0) { + odd_order >>= 1; + ++two_adicity; + } + boost::random::mt19937 rng(0x2a11); + + for (std::size_t i = 0; i < 2; ++i) { + const value_type x = random_fp12(rng); + BOOST_CHECK_EQUAL(fields::two_primary_component(x, odd_order, two_adicity), x.pow(odd_order)); + BOOST_CHECK_EQUAL(fields::two_primary_component(x, odd_order + 2, two_adicity), x.pow(odd_order + 2)); + + const value_type odd_subgroup_value = x.pow(cpp_int(1) << two_adicity); + const value_type root = fields::odd_subgroup_sqrt(odd_subgroup_value, odd_order); + BOOST_CHECK_EQUAL(root.squared(), odd_subgroup_value); + BOOST_CHECK_EQUAL(root, odd_subgroup_value.pow((odd_order + 1) >> 1)); + } +} + +BOOST_AUTO_TEST_CASE(bn254_roots_of_unity_have_order_32) { + using value_type = typename alt_bn128_254_fp12::value_type; + const value_type primitive_root = fields::primitive_two_power_root_of_unity(5); + const std::vector roots = fields::roots_of_unity(5); + + BOOST_REQUIRE_EQUAL(roots.size(), 32); + BOOST_CHECK_EQUAL(roots.front(), value_type::one()); + BOOST_CHECK_EQUAL(primitive_root.pow(32), value_type::one()); + BOOST_CHECK_NE(primitive_root.pow(16), value_type::one()); + for (std::size_t i = 0; i < roots.size(); ++i) { + BOOST_CHECK_EQUAL(roots[i], primitive_root.pow(i)); + } +} + +BOOST_AUTO_TEST_CASE(generic_subgroup_algorithms_match_direct_formulas) { + using field_type = bls12_381_fp12; + using value_type = typename field_type::value_type; + using boost::multiprecision::cpp_int; + + cpp_int odd_order = fields::field_order() - 1; + std::size_t two_adicity = 0; + while ((odd_order & 1) == 0) { + odd_order >>= 1; + ++two_adicity; + } + boost::random::mt19937 rng(0x3812); + const value_type x = random_fp12(rng); + BOOST_CHECK_EQUAL(fields::two_primary_component(x, odd_order, two_adicity), x.pow(odd_order)); + + const value_type odd_subgroup_value = x.pow(cpp_int(1) << two_adicity); + BOOST_CHECK_EQUAL(fields::odd_subgroup_sqrt(odd_subgroup_value, odd_order), + odd_subgroup_value.pow((odd_order + 1) >> 1)); + + const value_type square = x.squared(); + BOOST_CHECK_EQUAL(fields::sqrt_known_square(square).squared(), square); +} + BOOST_AUTO_TEST_CASE_TEMPLATE(coordinates_follow_tower_storage_order, Fp12Field, fp12_field_types) { using fp12_value_type = typename Fp12Field::value_type; using base_value_type = typename Fp12Field::base_field_type::value_type; From b6ac8b0aced0a005f58a07fa16db6573e680747d Mon Sep 17 00:00:00 2001 From: Brent Carmer Date: Mon, 24 Aug 2026 09:55:12 -0700 Subject: [PATCH 3/6] more tests --- .../fields/detail/field_algorithms.hpp | 25 ++++++-- .../field_algorithms/alt_bn128_fp12.hpp | 63 +++++++++++-------- .../algebra/fields/field_algorithms.hpp | 20 +++++- libs/algebra/test/field_order.cpp | 5 ++ libs/algebra/test/fp12.cpp | 42 +++++++++++++ 5 files changed, 124 insertions(+), 31 deletions(-) diff --git a/libs/algebra/include/nil/crypto3/algebra/fields/detail/field_algorithms.hpp b/libs/algebra/include/nil/crypto3/algebra/fields/detail/field_algorithms.hpp index f7f61ad3d..a727fbddd 100644 --- a/libs/algebra/include/nil/crypto3/algebra/fields/detail/field_algorithms.hpp +++ b/libs/algebra/include/nil/crypto3/algebra/fields/detail/field_algorithms.hpp @@ -1,7 +1,25 @@ //---------------------------------------------------------------------------// -// Copyright (c) 2026 +// Copyright (c) 2026 Alloc Init Labs Inc. // // 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_ALGEBRA_FIELDS_DETAIL_FIELD_ALGORITHMS_HPP @@ -24,9 +42,8 @@ namespace nil::crypto3::algebra::fields::detail { // public API while keeping field-specific types and algorithms private. template struct field_algorithms { - static Value two_primary_component(const Value &x, - const boost::multiprecision::cpp_int &odd_order, - std::size_t) { + static Value + two_primary_component(const Value &x, const boost::multiprecision::cpp_int &odd_order, std::size_t) { return x.pow(odd_order); } diff --git a/libs/algebra/include/nil/crypto3/algebra/fields/detail/field_algorithms/alt_bn128_fp12.hpp b/libs/algebra/include/nil/crypto3/algebra/fields/detail/field_algorithms/alt_bn128_fp12.hpp index a5252ec32..d32e4fc19 100644 --- a/libs/algebra/include/nil/crypto3/algebra/fields/detail/field_algorithms/alt_bn128_fp12.hpp +++ b/libs/algebra/include/nil/crypto3/algebra/fields/detail/field_algorithms/alt_bn128_fp12.hpp @@ -1,7 +1,25 @@ //---------------------------------------------------------------------------// -// Copyright (c) 2026 +// Copyright (c) 2026 Alloc Init Labs Inc. // // 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_ALGEBRA_FIELDS_DETAIL_ALT_BN128_FP12_ALGORITHMS_HPP @@ -48,15 +66,13 @@ namespace nil::crypto3::algebra::fields::detail { }; inline alt_bn128_fp4_subfield_value operator*(const alt_bn128_fp4_subfield_value &a, - const alt_bn128_fp4_subfield_value &b) { + const alt_bn128_fp4_subfield_value &b) { const alt_bn128_254_fp2_value a0b0 = a.c0 * b.c0; const alt_bn128_254_fp2_value a1b1 = a.c1 * b.c1; - return {a0b0 + alt_bn128_fp4_subfield_value::non_residue() * a1b1, - (a.c0 + a.c1) * (b.c0 + b.c1) - a0b0 - a1b1}; + return {a0b0 + alt_bn128_fp4_subfield_value::non_residue() * a1b1, (a.c0 + a.c1) * (b.c0 + b.c1) - a0b0 - a1b1}; } - inline alt_bn128_fp4_subfield_value pow(alt_bn128_fp4_subfield_value x, - boost::multiprecision::cpp_int exponent) { + inline alt_bn128_fp4_subfield_value pow(alt_bn128_fp4_subfield_value x, boost::multiprecision::cpp_int exponent) { alt_bn128_fp4_subfield_value result {alt_bn128_254_fp2_value::one(), alt_bn128_254_fp2_value::zero()}; while (exponent != 0) { if (boost::multiprecision::bit_test(exponent, 0)) { @@ -90,8 +106,7 @@ namespace nil::crypto3::algebra::fields::detail { } inline alt_bn128_254_fp12_value fp12_norm_one_sqrt_frobenius(const alt_bn128_254_fp12_value &x) { - static const boost::multiprecision::cpp_int exponent = - (field_order() - 1) >> 2; + static const boost::multiprecision::cpp_int exponent = (field_order() - 1) >> 2; const alt_bn128_254_fp12_value y = x.pow(exponent); return x * y * y.Frobenius_map(2) * y.Frobenius_map(4); } @@ -111,10 +126,10 @@ namespace nil::crypto3::algebra::fields::detail { return y * y.Frobenius_map(2) * y.Frobenius_map(4) * z4.Frobenius_map(2) * x * z8; } - inline alt_bn128_254_fp12_value alt_bn128_fp12_two_primary_component( - const alt_bn128_254_fp12_value &x, - const boost::multiprecision::cpp_int &odd_order, - std::size_t two_adicity) { + inline alt_bn128_254_fp12_value + alt_bn128_fp12_two_primary_component(const alt_bn128_254_fp12_value &x, + const boost::multiprecision::cpp_int &odd_order, + std::size_t two_adicity) { static const boost::multiprecision::cpp_int expected_odd_order = (field_order() - 1) >> 5; if (two_adicity != 5 || odd_order != expected_odd_order) { @@ -132,9 +147,8 @@ namespace nil::crypto3::algebra::fields::detail { return fp12_from_fp4_subfield(pow(fp12_to_fp4_subfield(norm), fp4_odd_order)); } - inline alt_bn128_254_fp12_value alt_bn128_fp12_odd_subgroup_sqrt( - const alt_bn128_254_fp12_value &x, - const boost::multiprecision::cpp_int &odd_order) { + inline alt_bn128_254_fp12_value alt_bn128_fp12_odd_subgroup_sqrt(const alt_bn128_254_fp12_value &x, + const boost::multiprecision::cpp_int &odd_order) { if (x.is_zero() || x.is_one()) { return x; } @@ -162,11 +176,10 @@ namespace nil::crypto3::algebra::fields::detail { } static const alt_bn128_254_fp12_value root = []() { - const boost::multiprecision::cpp_int exponent = - (field_order() - 1) >> 5; + const boost::multiprecision::cpp_int exponent = (field_order() - 1) >> 5; for (unsigned candidate = 2; candidate < 64; ++candidate) { - const alt_bn128_254_fp12_value value( - alt_bn128_254_fp6_value::zero(), alt_bn128_254_fp6_value(alt_bn128_254_fp_value(candidate))); + const alt_bn128_254_fp12_value value(alt_bn128_254_fp6_value::zero(), + alt_bn128_254_fp6_value(alt_bn128_254_fp_value(candidate))); const alt_bn128_254_fp12_value possible_root = value.pow(exponent); if (!possible_root.is_one() && possible_root.pow(16) != alt_bn128_254_fp12_value::one() && possible_root.pow(32) == alt_bn128_254_fp12_value::one()) { @@ -181,16 +194,14 @@ namespace nil::crypto3::algebra::fields::detail { template<> struct field_algorithms { - static alt_bn128_254_fp12_value two_primary_component( - const alt_bn128_254_fp12_value &x, - const boost::multiprecision::cpp_int &odd_order, - std::size_t two_adicity) { + static alt_bn128_254_fp12_value two_primary_component(const alt_bn128_254_fp12_value &x, + const boost::multiprecision::cpp_int &odd_order, + std::size_t two_adicity) { return alt_bn128_fp12_two_primary_component(x, odd_order, two_adicity); } - static alt_bn128_254_fp12_value odd_subgroup_sqrt( - const alt_bn128_254_fp12_value &x, - const boost::multiprecision::cpp_int &odd_order) { + static alt_bn128_254_fp12_value odd_subgroup_sqrt(const alt_bn128_254_fp12_value &x, + const boost::multiprecision::cpp_int &odd_order) { return alt_bn128_fp12_odd_subgroup_sqrt(x, odd_order); } diff --git a/libs/algebra/include/nil/crypto3/algebra/fields/field_algorithms.hpp b/libs/algebra/include/nil/crypto3/algebra/fields/field_algorithms.hpp index 33ac1d4c4..e8a10af54 100644 --- a/libs/algebra/include/nil/crypto3/algebra/fields/field_algorithms.hpp +++ b/libs/algebra/include/nil/crypto3/algebra/fields/field_algorithms.hpp @@ -1,7 +1,25 @@ //---------------------------------------------------------------------------// -// Copyright (c) 2026 +// Copyright (c) 2026 Alloc Init Labs Inc. // // 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_ALGEBRA_FIELDS_FIELD_ALGORITHMS_HPP diff --git a/libs/algebra/test/field_order.cpp b/libs/algebra/test/field_order.cpp index 43ac8efbe..8f2d04e38 100644 --- a/libs/algebra/test/field_order.cpp +++ b/libs/algebra/test/field_order.cpp @@ -55,12 +55,17 @@ BOOST_AUTO_TEST_CASE(prime_field_order_equals_its_characteristic) { BOOST_AUTO_TEST_CASE(extension_field_order_is_characteristic_to_the_extension_degree) { const boost::multiprecision::cpp_int characteristic = fields::field_order(); + boost::multiprecision::cpp_int expected_fq6_order = 1; + for (std::size_t i = 0; i < fq6_field_type::arity; ++i) { + expected_fq6_order *= characteristic; + } boost::multiprecision::cpp_int expected_fq12_order = 1; for (std::size_t i = 0; i < fq12_field_type::arity; ++i) { expected_fq12_order *= characteristic; } BOOST_CHECK(fields::field_order() == characteristic * characteristic); + BOOST_CHECK(fields::field_order() == expected_fq6_order); BOOST_CHECK(fields::field_order() == fields::field_order()); BOOST_CHECK(fields::field_order() == expected_fq12_order); BOOST_CHECK(fields::field_characteristic() == characteristic); diff --git a/libs/algebra/test/fp12.cpp b/libs/algebra/test/fp12.cpp index ce878e604..650575593 100644 --- a/libs/algebra/test/fp12.cpp +++ b/libs/algebra/test/fp12.cpp @@ -27,6 +27,8 @@ #include #include #include +#include +#include #include #include @@ -167,8 +169,18 @@ BOOST_AUTO_TEST_CASE_TEMPLATE(tower_values_embed_into_fp12, Fp12Field, fp12_fiel BOOST_CHECK_EQUAL(fp12_value_type(fp), fp12_value_type(fp6)); BOOST_CHECK_EQUAL(fp12_value_type(fp2), fp12_value_type(fp6)); BOOST_CHECK_EQUAL(fp12_value_type(fp6), fp12_value_type(fp6, fp6_value_type::zero())); + BOOST_CHECK_EQUAL(fp * fp2_value_type::one(), fp2_value_type(fp)); + BOOST_CHECK_EQUAL(fp2_value_type::one() * fp, fp2_value_type(fp)); + BOOST_CHECK_EQUAL(fp * fp6_value_type::one(), fp6_value_type(fp)); + BOOST_CHECK_EQUAL(fp6_value_type::one() * fp, fp6_value_type(fp)); + BOOST_CHECK_EQUAL(fp2 * fp6_value_type::one(), fp6_value_type(fp2)); + BOOST_CHECK_EQUAL(fp6_value_type::one() * fp2, fp6_value_type(fp2)); BOOST_CHECK_EQUAL(fp * fp12_value_type::one(), fp12_value_type(fp)); BOOST_CHECK_EQUAL(fp12_value_type::one() * fp, fp12_value_type(fp)); + BOOST_CHECK_EQUAL(fp2 * fp12_value_type::one(), fp12_value_type(fp2)); + BOOST_CHECK_EQUAL(fp12_value_type::one() * fp2, fp12_value_type(fp2)); + BOOST_CHECK_EQUAL(fp6 * fp12_value_type::one(), fp12_value_type(fp6)); + BOOST_CHECK_EQUAL(fp12_value_type::one() * fp6, fp12_value_type(fp6)); } BOOST_AUTO_TEST_CASE_TEMPLATE(square_roots_round_trip, Fp12Field, fp12_field_types) { @@ -188,6 +200,20 @@ BOOST_AUTO_TEST_CASE_TEMPLATE(square_roots_round_trip, Fp12Field, fp12_field_typ } } +BOOST_AUTO_TEST_CASE_TEMPLATE(is_square_rejects_a_non_square, Fp12Field, fp12_field_types) { + using fp12_value_type = typename Fp12Field::value_type; + boost::random::mt19937 rng(0x1105); + + for (std::size_t i = 0; i < 64; ++i) { + const fp12_value_type candidate = random_fp12(rng); + if (!candidate.is_square()) { + BOOST_CHECK(!candidate.is_zero()); + return; + } + } + BOOST_FAIL("failed to sample an Fp12 non-square"); +} + BOOST_AUTO_TEST_CASE(bn254_subgroup_algorithms_match_direct_exponentiation) { using field_type = alt_bn128_254_fp12; using value_type = typename field_type::value_type; @@ -210,6 +236,9 @@ BOOST_AUTO_TEST_CASE(bn254_subgroup_algorithms_match_direct_exponentiation) { const value_type root = fields::odd_subgroup_sqrt(odd_subgroup_value, odd_order); BOOST_CHECK_EQUAL(root.squared(), odd_subgroup_value); BOOST_CHECK_EQUAL(root, odd_subgroup_value.pow((odd_order + 1) >> 1)); + + const cpp_int fallback_order = odd_order + 2; + BOOST_CHECK_EQUAL(fields::odd_subgroup_sqrt(x, fallback_order), x.pow((fallback_order + 1) >> 1)); } } @@ -217,8 +246,11 @@ BOOST_AUTO_TEST_CASE(bn254_roots_of_unity_have_order_32) { using value_type = typename alt_bn128_254_fp12::value_type; const value_type primitive_root = fields::primitive_two_power_root_of_unity(5); const std::vector roots = fields::roots_of_unity(5); + const std::vector roots_from_supplied_root = fields::roots_of_unity(primitive_root, 5); BOOST_REQUIRE_EQUAL(roots.size(), 32); + BOOST_CHECK_EQUAL_COLLECTIONS(roots.begin(), roots.end(), roots_from_supplied_root.begin(), + roots_from_supplied_root.end()); BOOST_CHECK_EQUAL(roots.front(), value_type::one()); BOOST_CHECK_EQUAL(primitive_root.pow(32), value_type::one()); BOOST_CHECK_NE(primitive_root.pow(16), value_type::one()); @@ -227,6 +259,16 @@ BOOST_AUTO_TEST_CASE(bn254_roots_of_unity_have_order_32) { } } +BOOST_AUTO_TEST_CASE(root_of_unity_argument_validation) { + using bn254_value_type = typename alt_bn128_254_fp12::value_type; + using bls12_value_type = typename bls12_381_fp12::value_type; + + BOOST_CHECK_THROW(fields::primitive_two_power_root_of_unity(4), std::invalid_argument); + BOOST_CHECK_THROW(fields::primitive_two_power_root_of_unity(5), std::invalid_argument); + BOOST_CHECK_THROW(fields::roots_of_unity(bn254_value_type::one(), std::numeric_limits::digits), + std::invalid_argument); +} + BOOST_AUTO_TEST_CASE(generic_subgroup_algorithms_match_direct_formulas) { using field_type = bls12_381_fp12; using value_type = typename field_type::value_type; From cba428eb8d7dd4dc8036af36c68e194f949be4c4 Mon Sep 17 00:00:00 2001 From: Brent Carmer Date: Mon, 24 Aug 2026 13:30:31 -0700 Subject: [PATCH 4/6] bit ops for field_element in marshalling --- .../fields/detail/field_algorithms.hpp | 3 +- .../algebra/processing/field_element.hpp | 73 +++++++++++++++++++ .../nil/crypto3/math/linear_variable.hpp | 30 +++++--- 3 files changed, 96 insertions(+), 10 deletions(-) create mode 100644 libs/marshalling/algebra/include/nil/crypto3/marshalling/algebra/processing/field_element.hpp diff --git a/libs/algebra/include/nil/crypto3/algebra/fields/detail/field_algorithms.hpp b/libs/algebra/include/nil/crypto3/algebra/fields/detail/field_algorithms.hpp index a727fbddd..23f21057c 100644 --- a/libs/algebra/include/nil/crypto3/algebra/fields/detail/field_algorithms.hpp +++ b/libs/algebra/include/nil/crypto3/algebra/fields/detail/field_algorithms.hpp @@ -49,7 +49,8 @@ namespace nil::crypto3::algebra::fields::detail { static Value odd_subgroup_sqrt(const Value &x, const boost::multiprecision::cpp_int &odd_order) { assert((odd_order & 1) != 0); - return x.is_zero() || x.is_one() ? x : x.pow((odd_order + 1) >> 1); + const boost::multiprecision::cpp_int exponent = (odd_order + 1) >> 1; + return x.is_zero() || x.is_one() ? x : x.pow(exponent); } static Value sqrt_known_square(const Value &x) { diff --git a/libs/marshalling/algebra/include/nil/crypto3/marshalling/algebra/processing/field_element.hpp b/libs/marshalling/algebra/include/nil/crypto3/marshalling/algebra/processing/field_element.hpp new file mode 100644 index 000000000..54ab6562e --- /dev/null +++ b/libs/marshalling/algebra/include/nil/crypto3/marshalling/algebra/processing/field_element.hpp @@ -0,0 +1,73 @@ +//---------------------------------------------------------------------------// +// Copyright (c) 2026 Alloc Init Labs Inc. +// +// MIT License +//---------------------------------------------------------------------------// + +#ifndef CRYPTO3_MARSHALLING_PROCESSING_FIELD_ELEMENT_HPP +#define CRYPTO3_MARSHALLING_PROCESSING_FIELD_ELEMENT_HPP + +#include +#include +#include +#include + +#include +#include + +namespace nil::crypto3::marshalling::processing { + + template + constexpr std::size_t field_element_bit_length() { + if constexpr (requires { FieldValue::bitsize(); }) { + return FieldValue::bitsize(); + } else { + constexpr std::size_t value_bits = FieldValue::field_type::value_bits; + return ((value_bits + 7) / 8) * 8; + } + } + + template + std::vector field_element_to_le_bytes(const FieldValue &value) { + if constexpr (requires { value.to_le_bytes(); }) { + return value.to_le_bytes(); + } else { + nil::marshalling::status_type status; + std::vector bytes = + nil::marshalling::pack(value, status); + if (status != nil::marshalling::status_type::success) { + throw std::runtime_error("error encoding field element"); + } + return bytes; + } + } + + template + FieldValue field_element_from_le_bytes(const std::vector &bytes) { + if constexpr (requires { FieldValue::from_le_bytes(bytes); }) { + return FieldValue::from_le_bytes(bytes); + } else { + nil::marshalling::status_type status; + FieldValue value = nil::marshalling::pack(bytes, status); + if (status != nil::marshalling::status_type::success) { + throw std::runtime_error("error decoding field element"); + } + return value; + } + } + + template + std::vector field_element_to_le_bits(const FieldValue &value) { + std::vector bits(field_element_bit_length()); + std::size_t current = 0; + for (std::uint8_t byte : field_element_to_le_bytes(value)) { + for (std::size_t i = 0; i < 8 && current < bits.size(); ++i) { + bits[current++] = (byte >> i) & 1; + } + } + return bits; + } + +} // namespace nil::crypto3::marshalling::processing + +#endif // CRYPTO3_MARSHALLING_PROCESSING_FIELD_ELEMENT_HPP diff --git a/libs/math/include/nil/crypto3/math/linear_variable.hpp b/libs/math/include/nil/crypto3/math/linear_variable.hpp index 8e738b02b..79e77e66f 100644 --- a/libs/math/include/nil/crypto3/math/linear_variable.hpp +++ b/libs/math/include/nil/crypto3/math/linear_variable.hpp @@ -35,6 +35,16 @@ namespace nil::crypto3::math { + template + struct linear_variable_value_type { + using type = FieldOrValue; + }; + + template + struct linear_variable_value_type> { + using type = typename FieldOrValue::value_type; + }; + /********************************* Variable **********************************/ /** @@ -47,13 +57,13 @@ namespace nil::crypto3::math { public: using field_type = FieldType; - using value_type = typename FieldType::value_type; + using value_type = typename linear_variable_value_type::type; using index_type = std::size_t; std::size_t index; linear_variable(const std::size_t index = 0) : index(index) { }; - linear_term operator*(const typename FieldType::value_type &field_coeff) const { + linear_term operator*(const value_type &field_coeff) const { return linear_term(*this) * field_coeff; } @@ -71,7 +81,7 @@ namespace nil::crypto3::math { } linear_term operator-() const { - return linear_term(*this) * (-FieldType::value_type::one()); + return linear_term(*this) * (-value_type::one()); } bool operator==(const linear_variable &other) const { @@ -80,20 +90,22 @@ namespace nil::crypto3::math { }; template - linear_term> operator*(const typename FieldType::value_type &field_coeff, - const linear_variable &var) { + linear_term> operator*(const typename linear_variable::value_type &field_coeff, + const linear_variable &var) { return var * field_coeff; } template - linear_combination> operator+(const typename FieldType::value_type &field_coeff, - const linear_variable &var) { + linear_combination> operator+( + const typename linear_variable::value_type &field_coeff, + const linear_variable &var) { return var + field_coeff; } template - linear_combination> operator-(const typename FieldType::value_type &field_coeff, - const linear_variable &var) { + linear_combination> operator-( + const typename linear_variable::value_type &field_coeff, + const linear_variable &var) { return linear_combination>(field_coeff) - var; } From eb83c56b711295c5195014e3b30cdb8dca56d6b1 Mon Sep 17 00:00:00 2001 From: Brent Carmer Date: Mon, 24 Aug 2026 13:30:55 -0700 Subject: [PATCH 5/6] format --- .../fields/detail/element/fp12_2over3over2.hpp | 10 ++++------ .../algebra/fields/detail/element/fp2.hpp | 3 ++- .../algebra/fields/detail/field_algorithms.hpp | 4 ++-- .../nil/crypto3/math/linear_variable.hpp | 17 +++++++++-------- 4 files changed, 17 insertions(+), 17 deletions(-) diff --git a/libs/algebra/include/nil/crypto3/algebra/fields/detail/element/fp12_2over3over2.hpp b/libs/algebra/include/nil/crypto3/algebra/fields/detail/element/fp12_2over3over2.hpp index d406a4c7a..f3c6301a1 100644 --- a/libs/algebra/include/nil/crypto3/algebra/fields/detail/element/fp12_2over3over2.hpp +++ b/libs/algebra/include/nil/crypto3/algebra/fields/detail/element/fp12_2over3over2.hpp @@ -60,8 +60,7 @@ namespace nil { data({in_data, underlying_type::zero()}) { } - constexpr element_fp12_2over3over2( - const typename underlying_type::underlying_type &in_data) : + constexpr element_fp12_2over3over2(const typename underlying_type::underlying_type &in_data) : element_fp12_2over3over2(underlying_type(in_data)) { } @@ -239,8 +238,8 @@ namespace nil { static const element_fp12_2over3over2 non_residue_to_odd_part = []() { using base_type = typename underlying_type::underlying_type::underlying_type; for (unsigned candidate = 1; candidate < 256; ++candidate) { - const element_fp12_2over3over2 value( - underlying_type::zero(), underlying_type(base_type(candidate))); + 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); } @@ -265,8 +264,7 @@ namespace nil { } assert(i < remaining_power && "Fp12 Tonelli-Shanks step failed"); - const element_fp12_2over3over2 b = - c.pow(cpp_int(1) << (remaining_power - i - 1)); + const element_fp12_2over3over2 b = c.pow(cpp_int(1) << (remaining_power - i - 1)); const element_fp12_2over3over2 b_squared = b.squared(); root *= b; t *= b_squared; diff --git a/libs/algebra/include/nil/crypto3/algebra/fields/detail/element/fp2.hpp b/libs/algebra/include/nil/crypto3/algebra/fields/detail/element/fp2.hpp index 3cf713485..46584569a 100644 --- a/libs/algebra/include/nil/crypto3/algebra/fields/detail/element/fp2.hpp +++ b/libs/algebra/include/nil/crypto3/algebra/fields/detail/element/fp2.hpp @@ -71,7 +71,8 @@ namespace nil { constexpr element_fp2(const data_type &in_data) : data({in_data[0], in_data[1]}) { } - constexpr element_fp2(const underlying_type &in_data) : data({in_data, underlying_type::zero()}) { + constexpr element_fp2(const underlying_type &in_data) : + data({in_data, underlying_type::zero()}) { } constexpr element_fp2(const underlying_type &in_data0, const underlying_type &in_data1) : diff --git a/libs/algebra/include/nil/crypto3/algebra/fields/detail/field_algorithms.hpp b/libs/algebra/include/nil/crypto3/algebra/fields/detail/field_algorithms.hpp index 23f21057c..741ef1c3c 100644 --- a/libs/algebra/include/nil/crypto3/algebra/fields/detail/field_algorithms.hpp +++ b/libs/algebra/include/nil/crypto3/algebra/fields/detail/field_algorithms.hpp @@ -42,8 +42,8 @@ namespace nil::crypto3::algebra::fields::detail { // public API while keeping field-specific types and algorithms private. template struct field_algorithms { - static Value - two_primary_component(const Value &x, const boost::multiprecision::cpp_int &odd_order, std::size_t) { + static Value two_primary_component(const Value &x, const boost::multiprecision::cpp_int &odd_order, + std::size_t) { return x.pow(odd_order); } diff --git a/libs/math/include/nil/crypto3/math/linear_variable.hpp b/libs/math/include/nil/crypto3/math/linear_variable.hpp index 79e77e66f..9b434554b 100644 --- a/libs/math/include/nil/crypto3/math/linear_variable.hpp +++ b/libs/math/include/nil/crypto3/math/linear_variable.hpp @@ -90,22 +90,23 @@ namespace nil::crypto3::math { }; template - linear_term> operator*(const typename linear_variable::value_type &field_coeff, - const linear_variable &var) { + linear_term> + operator*(const typename linear_variable::value_type &field_coeff, + const linear_variable &var) { return var * field_coeff; } template - linear_combination> operator+( - const typename linear_variable::value_type &field_coeff, - const linear_variable &var) { + linear_combination> + operator+(const typename linear_variable::value_type &field_coeff, + const linear_variable &var) { return var + field_coeff; } template - linear_combination> operator-( - const typename linear_variable::value_type &field_coeff, - const linear_variable &var) { + linear_combination> + operator-(const typename linear_variable::value_type &field_coeff, + const linear_variable &var) { return linear_combination>(field_coeff) - var; } From d6e5554f0798b88d5d49d15a38575b01f02a77c5 Mon Sep 17 00:00:00 2001 From: Brent Carmer Date: Tue, 25 Aug 2026 09:16:52 -0700 Subject: [PATCH 6/6] remove processing/field_element.hpp --- .../algebra/processing/field_element.hpp | 73 ------------------- 1 file changed, 73 deletions(-) delete mode 100644 libs/marshalling/algebra/include/nil/crypto3/marshalling/algebra/processing/field_element.hpp diff --git a/libs/marshalling/algebra/include/nil/crypto3/marshalling/algebra/processing/field_element.hpp b/libs/marshalling/algebra/include/nil/crypto3/marshalling/algebra/processing/field_element.hpp deleted file mode 100644 index 54ab6562e..000000000 --- a/libs/marshalling/algebra/include/nil/crypto3/marshalling/algebra/processing/field_element.hpp +++ /dev/null @@ -1,73 +0,0 @@ -//---------------------------------------------------------------------------// -// Copyright (c) 2026 Alloc Init Labs Inc. -// -// MIT License -//---------------------------------------------------------------------------// - -#ifndef CRYPTO3_MARSHALLING_PROCESSING_FIELD_ELEMENT_HPP -#define CRYPTO3_MARSHALLING_PROCESSING_FIELD_ELEMENT_HPP - -#include -#include -#include -#include - -#include -#include - -namespace nil::crypto3::marshalling::processing { - - template - constexpr std::size_t field_element_bit_length() { - if constexpr (requires { FieldValue::bitsize(); }) { - return FieldValue::bitsize(); - } else { - constexpr std::size_t value_bits = FieldValue::field_type::value_bits; - return ((value_bits + 7) / 8) * 8; - } - } - - template - std::vector field_element_to_le_bytes(const FieldValue &value) { - if constexpr (requires { value.to_le_bytes(); }) { - return value.to_le_bytes(); - } else { - nil::marshalling::status_type status; - std::vector bytes = - nil::marshalling::pack(value, status); - if (status != nil::marshalling::status_type::success) { - throw std::runtime_error("error encoding field element"); - } - return bytes; - } - } - - template - FieldValue field_element_from_le_bytes(const std::vector &bytes) { - if constexpr (requires { FieldValue::from_le_bytes(bytes); }) { - return FieldValue::from_le_bytes(bytes); - } else { - nil::marshalling::status_type status; - FieldValue value = nil::marshalling::pack(bytes, status); - if (status != nil::marshalling::status_type::success) { - throw std::runtime_error("error decoding field element"); - } - return value; - } - } - - template - std::vector field_element_to_le_bits(const FieldValue &value) { - std::vector bits(field_element_bit_length()); - std::size_t current = 0; - for (std::uint8_t byte : field_element_to_le_bytes(value)) { - for (std::size_t i = 0; i < 8 && current < bits.size(); ++i) { - bits[current++] = (byte >> i) & 1; - } - } - return bits; - } - -} // namespace nil::crypto3::marshalling::processing - -#endif // CRYPTO3_MARSHALLING_PROCESSING_FIELD_ELEMENT_HPP