Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
pairing_impl.hpp
Go to the documentation of this file.
1// === AUDIT STATUS ===
2// internal: { status: Completed, auditors: [Federico], commit: }
3// external_1: { status: not started, auditors: [], commit: }
4// external_2: { status: not started, auditors: [], commit: }
5// =====================
6
7#pragma once
8
9#include "./fq12.hpp"
10#include "./g1.hpp"
11#include "./g2.hpp"
14
15namespace bb::pairing {
16
17// Precompute 2^{-1} mod q for gradient calculations of tangent lines
18constexpr fq two_inv = fq(2).invert();
19
28{
29 // We map a = [X : Y : Z] to its affine coordinates (X/Z, Y/Z) and then apply the Frobenius map to get
30 // (\xi^{(q-1)/3} X^q/Z^q, \xi^{(q-1)/2} Y^q/Z^q). We then homogeneize again to get
31 // [\xi^{(q-1)/3} X^q : \xi^{(q-1)/2} Y^q : Z^q]
32 fq2 T0 = a.x.frobenius_map();
33 fq2 T1 = a.y.frobenius_map();
34
35 return {
38 a.z.frobenius_map(),
39 };
40}
41
43{
44 fq2 A = work_point.x * work_point.y.mul_by_fq(two_inv); // A = (X * Y) / 2
45 fq2 B = work_point.y.sqr(); // B = Y^2
46 fq2 C = work_point.z.sqr(); // C = Z^2
47 fq2 D = fq2::twist_coeff_b() * C; // D = b' * C
48 fq2 E = D + D + D; // E = 3 * D
49 fq2 F = E + E + E; // F = 3 * E
50 fq2 G = (B + F).mul_by_fq(two_inv); // G = (B + F) / 2
51 fq2 H = (work_point.y + work_point.z).sqr() - B - C; // H = (Y + Z)^2 - B - C
52 fq2 I = work_point.x.sqr(); // I = X^2
53 fq2 J = E.sqr(); // J = E^2
54
55 line.o = H;
56 line.w = I + I + I;
57 line.vw = E - B;
58
59 work_point.x = A * (B - F);
60 work_point.y = G.sqr() - (J + J + J);
61 work_point.z = B * H;
62}
63
65 g2Projective& work_point,
66 fq12::ell_coeffs& line)
67{
68 fq2 A = Q.y * work_point.z; // A = Y2 * Z
69 fq2 B = Q.x * work_point.z; // B = X2 * Z
70 fq2 theta = work_point.y - A; // theta = Y - A
71 fq2 lambda = work_point.x - B; // lambda = X - B
72 fq2 C = theta.sqr(); // C = theta^2
73 fq2 D = lambda.sqr(); // D = lambda^2
74 fq2 E = lambda * D; // E = lambda * D
75 fq2 F = work_point.z * C; // F = Z * C
76 fq2 G = work_point.x * D; // G = X * D
77 fq2 H = E + F - G - G; // H = E + F - 2 * G
78 fq2 I = work_point.y * E; // I = Y * E
79 fq2 J = theta * Q.x - lambda * Q.y; // J = theta * X2 - lambda * Y2
80
81 work_point.x = lambda * H; // X3 = lambda * H
82 work_point.y = theta * (G - H) - I; // Y3 = theta * (G - H) - I
83 work_point.z = work_point.z * E; // Z3 = Z * E
84
85 line.o = lambda;
86 line.w = theta;
87 line.vw = J;
88}
89
90constexpr void precompute_miller_lines(const g2Projective& Q, miller_lines& lines)
91{
92 g2Projective Q_neg{ Q.x, -Q.y, Q.z };
93 g2Projective work_point = Q;
94
95 size_t it = 0;
96 for (unsigned char loop_bit : loop_bits) {
97 doubling_step_for_miller_loop(work_point, lines.lines[it]);
98 ++it;
99 if (loop_bit == 1) {
100 mixed_addition_step_for_miller_loop(Q, work_point, lines.lines[it]);
101 ++it;
102 } else if (loop_bit == 3) {
103 mixed_addition_step_for_miller_loop(Q_neg, work_point, lines.lines[it]);
104 ++it;
105 }
106 }
107
110 Q2.y = -Q2.y;
111 mixed_addition_step_for_miller_loop(Q1, work_point, lines.lines[it]);
112 ++it;
113 mixed_addition_step_for_miller_loop(Q2, work_point, lines.lines[it]);
114}
115
116constexpr void precompute_miller_lines(const g2::element& Q, miller_lines& lines)
117{
118 if (Q.is_point_at_infinity()) {
119 throw_or_abort("precompute_miller_lines: Cannot precompute Miller lines for the point at infinity.");
120 }
121 precompute_miller_lines(g2Projective{ Q.x, Q.y, Q.z }, lines);
122}
123
124constexpr fq12 miller_loop(const g1::affine_element& P, const miller_lines& lines)
125{
126 fq12 work_scalar = fq12::one();
127 fq minus_y_P = -P.y;
128 fq minus_x_P = -P.x;
129
130 size_t it = 0;
131 fq12::ell_coeffs work_line;
132
133 for (unsigned char loop_bit : loop_bits) {
134 work_scalar = work_scalar.sqr();
135
136 work_line.o = lines.lines[it].o.mul_by_fq(minus_y_P);
137 work_line.w = lines.lines[it].w.mul_by_fq(P.x);
138 work_line.vw = lines.lines[it].vw;
139 work_scalar.self_sparse_mul(work_line);
140 ++it;
141
142 if (loop_bit != 0) {
143 work_line.o = lines.lines[it].o.mul_by_fq(P.y);
144 work_line.w = lines.lines[it].w.mul_by_fq(minus_x_P);
145 work_line.vw = lines.lines[it].vw;
146 work_scalar.self_sparse_mul(work_line);
147 ++it;
148 }
149 }
150
151 work_line.o = lines.lines[it].o.mul_by_fq(P.y);
152 work_line.w = lines.lines[it].w.mul_by_fq(minus_x_P);
153 work_line.vw = lines.lines[it].vw;
154 work_scalar.self_sparse_mul(work_line);
155 ++it;
156 work_line.o = lines.lines[it].o.mul_by_fq(P.y);
157 work_line.w = lines.lines[it].w.mul_by_fq(minus_x_P);
158 work_line.vw = lines.lines[it].vw;
159 work_scalar.self_sparse_mul(work_line);
160 ++it;
161
162 return work_scalar;
163}
164
165constexpr fq12 miller_loop_batch(const g1::affine_element* points, const miller_lines* lines, size_t num_pairs)
166{
167 fq12 work_scalar = fq12::one();
168
169 size_t it = 0;
170 fq12::ell_coeffs work_line;
171
172 for (unsigned char loop_bit : loop_bits) {
173 work_scalar = work_scalar.sqr();
174 for (size_t j = 0; j < num_pairs; ++j) {
175 const auto& coeff = lines[j].lines[it];
176 work_line.o = coeff.o.mul_by_fq(-points[j].y);
177 work_line.w = coeff.w.mul_by_fq(points[j].x);
178 work_line.vw = coeff.vw;
179 work_scalar.self_sparse_mul(work_line);
180 }
181 ++it;
182 if (loop_bit != 0) {
183 for (size_t j = 0; j < num_pairs; ++j) {
184 const auto& coeff = lines[j].lines[it];
185 work_line.o = coeff.o.mul_by_fq(points[j].y);
186 work_line.w = coeff.w.mul_by_fq(-points[j].x);
187 work_line.vw = coeff.vw;
188 work_scalar.self_sparse_mul(work_line);
189 }
190 ++it;
191 }
192 }
193
194 for (size_t j = 0; j < num_pairs; ++j) {
195 const auto& coeff = lines[j].lines[it];
196 work_line.o = coeff.o.mul_by_fq(points[j].y);
197 work_line.w = coeff.w.mul_by_fq(-points[j].x);
198 work_line.vw = coeff.vw;
199 work_scalar.self_sparse_mul(work_line);
200 }
201 ++it;
202 for (size_t j = 0; j < num_pairs; ++j) {
203 const auto& coeff = lines[j].lines[it];
204 work_line.o = coeff.o.mul_by_fq(points[j].y);
205 work_line.w = coeff.w.mul_by_fq(-points[j].x);
206 work_line.vw = coeff.vw;
207 work_scalar.self_sparse_mul(work_line);
208 }
209 ++it;
210 return work_scalar;
211}
212
214{
215 fq12 a{ elt.c0, -elt.c1 }; // Conjugate of elt = elt^{q^6}
216 a *= elt.invert(); // elt^{q^6 - 1}
217 return a * a.frobenius_map_two(); // elt^{(q^6 - 1)(q^2 + 1)}
218}
219
221{
222 fq12 r = elt;
223
224 for (bool z_loop_bit : z_loop_bits) {
225 r = r.cyclotomic_squared();
226 if (z_loop_bit) {
227 r *= elt;
228 }
229 }
230 return r;
231}
232
234{
235 // We only keep count of the exponents on the right
237 fq12 B = A.cyclotomic_squared(); // 2z
238 fq12 C = B.cyclotomic_squared(); // 4z
239 fq12 D = B * C; // 6z
240 fq12 E = final_exponentiation_exp_by_z(D); // 6z^2
241 fq12 F = E.cyclotomic_squared(); // 12z^2
243 fq12 J = G * E; // G * E = 12z^3 + 6z^2
244 fq12 K = J * D; // J * D = 12z^3 + 6z^2 + 6z = \mu_2
245 fq12 L = J * C; // J * C = 12z^3 + 6z^2 + 4z = \mu_1
246 fq12 M = K * E; // K * E = 12z^3 + 12z^2 + 6z
247 fq12 N = M * elt; // M * elt = 12z^3 + 12z^2 + 6z + 1 = \mu_0
248 fq12 O = L * elt.unitary_inverse(); // L * elt^{-1} = 12z^3 + 6z^2 + 4z - 1 = \mu_3
249 fq12 P = L.frobenius_map_one(); // \mu_1 * q
250 fq12 Q = K.frobenius_map_two(); // \mu_2 * q^2
251 fq12 R = O.frobenius_map_three(); // \mu_3 * q^3
252 fq12 S = N * P; // \mu_0 + \mu_1 * q
253 fq12 T = S * Q; // \mu_0 + \mu_1 * q + \mu_2 * q^2
254
255 return T * R; // \mu_0 + \mu_1 * q + \mu_2 * q^2 + \mu_3 * q^3
256}
257
258constexpr fq12 reduced_ate_pairing(const g1::affine_element& P_affine, const g2::affine_element& Q_affine)
259{
260 if (!P_affine.on_curve()) {
261 throw_or_abort("reduced_ate_pairing: P is not on the curve.");
262 }
263
264 if (!Q_affine.on_curve()) {
265 throw_or_abort("reduced_ate_pairing: Q is not on the curve.");
266 }
267
268 // Early exit condition: e(P, Q) = 1 if either P or Q are the point at infinity
269 if (Q_affine.is_point_at_infinity() || P_affine.is_point_at_infinity()) {
270 return fq12::one();
271 }
272
273 g2Projective Q{ Q_affine.x, Q_affine.y, fq2::one() };
274
275 miller_lines lines;
276 precompute_miller_lines(Q, lines);
277
278 fq12 result = miller_loop(P_affine, lines);
279 result = final_exponentiation_easy_part(result);
280 result = final_exponentiation_tricky_part(result);
281 return result;
282}
283
285 const miller_lines* lines,
286 const size_t num_points)
287{
288 for (size_t i = 0; i < num_points; ++i) {
289 if (!P_affines[i].on_curve()) {
290 throw_or_abort("reduced_ate_pairing_batch_precomputed: one of the points is not on the curve.");
291 }
292 }
293
294 fq12 result = miller_loop_batch(P_affines, lines, num_points);
295 result = final_exponentiation_easy_part(result);
296 result = final_exponentiation_tricky_part(result);
297 return result;
298}
299
301 const g2::affine_element* Q_affines,
302 const size_t num_points)
303{
305 lines.reserve(num_points);
306
307 bool has_infinity_pair = false;
308 for (size_t i = 0; i < num_points; ++i) {
309 if (!P_affines[i].on_curve()) {
310 throw_or_abort("reduced_ate_pairing_batch: one of the P points is not on the curve.");
311 }
312 if (!Q_affines[i].on_curve()) {
313 throw_or_abort("reduced_ate_pairing_batch: one of the Q points is not on the curve.");
314 }
315
316 // If either P_i or Q_i is the point at infinity, then e(P_i, Q_i) = 1, so we can skip the calculation of
317 // that pairing
318 if (!P_affines[i].is_point_at_infinity() && !Q_affines[i].is_point_at_infinity()) {
319 lines.emplace_back(miller_lines{});
320 precompute_miller_lines(g2Projective{ Q_affines[i].x, Q_affines[i].y, fq2::one() }, lines.back());
321 } else {
322 has_infinity_pair = true;
323 }
324 }
325
326 if (lines.empty()) {
327 return fq12::one();
328 }
329
330 if (!has_infinity_pair) {
331 fq12 result = miller_loop_batch(P_affines, lines.data(), num_points);
332 result = final_exponentiation_easy_part(result);
333 result = final_exponentiation_tricky_part(result);
334 return result;
335 }
336
337 std::vector<g1::affine_element> filtered_points;
338 filtered_points.reserve(num_points);
339 for (size_t i = 0; i < num_points; ++i) {
340 if (!P_affines[i].is_point_at_infinity() && !Q_affines[i].is_point_at_infinity()) {
341 filtered_points.emplace_back(P_affines[i]);
342 }
343 }
344
345 fq12 result = miller_loop_batch(filtered_points.data(), lines.data(), filtered_points.size());
346 result = final_exponentiation_easy_part(result);
347 result = final_exponentiation_tricky_part(result);
348 return result;
349}
350
351} // namespace bb::pairing
base_field c1
Definition field12.hpp:48
constexpr field12 cyclotomic_squared() const
Definition field12.hpp:207
constexpr field12 frobenius_map_three() const
Definition field12.hpp:183
constexpr field12 invert() const
Definition field12.hpp:172
constexpr field12 unitary_inverse() const
Definition field12.hpp:209
constexpr void self_sparse_mul(const ell_coeffs &ell)
Multiply the element by a sparse element of the form ell.o + ell.w * w + ell.vw * wv.
Definition field12.hpp:142
static constexpr field12 one()
Definition field12.hpp:57
constexpr field12 sqr() const
Definition field12.hpp:158
base_field c0
Definition field12.hpp:47
constexpr field12 frobenius_map_two() const
Definition field12.hpp:191
constexpr field12 frobenius_map_one() const
Definition field12.hpp:199
constexpr bool is_point_at_infinity() const noexcept
constexpr bool on_curve() const noexcept
element class. Implements ecc group arithmetic using Jacobian coordinates See https://hyperelliptic....
Definition element.hpp:33
BB_INLINE constexpr bool is_point_at_infinity() const noexcept
FF a
#define G(r, i, a, b, c, d)
Definition blake2s.cpp:116
bb::avm2::Column C
constexpr void doubling_step_for_miller_loop(g2Projective &work_point, fq12::ell_coeffs &line)
Doubling step for Miller loop calculation.
constexpr fq12 final_exponentiation_exp_by_z(const fq12 &elt)
constexpr std::array< uint8_t, loop_length > loop_bits
Definition pairing.hpp:33
constexpr fq12 final_exponentiation_tricky_part(const fq12 &elt)
fq12 reduced_ate_pairing_batch_precomputed(const g1::affine_element *P_affines, const miller_lines *lines, const size_t num_points)
constexpr fq12 reduced_ate_pairing(const g1::affine_element &P_affine, const g2::affine_element &Q_affine)
constexpr g2Projective twisted_frobenius(const g2Projective &a)
Compute where is the untwisting isomorphism and is the Frobenius morphism.
constexpr fq12 final_exponentiation_easy_part(const fq12 &elt)
constexpr fq two_inv
constexpr void mixed_addition_step_for_miller_loop(const g2Projective &Q, g2Projective &work_point, fq12::ell_coeffs &line)
Addition step for Miller loop calculation.
constexpr fq12 miller_loop_batch(const g1::affine_element *points, const miller_lines *lines, size_t num_pairs)
Compute the Miller loop for multiple pairs of points.
constexpr void precompute_miller_lines(const g2Projective &Q, miller_lines &lines)
Precomputation of Miller lines for a point Q in G2.
fq12 reduced_ate_pairing_batch(const g1::affine_element *P_affines, const g2::affine_element *Q_affines, const size_t num_points)
constexpr std::array< bool, z_loop_length > z_loop_bits
Definition pairing.hpp:39
constexpr fq12 miller_loop(const g1::affine_element &P, const miller_lines &lines)
Miller loop implementation.
field< Bn254FqParams > fq
Definition fq.hpp:153
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
quadratic_field w
Definition field12.hpp:52
quadratic_field vw
Definition field12.hpp:53
quadratic_field o
Definition field12.hpp:51
constexpr field2 sqr() const noexcept
Definition field2.hpp:74
constexpr field2 mul_by_fq(const base_field &a) const noexcept
static constexpr field2 frobenius_on_twisted_curve_y()
static constexpr field2 twist_coeff_b()
static constexpr field2 frobenius_on_twisted_curve_x()
constexpr field invert() const noexcept
std::array< fq12::ell_coeffs, precomputed_coefficients_length > lines
Definition pairing.hpp:50
void throw_or_abort(std::string const &err)