From 37feca269f7cc43e80b701087bacd02241c4754a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Bylica?= Date: Tue, 11 Aug 2026 15:21:58 +0200 Subject: [PATCH] crypto: Spell out the BN254 ate loop schedule digits The Miller loop schedule was a uint128 of 2-bit groups, unpacked with a shift and two bit tests per iteration and paired with a separate length constant. Storing the digits as -1, 0 and 1 lets the loop range over them: the shift, the length constant and one of the two add branches go away. Instruction and branch counts are unchanged. The name claimed NAF, but the digits are only a semi-NAF: the non-adjacent form of 6x+2 leads with 1, 0, -1 where this leads with 1, 1, encoding the same value with the same 22 non-zero digits but one iteration less. Renamed accordingly. --- .../pairing/bn254/pairing.cpp | 33 +++++++++---------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/lib/evmone_precompiles/pairing/bn254/pairing.cpp b/lib/evmone_precompiles/pairing/bn254/pairing.cpp index 9a3b3e55cb..3a9188c169 100644 --- a/lib/evmone_precompiles/pairing/bn254/pairing.cpp +++ b/lib/evmone_precompiles/pairing/bn254/pairing.cpp @@ -36,40 +36,39 @@ constexpr void multiply_by_lin_func_value( f.coeffs[1].coeffs[2] * t0y + f.coeffs[0].coeffs[2] * t1x + f.coeffs[0].coeffs[1] * t[2]; } -// 0000000100010010000010000000010000100010000000010010000000001000000100100000010000000000100000100001001000000010001000000001000101 -// NAF rep 00 -> 0, 01 -> 1, 10 -> -1 -// miller loop goes from L-2 to 0 inclusively. NAF rep of 29793968203157093288 (6x+2) is two bits -// longer, but we omit lowest 2 bits. -inline constexpr auto ATE_LOOP_COUNT_NAF = 0x1120804220120081204008212022011_u128; -inline constexpr int LOG_ATE_LOOP_COUNT = 63; +/// The signed digits of the ate loop count 6x+2 = 29793968203157093288, most significant first, +/// with the leading 1 omitted. This is a semi-NAF: adjacent non-zeros occur only at the leading +/// 1, 1, which the non-adjacent form spells as 1, 0, -1. Both have 22 non-zero digits, but this +/// one is a digit shorter, i.e. one loop iteration less. +// clang-format off +inline constexpr int8_t ATE_LOOP_COUNT_DIGITS[] = { + 1, 0, 1, 0, 0, 0, -1, 0, -1, 0, 0, 0, -1, 0, 1, 0, + -1, 0, 0, -1, 0, 0, 0, 0, 0, 1, 0, 0, -1, 0, 1, 0, + 0, -1, 0, 0, 0, 0, -1, 0, 1, 0, 0, 0, -1, 0, -1, 0, + 0, 1, 0, 0, 0, -1, 0, 0, -1, 0, 1, 0, 1, 0, 0, 0, +}; +// clang-format on /// Miller loop according to https://eprint.iacr.org/2010/354.pdf Algorithm 1. Fq12 miller_loop(const ecc::AffinePoint& Q, const ecc::AffinePoint& P) noexcept { - auto T = ecc::ProjPoint{Q}; + auto T = ecc::ProjPoint{Q}; // Applies the omitted leading digit 1 of the loop count. const auto nQ = -Q; auto f = Fq12::one(); std::array t; - auto naf = ATE_LOOP_COUNT_NAF; const auto ny = -P.y; - for (int i = 0; i <= LOG_ATE_LOOP_COUNT; ++i) + for (const auto digit : ATE_LOOP_COUNT_DIGITS) { T = lin_func_and_dbl(T, t); f = square(f); multiply_by_lin_func_value(f, t, P.x, ny); - if (naf & 1) + if (digit != 0) { - T = lin_func_and_add(T, Q, t); + T = lin_func_and_add(T, digit > 0 ? Q : nQ, t); multiply_by_lin_func_value(f, t, P.x, P.y); } - else if (naf & 2) - { - T = lin_func_and_add(T, nQ, t); - multiply_by_lin_func_value(f, t, P.x, P.y); - } - naf >>= 2; } // Frobenius endomorphism for point Q from twisted curve over Fq2 field.