diff --git a/lib/evmone_precompiles/CMakeLists.txt b/lib/evmone_precompiles/CMakeLists.txt index b2259dfa7b..d29b6631cc 100644 --- a/lib/evmone_precompiles/CMakeLists.txt +++ b/lib/evmone_precompiles/CMakeLists.txt @@ -16,6 +16,10 @@ target_sources( bn254.hpp bn254.cpp ecc.hpp + pairing/bn254/fields.hpp + pairing/bn254/pairing.cpp + pairing/bn254/utils.hpp + pairing/field_template.hpp ripemd160.hpp ripemd160.cpp secp256k1.hpp diff --git a/lib/evmone_precompiles/bn254.hpp b/lib/evmone_precompiles/bn254.hpp index 1f0d5f2412..61997855f4 100644 --- a/lib/evmone_precompiles/bn254.hpp +++ b/lib/evmone_precompiles/bn254.hpp @@ -4,6 +4,9 @@ #pragma once #include "ecc.hpp" +#include +#include +#include namespace evmmax::bn254 { @@ -14,6 +17,7 @@ inline constexpr auto FieldPrime = 0x30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47_u256; using Point = ecc::Point; +using ExtPoint = ecc::Point>; /// Validates that point is from the bn254 curve group /// @@ -36,4 +40,12 @@ Point add(const Point& pt1, const Point& pt2) noexcept; /// Computes [c]P for a point in affine coordinate on the bn254 curve, Point mul(const Point& pt, const uint256& c) noexcept; +/// ate paring implementation for bn254 curve according to https://eips.ethereum.org/EIPS/eip-197 +/// +/// @param pairs Sequence of point pairs: a point from the bn254 curve G1 group over the base field +/// followed by a point from twisted curve G2 group over extension field Fq^2. +/// @return `true` when ∏e(vG2[i], vG1[i]) == 1 for i in [0, n] else `false`. +/// std::nullopt on error. +std::optional pairing_check(std::span> pairs) noexcept; + } // namespace evmmax::bn254 diff --git a/lib/evmone_precompiles/ecc.hpp b/lib/evmone_precompiles/ecc.hpp index 4ea616c84f..292da79af7 100644 --- a/lib/evmone_precompiles/ecc.hpp +++ b/lib/evmone_precompiles/ecc.hpp @@ -9,14 +9,16 @@ namespace evmmax::ecc { /// The affine (two coordinates) point on an Elliptic Curve over a prime field. -template +template struct Point { - IntT x = 0; - IntT y = 0; + ValueT x = {}; + ValueT y = {}; friend constexpr bool operator==(const Point& a, const Point& b) noexcept = default; + friend constexpr Point operator-(const Point& p) noexcept { return {p.x, -p.y}; } + /// Checks if the point represents the special "infinity" value. [[nodiscard]] constexpr bool is_inf() const noexcept { return *this == Point{}; } }; @@ -32,10 +34,41 @@ struct ProjPoint /// Checks if the point represents the special "infinity" value. [[nodiscard]] constexpr bool is_inf() const noexcept { return x == 0 && z == 0; } + + friend constexpr ProjPoint operator-(const ProjPoint& p) noexcept { return {p.x, -p.y, p.z}; } }; static_assert(ProjPoint{}.is_inf()); +// Jacobian (three) coordinates point implementation. +template +struct JacPoint +{ + ValueT x = 1; + ValueT y = 1; + ValueT z = 0; + + // Compares two Jacobian coordinates points + friend constexpr bool operator==(const JacPoint& a, const JacPoint& b) noexcept + { + const auto bz2 = b.z * b.z; + const auto az2 = a.z * a.z; + + const auto bz3 = bz2 * b.z; + const auto az3 = az2 * a.z; + + return a.x * bz2 == b.x * az2 && a.y * bz3 == b.y * az3; + } + + friend constexpr JacPoint operator-(const JacPoint& p) noexcept { return {p.x, -p.y, p.z}; } + + // Creates Jacobian coordinates point from affine point + static constexpr JacPoint from(const ecc::Point& ap) noexcept + { + return {ap.x, ap.y, ValueT::one()}; + } +}; + template using InvFn = IntT (*)(const ModArith&, const IntT& x) noexcept; diff --git a/lib/evmone_precompiles/pairing/bn254/fields.hpp b/lib/evmone_precompiles/pairing/bn254/fields.hpp new file mode 100644 index 0000000000..db5d1df21d --- /dev/null +++ b/lib/evmone_precompiles/pairing/bn254/fields.hpp @@ -0,0 +1,182 @@ +// evmone: Fast Ethereum Virtual Machine implementation +// Copyright 2024 The evmone Authors. +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#include "../../bn254.hpp" +#include "../../ecc.hpp" +#include "../field_template.hpp" + +namespace evmmax::bn254 +{ +using namespace intx; + +/// Specifies base field value type and modular arithmetic for bn254 curve. +struct BaseFieldConfig +{ + using ValueT = uint256; + static constexpr auto MOD_ARITH = ModArith{FieldPrime}; + static constexpr uint256 ONE = MOD_ARITH.to_mont(1); +}; +using Fq = ecc::BaseFieldElem; + +// Extension fields implemented based on https://hackmd.io/@jpw/bn254#Field-extension-towers + +/// Specifies Fq^2 extension field for bn254 curve. Base field extended with irreducible `u^2 + 1` +/// polynomial over the base field. `u` is the Fq^2 element. +struct Fq2Config +{ + using BaseFieldT = Fq; + using ValueT = Fq; + static constexpr auto DEGREE = 2; +}; +using Fq2 = ecc::ExtFieldElem; + +/// Specifies Fq^6 extension field for bn254 curve. Fq^2 field extended with irreducible +/// `v^3 - (9 + u)` polynomial over the Fq^2 field. `v` is the Fq^6 field element. +struct Fq6Config +{ + using BaseFieldT = Fq; + using ValueT = Fq2; + static constexpr uint8_t DEGREE = 3; + static constexpr auto ksi = Fq2({Fq::from_int(9_u256), Fq::from_int(1_u256)}); + static constexpr auto _3_ksi_inv = Fq2({ + Fq::from_int(0x2b149d40ceb8aaae81be18991be06ac3b5b4c5e559dbefa33267e6dc24a138e5_u256), + Fq::from_int(0x9713b03af0fed4cd2cafadeed8fdf4a74fa084e52d1852e4a2bd0685c315d2_u256), + }); +}; +using Fq6 = ecc::ExtFieldElem; + +/// Specifies Fq^12 extension field for bn254 curve. Fq^6 field extended with irreducible +/// `w^2 - v` polynomial over the Fq^2 field. `v` is the Fq^6 field element. +/// `w` is Fq^12 field element. +struct Fq12Config +{ + using BaseFieldT = Fq; + using ValueT = Fq6; + + static constexpr uint8_t DEGREE = 2; +}; +using Fq12 = ecc::ExtFieldElem; + +/// Multiplies two Fq^2 field elements +constexpr Fq2 multiply(const Fq2& a, const Fq2& b) +{ + return Fq2({ + a.coeffs[0] * b.coeffs[0] - a.coeffs[1] * b.coeffs[1], + a.coeffs[1] * b.coeffs[0] + a.coeffs[0] * b.coeffs[1], + }); +} + +/// Multiplies two Fq^6 field elements +constexpr Fq6 multiply(const Fq6& a, const Fq6& b) +{ + const auto& a0 = a.coeffs[0]; + const auto& a1 = a.coeffs[1]; + const auto& a2 = a.coeffs[2]; + const auto& b0 = b.coeffs[0]; + const auto& b1 = b.coeffs[1]; + const auto& b2 = b.coeffs[2]; + + 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 c2 = (a0 + a2) * (b0 + b2) - t0 - t2 + t1; + + return Fq6({c0, c1, c2}); +} + +/// Multiplies two Fq^12 field elements +constexpr Fq12 multiply(const Fq12& a, const Fq12& b) +{ + const auto& a0 = a.coeffs[0]; + const auto& a1 = a.coeffs[1]; + const auto& b0 = b.coeffs[0]; + const auto& b1 = b.coeffs[1]; + + 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 c1 = (a0 + a1) * (b0 + b1) - t0 - t1; + + return Fq12({c0, c1}); +} + +/// Inverses the base field element +inline Fq inverse(const Fq& x) +{ + return Fq(field_inv(BaseFieldConfig::MOD_ARITH, x.value())); +} + +/// Inverses the Fq^2 field element +inline Fq2 inverse(const Fq2& f) +{ + const auto& a0 = f.coeffs[0]; + const auto& a1 = f.coeffs[1]; + auto t0 = a0 * a0; + auto t1 = a1 * a1; + + t0 = t0 + t1; + t1 = t0.inv(); + + const auto c0 = a0 * t1; + const auto c1 = -(a1 * t1); + + return Fq2({c0, c1}); +} + +/// Inverses the Fq^6 field element +inline Fq6 inverse(const Fq6& f) +{ + const auto& a0 = f.coeffs[0]; + const auto& a1 = f.coeffs[1]; + const auto& a2 = f.coeffs[2]; + + const Fq2& ksi = Fq6Config::ksi; + + const auto t0 = a0 * a0; + const auto t1 = a1 * a1; + const auto t2 = a2 * a2; + + const auto t3 = a0 * a1; + const auto t4 = a0 * a2; + const auto t5 = a2 * a1; + + const auto c0 = t0 - ksi * t5; + const auto c1 = ksi * t2 - t3; + const auto c2 = t1 - t4; + + const auto t = a0 * c0 + (a2 * c1 + a1 * c2) * ksi; + const auto t6 = t.inv(); + + return Fq6({c0 * t6, c1 * t6, c2 * t6}); +} + +/// Inverses the Fq^12 field element +inline Fq12 inverse(const Fq12& f) +{ + const auto& a0 = f.coeffs[0]; + const auto& a1 = f.coeffs[1]; + + 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. + t1 = t0.inv(); + + const auto c0 = a0 * t1; + const auto c1 = -(a1 * t1); + + return Fq12({c0, c1}); +} +} // namespace evmmax::bn254 diff --git a/lib/evmone_precompiles/pairing/bn254/pairing.cpp b/lib/evmone_precompiles/pairing/bn254/pairing.cpp new file mode 100644 index 0000000000..d3dfd9a10e --- /dev/null +++ b/lib/evmone_precompiles/pairing/bn254/pairing.cpp @@ -0,0 +1,172 @@ +// evmone: Fast Ethereum Virtual Machine implementation +// Copyright 2024 The evmone Authors. +// SPDX-License-Identifier: Apache-2.0 + +#include "../../bn254.hpp" +#include "fields.hpp" +#include "utils.hpp" +#include + +namespace evmmax::bn254 +{ +namespace +{ +/// Multiplies `fr` (Fq12) values by sparse `v` (Fq12) value of the form +/// [[t[0] * y, 0, 0],[t[1] * x, t[0], 0]] where `v` coefficients are from Fq2 +constexpr void multiply_by_lin_func_value( + Fq12& fr, 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; + + fr.coeffs[0].coeffs[0] = f.coeffs[0].coeffs[0] * t0y + f.coeffs[1].coeffs[2] * t1x * ksi + + 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; + fr.coeffs[0].coeffs[2] = + f.coeffs[0].coeffs[2] * t0y + f.coeffs[1].coeffs[1] * t1x + f.coeffs[1].coeffs[0] * t[2]; + fr.coeffs[1].coeffs[0] = + f.coeffs[1].coeffs[0] * t0y + f.coeffs[0].coeffs[0] * t1x + f.coeffs[0].coeffs[2] * t2ksi; + fr.coeffs[1].coeffs[1] = + f.coeffs[1].coeffs[1] * t0y + f.coeffs[0].coeffs[1] * t1x + f.coeffs[0].coeffs[0] * t[2]; + fr.coeffs[1].coeffs[2] = + 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; + +/// Miller loop according to https://eprint.iacr.org/2010/354.pdf Algorithm 1. +Fq12 miller_loop(const ecc::Point& Q, const ecc::Point& P) noexcept +{ + auto T = ecc::JacPoint::from(Q); + 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) + { + T = lin_func_and_dbl(T, t); + f = square(f); + multiply_by_lin_func_value(f, t, P.x, ny); + + if (naf & 1) + { + T = lin_func_and_add(T, Q, 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. + // It's essentially untwist -> frobenius -> twist chain of transformation. + const auto Q1 = endomorphism<1>(Q); + + // Similar to above one. It makes untwist -> frobenius^2 -> twist transformation plus + // negation according to miller loop spec. + const auto nQ2 = -endomorphism<2>(Q); + + T = lin_func_and_add(T, Q1, t); + multiply_by_lin_func_value(f, t, P.x, P.y); + + lin_func(T, nQ2, t); + multiply_by_lin_func_value(f, t, P.x, P.y); + + return f; +} + +/// Final exponentiation formula. +/// Based on https://eprint.iacr.org/2010/354.pdf 4.2 Algorithm 31. +Fq12 final_exp(const Fq12& v) noexcept +{ + auto f = v; + auto f1 = f.conjugate(); + + f = f1 * f.inv(); // easy 1 + f = endomorphism<2>(f) * f; // easy 2 + + f1 = f.conjugate(); + + const auto ft1 = cyclotomic_pow_to_X(f); + const auto ft2 = cyclotomic_pow_to_X(ft1); + const auto ft3 = cyclotomic_pow_to_X(ft2); + const auto fp1 = endomorphism<1>(f); + const auto fp2 = endomorphism<2>(f); + const auto fp3 = endomorphism<3>(f); + const auto y0 = fp1 * fp2 * fp3; + const auto y1 = f1; + const auto y2 = endomorphism<2>(ft2); + const auto y3 = endomorphism<1>(ft1).conjugate(); + const auto y4 = (endomorphism<1>(ft2) * ft1).conjugate(); + const auto y5 = ft2.conjugate(); + const auto y6 = (endomorphism<1>(ft3) * ft3).conjugate(); + + auto t0 = cyclotomic_square(y6) * y4 * y5; + auto t1 = y3 * y5 * t0; + t0 = t0 * y2; + t1 = cyclotomic_square(t1) * t0; + t1 = cyclotomic_square(t1); + t0 = t1 * y1; + t1 = t1 * y0; + t0 = cyclotomic_square(t0); + return t1 * t0; +} +} // namespace + +std::optional pairing_check(std::span> pairs) noexcept +{ + if (pairs.empty()) + return true; + + auto f = Fq12::one(); + + for (const auto& [p, q] : pairs) + { + if (!is_field_element(p.x) || !is_field_element(p.y) || !is_field_element(q.x.first) || + !is_field_element(q.x.second) || !is_field_element(q.y.first) || + !is_field_element(q.y.second)) + { + return std::nullopt; + } + + // Converts points' coefficients in Montgomery form. + const auto P_aff = ecc::Point{Fq::from_int(p.x), Fq::from_int(p.y)}; + const auto Q_aff = ecc::Point{Fq2({Fq::from_int(q.x.first), Fq::from_int(q.x.second)}), + Fq2({Fq::from_int(q.y.first), Fq::from_int(q.y.second)})}; + + const bool g1_is_inf = is_infinity(P_aff); + const bool g2_is_inf = g2_is_infinity(Q_aff); + + // Verify that P in on curve. For this group it also means that P is in G1. + if (!g1_is_inf && !is_on_curve(P_aff)) + return std::nullopt; + + // Verify that Q in on curve and in proper subgroup. This subgroup is much smaller than + // group containing all the points from twisted curve over Fq2 field. + if (!g2_is_inf && (!is_on_twisted_curve(Q_aff) || !g2_subgroup_check(Q_aff))) + return std::nullopt; + + // If any of the points is infinity it means that miller_loop returns 1. so we can skip it. + if (!g1_is_inf && !g2_is_inf) + f = f * miller_loop(Q_aff, P_aff); + } + + // final exp is calculated on accumulated value + return final_exp(f) == Fq12::one(); +} +} // namespace evmmax::bn254 diff --git a/lib/evmone_precompiles/pairing/bn254/utils.hpp b/lib/evmone_precompiles/pairing/bn254/utils.hpp new file mode 100644 index 0000000000..7fcc46c567 --- /dev/null +++ b/lib/evmone_precompiles/pairing/bn254/utils.hpp @@ -0,0 +1,555 @@ +// evmone: Fast Ethereum Virtual Machine implementation +// Copyright 2024 The evmone Authors. +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#include "fields.hpp" + +namespace evmmax::bn254 +{ +consteval Fq2 make_fq2(const uint256& a, const uint256& b) noexcept +{ + return Fq2({Fq::from_int(a), Fq::from_int(b)}); +} + +/// Defines coefficients needed for fast Frobenius endomorphism computation. +/// For more ref see https://eprint.iacr.org/2010/354.pdf 3.2 Frobenius Operator. +/// TODO: Make it constexpr. +static inline std::array, 3> FROBENIUS_COEFFS = { + { + { + make_fq2( + 8376118865763821496583973867626364092589906065868298776909617916018768340080_u256, + 16469823323077808223889137241176536799009286646108169935659301613961712198316_u256), + make_fq2( + 21575463638280843010398324269430826099269044274347216827212613867836435027261_u256, + 10307601595873709700152284273816112264069230130616436755625194854815875713954_u256), + make_fq2( + 2821565182194536844548159561693502659359617185244120367078079554186484126554_u256, + 3505843767911556378687030309984248845540243509899259641013678093033130930403_u256), + make_fq2( + 2581911344467009335267311115468803099551665605076196740867805258568234346338_u256, + 19937756971775647987995932169929341994314640652964949448313374472400716661030_u256), + make_fq2( + 685108087231508774477564247770172212460312782337200605669322048753928464687_u256, + 8447204650696766136447902020341177575205426561248465145919723016860428151883_u256), + }, + { + make_fq2( + 21888242871839275220042445260109153167277707414472061641714758635765020556617_u256, + 0_u256), + make_fq2( + 21888242871839275220042445260109153167277707414472061641714758635765020556616_u256, + 0_u256), + make_fq2( + 21888242871839275222246405745257275088696311157297823662689037894645226208582_u256, + 0_u256), + make_fq2(2203960485148121921418603742825762020974279258880205651966_u256, 0_u256), + make_fq2(2203960485148121921418603742825762020974279258880205651967_u256, 0_u256), + }, + { + make_fq2( + 11697423496358154304825782922584725312912383441159505038794027105778954184319_u256, + 303847389135065887422783454877609941456349188919719272345083954437860409601_u256), + make_fq2( + 3772000881919853776433695186713858239009073593817195771773381919316419345261_u256, + 2236595495967245188281701248203181795121068902605861227855261137820944008926_u256), + make_fq2( + 19066677689644738377698246183563772429336693972053703295610958340458742082029_u256, + 18382399103927718843559375435273026243156067647398564021675359801612095278180_u256), + make_fq2( + 5324479202449903542726783395506214481928257762400643279780343368557297135718_u256, + 16208900380737693084919495127334387981393726419856888799917914180988844123039_u256), + make_fq2( + 8941241848238582420466759817324047081148088512956452953208002715982955420483_u256, + 10338197737521362862238855242243140895517409139741313354160881284257516364953_u256), + }, + }, +}; + +/// Verifies that value is in the proper prime field. +constexpr bool is_field_element(const uint256& v) +{ + return v < FieldPrime; +} + +/// Verifies that affine point is on the curve (not twisted) +constexpr bool is_on_curve(const ecc::Point& p) noexcept +{ + // TODO(C++23): make static + constexpr auto B = Fq::from_int(3); + + const auto x3 = p.x * p.x * p.x; + const auto y2 = p.y * p.y; + return y2 == x3 + B; +} + +/// Verifies that affine point over Fq^2 field is on the twisted curve. +constexpr bool is_on_twisted_curve(const evmmax::ecc::Point& p) +{ + const auto x3 = p.x * p.x * p.x; + const auto y2 = p.y * p.y; + + return y2 == x3 + Fq6Config::_3_ksi_inv; +} + +/// Verifies that affine point over the base field is infinity. +constexpr bool is_infinity(const evmmax::ecc::Point& p) +{ + return p.x.is_zero() && p.y.is_zero(); +} + +/// Verifies that affine point over the Fq^2 extended field is infinity. +constexpr bool g2_is_infinity(const evmmax::ecc::Point& p) +{ + return p.x == Fq2::zero() && p.y == Fq2::zero(); +} + +// Forbenius endomorphism related functions are implemented based on +// https://hackmd.io/@jpw/bn254#mathbb-G_2-membership-check-using-efficient-endomorphism +// and +// https://eprint.iacr.org/2010/354.pdf 3.2 Frobenius Operator + +/// Computes Frobenius endomorphism of point `p` in Jacobian coordinates from twisted curve +/// over Fq2 extended field. +/// This specialisation computes Frobenius and Frobenius^3 +/// TODO: add reference that it's exactly the same as untwist->frobenius->twist +template +constexpr ecc::JacPoint endomorphism(const ecc::JacPoint& p) noexcept + requires(P == 1 || P == 3) +{ + return { + p.x.conjugate() * FROBENIUS_COEFFS[P - 1][1], + p.y.conjugate() * FROBENIUS_COEFFS[P - 1][2], + p.z.conjugate(), + }; +} + +/// Computes Frobenius endomorphism of point `p` in Jacobian coordinates from twisted curve +/// over Fq^2 extended field. +/// This specialisation computes Frobenius^2 +template +constexpr ecc::JacPoint endomorphism(const ecc::JacPoint& p) noexcept + requires(P == 2) +{ + return { + p.x * FROBENIUS_COEFFS[1][1], + p.y * FROBENIUS_COEFFS[1][2], + p.z, + }; +} + +/// Computes Frobenius endomorphism of point `p` in affine coordinates from twisted curve +/// over Fq^2 extended field. +/// This specialisation computes Frobenius and Frobenius^3 +template +constexpr ecc::Point endomorphism(const ecc::Point& p) noexcept + requires(P == 1 || P == 3) +{ + return { + p.x.conjugate() * FROBENIUS_COEFFS[P - 1][1], + p.y.conjugate() * FROBENIUS_COEFFS[P - 1][2], + }; +} + +/// Computes Frobenius endomorphism of point `p` in affine coordinates from twisted curve +/// over Fq^2 extended field. +/// This specialisation computes Frobenius^2 +template +constexpr ecc::Point endomorphism(const ecc::Point& p) noexcept + requires(P == 2) +{ + return { + p.x * FROBENIUS_COEFFS[1][1], + p.y * FROBENIUS_COEFFS[1][2], + }; +} + +/// Computes Frobenius endomorphism for Fq12 field member values +/// This specialisation computes Frobenius and Frobenius^3 +template +constexpr Fq12 endomorphism(const Fq12& f) noexcept + requires(P == 1 || P == 3) +{ + return Fq12({ + Fq6({ + f.coeffs[0].coeffs[0].conjugate(), + f.coeffs[0].coeffs[1].conjugate() * FROBENIUS_COEFFS[P - 1][1], + f.coeffs[0].coeffs[2].conjugate() * FROBENIUS_COEFFS[P - 1][3], + }), + Fq6({ + f.coeffs[1].coeffs[0].conjugate() * FROBENIUS_COEFFS[P - 1][0], + f.coeffs[1].coeffs[1].conjugate() * FROBENIUS_COEFFS[P - 1][2], + f.coeffs[1].coeffs[2].conjugate() * FROBENIUS_COEFFS[P - 1][4], + }), + }); +} + +/// Computes Frobenius operator for Fq12 field member values +/// This specialization computes Frobenius^2 +template +constexpr Fq12 endomorphism(const Fq12& f) noexcept + requires(P == 2) +{ + return Fq12({ + Fq6({ + f.coeffs[0].coeffs[0], + f.coeffs[0].coeffs[1] * FROBENIUS_COEFFS[1][1], + f.coeffs[0].coeffs[2] * FROBENIUS_COEFFS[1][3], + }), + Fq6({ + f.coeffs[1].coeffs[0] * FROBENIUS_COEFFS[1][0], + f.coeffs[1].coeffs[1] * FROBENIUS_COEFFS[1][2], + f.coeffs[1].coeffs[2] * FROBENIUS_COEFFS[1][4], + }), + }); +} + + +/// Computes `P0 + P1` in Jacobian coordinates. +constexpr ecc::JacPoint add( + const ecc::JacPoint& P0, const ecc::JacPoint& P1) noexcept +{ + const auto& x0 = P0.x; + const auto& y0 = P0.y; + const auto& z0 = P0.z; + + const auto& x1 = P1.x; + const auto& y1 = P1.y; + const auto& z1 = P1.z; + + const auto z0_squared = z0 * z0; + const auto z0_cubed = z0 * z0_squared; + + const auto z1_squared = z1 * z1; + const auto z1_cubed = z1 * z1_squared; + + const auto U1 = x0 * z1_squared; + const auto U2 = x1 * z0_squared; + const auto S1 = y0 * z1_cubed; + const auto S2 = y1 * z0_cubed; + const auto H = U2 - U1; // x1 * z0^2 - x0 * z1^2 + const auto R = S2 - S1; // y1 * z0^3 - y0 * z1 ^3 + + const auto H_squared = H * H; + const auto H_cubed = H * H_squared; + const auto R_squared = R * R; + + const auto V = U1 * H_squared; + + const auto X3 = R_squared - H_cubed - (V + V); + const auto Y3 = R * (U1 * H_squared - X3) - S1 * H_cubed; + const auto Z3 = H * z0 * z1; + + return {X3, Y3, Z3}; +} + +/// Computes `Q + Q` in Jacobian coordinates. +constexpr ecc::JacPoint dbl(const ecc::JacPoint& Q) noexcept +{ + const auto& x = Q.x; + const auto& y = Q.y; + const auto& z = Q.z; + + const auto y_squared = y * y; + const auto x_squared = x * x; + const auto z_squared = z * z; + const auto y_4 = y_squared * y_squared; + const auto _4y_4 = y_4 + y_4 + y_4 + y_4; + + const auto R = y_squared + y_squared; + const auto A = (x + R); + const auto S = A * A - x_squared - _4y_4; // 2xR = (x+R)^2 - x^2 - R^2 + const auto M = x_squared + x_squared + x_squared; + + const auto N = y + z; + + const auto Xp = M * M - (S + S); + const auto Yp = M * (S - Xp) - (_4y_4 + _4y_4); + const auto Zp = N * N - y_squared - z_squared; // 2yz = (y+z)^2 - y^2 - z^2 + + return {Xp, Yp, Zp}; +} + +/// Computes `N` doubles of the point `a` in Jacobian coordinates. +template +constexpr ecc::JacPoint n_dbl(const ecc::JacPoint& a) noexcept +{ + auto r = dbl(a); + for (int i = 0; i < N - 1; ++i) + r = dbl(r); + + return r; +} + +/// Addchain generated algorithm which multiplies point `a` in Jacobian coordinated +/// by X (curve seed). +constexpr ecc::JacPoint mul_by_X(const ecc::JacPoint& a) noexcept +{ + auto t0 = dbl(a); + auto t2 = dbl(t0); + auto c = dbl(t2); + auto t4 = dbl(c); + auto t6 = add(a, t4); + t4 = add(t6, t0); + auto t8 = add(a, t4); + auto t10 = add(c, t6); + auto t12 = dbl(t6); + t8 = add(t8, t4); + t4 = add(t8, t0); + t12 = n_dbl<6>(t12); + t2 = add(t12, t2); + t2 = add(t2, t10); + t2 = n_dbl<7>(t2); + t10 = add(t2, t10); + t10 = n_dbl<8>(t10); + t10 = add(t10, t4); + t0 = add(t10, t0); + t0 = n_dbl<6>(t0); + t6 = add(t0, t6); + t6 = n_dbl<8>(t6); + t6 = add(t6, t4); + t6 = n_dbl<6>(t6); + t6 = add(t6, t4); + t6 = n_dbl<10>(t6); + t8 = add(t6, t8); + t8 = n_dbl<6>(t8); + t4 = add(t4, t8); + c = add(t4, c); + + return c; +} + +/// Checks that point `p_aff` is in proper subgroup of points from twisted curve over Fq2 field. +/// For more details see https://eprint.iacr.org/2022/348.pdf Example 1 from 3.1.2 Examples +constexpr bool g2_subgroup_check(const ecc::Point& p_aff) +{ + const auto p = ecc::JacPoint::from(p_aff); + + const auto px = mul_by_X(p); + const auto px1 = add(px, p); + const auto _2px = dbl(px); + + const auto e_px = endomorphism<1>(px); + const auto ee_px = endomorphism<1>(e_px); + const auto eee_2px = endomorphism<1>(endomorphism<2>(_2px)); + + const auto l = add(add(px1, e_px), ee_px); + + return l == eee_2px; +} + +/// Computes point Q doubling for twisted curve + line tangent (in untwisted Q) to +/// the curve (not twisted curve) evaluated at point P +/// Returns live evaluation coefficients (-t, tw, tvw) +/// For more details see https://notes.ethereum.org/@ipsilon/Hkn2a2qk0 +constexpr ecc::JacPoint lin_func_and_dbl( + const ecc::JacPoint& Q, std::array& t) noexcept +{ + const auto& x = Q.x; + const auto& y = Q.y; + const auto& z = Q.z; + + const auto y_squared = y * y; + const auto x_squared = x * x; + const auto z_squared = z * z; + const auto y_4 = y_squared * y_squared; + const auto _4y_4 = y_4 + y_4 + y_4 + y_4; + + const auto R = y_squared + y_squared; + const auto A = (x + R); + const auto S = A * A - x_squared - _4y_4; // 2xR = (x+R)^2 - x^2 - R^2 + const auto M = x_squared + x_squared + x_squared; + + const auto N = y + z; + + const auto Xp = M * M - (S + S); + const auto Yp = M * (S - Xp) - (_4y_4 + _4y_4); + const auto Zp = N * N - y_squared - z_squared; // 2yz = (y+z)^2 - y^2 - z^2 + + t[0] = Zp * z_squared; + t[1] = M * z_squared; + t[2] = R - M * x; + + return ecc::JacPoint{Xp, Yp, Zp}; +} + +/// Computes points P0 and P1 addition for twisted curve + line defined by untwisted P1 and P2 +/// points on the curve (not twisted curve) evaluated at point P. Formula is simplified for P1.z +/// == 1. For more details see https://notes.ethereum.org/@ipsilon/Hkn2a2qk0 +[[nodiscard]] constexpr ecc::JacPoint lin_func_and_add( + const ecc::JacPoint& P0, const ecc::Point& P1, std::array& t) noexcept +{ + const auto& x0 = P0.x; + const auto& y0 = P0.y; + const auto& z0 = P0.z; + + const auto& x1 = P1.x; + const auto& y1 = P1.y; + + const auto z0_squared = z0 * z0; + const auto z0_cubed = z0 * z0_squared; + + const auto U2 = x1 * z0_squared; + const auto S2 = y1 * z0_cubed; + const auto H = U2 - x0; // x1 * z0^2 - x0 * z1^2 + const auto R = S2 - y0; // y1 * z0^3 - y0 * z1 ^3 + + const auto H_squared = H * H; + const auto H_cubed = H * H_squared; + const auto R_squared = R * R; + + const auto V = x0 * H_squared; + + const auto X3 = R_squared - H_cubed - (V + V); + const auto Y3 = R * (x0 * H_squared - X3) - y0 * H_cubed; + const auto Z3 = H * z0; + + t[0] = (z0 * z0_squared * x0 - U2 * z0_cubed); + t[1] = (S2 * z0_squared - y0 * z0_squared); + t[2] = y0 * U2 - x0 * S2; + + return ecc::JacPoint{X3, Y3, Z3}; +} + +/// Computes points P0 and P1 addition for twisted curve + line defined by untwisted P1 and P2 +/// points on the curve (not twisted curve) evaluated at point P. Formula is simplified for P1.z +/// == 1. For more details see https://notes.ethereum.org/@ipsilon/Hkn2a2qk0 +constexpr void lin_func( + const ecc::JacPoint& P0, const ecc::Point& P1, std::array& t) noexcept +{ + const auto& x0 = P0.x; + const auto& y0 = P0.y; + const auto& z0 = P0.z; + + const auto& x1 = P1.x; + const auto& y1 = P1.y; + + const auto z0_squared = z0 * z0; + const auto z0_cubed = z0 * z0_squared; + + const auto U2 = x1 * z0_squared; + const auto S2 = y1 * z0_cubed; + + t[0] = (z0 * z0_squared * x0 - U2 * z0_cubed); + t[1] = (S2 * z0_squared - y0 * z0_squared); + t[2] = y0 * U2 - x0 * S2; +} + +/// 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 c2 = a0 * a1; + c0 = c0 * c3 + c2; + const auto c1 = c2 + c2; + c2 = Fq6({ksi * c2.coeffs[2], c2.coeffs[0], c2.coeffs[1]}); + c0 = c0 + c2; + + return Fq12({c0, c1}); +} + +/// Computes `a^2` for `a` from `Fq^4 = Fq^2[V](V^2 - ksi)` where `V` is from Fq^2 extended field. +/// For more reference see https://eprint.iacr.org/2010/354.pdf Algorithm 9 +constexpr std::pair fq4_square(const std::pair& a) +{ + const auto& a0 = a.first; + const auto& a1 = a.second; + + const auto t0 = a0 * a0; + const auto t1 = a1 * a1; + + const auto c0 = t1 * Fq6Config::ksi + t0; + auto c1 = a0 + a1; + c1 = c1 * c1 - t0 - t1; + + return {c0, c1}; +} + +/// Computes `c^2` for `x` from Fq^12 where `x^(FieldPrime^6 - 1) == 1`. +/// This is Fq^12 subgroup called cyclotomic polynomials or group of `r` roots of unity. +constexpr Fq12 cyclotomic_square(const Fq12& c) +{ + const auto& g = c.coeffs[0]; + const auto& h = c.coeffs[1]; + + const auto& g0 = g.coeffs[0]; + const auto& g1 = g.coeffs[1]; + const auto& g2 = g.coeffs[2]; + + const auto& h0 = h.coeffs[0]; + const auto& h1 = h.coeffs[1]; + const auto& h2 = h.coeffs[2]; + + const auto [t00, t11] = fq4_square({g0, h1}); + 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 c00 = (t00 + t00 + t00) - (g0 + g0); + const auto c01 = (t01 + t01 + t01) - (g1 + g1); + const auto c02 = (t02 + t02 + t02) - (g2 + g2); + + const auto c10 = h0 + h0 + t10 + t10 + t10; + const auto c11 = h1 + h1 + t11 + t11 + t11; + const auto c12 = h2 + h2 + t12 + t12 + t12; + + return Fq12({Fq6({c00, c01, c02}), Fq6({c10, c11, c12})}); +} + +/// Computes `cyclotomic_square` N times. +template +constexpr Fq12 n_cyclotomic_square(const Fq12& c) +{ + auto r = c; + for (int i = 0; i < N; ++i) + r = cyclotomic_square(r); + + return r; +} + +/// Computes `a^X` where `X` is the curve seed parameter +/// and `a` is from cyclotomic subgroup of Fq^12. +constexpr Fq12 cyclotomic_pow_to_X(const Fq12& a) +{ + auto t0 = cyclotomic_square(a); + auto t2 = cyclotomic_square(t0); + auto c = cyclotomic_square(t2); + auto t4 = cyclotomic_square(c); + auto t6 = a * t4; + t4 = t6 * t0; + auto t8 = a * t4; + auto t10 = c * t6; + auto t12 = cyclotomic_square(t6); + t8 = t8 * t4; + t4 = t8 * t0; + t12 = n_cyclotomic_square<6>(t12); + t2 = t12 * t2; + t2 = t2 * t10; + t2 = n_cyclotomic_square<7>(t2); + t10 = t2 * t10; + t10 = n_cyclotomic_square<8>(t10); + t10 = t10 * t4; + t0 = t10 * t0; + t0 = n_cyclotomic_square<6>(t0); + t6 = t0 * t6; + t6 = n_cyclotomic_square<8>(t6); + t6 = t6 * t4; + t6 = n_cyclotomic_square<6>(t6); + t6 = t6 * t4; + t6 = n_cyclotomic_square<10>(t6); + t8 = t6 * t8; + t8 = n_cyclotomic_square<6>(t8); + t4 = t4 * t8; + c = t4 * c; + + return c; +} + +} // namespace evmmax::bn254 diff --git a/lib/evmone_precompiles/pairing/field_template.hpp b/lib/evmone_precompiles/pairing/field_template.hpp new file mode 100644 index 0000000000..7e56b93fb8 --- /dev/null +++ b/lib/evmone_precompiles/pairing/field_template.hpp @@ -0,0 +1,148 @@ +// evmone: Fast Ethereum Virtual Machine implementation +// Copyright 2024 The evmone Authors. +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +#include + +namespace evmmax::ecc +{ +/// Implements computations over base field defined by prime number. +/// Wraps around ModArith struct and implements additional functions needed for pairing. +/// It is a template struct which can be reused for different pairing implementations. +template +class BaseFieldElem +{ + using ValueT = typename ConfigT::ValueT; + + static constexpr ModArith Fp = ConfigT::MOD_ARITH; + + ValueT m_value; + +public: + constexpr BaseFieldElem() noexcept = default; + + explicit constexpr BaseFieldElem(const ValueT& v) noexcept : m_value(v) {} + + static constexpr BaseFieldElem from_int(const ValueT& v) noexcept + { + return BaseFieldElem(Fp.to_mont(v)); + } + + constexpr const ValueT& value() const noexcept { return m_value; } + + BaseFieldElem inv() const noexcept { return inverse(*this); } + + constexpr bool is_zero() const noexcept { return m_value == 0; } + + static constexpr BaseFieldElem one() noexcept { return BaseFieldElem(ConfigT::ONE); } + + static constexpr BaseFieldElem zero() noexcept { return BaseFieldElem(0); } + + friend constexpr BaseFieldElem operator+( + const BaseFieldElem& e1, const BaseFieldElem& e2) noexcept + { + return BaseFieldElem(Fp.add(e1.m_value, e2.m_value)); + } + + friend constexpr BaseFieldElem operator-( + const BaseFieldElem& e1, const BaseFieldElem& e2) noexcept + { + return BaseFieldElem(Fp.sub(e1.m_value, e2.m_value)); + } + + friend constexpr BaseFieldElem operator*( + const BaseFieldElem& e1, const BaseFieldElem& e2) noexcept + { + return BaseFieldElem(Fp.mul(e1.m_value, e2.m_value)); + } + + friend constexpr BaseFieldElem operator-(const BaseFieldElem& e) noexcept + { + return BaseFieldElem(Fp.sub(ValueT{0}, e.m_value)); + } + + friend constexpr bool operator==( + const BaseFieldElem& e1, const BaseFieldElem& e2) noexcept = default; +}; + +/// Implements extension field over the base field or other extension fields. +/// It is a template struct which can be reused for different pairing implementations. +template +struct ExtFieldElem +{ + using ValueT = typename ConfigT::ValueT; + using Base = typename ConfigT::BaseFieldT; + static constexpr auto DEGREE = ConfigT::DEGREE; + using CoeffArrT = std::array; + + // TODO: Add operator[] for nicer access. + CoeffArrT coeffs = {}; + + constexpr ExtFieldElem() noexcept = default; + + /// Create an element from an array of coefficients. + /// TODO: This constructor may be optimized to avoid copying the array. + explicit constexpr ExtFieldElem(const CoeffArrT& cs) noexcept : coeffs{cs} {} + + constexpr ExtFieldElem conjugate() const noexcept + { + auto res = this->coeffs; + for (size_t i = 1; i < DEGREE; i += 2) + res[i] = -res[i]; + return ExtFieldElem(res); + } + + static constexpr ExtFieldElem one() noexcept + { + ExtFieldElem res; + res.coeffs[0] = ValueT::one(); + return res; + } + + static constexpr ExtFieldElem zero() noexcept { return ExtFieldElem{}; } + + constexpr ExtFieldElem inv() const noexcept { return inverse(*this); } + + friend constexpr ExtFieldElem operator+(const ExtFieldElem& e1, const ExtFieldElem& e2) noexcept + { + auto res = e1.coeffs; + for (size_t i = 0; i < DEGREE; ++i) + res[i] = res[i] + e2.coeffs[i]; + return ExtFieldElem(res); + } + + friend constexpr ExtFieldElem operator-(const ExtFieldElem& e1, const ExtFieldElem& e2) noexcept + { + auto res = e1.coeffs; + for (size_t i = 0; i < DEGREE; ++i) + res[i] = res[i] - e2.coeffs[i]; + return ExtFieldElem(res); + } + + friend constexpr ExtFieldElem operator-(const ExtFieldElem& e) noexcept + { + CoeffArrT ret; + for (size_t i = 0; i < DEGREE; ++i) + ret[i] = -e.coeffs[i]; + return ExtFieldElem(ret); + } + + friend constexpr ExtFieldElem operator*(const ExtFieldElem& e1, const ExtFieldElem& e2) noexcept + { + return multiply(e1, e2); + } + + friend constexpr bool operator==( + const ExtFieldElem& e1, const ExtFieldElem& e2) noexcept = default; + + friend constexpr ExtFieldElem operator*(const ExtFieldElem& e, const Base& s) noexcept + { + auto res = e; + for (auto& c : res.coeffs) + c = c * s; + return res; + } +}; + +} // namespace evmmax::ecc diff --git a/test/precompiles_bench/precompiles_bench.cpp b/test/precompiles_bench/precompiles_bench.cpp index 0209feb3d2..3e3cc6375f 100644 --- a/test/precompiles_bench/precompiles_bench.cpp +++ b/test/precompiles_bench/precompiles_bench.cpp @@ -206,6 +206,8 @@ BENCHMARK_TEMPLATE(precompile, PrecompileId::ecmul, libff); namespace bench_ecpairing { +constexpr auto evmmax_cpp = ecpairing_execute; +BENCHMARK_TEMPLATE(precompile, PrecompileId::ecpairing, evmmax_cpp); #ifdef EVMONE_PRECOMPILES_SILKPRE constexpr auto libff = silkpre_ecpairing_execute; BENCHMARK_TEMPLATE(precompile, PrecompileId::ecpairing, libff); diff --git a/test/state/precompiles.cpp b/test/state/precompiles.cpp index e2d280ac54..b76a6f492c 100644 --- a/test/state/precompiles.cpp +++ b/test/state/precompiles.cpp @@ -348,6 +348,55 @@ ExecutionResult ecmul_execute(const uint8_t* input, size_t input_size, uint8_t* return {EVMC_PRECOMPILE_FAILURE, 0}; } +ExecutionResult ecpairing_execute(const uint8_t* input, size_t input_size, uint8_t* output, + [[maybe_unused]] size_t output_size) noexcept +{ + assert(output_size >= 32); + + static constexpr size_t PAIR_SIZE = 192; + + if (input_size % PAIR_SIZE != 0) + return {EVMC_PRECOMPILE_FAILURE, 0}; + + if (const auto n_pairs = input_size / PAIR_SIZE; n_pairs > 0) + { + std::vector> pairs; + pairs.reserve(n_pairs); + auto input_idx = input; + for (size_t i = 0; i < n_pairs; ++i) + { + const evmmax::bn254::Point p{ + intx::be::unsafe::load(input_idx), + intx::be::unsafe::load(input_idx + 32), + }; + + const evmmax::bn254::ExtPoint q{ + {intx::be::unsafe::load(input_idx + 96), + intx::be::unsafe::load(input_idx + 64)}, + {intx::be::unsafe::load(input_idx + 160), + intx::be::unsafe::load(input_idx + 128)}, + }; + pairs.emplace_back(p, q); + input_idx += PAIR_SIZE; + } + + const auto res = evmmax::bn254::pairing_check(pairs); + + if (res.has_value()) + { + intx::be::unsafe::store(output, res.value() ? intx::uint256{1} : intx::uint256{0}); + return {EVMC_SUCCESS, 64}; + } + else + return {EVMC_PRECOMPILE_FAILURE, 0}; + } + else + { + intx::be::unsafe::store(output, intx::uint256{1}); + return {EVMC_SUCCESS, 32}; + } +} + ExecutionResult identity_execute(const uint8_t* input, size_t input_size, uint8_t* output, [[maybe_unused]] size_t output_size) noexcept { @@ -527,7 +576,7 @@ inline constexpr auto traits = []() noexcept { {expmod_analyze, expmod_stub}, {ecadd_analyze, ecadd_execute}, {ecmul_analyze, ecmul_execute}, - {ecpairing_analyze, ecpairing_stub}, + {ecpairing_analyze, ecpairing_execute}, {blake2bf_analyze, blake2bf_execute}, {point_evaluation_analyze, point_evaluation_execute}, {bls12_g1add_analyze, bls12_g1add_execute}, @@ -545,7 +594,7 @@ inline constexpr auto traits = []() noexcept { tbl[static_cast(PrecompileId::expmod)].execute = silkpre_expmod_execute; // tbl[static_cast(PrecompileId::ecadd)].execute = silkpre_ecadd_execute; // tbl[static_cast(PrecompileId::ecmul)].execute = silkpre_ecmul_execute; - tbl[static_cast(PrecompileId::ecpairing)].execute = silkpre_ecpairing_execute; + // tbl[static_cast(PrecompileId::ecpairing)].execute = silkpre_ecpairing_execute; // tbl[static_cast(PrecompileId::blake2bf)].execute = silkpre_blake2bf_execute; #endif return tbl; diff --git a/test/state/precompiles_internal.hpp b/test/state/precompiles_internal.hpp index f8cd17edf7..fdfdbedf89 100644 --- a/test/state/precompiles_internal.hpp +++ b/test/state/precompiles_internal.hpp @@ -51,6 +51,8 @@ ExecutionResult ecmul_execute( const uint8_t* input, size_t input_size, uint8_t* output, size_t output_size) noexcept; ExecutionResult blake2bf_execute( const uint8_t* input, size_t input_size, uint8_t* output, size_t output_size) noexcept; +ExecutionResult ecpairing_execute( + const uint8_t* input, size_t input_size, uint8_t* output, size_t output_size) noexcept; ExecutionResult point_evaluation_execute( const uint8_t* input, size_t input_size, uint8_t* output, size_t output_size) noexcept; ExecutionResult bls12_g1add_execute( diff --git a/test/state/precompiles_stubs.cpp b/test/state/precompiles_stubs.cpp index 37626fe444..de01a3822b 100644 --- a/test/state/precompiles_stubs.cpp +++ b/test/state/precompiles_stubs.cpp @@ -384,76 +384,4 @@ ExecutionResult expmod_stub( }; return stubs.lookup({input, input_size}, output, max_output_size); } - -ExecutionResult ecpairing_stub( - const uint8_t* input, size_t input_size, uint8_t* output, size_t max_output_size) noexcept -{ - static const auto _0 = "0000000000000000000000000000000000000000000000000000000000000000"_hex; - static const auto _1 = "0000000000000000000000000000000000000000000000000000000000000001"_hex; - static const HashedInputStubs stubs{ - {0x006b0484c653b1be16a359057269baa24e343db52e44dce7c6cefeef149735f3_bytes32, _1}, - {0x086b338f7e1848d3b4403b1c68ec8aca7a528ede6b8ec2fe224befd44be88fce_bytes32, _0}, - {0x1c99829c90fc052bddb247280db7e125d01ea5fa3837be302bb4385faf04c21e_bytes32, _0}, - {0x2fe4971f59c3dedb114db9798d3bdd9141ff8396e083e72ac3ce6cfedcf4e0b0_bytes32, _1}, - {0x34ab9af661244dbcf8f5579970f5052c7d3c210ce13b6ca1c11172c7863a10f0_bytes32, _1}, - {0x40653f9463fc27af1ae2d102513f34a73d3b3b7ddcecdd5b610ff03edeaf72c9_bytes32, _0}, - {0x5101633c6b687ee8aee29d9618ac1397a30cd7a14ff339cb19608101ed74813c_bytes32, _1}, - {0x58272cd6bf0816a2a7bcc15804d2e94efb1dc38505d3349b6372f0bbcf5e1a6c_bytes32, _1}, - {0x5a815339f8a25c8e84000b0372b4cbb4dd70277eb1f53dba5f51a419673348be_bytes32, _0}, - {0x5e8c3b1154f324f528a87fd8ee700753aa61df9ac087029cca81909985a16ccd_bytes32, _1}, - {0x722bf5c9235aae2151346ce1d19ef04b006aa7766268bf836377445092da5441_bytes32, _0}, - {0x730792bc8e23a24142495d77d62d22713f03bd3171abbe79e41eed071b064347_bytes32, _0}, - {0x79b0f933a47d870f4605811e2bcb2ca804b5d0afbae398b2f1b8106b30afa6af_bytes32, _1}, - {0x86400f22e06689a2968f0a4089bda8c60df97f463e7166b7b967c7e919edf0ff_bytes32, _1}, - {0x982485363eccb2a35b40cb7c5389901fe540e00bc4261bb3d856a9b49f11207f_bytes32, _1}, - {0xa60b9fc20ee07592f3581aca8ac1f77e609a1b353b3a66a6f4c29f521e533b73_bytes32, _1}, - {0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470_bytes32, _1}, - {0xd0c775f8d408d1c6e863c2d529a7edfbe3df1737d86bb6b69edc7508ad4e4926_bytes32, _1}, - {0xd3eb0c32becbfb2d9c6c15edf0d51f84daac9dda3ce2d6506e1d4573db29bb89_bytes32, _1}, - {0xd41981a62a65bc8d3ec7e86aeaadf0a5069f58c61705f64ead9d6a33f56f6b5b_bytes32, _1}, - - // https://github.com/ethereum/tests/blob/v13.3/src/GeneralStateTestsFiller/stZeroKnowledge/ecpairing_inputsFiller.yml#L88-L92 - {0xedf5a91b7972b77cef24e42ebf683ef81bdbc2f97cad1e6197f42cdea5e9d489_bytes32, _0}, - {0x38052a6ac4c5131f3391bb49ad3f1d5f00c9eff3a68696eccaf776c31de66755_bytes32, _0}, - {0x4bff5699271962bf77165efea69c77ae9620d5d63d9e6cf2aacfa6180d76be64_bytes32, _0}, - {0xb4e6d4451f859e560863f3097450b57c481ac2604889f528ae25fd82294bb2ee_bytes32, _0}, - {0x2c6a9ba3568c7c3a4d167645185dc247e56fee419984c63071ad398315c91e10_bytes32, _0}, - - // https://github.com/ethereum/tests/blob/v13.3/src/GeneralStateTestsFiller/stZeroKnowledge/ecpairing_inputsFiller.yml#L93-L125 - {0xb67d886462fac350fdee4f6c7e9039e76ecd8b76bb3966da171a3e27667c1ec9_bytes32, _1}, - {0x1335e5982cf4b93dd6841c0037bdebe204b87e82e10a3c79c01f762a1e7c4d1a_bytes32, _1}, - {0x954e14906cb50e29de9f03d4a36c64f5404022853d22bdceae6fb4283f8c1832_bytes32, _1}, - {0xe5b53f3cf626793a6025fdd553f7b541ac3cb92dfb93601902e87b2827eff7a6_bytes32, _1}, - {0x34d0e58d51512383475806ca14f4a5c78459060fd01cd4be57ff9f7c69a6351b_bytes32, _1}, - {0xd59908760c797c4e1abb89ac297c3584c0fc0f56ec5c4edd807813fc3104143c_bytes32, _1}, - {0x38fccf0bf46a22541e77dbe7f8349b5ee76dc074ef550c3b0c79919f102a3d7c_bytes32, _1}, - {0xff13ad7025ffe82f4f3d05bd7981640865cbe06f8ad8f10305410c57e57c853f_bytes32, _1}, - {0x3567df81192e58f480ee7424c01bef8ecd7cb831f30c3550977acda477c7d457_bytes32, _1}, - {0x4e399cd983cb3849d73c8132b3af7700fe8eb76aa1ea359462abea6d6b385341_bytes32, _1}, - {0x4d52c96da659d1fba37d0b5658afc3ba1d2c593753e01dc37be19c3f723f6ffb_bytes32, _1}, - {0x271eea9d94639a22eae52eee98aee895210f60539d20bc4dec1949e461107395_bytes32, _1}, - {0x06a5d258a138e0430e5fa9e5d3d0456d2081ee35014d06a44559120272b04927_bytes32, _1}, - {0xf1c5b77850ef1d521ded25b7b6940b54b47601c28b46bc512b22721d953a8f45_bytes32, _1}, - {0x76d73dac407ca9a4aa671c25628f3d9ca60bd7490c42f27201c5e5214b726353_bytes32, _1}, - {0x642ce9262387f9368e917753262ee445c20c27334e9808b924c8ef00e12fcb75_bytes32, _1}, - {0x7407aa98cf3a0263df6d1c3f5d24a0ffe29bd8cebba48552a50cb06b4fb225ad_bytes32, _1}, - {0x50847a4741f81644bf49a4ce22329d8487c34897001701b35cf1fcb37cd0590e_bytes32, _1}, - {0x73cfc82608f54bd4e6c7d729afe9e690adf9d88e856f0aa6cac2b26fc7890ef2_bytes32, _1}, - {0x0676ac886e215342147e4ff6f2d62a3d2a79b41ba532760394098840efb5b11a_bytes32, _1}, - {0x46958a07ceea64e3e46b9338b85718027ddca6b6531ad59912ad98fc30bdfb74_bytes32, _1}, - {0x29780bc45a549776559b934dbb1ad289a45e5d0b07c7a75401ac311a99d4bf8e_bytes32, _1}, - {0x4f519b293c0fc788e39591955bed841763afa836b07afaba0dbf28db756122e8_bytes32, _1}, - {0x59c33dc069ff2818c161df2baafe51a21106739aa4f54e4251536833aa0d28cc_bytes32, _1}, - {0xede6d3ebd333189a99f4d5b6ca6dd03296f7b3c83dba9ebe5a99a8eecac126d3_bytes32, _1}, - {0x75aef1dc2eb432680d33ed9d330008dce5cf71e9e3cd6bf4b68575e5e613a076_bytes32, _1}, - {0x151397d397dd7647321e386990a1fcbb13544b507ffb29c38ec95f10f9ea65d5_bytes32, _1}, - {0x3ef93cb30d7b786f9d294566676aecd29dd7cd05ee1da48a3f8281f7cfd5923c_bytes32, _1}, - {0x45f50343a48f7ea57bfc317e5170b945e748124cdbedacf163fe0674790149ad_bytes32, _1}, - {0xbfa15492c7462ec9ac2a2c4a1f02ef12518746920ce5254673a9af0917f5b856_bytes32, _1}, - {0x06ea20597bf9cf2951d7a127cbbac029ab479d668e8cb848595fb8af0d2620d0_bytes32, _1}, - {0x14fe90e9791ada31b07cc85195109e573f1902fa2692a12113ae0a1af8c9753f_bytes32, _1}, - {0x7ac98487a7b592bff7e5b04f9e5454f218ebcf1b51c705794b795860a35f8253_bytes32, _1}, - }; - return stubs.lookup({input, input_size}, output, max_output_size); -} } // namespace evmone::state diff --git a/test/state/precompiles_stubs.hpp b/test/state/precompiles_stubs.hpp index 8c16d96357..5920f6360c 100644 --- a/test/state/precompiles_stubs.hpp +++ b/test/state/precompiles_stubs.hpp @@ -9,6 +9,4 @@ namespace evmone::state { ExecutionResult expmod_stub( const uint8_t* input, size_t input_size, uint8_t* output, size_t max_output_size) noexcept; -ExecutionResult ecpairing_stub( - const uint8_t* input, size_t input_size, uint8_t* output, size_t max_output_size) noexcept; } // namespace evmone::state diff --git a/test/unittests/CMakeLists.txt b/test/unittests/CMakeLists.txt index d86803d71c..396d59fb17 100644 --- a/test/unittests/CMakeLists.txt +++ b/test/unittests/CMakeLists.txt @@ -40,6 +40,7 @@ target_sources( evm_benchmark_test.cpp evmmax_bn254_add_test.cpp evmmax_bn254_mul_test.cpp + evmmax_bn254_pairing_test.cpp evmmax_test.cpp evmmax_secp256k1_test.cpp evmone_test.cpp diff --git a/test/unittests/evmmax_bn254_pairing_test.cpp b/test/unittests/evmmax_bn254_pairing_test.cpp new file mode 100644 index 0000000000..7ba76da910 --- /dev/null +++ b/test/unittests/evmmax_bn254_pairing_test.cpp @@ -0,0 +1,169 @@ +// evmone: Fast Ethereum Virtual Machine implementation +// Copyright 2024 The evmone Authors. +// SPDX-License-Identifier: Apache-2.0 + +#include "evmone_precompiles/bn254.hpp" +#include + +using namespace evmmax::bn254; +using namespace intx; + + +TEST(evmmax, bn254_pairing) +{ + const auto P1 = Point{ + 0x1c76476f4def4bb94541d57ebba1193381ffa7aa76ada664dd31c16024c43f59_u256, + 0x3034dd2920f673e204fee2811c678745fc819b55d3e9d294e45c9b03a76aef41_u256, + }; + // -P1: + const auto nP1 = Point{P1.x, FieldPrime - P1.y}; + // P1 * 17: + const auto P1_17 = Point{ + 0x22980b2e458ec77e258b19ca3a7b46181f63c6536307acae03eea236f6919eeb_u256, + 0x4eab993e2ba2cca2b08c216645e3fbcf80ae67515b2c49806c17b90c9d3cad3_u256, + }; + + const auto Q1 = ExtPoint{ + { + 0x04bf11ca01483bfa8b34b43561848d28905960114c8ac04049af4b6315a41678_u256, + 0x209dd15ebff5d46c4bd888e51a93cf99a7329636c63514396b4a452003a35bf7_u256, + }, + { + 0x120a2a4cf30c1bf9845f20c6fe39e07ea2cce61f0c9bb048165fe5e4de877550_u256, + 0x2bb8324af6cfc93537a2ad1a445cfd0ca2a71acd7ac41fadbf933c2a51be344d_u256, + }, + }; + // -Q1: + const auto nQ1 = ExtPoint{Q1.x, {FieldPrime - Q1.y.first, FieldPrime - Q1.y.second}}; + // -Q1 * 16: + const auto nQ1_16 = ExtPoint{ + { + 0x14191bd65f51663a1d4ad71d8480c3c3260d598aab6ed95681f773abade7fd7a_u256, + 0x299c79589dfb51fd6925fce3a7fc15c441fdafaa24f0d09b7c443befdddde4e5_u256, + }, + { + 0x1d710ac19a995c6395f33be7f3dcd75e0632a006d196da6b4c9ba78708b6bb78_u256, + 0xcae1001513ae5ddf742aa6dc2f52457d9b14e17765dd74fc098ad06045d434e_u256, + }, + }; + // -Q1 * 17: + const auto nQ1_17 = ExtPoint{ + { + 0x11eeb08db4fe0df9d7617f11f5f8f488d643510f825f3730ffb038c84c9260fd_u256, + 0x12bf46039aa40a61762bf97b1bb028cebc6d42e46bbbe67f715eda54808b74c4_u256, + }, + { + 0x42b65e62de1fd24534db81fd72e7ee832637948c1c466ccb08171e503f23e72_u256, + 0x197a5efb333448885788690df5af2211c1697dd8b7b1f8845b4e30a909d2b0f5_u256, + }, + }; + + { + // p1*q1 - (-p1*q1) = 0? + const std::vector> pairs{ + {P1, Q1}, + {nP1, Q1}, + }; + EXPECT_EQ(pairing_check(pairs), true); + } + + { + // p1*q1 - (p1*-q1) = 0? + const std::vector> pairs{ + {P1, Q1}, + {P1, nQ1}, + }; + EXPECT_EQ(pairing_check(pairs), true); + } + + { + // p1*17*q1 - (p1*-q1*16) = 0? + const std::vector> pairs{ + {P1_17, Q1}, + {P1, nQ1_16}, + }; + EXPECT_EQ(pairing_check(pairs), false); + } + + { + // p1*17 * q1 - (p1 * -q1*17) = 0? + const std::vector> pairs{ + {P1_17, Q1}, + {P1, nQ1_17}, + }; + EXPECT_EQ(pairing_check(pairs), true); + } + + // Empty input + { + EXPECT_EQ(pairing_check({}), true); + } +} + +TEST(evmmax, bn254_pairing_invalid_input) +{ + const std::vector> valid_input{{ + { + 0x22980b2e458ec77e258b19ca3a7b46181f63c6536307acae03eea236f6919eeb_u256, + 0x4eab993e2ba2cca2b08c216645e3fbcf80ae67515b2c49806c17b90c9d3cad3_u256, + }, + { + { + 0x04bf11ca01483bfa8b34b43561848d28905960114c8ac04049af4b6315a41678_u256, + 0x209dd15ebff5d46c4bd888e51a93cf99a7329636c63514396b4a452003a35bf7_u256, + }, + { + 0x120a2a4cf30c1bf9845f20c6fe39e07ea2cce61f0c9bb048165fe5e4de877550_u256, + 0x2bb8324af6cfc93537a2ad1a445cfd0ca2a71acd7ac41fadbf933c2a51be344d_u256, + }, + }, + }}; + + EXPECT_EQ(pairing_check(valid_input), false); + + { + // Coordinate not a field element + auto input = valid_input; + input[0].first.x = FieldPrime; + EXPECT_EQ(pairing_check(input), std::nullopt); + } + + { + // Coordinate not a field element + auto input = valid_input; + input[0].second.x.second = FieldPrime; + EXPECT_EQ(pairing_check(input), std::nullopt); + } + + { + // Point P (G1) not on curve + auto input = valid_input; + input[0].first.x += 1; + EXPECT_EQ(pairing_check(input), std::nullopt); + } + + { + // Point Q (G2) not on curve + auto input = valid_input; + input[0].second.x.first += 1; + EXPECT_EQ(pairing_check(input), std::nullopt); + } + + { + // Q not in proper group. Q id a member of small subgroup on twisted curve over Fq^2 + const ExtPoint Q{ + { + 0x13d841ba7ff3c6efd6870c3fea13a3ecab0423af5e4db9c5d28a6b46a05cd57b_u256, + 0x1a2b1eaa7b20faae36d26eff4db6e336c34434b66eded3cc5303d51ae353f478_u256, + }, + { + 0x2d3e8808aa7a7fffa8f871f10df8d59c6dd725889c46e9136e01cb2465b20723_u256, + 0x1d5224817b8714531fc77e20b975178b1b3044f4b729fa3230db03dc0088ebdb_u256, + }, + }; + + auto input = valid_input; + input[0].second = Q; + EXPECT_EQ(pairing_check(input), std::nullopt); + } +}