openvm_pairing_guest/bn254/
pairing.rs
1use 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(
155 Q_acc: Vec<AffinePoint<Self::Fp2>>,
156 _Q: &[AffinePoint<Self::Fp2>],
157 c: Option<Self::Fp12>,
158 xy_fracs: &[(Self::Fp, Self::Fp)],
159 ) -> (Self::Fp12, Vec<AffinePoint<Self::Fp2>>) {
160 let mut f = if let Some(mut c) = c {
161 c.square_assign();
165 c
166 } else {
167 Self::Fp12::ONE
168 };
169
170 let mut Q_acc = Q_acc;
171 let mut initial_lines = Vec::<EvaluatedLine<Self::Fp2>>::new();
172
173 let (Q_out_double, lines_2S) = Q_acc
176 .into_iter()
177 .map(|Q| Self::miller_double_step(&Q))
178 .unzip::<_, _, Vec<_>, Vec<_>>();
179 Q_acc = Q_out_double;
180
181 let lines_iter = izip!(lines_2S.iter(), xy_fracs.iter());
182 for (line_2S, xy_frac) in lines_iter {
183 let line = line_2S.evaluate(xy_frac);
184 initial_lines.push(line);
185 }
186
187 f = Self::evaluate_lines_vec(f, initial_lines);
188
189 (f, Q_acc)
190 }
191
192 fn post_loop(
194 f: &Self::Fp12,
195 Q_acc: Vec<AffinePoint<Self::Fp2>>, Q: &[AffinePoint<Self::Fp2>],
197 _c: Option<Self::Fp12>,
198 xy_fracs: &[(Self::Fp, Self::Fp)],
199 ) -> (Self::Fp12, Vec<AffinePoint<Self::Fp2>>) {
200 let mut Q_acc = Q_acc;
201 let mut lines = Vec::<EvaluatedLine<Self::Fp2>>::new();
202
203 let x_to_q_minus_1_over_3 = &Self::FROBENIUS_COEFF_FQ6_C1[1];
204 let x_to_q_sq_minus_1_over_3 = &Self::FROBENIUS_COEFF_FQ6_C1[2];
205
206 let q1_vec = Q
208 .iter()
209 .map(|Q| {
210 let x = Q.x.frobenius_map(1);
211 let x = x * x_to_q_minus_1_over_3;
212 let y = Q.y.frobenius_map(1);
213 let y = y * &Self::XI_TO_Q_MINUS_1_OVER_2;
214 AffinePoint { x, y }
215 })
216 .collect::<Vec<_>>();
217
218 let (Q_out_add, lines_S_plus_Q) = Q_acc
220 .iter()
221 .zip(q1_vec.iter())
222 .map(|(Q_acc, q1)| Self::miller_add_step(Q_acc, q1))
223 .unzip::<_, _, Vec<_>, Vec<_>>();
224 Q_acc = Q_out_add;
225
226 let lines_iter = izip!(lines_S_plus_Q.iter(), xy_fracs.iter());
227 for (lines_S_plus_Q, xy_frac) in lines_iter {
228 let line = lines_S_plus_Q.evaluate(xy_frac);
229 lines.push(line);
230 }
231
232 let q2_vec = Q
234 .iter()
235 .map(|Q| {
236 let x = &Q.x * x_to_q_sq_minus_1_over_3;
238 AffinePoint { x, y: Q.y.clone() }
239 })
240 .collect::<Vec<_>>();
241
242 let (Q_out_add, lines_S_plus_Q) = Q_acc
244 .iter()
245 .zip(q2_vec.iter())
246 .map(|(Q_acc, q2)| Self::miller_add_step(Q_acc, q2))
247 .unzip::<_, _, Vec<_>, Vec<_>>();
248 Q_acc = Q_out_add;
249
250 let lines_iter = izip!(lines_S_plus_Q.iter(), xy_fracs.iter());
251 for (lines_S_plus_Q, xy_frac) in lines_iter {
252 let line = lines_S_plus_Q.evaluate(xy_frac);
253 lines.push(line);
254 }
255
256 let mut f = f.clone();
257 f = Self::evaluate_lines_vec(f, lines);
258
259 (f, Q_acc)
260 }
261}
262
263#[allow(non_snake_case)]
264impl PairingCheck for Bn254 {
265 type Fp = Fp;
266 type Fp2 = Fp2;
267 type Fp12 = Fp12;
268
269 #[allow(unused_variables)]
270 fn pairing_check_hint(
271 P: &[AffinePoint<Self::Fp>],
272 Q: &[AffinePoint<Self::Fp2>],
273 ) -> (Self::Fp12, Self::Fp12) {
274 #[cfg(not(target_os = "zkvm"))]
275 {
276 #[cfg(not(feature = "halo2curves"))]
277 panic!("`halo2curves` feature must be enabled to use pairing check hint on host");
278
279 #[cfg(feature = "halo2curves")]
280 {
281 let p_halo2 = P
282 .iter()
283 .map(|p| {
284 AffinePoint::new(
285 convert_bn254_fp_to_halo2_fq(p.x.clone()),
286 convert_bn254_fp_to_halo2_fq(p.y.clone()),
287 )
288 })
289 .collect::<Vec<_>>();
290 let q_halo2 = Q
291 .iter()
292 .map(|q| {
293 AffinePoint::new(
294 convert_bn254_fp2_to_halo2_fq2(q.x.clone()),
295 convert_bn254_fp2_to_halo2_fq2(q.y.clone()),
296 )
297 })
298 .collect::<Vec<_>>();
299 let fq12 = Halo2CurvesBn254::multi_miller_loop(&p_halo2, &q_halo2);
300 let (c_fq12, s_fq12) = Halo2CurvesBn254::final_exp_hint(&fq12);
301 let c = convert_bn254_halo2_fq12_to_fp12(c_fq12);
302 let s = convert_bn254_halo2_fq12_to_fp12(s_fq12);
303 (c, s)
304 }
305 }
306 #[cfg(target_os = "zkvm")]
307 {
308 let hint = MaybeUninit::<(Fp12, Fp12)>::uninit();
309 let p_fat_ptr = (P.as_ptr() as u32, P.len() as u32);
311 let q_fat_ptr = (Q.as_ptr() as u32, Q.len() as u32);
312 unsafe {
313 custom_insn_r!(
314 opcode = OPCODE,
315 funct3 = PAIRING_FUNCT3,
316 funct7 = ((Bn254::PAIRING_IDX as u8) * PairingBaseFunct7::PAIRING_MAX_KINDS + PairingBaseFunct7::HintFinalExp as u8),
317 rd = Const "x0",
318 rs1 = In &p_fat_ptr,
319 rs2 = In &q_fat_ptr
320 );
321 let ptr = hint.as_ptr() as *const u8;
322 hint_buffer_u32!(ptr, (32 * 12 * 2) / 4);
323 hint.assume_init()
324 }
325 }
326 }
327
328 fn pairing_check(
329 P: &[AffinePoint<Self::Fp>],
330 Q: &[AffinePoint<Self::Fp2>],
331 ) -> Result<(), PairingCheckError> {
332 Self::try_honest_pairing_check(P, Q).unwrap_or_else(|| {
333 let f = Self::multi_miller_loop(P, Q);
334 exp_check_fallback(&f, &Self::FINAL_EXPONENT)
335 })
336 }
337}
338
339#[allow(non_snake_case)]
340impl Bn254 {
341 fn try_honest_pairing_check(
342 P: &[AffinePoint<<Self as PairingCheck>::Fp>],
343 Q: &[AffinePoint<<Self as PairingCheck>::Fp2>],
344 ) -> Option<Result<(), PairingCheckError>> {
345 let (c, u) = Self::pairing_check_hint(P, Q);
346 if c == Fp12::ZERO {
347 return None;
348 }
349 let c_inv = Fp12::ONE.div_unsafe(&c);
350
351 let c_q3_inv = FieldExtension::frobenius_map(&c_inv, 3);
359 let c_q2 = FieldExtension::frobenius_map(&c, 2);
360 let c_q_inv = FieldExtension::frobenius_map(&c_inv, 1);
361 let c_mul = c_q3_inv * c_q2 * c_q_inv;
362
363 let fc = Self::multi_miller_loop_embedded_exp(P, Q, Some(c_inv));
365
366 if fc * c_mul * u == Fp12::ONE {
367 Some(Ok(()))
368 } else {
369 None
370 }
371 }
372}