diff --git a/extensions/pairing/guest/src/bls12_381/pairing.rs b/extensions/pairing/guest/src/bls12_381/pairing.rs index 0d8c4cf7e4..7635682281 100644 --- a/extensions/pairing/guest/src/bls12_381/pairing.rs +++ b/extensions/pairing/guest/src/bls12_381/pairing.rs @@ -6,6 +6,12 @@ use openvm_algebra_guest::{ DivUnsafe, Field, }; use openvm_ecc_guest::AffinePoint; +#[cfg(not(target_os = "zkvm"))] +use { + crate::curve_const::bls12_381::{FINAL_EXP_FACTOR, LAMBDA, POLY_FACTOR}, + num_bigint::BigUint, + openvm_algebra_guest::ExpBytes, +}; #[cfg(target_os = "zkvm")] use { crate::pairing::shifted_funct7, @@ -275,7 +281,68 @@ impl PairingCheck for Bls12_381 { ) -> (Self::Fp12, Self::Fp12) { #[cfg(not(target_os = "zkvm"))] { - todo!() + let f = Self::multi_miller_loop(P, Q); + + // 1. get p-th root inverse + let mut exp = FINAL_EXP_FACTOR.clone() * BigUint::from(27u32); + let mut root = f.exp_bytes(true, &exp.to_bytes_be()); + let root_pth_inv: Fp12; + if root == Fp12::ONE { + root_pth_inv = Fp12::ONE; + } else { + let exp_inv = exp.modinv(&POLY_FACTOR.clone()).unwrap(); + exp = exp_inv % POLY_FACTOR.clone(); + root_pth_inv = root.exp_bytes(false, &exp.to_bytes_be()); + } + + // 2.1. get order of 3rd primitive root + let three = BigUint::from(3u32); + let mut order_3rd_power: u32 = 0; + exp = POLY_FACTOR.clone() * FINAL_EXP_FACTOR.clone(); + + root = f.exp_bytes(true, &exp.to_bytes_be()); + let three_be = three.to_bytes_be(); + // NOTE[yj]: we can probably remove this first check as an optimization since we initizlize order_3rd_power to 0 + if root == Fp12::ONE { + order_3rd_power = 0; + } + root = root.exp_bytes(true, &three_be); + if root == Fp12::ONE { + order_3rd_power = 1; + } + root = root.exp_bytes(true, &three_be); + if root == Fp12::ONE { + order_3rd_power = 2; + } + root = root.exp_bytes(true, &three_be); + if root == Fp12::ONE { + order_3rd_power = 3; + } + + // 2.2. get 27th root inverse + let root_27th_inv: Fp12; + if order_3rd_power == 0 { + root_27th_inv = Fp12::ONE; + } else { + let order_3rd = three.pow(order_3rd_power); + exp = POLY_FACTOR.clone() * FINAL_EXP_FACTOR.clone(); + root = f.exp_bytes(true, &exp.to_bytes_be()); + let exp_inv = exp.modinv(&order_3rd).unwrap(); + exp = exp_inv % order_3rd; + root_27th_inv = root.exp_bytes(false, &exp.to_bytes_be()); + } + + // 2.3. shift the Miller loop result so that millerLoop * scalingFactor + // is of order finalExpFactor + let s = root_pth_inv * root_27th_inv; + let f = f * s.clone(); + + // 3. get the witness residue + // lambda = q - u, the optimal exponent + exp = LAMBDA.clone().modinv(&FINAL_EXP_FACTOR.clone()).unwrap(); + let c = f.exp_bytes(true, &exp.to_bytes_be()); + + (c, s) } #[cfg(target_os = "zkvm")] { diff --git a/extensions/pairing/guest/src/bls12_381/tests.rs b/extensions/pairing/guest/src/bls12_381/tests.rs index 60963e19b1..1294bb0d7b 100644 --- a/extensions/pairing/guest/src/bls12_381/tests.rs +++ b/extensions/pairing/guest/src/bls12_381/tests.rs @@ -12,7 +12,8 @@ use super::{Fp, Fp12, Fp2}; use crate::{ bls12_381::{Bls12_381, G2Affine as OpenVmG2Affine}, pairing::{ - fp2_invert_assign, fp6_invert_assign, fp6_square_assign, MultiMillerLoop, PairingIntrinsics, + fp2_invert_assign, fp6_invert_assign, fp6_square_assign, FinalExp, MultiMillerLoop, + PairingCheck, PairingIntrinsics, }, }; @@ -300,3 +301,39 @@ fn test_bls12381_g2_affine() { } } } + +#[test] +fn test_bls12381_pairing_check_hint_host() { + let mut rng = StdRng::seed_from_u64(83); + let h2c_p = G1Affine::random(&mut rng); + let h2c_q = G2Affine::random(&mut rng); + + let p = AffinePoint { + x: convert_bls12381_halo2_fq_to_fp(h2c_p.x), + y: convert_bls12381_halo2_fq_to_fp(h2c_p.y), + }; + let q = AffinePoint { + x: convert_bls12381_halo2_fq2_to_fp2(h2c_q.x), + y: convert_bls12381_halo2_fq2_to_fp2(h2c_q.y), + }; + + let (c, s) = Bls12_381::pairing_check_hint(&[p], &[q]); + + let p_cmp = AffinePoint { + x: h2c_p.x, + y: h2c_p.y, + }; + let q_cmp = AffinePoint { + x: h2c_q.x, + y: h2c_q.y, + }; + + let f_cmp = + crate::halo2curves_shims::bls12_381::Bls12_381::multi_miller_loop(&[p_cmp], &[q_cmp]); + let (c_cmp, s_cmp) = crate::halo2curves_shims::bls12_381::Bls12_381::final_exp_hint(&f_cmp); + let c_cmp = convert_bls12381_halo2_fq12_to_fp12(c_cmp); + let s_cmp = convert_bls12381_halo2_fq12_to_fp12(s_cmp); + + assert_eq!(c, c_cmp); + assert_eq!(s, s_cmp); +} diff --git a/extensions/pairing/guest/src/bn254/pairing.rs b/extensions/pairing/guest/src/bn254/pairing.rs index 6b33a0a020..908483b218 100644 --- a/extensions/pairing/guest/src/bn254/pairing.rs +++ b/extensions/pairing/guest/src/bn254/pairing.rs @@ -3,6 +3,11 @@ use alloc::vec::Vec; use itertools::izip; use openvm_algebra_guest::{field::FieldExtension, DivUnsafe, Field}; use openvm_ecc_guest::AffinePoint; +#[cfg(not(target_os = "zkvm"))] +use { + crate::curve_const::bn254::{EXP1, EXP2, M_INV, R_INV, U27_COEFF_0, U27_COEFF_1}, + openvm_algebra_guest::{ExpBytes, IntMod}, +}; #[cfg(target_os = "zkvm")] use { crate::pairing::shifted_funct7, @@ -307,7 +312,101 @@ impl PairingCheck for Bn254 { ) -> (Self::Fp12, Self::Fp12) { #[cfg(not(target_os = "zkvm"))] { - todo!() + let f = Self::multi_miller_loop(P, Q); + + // Residue witness + let mut c; + // Cubic nonresidue power + let u; + + // get the 27th root of unity + let u0 = U27_COEFF_0.to_bytes_be(); + let u1 = U27_COEFF_1.to_bytes_be(); + let u_coeffs = Fp2::from_coeffs([Fp::from_be_bytes(&u0), Fp::from_be_bytes(&u1)]); + + let mut unity_root_27 = Fp12::from_coeffs([ + Fp2::ZERO, + Fp2::ZERO, + u_coeffs, + Fp2::ZERO, + Fp2::ZERO, + Fp2::ZERO, + ]); + debug_assert_eq!( + unity_root_27.exp_bytes(true, &27u32.to_be_bytes()), + Fp12::ONE + ); + + if f.exp_bytes(true, &EXP1.to_bytes_be()) == Fp12::ONE { + c = f; + u = Fp12::ONE; + } else { + let f_mul_unity_root_27 = f * unity_root_27.clone(); + if f_mul_unity_root_27.exp_bytes(true, &EXP1.to_bytes_be()) == Fp12::ONE { + c = f_mul_unity_root_27; + u = unity_root_27.clone(); + } else { + c = f_mul_unity_root_27 * unity_root_27.clone(); + unity_root_27.square_assign(); + u = unity_root_27.clone(); + } + } + + // 1. Compute r-th root and exponentiate to rInv where + // rInv = 1/r mod (p^12-1)/r + c = c.exp_bytes(true, &R_INV.to_bytes_be()); + + // 2. Compute m-th root where + // m = (6x + 2 + q^3 - q^2 +q)/3r + // Exponentiate to mInv where + // mInv = 1/m mod p^12-1 + c = c.exp_bytes(true, &M_INV.to_bytes_be()); + + // 3. Compute cube root + // since gcd(3, (p^12-1)/r) != 1, we use a modified Tonelli-Shanks algorithm + // see Alg.4 of https://eprint.iacr.org/2024/640.pdf + // Typo in the paper: p^k-1 = 3^n * s instead of p-1 = 3^r * s + // where k=12 and n=3 here and exp2 = (s+1)/3 + let mut x = c.exp_bytes(true, &EXP2.to_bytes_be()); + + // 3^t is ord(x^3 / residueWitness) + let c_inv = c.invert(); + let mut x2 = x.clone(); + x2.square_assign(); + let mut x3 = x2 * x.clone() * c_inv.clone(); + let mut t = 0; + let mut tmp = x3.clone(); + tmp.square_assign(); + + // Modified Tonelli-Shanks algorithm for computing the cube root + fn tonelli_shanks_loop(x3: &mut Fp12, tmp: &mut Fp12, t: &mut i32) { + while *x3 != Fp12::ONE { + *tmp = x3.clone(); + tmp.square_assign(); + *x3 *= tmp.clone(); + *t += 1; + } + } + + tonelli_shanks_loop(&mut x3, &mut tmp, &mut t); + + while t != 0 { + tmp = unity_root_27.exp_bytes(true, &EXP2.to_bytes_be()); + x *= tmp.clone(); + + let mut x2 = x.clone(); + x2.square_assign(); + x3 = x2 * x.clone() * c_inv.clone(); + t = 0; + tonelli_shanks_loop(&mut x3, &mut tmp, &mut t); + } + + debug_assert_eq!(c, x.clone() * x.clone() * x.clone()); + + // x is the cube root of the residue witness c + c = x.clone(); + + (c, u) } #[cfg(target_os = "zkvm")] { diff --git a/extensions/pairing/guest/src/bn254/tests.rs b/extensions/pairing/guest/src/bn254/tests.rs index 8b1ffea3fe..a0eba0e5b9 100644 --- a/extensions/pairing/guest/src/bn254/tests.rs +++ b/extensions/pairing/guest/src/bn254/tests.rs @@ -12,7 +12,8 @@ use super::{Fp, Fp12, Fp2}; use crate::{ bn254::{Bn254, G2Affine as OpenVmG2Affine}, pairing::{ - fp2_invert_assign, fp6_invert_assign, fp6_square_assign, MultiMillerLoop, PairingIntrinsics, + fp2_invert_assign, fp6_invert_assign, fp6_square_assign, FinalExp, MultiMillerLoop, + PairingCheck, PairingIntrinsics, }, }; @@ -286,3 +287,38 @@ fn test_bn254_g2_affine() { } } } + +#[test] +fn test_bn254_pairing_check_hint_host() { + let mut rng = StdRng::seed_from_u64(83); + let h2c_p = G1Affine::random(&mut rng); + let h2c_q = G2Affine::random(&mut rng); + + let p = AffinePoint { + x: convert_bn254_halo2_fq_to_fp(h2c_p.x), + y: convert_bn254_halo2_fq_to_fp(h2c_p.y), + }; + let q = AffinePoint { + x: convert_bn254_halo2_fq2_to_fp2(h2c_q.x), + y: convert_bn254_halo2_fq2_to_fp2(h2c_q.y), + }; + + let (c, u) = Bn254::pairing_check_hint(&[p], &[q]); + + let p_cmp = AffinePoint { + x: h2c_p.x, + y: h2c_p.y, + }; + let q_cmp = AffinePoint { + x: h2c_q.x, + y: h2c_q.y, + }; + + let f_cmp = crate::halo2curves_shims::bn254::Bn254::multi_miller_loop(&[p_cmp], &[q_cmp]); + let (c_cmp, u_cmp) = crate::halo2curves_shims::bn254::Bn254::final_exp_hint(&f_cmp); + let c_cmp = convert_bn254_halo2_fq12_to_fp12(c_cmp); + let u_cmp = convert_bn254_halo2_fq12_to_fp12(u_cmp); + + assert_eq!(c, c_cmp); + assert_eq!(u, u_cmp); +} diff --git a/extensions/pairing/guest/src/curve_const.rs b/extensions/pairing/guest/src/curve_const.rs deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/extensions/pairing/guest/src/halo2curves_shims/bls12_381/curve.rs b/extensions/pairing/guest/src/curve_const/bls12_381.rs similarity index 91% rename from extensions/pairing/guest/src/halo2curves_shims/bls12_381/curve.rs rename to extensions/pairing/guest/src/curve_const/bls12_381.rs index dcf29813cc..82f2566418 100644 --- a/extensions/pairing/guest/src/halo2curves_shims/bls12_381/curve.rs +++ b/extensions/pairing/guest/src/curve_const/bls12_381.rs @@ -1,12 +1,18 @@ -use halo2curves_axiom::bls12_381::{Fq, Fq2}; use lazy_static::lazy_static; use num_bigint::BigUint; use num_traits::Num; -use openvm_ecc_guest::algebra::{field::FieldExtension, Field}; +#[cfg(feature = "halo2curves")] +use { + halo2curves_axiom::bls12_381::{Fq, Fq2}, + openvm_ecc_guest::algebra::{field::FieldExtension, Field}, +}; +#[cfg(feature = "halo2curves")] lazy_static! { pub static ref BLS12_381_XI: Fq2 = Fq2::from_coeffs([Fq::ONE, Fq::ONE]); +} +lazy_static! { // polyFactor = (1-x)/3 pub static ref POLY_FACTOR: BigUint = BigUint::from_str_radix("5044125407647214251", 10).unwrap(); @@ -19,5 +25,3 @@ lazy_static! { // t = -x = 15132376222941642752 pub static ref SEED_NEG: BigUint = BigUint::from_str_radix("15132376222941642752", 10).unwrap(); } - -pub struct Bls12_381; diff --git a/extensions/pairing/guest/src/halo2curves_shims/bn254/curve.rs b/extensions/pairing/guest/src/curve_const/bn254.rs similarity index 96% rename from extensions/pairing/guest/src/halo2curves_shims/bn254/curve.rs rename to extensions/pairing/guest/src/curve_const/bn254.rs index 91092a135d..badeab4918 100644 --- a/extensions/pairing/guest/src/halo2curves_shims/bn254/curve.rs +++ b/extensions/pairing/guest/src/curve_const/bn254.rs @@ -1,12 +1,18 @@ -use halo2curves_axiom::bn256::{Fq, Fq2}; use lazy_static::lazy_static; use num_bigint::BigUint; use num_traits::Num; -use openvm_ecc_guest::algebra::field::FieldExtension; +#[cfg(feature = "halo2curves")] +use { + halo2curves_axiom::bn256::{Fq, Fq2}, + openvm_ecc_guest::algebra::field::FieldExtension, +}; +#[cfg(feature = "halo2curves")] lazy_static! { pub static ref BN254_XI: Fq2 = Fq2::from_coeffs([Fq::from_raw([9, 0, 0, 0]), Fq::one()]); +} +lazy_static! { // exp1 = (p^12 - 1) / 3 pub static ref EXP1: BigUint = BigUint::from_str_radix( "4030969696062745741797811005853058291874379204406359442560681893891674450106959530046539719647151210908190211459382793062006703141168852426020468083171325367934590379984666859998399967609544754664110191464072930598755441160008826659219834762354786403012110463250131961575955268597858015384895449311534622125256548620283853223733396368939858981844663598065852816056384933498610930035891058807598891752166582271931875150099691598048016175399382213304673796601585080509443902692818733420199004555566113537482054218823936116647313678747500267068559627206777530424029211671772692598157901876223857571299238046741502089890557442500582300718504160740314926185458079985126192563953772118929726791041828902047546977272656240744693339962973939047279285351052107950250121751682659529260304162131862468322644288196213423232132152125277136333208005221619443705106431645884840489295409272576227859206166894626854018093044908314720", @@ -38,5 +44,3 @@ lazy_static! { 10 ).unwrap(); } - -pub struct Bn254; diff --git a/extensions/pairing/guest/src/curve_const/mod.rs b/extensions/pairing/guest/src/curve_const/mod.rs new file mode 100644 index 0000000000..642f2b668f --- /dev/null +++ b/extensions/pairing/guest/src/curve_const/mod.rs @@ -0,0 +1,5 @@ +#[cfg(any(feature = "bls12_381", feature = "halo2curves"))] +pub mod bls12_381; + +#[cfg(any(feature = "bn254", feature = "halo2curves"))] +pub mod bn254; diff --git a/extensions/pairing/guest/src/halo2curves_shims/bls12_381/final_exp.rs b/extensions/pairing/guest/src/halo2curves_shims/bls12_381/final_exp.rs index b7a76bd895..1c84ea2b3b 100644 --- a/extensions/pairing/guest/src/halo2curves_shims/bls12_381/final_exp.rs +++ b/extensions/pairing/guest/src/halo2curves_shims/bls12_381/final_exp.rs @@ -5,8 +5,11 @@ use openvm_ecc_guest::{ AffinePoint, }; -use super::{Bls12_381, FINAL_EXP_FACTOR, LAMBDA, POLY_FACTOR}; -use crate::pairing::{FinalExp, MultiMillerLoop}; +use super::Bls12_381; +use crate::{ + curve_const::bls12_381::{FINAL_EXP_FACTOR, LAMBDA, POLY_FACTOR}, + pairing::{FinalExp, MultiMillerLoop}, +}; #[allow(non_snake_case)] impl FinalExp for Bls12_381 { diff --git a/extensions/pairing/guest/src/halo2curves_shims/bls12_381/line.rs b/extensions/pairing/guest/src/halo2curves_shims/bls12_381/line.rs index 8c25f44d19..9bf29a4f98 100644 --- a/extensions/pairing/guest/src/halo2curves_shims/bls12_381/line.rs +++ b/extensions/pairing/guest/src/halo2curves_shims/bls12_381/line.rs @@ -8,8 +8,11 @@ use openvm_ecc_guest::{ AffinePoint, }; -use super::{Bls12_381, BLS12_381_XI}; -use crate::pairing::{EvaluatedLine, LineMulMType}; +use super::Bls12_381; +use crate::{ + curve_const::bls12_381::BLS12_381_XI, + pairing::{EvaluatedLine, LineMulMType}, +}; impl LineMulMType for Bls12_381 { fn mul_023_by_023(l0: &EvaluatedLine, l1: &EvaluatedLine) -> [Fq2; 5] { diff --git a/extensions/pairing/guest/src/halo2curves_shims/bls12_381/mod.rs b/extensions/pairing/guest/src/halo2curves_shims/bls12_381/mod.rs index bcaaa6070c..97abdc0024 100644 --- a/extensions/pairing/guest/src/halo2curves_shims/bls12_381/mod.rs +++ b/extensions/pairing/guest/src/halo2curves_shims/bls12_381/mod.rs @@ -1,9 +1,7 @@ -mod curve; mod final_exp; mod line; mod miller_loop; -pub use curve::*; pub use line::*; #[cfg(test)] @@ -18,6 +16,8 @@ use crate::{ pairing::{Evaluatable, EvaluatedLine, FromLineMType, UnevaluatedLine}, }; +pub struct Bls12_381; + impl FromLineMType for Fq12 { fn from_evaluated_line_m_type(line: EvaluatedLine) -> Fq12 { Fq12::from_coeffs([ diff --git a/extensions/pairing/guest/src/halo2curves_shims/bls12_381/tests/test_final_exp.rs b/extensions/pairing/guest/src/halo2curves_shims/bls12_381/tests/test_final_exp.rs index 2b5cb402c5..9041d1a5f9 100644 --- a/extensions/pairing/guest/src/halo2curves_shims/bls12_381/tests/test_final_exp.rs +++ b/extensions/pairing/guest/src/halo2curves_shims/bls12_381/tests/test_final_exp.rs @@ -7,7 +7,8 @@ use num_traits::Num; use openvm_ecc_guest::{algebra::ExpBytes, AffinePoint}; use crate::{ - halo2curves_shims::bls12_381::{Bls12_381, SEED_NEG}, + curve_const::bls12_381::SEED_NEG, + halo2curves_shims::bls12_381::Bls12_381, pairing::{FinalExp, MultiMillerLoop}, }; diff --git a/extensions/pairing/guest/src/halo2curves_shims/bn254/final_exp.rs b/extensions/pairing/guest/src/halo2curves_shims/bn254/final_exp.rs index d67813086e..6187e45f06 100644 --- a/extensions/pairing/guest/src/halo2curves_shims/bn254/final_exp.rs +++ b/extensions/pairing/guest/src/halo2curves_shims/bn254/final_exp.rs @@ -4,8 +4,11 @@ use halo2curves_axiom::{ }; use openvm_ecc_guest::{algebra::field::FieldExtension, AffinePoint}; -use super::{Bn254, EXP1, EXP2, M_INV, R_INV, U27_COEFF_0, U27_COEFF_1}; -use crate::pairing::{FinalExp, MultiMillerLoop}; +use super::Bn254; +use crate::{ + curve_const::bn254::{EXP1, EXP2, M_INV, R_INV, U27_COEFF_0, U27_COEFF_1}, + pairing::{FinalExp, MultiMillerLoop}, +}; #[allow(non_snake_case)] impl FinalExp for Bn254 { diff --git a/extensions/pairing/guest/src/halo2curves_shims/bn254/line.rs b/extensions/pairing/guest/src/halo2curves_shims/bn254/line.rs index 2570b4ef54..6aa3091fb8 100644 --- a/extensions/pairing/guest/src/halo2curves_shims/bn254/line.rs +++ b/extensions/pairing/guest/src/halo2curves_shims/bn254/line.rs @@ -8,8 +8,11 @@ use openvm_ecc_guest::{ AffinePoint, }; -use super::{Bn254, BN254_XI}; -use crate::pairing::{EvaluatedLine, LineMulDType}; +use super::Bn254; +use crate::{ + curve_const::bn254::BN254_XI, + pairing::{EvaluatedLine, LineMulDType}, +}; impl LineMulDType for Bn254 { /// Multiplies two lines in 013-form to get an element in 01234-form diff --git a/extensions/pairing/guest/src/halo2curves_shims/bn254/mod.rs b/extensions/pairing/guest/src/halo2curves_shims/bn254/mod.rs index a86d321402..e8a2e95a19 100644 --- a/extensions/pairing/guest/src/halo2curves_shims/bn254/mod.rs +++ b/extensions/pairing/guest/src/halo2curves_shims/bn254/mod.rs @@ -1,9 +1,7 @@ -mod curve; mod final_exp; mod line; mod miller_loop; -pub use curve::*; pub use line::*; #[cfg(test)] @@ -18,6 +16,8 @@ use crate::{ pairing::{Evaluatable, EvaluatedLine, FromLineDType, UnevaluatedLine}, }; +pub struct Bn254; + impl FromLineDType for Fq12 { fn from_evaluated_line_d_type(line: EvaluatedLine) -> Fq12 { FieldExtension::::from_coeffs([ diff --git a/extensions/pairing/guest/src/lib.rs b/extensions/pairing/guest/src/lib.rs index 198f3af46e..5a98e5c1db 100644 --- a/extensions/pairing/guest/src/lib.rs +++ b/extensions/pairing/guest/src/lib.rs @@ -38,9 +38,17 @@ pub mod affine_point; /// These should **only** be importable on a host machine. #[cfg(all(feature = "halo2curves", not(target_os = "zkvm")))] pub mod halo2curves_shims; + /// Traits for optimal Ate pairing check using intrinsic functions. pub mod pairing; +/// Pairing curve constants. Only importable on a host machine. +#[cfg(all( + any(feature = "bls12_381", feature = "bn254", feature = "halo2curves"), + not(target_os = "zkvm") +))] +pub mod curve_const; + /// Types for BLS12-381 curve with intrinsic functions. #[cfg(feature = "bls12_381")] pub mod bls12_381; diff --git a/extensions/pairing/tests/programs/examples/final_exp_hint.rs b/extensions/pairing/tests/programs/examples/final_exp_hint.rs index c5e0e2100a..a39a3c4aba 100644 --- a/extensions/pairing/tests/programs/examples/final_exp_hint.rs +++ b/extensions/pairing/tests/programs/examples/final_exp_hint.rs @@ -20,6 +20,7 @@ openvm_algebra_moduli_setup::moduli_init! { } pub fn main() { + #[allow(clippy::type_complexity)] let (p, q, expected): (Vec>, Vec>, (Fp12, Fp12)) = read(); let actual = Bls12_381::pairing_check_hint(&p, &q); assert_eq!(actual, expected);