Skip to main content

airbender_crypto/bn254/curves/
pairing_impl.rs

1use super::*;
2use crate::bn254::Fq12;
3use ark_ec::bn::g2::EllCoeff;
4use ark_ec::pairing::Pairing;
5use ark_ec::pairing::PairingOutput;
6use ark_ec::short_weierstrass::SWCurveConfig;
7use ark_ec::AdditiveGroup;
8use ark_ec::AffineRepr;
9use ark_ec::CurveGroup;
10use ark_ff::One;
11use ark_ff::{CyclotomicMultSubgroup, Field};
12use ark_serialize::CanonicalDeserialize;
13use ark_serialize::CanonicalSerialize;
14
15impl Bn254 {
16    /// Evaluates the line function at point p.
17    fn ell(f: &mut Fq12, coeffs: &EllCoeff<Config>, p: &G1Affine) {
18        let mut c0 = coeffs.0;
19        let mut c1 = coeffs.1;
20        let mut c2 = coeffs.2;
21
22        match Config::TWIST_TYPE {
23            TwistType::M => {
24                c2.mul_assign_by_fp(&p.y);
25                c1.mul_assign_by_fp(&p.x);
26                f.mul_by_014(&c0, &c1, &c2);
27            }
28            TwistType::D => {
29                c0.mul_assign_by_fp(&p.y);
30                c1.mul_assign_by_fp(&p.x);
31                f.mul_by_034(&c0, &c1, &c2);
32            }
33        }
34    }
35
36    fn exp_by_neg_x(mut f: Fq12) -> Fq12 {
37        Self::spec_cyclotomic_exp_by_x_inplace(&mut f);
38
39        if !Config::X_IS_NEGATIVE {
40            f.cyclotomic_inverse_in_place();
41        }
42        f
43    }
44
45    fn spec_cyclotomic_exp_by_x_inplace(f: &mut Fq12) {
46        use ark_ff::Zero;
47        if f.is_zero() {
48            return;
49        }
50
51        Self::fast_exp_loop_with_naf(f, Self::X_NAF.iter().copied());
52    }
53
54    const X_NAF: [i8; 63] = [
55        1, 0, 0, 0, 1, 0, 1, 0, 0, -1, 0, 1, 0, 1, 0, -1, 0, 0, 1, 0, 1, 0, -1, 0, -1, 0, -1, 0, 1,
56        0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, -1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, -1,
57        0, 0, 0, 1,
58    ];
59
60    /// `exp_loop` taken from arkworks
61    fn fast_exp_loop_with_naf<I: Iterator<Item = i8>>(f: &mut Fq12, e: I) {
62        let self_inverse = f.cyclotomic_inverse().unwrap();
63        let mut res = Fq12::one();
64        let mut found_nonzero = false;
65        for value in e {
66            if found_nonzero {
67                res.cyclotomic_square_in_place();
68            }
69
70            if value != 0 {
71                found_nonzero = true;
72
73                if value > 0 {
74                    res *= &*f;
75                } else {
76                    res *= &self_inverse;
77                }
78            }
79        }
80        *f = res;
81    }
82}
83
84impl Pairing for Bn254 {
85    type BaseField = Fq;
86    type ScalarField = crate::bn254::Fr;
87    type G1 = G1Projective;
88    type G1Affine = G1Affine;
89    type G1Prepared = ark_ec::bn::G1Prepared<Config>;
90    type G2 = G2Projective;
91    type G2Affine = G2Affine;
92    type G2Prepared = G2PreparedNoAlloc;
93    type TargetField = Fq12;
94
95    fn multi_miller_loop(
96        a: impl IntoIterator<Item = impl Into<Self::G1Prepared>>,
97        b: impl IntoIterator<Item = impl Into<Self::G2Prepared>>,
98    ) -> ark_ec::pairing::MillerLoopOutput<Self> {
99        let mut a = a.into_iter();
100        let mut b = b.into_iter();
101        let mut result = Fq12::one();
102        loop {
103            match (a.next(), b.next()) {
104                (Some(p), Some(q)) => {
105                    let p: Self::G1Prepared = p.into();
106                    if p.is_zero() {
107                        continue;
108                    }
109                    let q: Self::G2Prepared = q.into();
110                    if q.is_zero() {
111                        continue;
112                    }
113
114                    let mut f = Fq12::one();
115                    let mut ell_coeffs = q.ell_coeffs.iter();
116
117                    for i in (1..Config::ATE_LOOP_COUNT.len()).rev() {
118                        if i != Config::ATE_LOOP_COUNT.len() - 1 {
119                            f.square_in_place();
120                        }
121
122                        Self::ell(&mut f, ell_coeffs.next().unwrap(), &p.0);
123
124                        let bit = Config::ATE_LOOP_COUNT[i - 1];
125                        if bit == 1 || bit == -1 {
126                            Self::ell(&mut f, &ell_coeffs.next().unwrap(), &p.0);
127                        }
128                    }
129
130                    if Config::X_IS_NEGATIVE {
131                        f.cyclotomic_inverse_in_place();
132                    }
133
134                    Self::ell(&mut f, ell_coeffs.next().unwrap(), &p.0);
135                    Self::ell(&mut f, ell_coeffs.next().unwrap(), &p.0);
136
137                    result *= f;
138                }
139                (None, None) => break,
140                _ => {
141                    panic!("Caller must check input lengths");
142                }
143            }
144        }
145
146        ark_ec::pairing::MillerLoopOutput(result)
147    }
148
149    fn final_exponentiation(
150        f: ark_ec::pairing::MillerLoopOutput<Self>,
151    ) -> Option<PairingOutput<Self>> {
152        // Easy part: result = elt^((q^6-1)*(q^2+1)).
153        // Follows, e.g., Beuchat et al page 9, by computing result as follows:
154        //   elt^((q^6-1)*(q^2+1)) = (conj(elt) * elt^(-1))^(q^2+1)
155        let f: crate::bn254::Fq12 = f.0;
156
157        // f1 = r.cyclotomic_inverse_in_place() = f^(p^6)
158        let mut f1 = f;
159        f1.cyclotomic_inverse_in_place();
160
161        f.inverse().map(|mut f2| {
162            // f2 = f^(-1);
163            // r = f^(p^6 - 1)
164            let mut r = f1 * &f2;
165
166            // f2 = f^(p^6 - 1)
167            f2 = r;
168            // r = f^((p^6 - 1)(p^2))
169            r.frobenius_map_in_place(2);
170
171            // r = f^((p^6 - 1)(p^2) + (p^6 - 1))
172            // r = f^((p^6 - 1)(p^2 + 1))
173            r *= &f2;
174
175            // Hard part follows Laura Fuentes-Castaneda et al. "Faster hashing to G2"
176            // by computing:
177            //
178            // result = elt^(q^3 * (12*z^3 + 6z^2 + 4z - 1) +
179            //               q^2 * (12*z^3 + 6z^2 + 6z) +
180            //               q   * (12*z^3 + 6z^2 + 4z) +
181            //               1   * (12*z^3 + 12z^2 + 6z + 1))
182            // which equals
183            //
184            // result = elt^( 2z * ( 6z^2 + 3z + 1 ) * (q^4 - q^2 + 1)/r ).
185
186            let y0 = Self::exp_by_neg_x(r);
187            let y1 = y0.cyclotomic_square();
188            let y2 = y1.cyclotomic_square();
189            let mut y3 = y2 * &y1;
190            let y4 = Self::exp_by_neg_x(y3);
191            let y5 = y4.cyclotomic_square();
192            let mut y6 = Self::exp_by_neg_x(y5);
193            y3.cyclotomic_inverse_in_place();
194            y6.cyclotomic_inverse_in_place();
195            let y7 = y6 * &y4;
196            let mut y8 = y7 * &y3;
197            let y9 = y8 * &y1;
198            let y10 = y8 * &y4;
199            let y11 = y10 * &r;
200            let mut y12 = y9;
201            y12.frobenius_map_in_place(1);
202            let y13 = y12 * &y11;
203            y8.frobenius_map_in_place(2);
204            let y14 = y8 * &y13;
205            r.cyclotomic_inverse_in_place();
206            let mut y15 = r * &y9;
207            y15.frobenius_map_in_place(3);
208            let y16 = y15 * &y14;
209
210            PairingOutput(y16)
211        })
212    }
213}
214
215impl From<G2Affine> for G2PreparedNoAlloc {
216    fn from(q: G2Affine) -> Self {
217        if q.infinity {
218            // coeffs should not be used
219            Self {
220                ell_coeffs: [Default::default(); BN254_NUM_ELL_COEFFS],
221                infinity: true,
222            }
223        } else {
224            use ark_ff::{AdditiveGroup, One};
225            let two_inv = Fq::one().double().inverse().unwrap();
226            let mut i = 0;
227            let mut ell_coeffs: [core::mem::MaybeUninit<EllCoeff<Config>>; BN254_NUM_ELL_COEFFS] =
228                [const { core::mem::MaybeUninit::uninit() }; BN254_NUM_ELL_COEFFS];
229
230            let mut r = G2HomProjective {
231                x: q.x,
232                y: q.y,
233                z: Fq2::one(),
234            };
235
236            let neg_q = -q;
237
238            for bit in Config::ATE_LOOP_COUNT.iter().rev().skip(1) {
239                ell_coeffs[i].write(r.double_in_place(&two_inv));
240                i += 1;
241
242                let coeff = match bit {
243                    1 => r.add_in_place(&q),
244                    -1 => r.add_in_place(&neg_q),
245                    _ => continue,
246                };
247                ell_coeffs[i].write(coeff);
248                i += 1;
249            }
250
251            let q1 = mul_by_char(q);
252            let mut q2 = mul_by_char(q1);
253
254            if Config::X_IS_NEGATIVE {
255                r.y = -r.y;
256            }
257
258            q2.y = -q2.y;
259
260            ell_coeffs[i].write(r.add_in_place(&q1));
261            i += 1;
262            ell_coeffs[i].write(r.add_in_place(&q2));
263            i += 1;
264
265            assert_eq!(i, ell_coeffs.len());
266
267            Self {
268                ell_coeffs: unsafe { ell_coeffs.map(|el| el.assume_init()) },
269                infinity: false,
270            }
271        }
272    }
273}
274
275#[derive(Clone, Copy, Debug)]
276struct G2HomProjective {
277    x: Fq2,
278    y: Fq2,
279    z: Fq2,
280}
281
282impl G2HomProjective {
283    fn double_in_place(&mut self, two_inv: &Fq) -> EllCoeff<Config> {
284        // Formula for line function when working with
285        // homogeneous projective coordinates.
286
287        let mut a = self.x * &self.y;
288        a.mul_assign_by_fp(two_inv);
289        let b = self.y.square();
290        let c = self.z.square();
291        let e = <Config as BnConfig>::G2Config::COEFF_B * &(c.double() + &c);
292        let f = e.double() + &e;
293        let mut g = b + &f;
294        g.mul_assign_by_fp(two_inv);
295        let h = (self.y + &self.z).square() - &(b + &c);
296        let i = e - &b;
297        let j = self.x.square();
298        let e_square = e.square();
299
300        self.x = a * &(b - &f);
301        self.y = g.square() - &(e_square.double() + &e_square);
302        self.z = b * &h;
303        match Config::TWIST_TYPE {
304            TwistType::M => (i, j.double() + &j, -h),
305            TwistType::D => (-h, j.double() + &j, i),
306        }
307    }
308
309    fn add_in_place(&mut self, q: &G2Affine) -> EllCoeff<Config> {
310        // Formula for line function when working with
311        // homogeneous projective coordinates.
312        let theta = self.y - &(q.y * &self.z);
313        let lambda = self.x - &(q.x * &self.z);
314        let c = theta.square();
315        let d = lambda.square();
316        let e = lambda * &d;
317        let f = self.z * &c;
318        let g = self.x * &d;
319        let h = e + &f - &g.double();
320        self.x = lambda * &h;
321        self.y = theta * &(g - &h) - &(e * &self.y);
322        self.z *= &e;
323        let j = theta * &q.x - &(lambda * &q.y);
324
325        match Config::TWIST_TYPE {
326            TwistType::M => (j, -theta, lambda),
327            TwistType::D => (lambda, -theta, j),
328        }
329    }
330}
331
332impl Default for G2PreparedNoAlloc {
333    fn default() -> Self {
334        Self::from(G2Affine::generator())
335    }
336}
337
338impl From<G2Projective> for G2PreparedNoAlloc {
339    fn from(q: G2Projective) -> Self {
340        q.into_affine().into()
341    }
342}
343
344impl<'a> From<&'a G2Affine> for G2PreparedNoAlloc {
345    fn from(other: &'a G2Affine) -> Self {
346        (*other).into()
347    }
348}
349
350impl<'a> From<&'a G2Projective> for G2PreparedNoAlloc {
351    fn from(q: &'a G2Projective) -> Self {
352        q.into_affine().into()
353    }
354}
355
356impl G2PreparedNoAlloc {
357    pub fn is_zero(&self) -> bool {
358        self.infinity
359    }
360}
361
362fn mul_by_char(r: G2Affine) -> G2Affine {
363    // multiply by field characteristic
364    use ark_ff::Field;
365
366    let mut s = r;
367    s.x.frobenius_map_in_place(1);
368    s.x *= &Config::TWIST_MUL_BY_Q_X;
369    s.y.frobenius_map_in_place(1);
370    s.y *= &Config::TWIST_MUL_BY_Q_Y;
371
372    s
373}
374
375pub const BN254_NUM_ELL_COEFFS: usize = const {
376    let mut result = 2;
377
378    let mut i = 0;
379    while i < Config::ATE_LOOP_COUNT.len() - 1 {
380        result += 1;
381        if Config::ATE_LOOP_COUNT[i] != 0 {
382            result += 1;
383        }
384
385        i += 1;
386    }
387
388    result
389};
390
391#[derive(Clone, Debug, PartialEq, Eq)]
392pub struct G2PreparedNoAlloc {
393    /// Stores the coefficients of the line evaluations as calculated in
394    /// <https://eprint.iacr.org/2013/722.pdf>
395    pub ell_coeffs: [ark_ec::bn::g2::EllCoeff<Config>; BN254_NUM_ELL_COEFFS],
396    pub infinity: bool,
397}
398
399impl CanonicalSerialize for G2PreparedNoAlloc {
400    fn serialize_with_mode<W: ark_serialize::Write>(
401        &self,
402        _writer: W,
403        _compress: ark_serialize::Compress,
404    ) -> Result<(), ark_serialize::SerializationError> {
405        unimplemented!("not supported");
406    }
407
408    fn serialized_size(&self, _compress: ark_serialize::Compress) -> usize {
409        unimplemented!("not supported");
410    }
411}
412
413impl ark_serialize::Valid for G2PreparedNoAlloc {
414    fn check(&self) -> Result<(), ark_serialize::SerializationError> {
415        unimplemented!("not supported");
416    }
417}
418
419impl CanonicalDeserialize for G2PreparedNoAlloc {
420    fn deserialize_with_mode<R: ark_serialize::Read>(
421        _reader: R,
422        _compress: ark_serialize::Compress,
423        _validate: ark_serialize::Validate,
424    ) -> Result<Self, ark_serialize::SerializationError> {
425        unimplemented!("not supported");
426    }
427}
428
429#[cfg(test)]
430mod test {
431    use super::*;
432
433    #[test]
434    fn compute_x_naf() {
435        let t = ark_ff::biginteger::arithmetic::find_naf(Config::X);
436        let t: Vec<_> = t.into_iter().rev().collect();
437        dbg!(t);
438    }
439}