openvm_pairing_guest/bn254/
pairing.rs1use alloc::vec::Vec;
2
3use itertools::izip;
4use openvm_algebra_guest::{field::FieldExtension, DivUnsafe, Field};
5use openvm_ecc_guest::AffinePoint;
6#[cfg(target_os = "zkvm")]
7use {
8 crate::{PairingBaseFunct7, OPCODE, PAIRING_FUNCT3},
9 core::mem::MaybeUninit,
10 openvm_platform::custom_insn_r,
11 openvm_rv32im_guest::hint_buffer_u32,
12};
13
14use super::{Bn254, Fp, Fp12, Fp2, BN254_PSEUDO_BINARY_ENCODING, BN254_SEED};
15use crate::pairing::{
16 exp_check_fallback, Evaluatable, EvaluatedLine, FromLineDType, LineMulDType, MillerStep,
17 MultiMillerLoop, PairingCheck, PairingCheckError, PairingIntrinsics, UnevaluatedLine,
18};
19#[cfg(all(feature = "halo2curves", not(target_os = "zkvm")))]
20use crate::{
21 bn254::utils::{
22 convert_bn254_fp2_to_halo2_fq2, convert_bn254_fp_to_halo2_fq,
23 convert_bn254_halo2_fq12_to_fp12,
24 },
25 halo2curves_shims::bn254::Bn254 as Halo2CurvesBn254,
26 pairing::FinalExp,
27};
28
29impl Evaluatable<Fp, Fp2> for UnevaluatedLine<Fp2> {
30 fn evaluate(&self, xy_frac: &(Fp, Fp)) -> EvaluatedLine<Fp2> {
31 let (x_over_y, y_inv) = xy_frac;
32 EvaluatedLine {
34 b: self.b.mul_base(x_over_y),
35 c: self.c.mul_base(y_inv),
36 }
37 }
38}
39
40impl FromLineDType<Fp2> for Fp12 {
41 fn from_evaluated_line_d_type(line: EvaluatedLine<Fp2>) -> Fp12 {
42 FieldExtension::<Fp2>::from_coeffs([
43 Fp2::ONE,
44 line.b,
45 Fp2::ZERO,
46 line.c,
47 Fp2::ZERO,
48 Fp2::ZERO,
49 ])
50 }
51}
52
53impl LineMulDType<Fp2, Fp12> for Bn254 {
55 fn mul_013_by_013(l0: &EvaluatedLine<Fp2>, l1: &EvaluatedLine<Fp2>) -> [Fp2; 5] {
57 let b0 = &l0.b;
58 let c0 = &l0.c;
59 let b1 = &l1.b;
60 let c1 = &l1.c;
61
62 let x0 = Fp2::ONE + c0 * c1 * &Bn254::XI;
66 let x1 = b0 + b1;
67 let x2 = b0 * b1;
68 let x3 = c0 + c1;
69 let x4 = b0 * c1 + b1 * c0;
70
71 [x0, x1, x2, x3, x4]
72 }
73
74 fn mul_by_013(f: &Fp12, l: &EvaluatedLine<Fp2>) -> Fp12 {
76 Fp12::from_evaluated_line_d_type(l.clone()) * f
77 }
78
79 fn mul_by_01234(f: &Fp12, x: &[Fp2; 5]) -> Fp12 {
81 let o0 = &x[0];
95 let o1 = &x[2];
96 let o2 = &x[4];
97 let o3 = &x[1];
98 let o4 = &x[3];
99
100 let xi = &Bn254::XI;
101
102 let self_coeffs = &f.c;
103 let s0 = &self_coeffs[0];
104 let s1 = &self_coeffs[2];
105 let s2 = &self_coeffs[4];
106 let s3 = &self_coeffs[1];
107 let s4 = &self_coeffs[3];
108 let s5 = &self_coeffs[5];
109
110 let c00 = s0 * o0 + xi * &(s1 * o2 + s2 * o1 + s4 * o4 + s5 * o3);
118 let c01 = s0 * o1 + s1 * o0 + s3 * o3 + xi * &(s2 * o2 + s5 * o4);
119 let c02 = s0 * o2 + s1 * o1 + s2 * o0 + s3 * o4 + s4 * o3;
120 let c10 = s0 * o3 + s3 * o0 + xi * &(s2 * o4 + s4 * o2 + s5 * o1);
121 let c11 = s0 * o4 + s1 * o3 + s3 * o1 + s4 * o0 + xi * &(s5 * o2);
122 let c12 = s1 * o4 + s2 * o3 + s3 * o2 + s4 * o1 + s5 * o0;
123
124 Fp12::from_coeffs([c00, c10, c01, c11, c02, c12])
125 }
126}
127
128#[allow(non_snake_case)]
129impl MultiMillerLoop for Bn254 {
130 type Fp = Fp;
131 type Fp12 = Fp12;
132
133 const SEED_ABS: u64 = BN254_SEED;
134 const PSEUDO_BINARY_ENCODING: &[i8] = &BN254_PSEUDO_BINARY_ENCODING;
135
136 fn evaluate_lines_vec(f: Self::Fp12, lines: Vec<EvaluatedLine<Self::Fp2>>) -> Self::Fp12 {
137 let mut f = f;
138 let mut lines = lines;
139 if lines.len() % 2 == 1 {
140 f = Self::mul_by_013(&f, &lines.pop().unwrap());
141 }
142 for chunk in lines.chunks(2) {
143 if let [line0, line1] = chunk {
144 let prod = Self::mul_013_by_013(line0, line1);
145 f = Self::mul_by_01234(&f, &prod);
146 } else {
147 panic!("lines.len() % 2 should be 0 at this point");
148 }
149 }
150 f
151 }
152
153 fn pre_loop(
156 Q_acc: Vec<AffinePoint<Self::Fp2>>,
157 _Q: &[AffinePoint<Self::Fp2>],
158 c: Option<Self::Fp12>,
159 xy_fracs: &[(Self::Fp, Self::Fp)],
160 ) -> (Self::Fp12, Vec<AffinePoint<Self::Fp2>>) {
161 let mut f = if let Some(mut c) = c {
162 c.square_assign();
167 c
168 } else {
169 Self::Fp12::ONE
170 };
171
172 let mut Q_acc = Q_acc;
173 let mut initial_lines = Vec::<EvaluatedLine<Self::Fp2>>::new();
174
175 let (Q_out_double, lines_2S) = Q_acc
179 .into_iter()
180 .map(|Q| Self::miller_double_step(&Q))
181 .unzip::<_, _, Vec<_>, Vec<_>>();
182 Q_acc = Q_out_double;
183
184 let lines_iter = izip!(lines_2S.iter(), xy_fracs.iter());
185 for (line_2S, xy_frac) in lines_iter {
186 let line = line_2S.evaluate(xy_frac);
187 initial_lines.push(line);
188 }
189
190 f = Self::evaluate_lines_vec(f, initial_lines);
191
192 (f, Q_acc)
193 }
194
195 fn post_loop(
197 f: &Self::Fp12,
198 Q_acc: Vec<AffinePoint<Self::Fp2>>, Q: &[AffinePoint<Self::Fp2>],
200 _c: Option<Self::Fp12>,
201 xy_fracs: &[(Self::Fp, Self::Fp)],
202 ) -> (Self::Fp12, Vec<AffinePoint<Self::Fp2>>) {
203 let mut Q_acc = Q_acc;
204 let mut lines = Vec::<EvaluatedLine<Self::Fp2>>::new();
205
206 let x_to_q_minus_1_over_3 = &Self::FROBENIUS_COEFF_FQ6_C1[1];
207 let x_to_q_sq_minus_1_over_3 = &Self::FROBENIUS_COEFF_FQ6_C1[2];
208
209 let q1_vec = Q
211 .iter()
212 .map(|Q| {
213 let x = Q.x.frobenius_map(1);
214 let x = x * x_to_q_minus_1_over_3;
215 let y = Q.y.frobenius_map(1);
216 let y = y * &Self::XI_TO_Q_MINUS_1_OVER_2;
217 AffinePoint { x, y }
218 })
219 .collect::<Vec<_>>();
220
221 let (Q_out_add, lines_S_plus_Q) = Q_acc
223 .iter()
224 .zip(q1_vec.iter())
225 .map(|(Q_acc, q1)| Self::miller_add_step(Q_acc, q1))
226 .unzip::<_, _, Vec<_>, Vec<_>>();
227 Q_acc = Q_out_add;
228
229 let lines_iter = izip!(lines_S_plus_Q.iter(), xy_fracs.iter());
230 for (lines_S_plus_Q, xy_frac) in lines_iter {
231 let line = lines_S_plus_Q.evaluate(xy_frac);
232 lines.push(line);
233 }
234
235 let q2_vec = Q
237 .iter()
238 .map(|Q| {
239 let x = &Q.x * x_to_q_sq_minus_1_over_3;
242 AffinePoint { x, y: Q.y.clone() }
243 })
244 .collect::<Vec<_>>();
245
246 let (Q_out_add, lines_S_plus_Q) = Q_acc
249 .iter()
250 .zip(q2_vec.iter())
251 .map(|(Q_acc, q2)| Self::miller_add_step(Q_acc, q2))
252 .unzip::<_, _, Vec<_>, Vec<_>>();
253 Q_acc = Q_out_add;
254
255 let lines_iter = izip!(lines_S_plus_Q.iter(), xy_fracs.iter());
256 for (lines_S_plus_Q, xy_frac) in lines_iter {
257 let line = lines_S_plus_Q.evaluate(xy_frac);
258 lines.push(line);
259 }
260
261 let mut f = f.clone();
262 f = Self::evaluate_lines_vec(f, lines);
263
264 (f, Q_acc)
265 }
266}
267
268#[allow(non_snake_case)]
269impl PairingCheck for Bn254 {
270 type Fp = Fp;
271 type Fp2 = Fp2;
272 type Fp12 = Fp12;
273
274 #[allow(unused_variables)]
275 fn pairing_check_hint(
276 P: &[AffinePoint<Self::Fp>],
277 Q: &[AffinePoint<Self::Fp2>],
278 ) -> (Self::Fp12, Self::Fp12) {
279 #[cfg(not(target_os = "zkvm"))]
280 {
281 #[cfg(not(feature = "halo2curves"))]
282 panic!("`halo2curves` feature must be enabled to use pairing check hint on host");
283
284 #[cfg(feature = "halo2curves")]
285 {
286 let p_halo2 = P
287 .iter()
288 .map(|p| {
289 AffinePoint::new(
290 convert_bn254_fp_to_halo2_fq(p.x.clone()),
291 convert_bn254_fp_to_halo2_fq(p.y.clone()),
292 )
293 })
294 .collect::<Vec<_>>();
295 let q_halo2 = Q
296 .iter()
297 .map(|q| {
298 AffinePoint::new(
299 convert_bn254_fp2_to_halo2_fq2(q.x.clone()),
300 convert_bn254_fp2_to_halo2_fq2(q.y.clone()),
301 )
302 })
303 .collect::<Vec<_>>();
304 let fq12 = Halo2CurvesBn254::multi_miller_loop(&p_halo2, &q_halo2);
305 let (c_fq12, s_fq12) = Halo2CurvesBn254::final_exp_hint(&fq12);
306 let c = convert_bn254_halo2_fq12_to_fp12(c_fq12);
307 let s = convert_bn254_halo2_fq12_to_fp12(s_fq12);
308 (c, s)
309 }
310 }
311 #[cfg(target_os = "zkvm")]
312 {
313 let hint = MaybeUninit::<(Fp12, Fp12)>::uninit();
314 let p_fat_ptr = (P.as_ptr() as u32, P.len() as u32);
317 let q_fat_ptr = (Q.as_ptr() as u32, Q.len() as u32);
318 unsafe {
319 custom_insn_r!(
320 opcode = OPCODE,
321 funct3 = PAIRING_FUNCT3,
322 funct7 = ((Bn254::PAIRING_IDX as u8) * PairingBaseFunct7::PAIRING_MAX_KINDS + PairingBaseFunct7::HintFinalExp as u8),
323 rd = Const "x0",
324 rs1 = In &p_fat_ptr,
325 rs2 = In &q_fat_ptr
326 );
327 let ptr = hint.as_ptr() as *const u8;
328 hint_buffer_u32!(ptr, (32 * 12 * 2) / 4);
329 hint.assume_init()
330 }
331 }
332 }
333
334 fn pairing_check(
335 P: &[AffinePoint<Self::Fp>],
336 Q: &[AffinePoint<Self::Fp2>],
337 ) -> Result<(), PairingCheckError> {
338 Self::try_honest_pairing_check(P, Q).unwrap_or_else(|| {
339 let f = Self::multi_miller_loop(P, Q);
340 exp_check_fallback(&f, &Self::FINAL_EXPONENT)
341 })
342 }
343}
344
345#[allow(non_snake_case)]
346impl Bn254 {
347 fn try_honest_pairing_check(
348 P: &[AffinePoint<<Self as PairingCheck>::Fp>],
349 Q: &[AffinePoint<<Self as PairingCheck>::Fp2>],
350 ) -> Option<Result<(), PairingCheckError>> {
351 let (c, u) = Self::pairing_check_hint(P, Q);
352 if c == Fp12::ZERO {
353 return None;
354 }
355 let c_inv = Fp12::ONE.div_unsafe(&c);
356
357 let c_q3_inv = FieldExtension::frobenius_map(&c_inv, 3);
366 let c_q2 = FieldExtension::frobenius_map(&c, 2);
367 let c_q_inv = FieldExtension::frobenius_map(&c_inv, 1);
368 let c_mul = c_q3_inv * c_q2 * c_q_inv;
369
370 let fc = Self::multi_miller_loop_embedded_exp(P, Q, Some(c_inv));
372
373 if fc * c_mul * u == Fp12::ONE {
374 Some(Ok(()))
375 } else {
376 None
377 }
378 }
379}