Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 5e2e777

Browse files
committedApr 2, 2024
evmmax: Add ate pairing impl. Miller loop and final exponentiation.
1 parent e26c518 commit 5e2e777

File tree

3 files changed

+191
-0
lines changed

3 files changed

+191
-0
lines changed
 

‎lib/evmone_precompiles/CMakeLists.txt

+1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ target_sources(
1111
bn254.cpp
1212
ecc.hpp
1313
pairing/bn254/fields.hpp
14+
pairing/bn254/pairing.cpp
1415
pairing/bn254/utils.hpp
1516
pairing/field_template.hpp
1617
secp256k1.cpp

‎lib/evmone_precompiles/bn254.hpp

+12
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
#pragma once
55

66
#include "ecc.hpp"
7+
#include <optional>
8+
#include <vector>
79

810
namespace evmmax::bn254
911
{
@@ -14,6 +16,7 @@ inline constexpr auto FieldPrime =
1416
0x30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47_u256;
1517

1618
using Point = ecc::Point<uint256>;
19+
using ExtPoint = ecc::Point<std::pair<uint256, uint256>>;
1720

1821
/// Validates that point is from the bn254 curve group
1922
///
@@ -36,4 +39,13 @@ Point add(const Point& pt1, const Point& pt2) noexcept;
3639
/// Computes [c]P for a point in affine coordinate on the bn254 curve,
3740
Point mul(const Point& pt, const uint256& c) noexcept;
3841

42+
/// ate paring implementation for bn254 curve according to https://eips.ethereum.org/EIPS/eip-197
43+
///
44+
/// \param vG2 vector containing points from twisted curve G2 group over extension field Fq^2
45+
/// \param vG1 vector of points from the bn254 curve G1 group over the base field
46+
/// These vectors must be same size n.
47+
/// \return `true` when ∏e(vG2[i], vG1[i]) == 1 for i in [0, n] else `false`. std::nullopt on error
48+
std::optional<bool> pairing(
49+
const std::vector<ExtPoint>& vG2, const std::vector<Point>& vG1) noexcept;
50+
3951
} // namespace evmmax::bn254
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
// evmone: Fast Ethereum Virtual Machine implementation
2+
// Copyright 2023 The evmone Authors.
3+
// SPDX-License-Identifier: Apache-2.0
4+
5+
#include "../../bn254.hpp"
6+
#include "fields.hpp"
7+
#include "utils.hpp"
8+
#include <vector>
9+
10+
namespace evmmax::bn254
11+
{
12+
namespace
13+
{
14+
// Multiplies `fr` (Fq12) values by sparce `v` (Fq12) value of the form
15+
// [[t[0] * y, 0, 0],[t[1] * x, t[0], 0]] where `v` coefficients are from Fq2
16+
inline constexpr void multiply_by_lin_func_value(
17+
Fq12& fr, std::array<Fq2, 3> t, const Fq& x, const Fq& y) noexcept
18+
{
19+
const Fq12 f = fr;
20+
const auto& ksi = Fq6Config::ksi;
21+
22+
const auto t0y = t[0] * y;
23+
const auto t1x = t[1] * x;
24+
const auto t2ksi = t[2] * ksi;
25+
26+
fr.coeffs[0].coeffs[0] = f.coeffs[0].coeffs[0] * t0y + f.coeffs[1].coeffs[2] * t1x * ksi +
27+
f.coeffs[1].coeffs[1] * t2ksi;
28+
fr.coeffs[0].coeffs[1] =
29+
f.coeffs[0].coeffs[1] * t0y + f.coeffs[1].coeffs[0] * t1x + f.coeffs[1].coeffs[2] * t2ksi;
30+
fr.coeffs[0].coeffs[2] =
31+
f.coeffs[0].coeffs[2] * t0y + f.coeffs[1].coeffs[1] * t1x + f.coeffs[1].coeffs[0] * t[2];
32+
fr.coeffs[1].coeffs[0] =
33+
f.coeffs[1].coeffs[0] * t0y + f.coeffs[0].coeffs[0] * t1x + f.coeffs[0].coeffs[2] * t2ksi;
34+
fr.coeffs[1].coeffs[1] =
35+
f.coeffs[1].coeffs[1] * t0y + f.coeffs[0].coeffs[1] * t1x + f.coeffs[0].coeffs[0] * t[2];
36+
fr.coeffs[1].coeffs[2] =
37+
f.coeffs[1].coeffs[2] * t0y + f.coeffs[0].coeffs[2] * t1x + f.coeffs[0].coeffs[1] * t[2];
38+
}
39+
40+
// 0000000100010010000010000000010000100010000000010010000000001000000100100000010000000000100000100001001000000010001000000001000101
41+
// NAF rep 00 -> 0, 01 -> 1, 10 -> -1
42+
// miller loop goes from L-2 to 0 inclusively. NAF rep of 29793968203157093288 (6x+2) is two bits
43+
// longer, but we omit lowest 2 bits.
44+
inline constexpr auto ate_loop_count_naf = 1422851925951872564983012481770332177_u128;
45+
inline constexpr int log_ate_loop_count = 63;
46+
47+
// Implements Miller loop according to https://eprint.iacr.org/2010/354.pdf Algorithm 1.
48+
inline constexpr Fq12 miller_loop(const ecc::Point<Fq2>& Q, const ecc::Point<Fq>& P) noexcept
49+
{
50+
auto T = ecc::JacPoint<Fq2>::from(Q);
51+
auto nQ = -Q;
52+
auto f = Fq12::one();
53+
std::array<Fq2, 3> t;
54+
auto naf = ate_loop_count_naf;
55+
const auto ny = -P.y;
56+
57+
for (int i = 0; i <= log_ate_loop_count; ++i)
58+
{
59+
T = lin_func_and_dbl(T, t);
60+
f = square(f);
61+
multiply_by_lin_func_value(f, t, P.x, ny);
62+
63+
if (naf & 1)
64+
{
65+
T = lin_func_and_add(T, Q, t);
66+
multiply_by_lin_func_value(f, t, P.x, P.y);
67+
}
68+
else if (naf & 2)
69+
{
70+
T = lin_func_and_add(T, nQ, t);
71+
multiply_by_lin_func_value(f, t, P.x, P.y);
72+
}
73+
naf >>= 2;
74+
}
75+
76+
// Frobenius endomorphism for point Q from twisted curve over Fq2 field.
77+
// It's essentially untwist -> frobenius -> twist chain of transformation
78+
const auto Q1 = endomorphism<1>(Q);
79+
80+
// Similar to above one. It makes untwist -> frobenius^2 -> twist transformation plus
81+
// negation according to miller loop spec.
82+
const auto nQ2 = -endomorphism<2>(Q);
83+
84+
T = lin_func_and_add(T, Q1, t);
85+
multiply_by_lin_func_value(f, t, P.x, P.y);
86+
87+
lin_func(T, nQ2, t);
88+
multiply_by_lin_func_value(f, t, P.x, P.y);
89+
90+
return f;
91+
}
92+
93+
// Final exponentiation formula implemented based on https://eprint.iacr.org/2010/354.pdf 4.2
94+
// Algorithm 31
95+
inline Fq12 final_exp(const Fq12& v) noexcept
96+
{
97+
auto f = v;
98+
auto f1 = f.conjugate();
99+
100+
f = f1 * f.inv(); // easy 1
101+
f = endomorphism<2>(f) * f; // easy 2
102+
103+
f1 = f.conjugate();
104+
105+
const auto ft1 = cyclotomic_pow_to_X(f);
106+
const auto ft2 = cyclotomic_pow_to_X(ft1);
107+
const auto ft3 = cyclotomic_pow_to_X(ft2);
108+
const auto fp1 = endomorphism<1>(f);
109+
const auto fp2 = endomorphism<2>(f);
110+
const auto fp3 = endomorphism<3>(f);
111+
const auto y0 = fp1 * fp2 * fp3;
112+
const auto y1 = f1;
113+
const auto y2 = endomorphism<2>(ft2);
114+
const auto y3 = endomorphism<1>(ft1).conjugate();
115+
const auto y4 = (endomorphism<1>(ft2) * ft1).conjugate();
116+
const auto y5 = ft2.conjugate();
117+
const auto y6 = (endomorphism<1>(ft3) * ft3).conjugate();
118+
119+
auto t0 = cyclotomic_square(y6) * y4 * y5;
120+
auto t1 = y3 * y5 * t0;
121+
t0 = t0 * y2;
122+
t1 = cyclotomic_square(t1) * t0;
123+
t1 = cyclotomic_square(t1);
124+
t0 = t1 * y1;
125+
t1 = t1 * y0;
126+
t0 = cyclotomic_square(t0);
127+
return t1 * t0;
128+
}
129+
} // namespace
130+
131+
std::optional<bool> pairing(
132+
const std::vector<ExtPoint>& vG2, const std::vector<Point>& vG1) noexcept
133+
{
134+
if (vG1.size() != vG2.size())
135+
return std::nullopt;
136+
137+
if (vG1.empty())
138+
return true;
139+
140+
auto f = Fq12::one();
141+
142+
for (size_t i = 0; i < vG2.size(); ++i)
143+
{
144+
if (!is_field_element(vG1[i].x) || !is_field_element(vG1[i].y) ||
145+
!is_field_element(vG2[i].x.first) || !is_field_element(vG2[i].x.second) ||
146+
!is_field_element(vG2[i].y.first) || !is_field_element(vG2[i].y.second))
147+
{
148+
return std::nullopt;
149+
}
150+
151+
// Converts points' coefficients in Montgomery form.
152+
const auto Q_aff =
153+
ecc::Point<Fq2>{Fq2({Fq::from_int(vG2[i].x.first), Fq::from_int(vG2[i].x.second)}),
154+
Fq2({Fq::from_int(vG2[i].y.first), Fq::from_int(vG2[i].y.second)})};
155+
156+
const auto P_aff = ecc::Point<Fq>{Fq::from_int(vG1[i].x), Fq::from_int(vG1[i].y)};
157+
158+
const bool g1_is_inf = is_infinity(P_aff);
159+
const bool g2_is_inf = g2_is_infinity(Q_aff);
160+
161+
// Verify that P in on curve. For this group it also means that P is in G1.
162+
if (!g1_is_inf && !is_on_curve(P_aff))
163+
return std::nullopt;
164+
165+
// Verify that Q in on curve and in proper subgroup. This subgroup is much smaller than
166+
// group cointaning all the points from twisted curve over Fq2 field.
167+
if (!g2_is_inf && (!is_on_twisted_curve(Q_aff) || !g2_subgroup_check(Q_aff)))
168+
return std::nullopt;
169+
170+
// If any of the points is infinity it means that miller_loop returns 1. so we can skip it.
171+
if (!g1_is_inf && !g2_is_inf)
172+
f = f * miller_loop(Q_aff, P_aff);
173+
}
174+
175+
// final exp is calculated on accumulated value
176+
return final_exp(f) == Fq12::one();
177+
}
178+
} // namespace evmmax::bn254

0 commit comments

Comments
 (0)
Please sign in to comment.