From 8503d0a9b0095009ffba027a430b678aa70233ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Bylica?= Date: Wed, 12 Aug 2026 13:19:14 +0200 Subject: [PATCH] crypto: Specialize the Fq2 multiplication by ksi Multiplication by ksi = 9 + u, the non-residue defining the Fq6 extension, went through the generic Fq2 multiplication, which needs 4 field multiplications where 2 suffice: the u coefficient of ksi is 1, so a1 * 1 and a0 * 1 were computed for nothing. Add mul_by_ksi() computing (a0 + a1*u)*(9 + u) = (9a0 - a1) + (a0 + 9a1)*u directly and use it at the 13 sites, spread over the Fq6 and Fq12 multiplication, squaring and inversion, the cyclotomic squaring and the Miller loop's line multiplication. Cuts up to 7% off the ECPAIRING instruction count and a few percent off its cycles. --- .../pairing/bn254/fields.hpp | 35 +++++++++++-------- .../pairing/bn254/pairing.cpp | 5 ++- .../pairing/bn254/utils.hpp | 10 +++--- 3 files changed, 26 insertions(+), 24 deletions(-) diff --git a/lib/evmone_precompiles/pairing/bn254/fields.hpp b/lib/evmone_precompiles/pairing/bn254/fields.hpp index 9449a6221b..89c1cbf5e7 100644 --- a/lib/evmone_precompiles/pairing/bn254/fields.hpp +++ b/lib/evmone_precompiles/pairing/bn254/fields.hpp @@ -37,6 +37,18 @@ struct Fq12Config }; using Fq12 = ecc::ExtFieldElem; +/// Multiplies an Fq^2 field element by ksi, the non-residue defining the Fq^6 extension. +constexpr Fq2 mul_by_ksi(const Fq2& a) noexcept +{ + // ksi = 9 + u, so (a0 + a1·u)·ksi = (9a0 − a1) + (a0 + 9a1)·u. + // TODO: The multiplications by 9 can be done with additions only, but that was not faster. + static_assert(Fq6Config::ksi == Fq2{9, 1}); + const auto& nine = Fq6Config::ksi.coeffs[0]; + + const auto& [a0, a1] = a.coeffs; + return Fq2({a0 * nine - a1, a1 * nine + a0}); +} + /// Multiplies two Fq^2 field elements constexpr Fq2 multiply(const Fq2& a, const Fq2& b) noexcept { @@ -61,14 +73,12 @@ constexpr Fq6 multiply(const Fq6& a, const Fq6& b) noexcept const auto& [a0, a1, a2] = a.coeffs; const auto& [b0, b1, b2] = b.coeffs; - const Fq2& ksi = Fq6Config::ksi; - const auto t0 = a0 * b0; const auto t1 = a1 * b1; const auto t2 = a2 * b2; - const auto c0 = ((a1 + a2) * (b1 + b2) - t1 - t2) * ksi + t0; - const auto c1 = (a0 + a1) * (b0 + b1) - t0 - t1 + ksi * t2; + const auto c0 = mul_by_ksi((a1 + a2) * (b1 + b2) - t1 - t2) + t0; + const auto c1 = (a0 + a1) * (b0 + b1) - t0 - t1 + mul_by_ksi(t2); const auto c2 = (a0 + a2) * (b0 + b2) - t0 - t2 + t1; return Fq6({c0, c1, c2}); @@ -83,9 +93,8 @@ constexpr Fq12 multiply(const Fq12& a, const Fq12& b) noexcept const auto t0 = a0 * b0; const auto t1 = a1 * b1; - const Fq2& ksi = Fq6Config::ksi; - - const auto c0 = t0 + Fq6({ksi * t1.coeffs[2], t1.coeffs[0], t1.coeffs[1]}); // gamma is sparse. + const auto c0 = + t0 + Fq6({mul_by_ksi(t1.coeffs[2]), t1.coeffs[0], t1.coeffs[1]}); // gamma is sparse. const auto c1 = (a0 + a1) * (b0 + b1) - t0 - t1; return Fq12({c0, c1}); @@ -113,8 +122,6 @@ inline Fq6 inverse(const Fq6& f) noexcept { const auto& [a0, a1, a2] = f.coeffs; - const Fq2& ksi = Fq6Config::ksi; - const auto t0 = a0 * a0; const auto t1 = a1 * a1; const auto t2 = a2 * a2; @@ -123,11 +130,11 @@ inline Fq6 inverse(const Fq6& f) noexcept const auto t4 = a0 * a2; const auto t5 = a2 * a1; - const auto c0 = t0 - ksi * t5; - const auto c1 = ksi * t2 - t3; + const auto c0 = t0 - mul_by_ksi(t5); + const auto c1 = mul_by_ksi(t2) - t3; const auto c2 = t1 - t4; - const auto t = a0 * c0 + (a2 * c1 + a1 * c2) * ksi; + const auto t = a0 * c0 + mul_by_ksi(a2 * c1 + a1 * c2); const auto t6 = t.inv(); return Fq6({c0 * t6, c1 * t6, c2 * t6}); @@ -141,9 +148,7 @@ inline Fq12 inverse(const Fq12& f) noexcept auto t0 = a0 * a0; auto t1 = a1 * a1; - const Fq2& ksi = Fq6Config::ksi; - - t0 = t0 - Fq6({ksi * t1.coeffs[2], t1.coeffs[0], t1.coeffs[1]}); // gamma is sparse. + t0 = t0 - Fq6({mul_by_ksi(t1.coeffs[2]), t1.coeffs[0], t1.coeffs[1]}); // gamma is sparse. t1 = t0.inv(); const auto c0 = a0 * t1; diff --git a/lib/evmone_precompiles/pairing/bn254/pairing.cpp b/lib/evmone_precompiles/pairing/bn254/pairing.cpp index 657eafd05f..078b5d9643 100644 --- a/lib/evmone_precompiles/pairing/bn254/pairing.cpp +++ b/lib/evmone_precompiles/pairing/bn254/pairing.cpp @@ -16,13 +16,12 @@ constexpr void multiply_by_lin_func_value( Fq12& fr, const std::array& t, const Fq& x, const Fq& y) noexcept { const Fq12 f = fr; - const auto& ksi = Fq6Config::ksi; const auto t0y = t[0] * y; const auto t1x = t[1] * x; - const auto t2ksi = t[2] * ksi; + const auto t2ksi = mul_by_ksi(t[2]); - fr.coeffs[0].coeffs[0] = f.coeffs[0].coeffs[0] * t0y + f.coeffs[1].coeffs[2] * t1x * ksi + + fr.coeffs[0].coeffs[0] = f.coeffs[0].coeffs[0] * t0y + mul_by_ksi(f.coeffs[1].coeffs[2] * t1x) + f.coeffs[1].coeffs[1] * t2ksi; fr.coeffs[0].coeffs[1] = f.coeffs[0].coeffs[1] * t0y + f.coeffs[1].coeffs[0] * t1x + f.coeffs[1].coeffs[2] * t2ksi; diff --git a/lib/evmone_precompiles/pairing/bn254/utils.hpp b/lib/evmone_precompiles/pairing/bn254/utils.hpp index f0c301a709..00d62b7fbc 100644 --- a/lib/evmone_precompiles/pairing/bn254/utils.hpp +++ b/lib/evmone_precompiles/pairing/bn254/utils.hpp @@ -359,16 +359,14 @@ constexpr void lin_func( /// Computes f^2 for f in Fq12. For more ref https://eprint.iacr.org/2010/354.pdf Algorithm 22 [[nodiscard]] constexpr Fq12 square(const Fq12& f) noexcept { - const Fq2& ksi = Fq6Config::ksi; - const auto& a0 = f.coeffs[0]; const auto& a1 = f.coeffs[1]; auto c0 = a0 - a1; - auto c3 = a0 - Fq6({ksi * a1.coeffs[2], a1.coeffs[0], a1.coeffs[1]}); + auto c3 = a0 - Fq6({mul_by_ksi(a1.coeffs[2]), a1.coeffs[0], a1.coeffs[1]}); auto c2 = a0 * a1; c0 = c0 * c3 + c2; const auto c1 = c2 + c2; - c2 = Fq6({ksi * c2.coeffs[2], c2.coeffs[0], c2.coeffs[1]}); + c2 = Fq6({mul_by_ksi(c2.coeffs[2]), c2.coeffs[0], c2.coeffs[1]}); c0 = c0 + c2; return Fq12({c0, c1}); @@ -384,7 +382,7 @@ constexpr std::pair fq4_square(const std::pair& a) noexcept const auto t0 = a0 * a0; const auto t1 = a1 * a1; - const auto c0 = t1 * Fq6Config::ksi + t0; + const auto c0 = mul_by_ksi(t1) + t0; auto c1 = a0 + a1; c1 = c1 * c1 - t0 - t1; @@ -410,7 +408,7 @@ constexpr Fq12 cyclotomic_square(const Fq12& c) noexcept const auto [t01, t12] = fq4_square({h0, g2}); // Typo in paper t01 <-> t12 const auto [t02, aux] = fq4_square({g1, h2}); - const auto t10 = aux * Fq6Config::ksi; + const auto t10 = mul_by_ksi(aux); const auto c00 = (t00 + t00 + t00) - (g0 + g0); const auto c01 = (t01 + t01 + t01) - (g1 + g1);