Skip to main content

nam_blstrs/
gt.rs

1use core::{
2    borrow::Borrow,
3    fmt,
4    iter::Sum,
5    ops::{Add, AddAssign, Mul, MulAssign, Neg, Sub, SubAssign},
6};
7
8use blst::*;
9use ff::Field;
10use group::Group;
11use rand_core::RngCore;
12use subtle::{Choice, ConstantTimeEq};
13use zeroize::Zeroize;
14
15use crate::{Scalar, fp::Fp, fp2::Fp2, fp6::Fp6, fp12::Fp12, traits::Compress};
16
17/// This is an element of $\mathbb{G}_T$, the target group of the pairing function. As with
18/// $\mathbb{G}_1$ and $\mathbb{G}_2$ this group has order $q$.
19///
20/// Typically, $\mathbb{G}_T$ is written multiplicatively but we will write it additively to
21/// keep code and abstractions consistent.
22#[derive(Copy, Clone, Debug, Default, Zeroize)]
23#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
24#[repr(transparent)]
25pub struct Gt(pub(crate) Fp12);
26
27impl fmt::Display for Gt {
28    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
29        write!(f, "{:?}", self)
30    }
31}
32
33impl From<Fp12> for Gt {
34    fn from(fp12: Fp12) -> Self {
35        Gt(fp12)
36    }
37}
38
39impl From<Gt> for Fp12 {
40    fn from(gt: Gt) -> Self {
41        gt.0
42    }
43}
44
45impl Eq for Gt {}
46
47impl PartialEq for Gt {
48    #[inline]
49    fn eq(&self, other: &Self) -> bool {
50        self.0 == other.0
51    }
52}
53
54impl Neg for &Gt {
55    type Output = Gt;
56
57    #[inline]
58    fn neg(self) -> Gt {
59        // The element is unitary, so we just conjugate.
60        let mut res = *self;
61        res.0.conjugate();
62        res
63    }
64}
65
66impl Neg for Gt {
67    type Output = Gt;
68
69    #[inline]
70    fn neg(self) -> Gt {
71        -&self
72    }
73}
74
75impl Add<&Gt> for &Gt {
76    type Output = Gt;
77
78    #[inline]
79    #[allow(clippy::suspicious_arithmetic_impl)]
80    fn add(self, rhs: &Gt) -> Gt {
81        Gt(self.0 * rhs.0)
82    }
83}
84
85impl Sub<&Gt> for &Gt {
86    type Output = Gt;
87
88    #[inline]
89    fn sub(self, rhs: &Gt) -> Gt {
90        self + (-rhs)
91    }
92}
93
94impl Mul<&Scalar> for &Gt {
95    type Output = Gt;
96
97    #[allow(clippy::suspicious_arithmetic_impl)]
98    fn mul(self, scalar: &Scalar) -> Self::Output {
99        let mut acc = Gt::identity();
100
101        // This is a simple double-and-add implementation of group element
102        // multiplication, moving from most significant to least
103        // significant bit of the scalar.
104        //
105        // We skip the leading bit because it's always unset for Fq
106        // elements.
107        for bit in scalar
108            .to_bytes_be()
109            .iter()
110            .flat_map(|byte| (0..8).rev().map(move |i| (byte >> i) & 1 == 1))
111            .skip(1)
112        {
113            acc = acc.double();
114            if bit {
115                acc += self;
116            }
117        }
118
119        acc
120    }
121}
122
123impl AddAssign<&Gt> for Gt {
124    #[inline]
125    fn add_assign(&mut self, rhs: &Gt) {
126        *self = *self + rhs;
127    }
128}
129
130impl SubAssign<&Gt> for Gt {
131    #[inline]
132    fn sub_assign(&mut self, rhs: &Gt) {
133        *self = *self - rhs;
134    }
135}
136
137impl MulAssign<&Scalar> for Gt {
138    #[inline]
139    fn mul_assign(&mut self, rhs: &Scalar) {
140        *self = *self * rhs;
141    }
142}
143
144impl_add_sub!(Gt);
145impl_add_sub_assign!(Gt);
146impl_mul!(Gt, Scalar);
147impl_mul_assign!(Gt, Scalar);
148
149impl<T> Sum<T> for Gt
150where
151    T: Borrow<Gt>,
152{
153    fn sum<I>(iter: I) -> Self
154    where
155        I: Iterator<Item = T>,
156    {
157        iter.fold(Self::identity(), |acc, item| acc + item.borrow())
158    }
159}
160
161impl Group for Gt {
162    type Scalar = Scalar;
163
164    fn random(mut rng: impl RngCore) -> Self {
165        loop {
166            let mut out = Fp12::random(&mut rng);
167
168            // Not all elements of Fp12 are elements of the prime-order multiplicative
169            // subgroup. We run the random element through final_exponentiation to obtain
170            // a valid element, which requires that it is non-zero.
171            if !bool::from(out.is_zero()) {
172                unsafe { blst_final_exp(&mut out.0, &out.0) };
173                return Gt(out);
174            }
175        }
176    }
177
178    /// Returns the group identity, which is $1$.
179    fn identity() -> Self {
180        Gt(Fp12::ONE)
181    }
182
183    fn generator() -> Self {
184        // pairing(&G1Affine::generator(), &G2Affine::generator())
185        Gt(Fp12::new(
186            Fp6::new(
187                Fp2::new(
188                    Fp::from_raw_unchecked([
189                        0x1972_e433_a01f_85c5,
190                        0x97d3_2b76_fd77_2538,
191                        0xc8ce_546f_c96b_cdf9,
192                        0xcef6_3e73_66d4_0614,
193                        0xa611_3427_8184_3780,
194                        0x13f3_448a_3fc6_d825,
195                    ]),
196                    Fp::from_raw_unchecked([
197                        0xd263_31b0_2e9d_6995,
198                        0x9d68_a482_f779_7e7d,
199                        0x9c9b_2924_8d39_ea92,
200                        0xf480_1ca2_e131_07aa,
201                        0xa16c_0732_bdbc_b066,
202                        0x083c_a4af_ba36_0478,
203                    ]),
204                ),
205                Fp2::new(
206                    Fp::from_raw_unchecked([
207                        0x59e2_61db_0916_b641,
208                        0x2716_b6f4_b23e_960d,
209                        0xc8e5_5b10_a0bd_9c45,
210                        0x0bdb_0bd9_9c4d_eda8,
211                        0x8cf8_9ebf_57fd_aac5,
212                        0x12d6_b792_9e77_7a5e,
213                    ]),
214                    Fp::from_raw_unchecked([
215                        0x5fc8_5188_b0e1_5f35,
216                        0x34a0_6e3a_8f09_6365,
217                        0xdb31_26a6_e02a_d62c,
218                        0xfc6f_5aa9_7d9a_990b,
219                        0xa12f_55f5_eb89_c210,
220                        0x1723_703a_926f_8889,
221                    ]),
222                ),
223                Fp2::new(
224                    Fp::from_raw_unchecked([
225                        0x9358_8f29_7182_8778,
226                        0x43f6_5b86_11ab_7585,
227                        0x3183_aaf5_ec27_9fdf,
228                        0xfa73_d7e1_8ac9_9df6,
229                        0x64e1_76a6_a64c_99b0,
230                        0x179f_a78c_5838_8f1f,
231                    ]),
232                    Fp::from_raw_unchecked([
233                        0x672a_0a11_ca2a_ef12,
234                        0x0d11_b9b5_2aa3_f16b,
235                        0xa444_12d0_699d_056e,
236                        0xc01d_0177_221a_5ba5,
237                        0x66e0_cede_6c73_5529,
238                        0x05f5_a71e_9fdd_c339,
239                    ]),
240                ),
241            ),
242            Fp6::new(
243                Fp2::new(
244                    Fp::from_raw_unchecked([
245                        0xd30a_88a1_b062_c679,
246                        0x5ac5_6a5d_35fc_8304,
247                        0xd0c8_34a6_a81f_290d,
248                        0xcd54_30c2_da37_07c7,
249                        0xf0c2_7ff7_8050_0af0,
250                        0x0924_5da6_e2d7_2eae,
251                    ]),
252                    Fp::from_raw_unchecked([
253                        0x9f2e_0676_791b_5156,
254                        0xe2d1_c823_4918_fe13,
255                        0x4c9e_459f_3c56_1bf4,
256                        0xa3e8_5e53_b9d3_e3c1,
257                        0x820a_121e_21a7_0020,
258                        0x15af_6183_41c5_9acc,
259                    ]),
260                ),
261                Fp2::new(
262                    Fp::from_raw_unchecked([
263                        0x7c95_658c_2499_3ab1,
264                        0x73eb_3872_1ca8_86b9,
265                        0x5256_d749_4774_34bc,
266                        0x8ba4_1902_ea50_4a8b,
267                        0x04a3_d3f8_0c86_ce6d,
268                        0x18a6_4a87_fb68_6eaa,
269                    ]),
270                    Fp::from_raw_unchecked([
271                        0xbb83_e71b_b920_cf26,
272                        0x2a52_77ac_92a7_3945,
273                        0xfc0e_e59f_94f0_46a0,
274                        0x7158_cdf3_7860_58f7,
275                        0x7cc1_061b_82f9_45f6,
276                        0x03f8_47aa_9fdb_e567,
277                    ]),
278                ),
279                Fp2::new(
280                    Fp::from_raw_unchecked([
281                        0x8078_dba5_6134_e657,
282                        0x1cd7_ec9a_4399_8a6e,
283                        0xb1aa_599a_1a99_3766,
284                        0xc9a0_f62f_0842_ee44,
285                        0x8e15_9be3_b605_dffa,
286                        0x0c86_ba0d_4af1_3fc2,
287                    ]),
288                    Fp::from_raw_unchecked([
289                        0xe80f_f2a0_6a52_ffb1,
290                        0x7694_ca48_721a_906c,
291                        0x7583_183e_03b0_8514,
292                        0xf567_afdd_40ce_e4e2,
293                        0x9a6d_96d2_e526_a5fc,
294                        0x197e_9f49_861f_2242,
295                    ]),
296                ),
297            ),
298        ))
299    }
300
301    fn is_identity(&self) -> Choice {
302        self.0.ct_eq(&Self::identity().0)
303    }
304
305    fn double(&self) -> Self {
306        Gt(self.0.square())
307    }
308}
309
310/// Compressed representation of `Fp12`.
311#[derive(Copy, Clone, PartialEq, Eq, Debug, Zeroize)]
312#[repr(transparent)]
313pub struct GtCompressed(pub(crate) Fp6);
314
315impl Gt {
316    /// Compress this point. Returns `None` if the element is not in the cyclomtomic subgroup.
317    pub fn compress(&self) -> Option<GtCompressed> {
318        // Use torus-based compression from Section 4.1 in
319        // "On Compressible Pairings and Their Computation" by Naehrig et al.
320        let mut c0 = self.0.c0();
321
322        c0.0.fp2[0] = (c0.c0() + Fp2::from(1)).0;
323        let b = c0 * self.0.c1().invert().unwrap();
324
325        Some(GtCompressed(b))
326    }
327
328    fn is_in_subgroup(&self) -> bool {
329        unsafe { blst_fp12_in_group(&(self.0).0) }
330    }
331}
332
333impl GtCompressed {
334    /// Uncompress the element, returns `None` if the element is an invalid compression
335    /// format.
336    pub fn uncompress(self) -> Option<Gt> {
337        // Formula for decompression for the odd q case from Section 2 in
338        // "Compression in finite fields and torus-based cryptography" by
339        // Rubin-Silverberg.
340        let fp6_neg_one = Fp6::from(1).neg();
341        let t = Fp12::new(self.0, fp6_neg_one).invert().unwrap();
342        let c = Fp12::new(self.0, Fp6::from(1)) * t;
343        let g = Gt(c);
344
345        if g.is_in_subgroup() {
346            return Some(g);
347        }
348
349        None
350    }
351}
352
353impl Compress for Gt {
354    fn write_compressed<W: std::io::Write>(self, mut out: W) -> std::io::Result<()> {
355        let c = self.compress().unwrap();
356
357        out.write_all(&c.0.c0().c0().to_bytes_le())?;
358        out.write_all(&c.0.c0().c1().to_bytes_le())?;
359
360        out.write_all(&c.0.c1().c0().to_bytes_le())?;
361        out.write_all(&c.0.c1().c1().to_bytes_le())?;
362
363        out.write_all(&c.0.c2().c0().to_bytes_le())?;
364        out.write_all(&c.0.c2().c1().to_bytes_le())?;
365
366        Ok(())
367    }
368
369    fn read_compressed<R: std::io::Read>(mut source: R) -> std::io::Result<Self> {
370        let mut buffer = [0u8; 48];
371        let read_fp = |source: &mut dyn std::io::Read, buffer: &mut [u8; 48]| {
372            source.read_exact(buffer)?;
373            let fp = Fp::from_bytes_le(buffer);
374            Option::from(fp)
375                .ok_or_else(|| std::io::Error::new(std::io::ErrorKind::InvalidData, "invalid fp"))
376        };
377
378        let x0 = read_fp(&mut source, &mut buffer)?;
379        let x1 = read_fp(&mut source, &mut buffer)?;
380
381        let y0 = read_fp(&mut source, &mut buffer)?;
382        let y1 = read_fp(&mut source, &mut buffer)?;
383
384        let z0 = read_fp(&mut source, &mut buffer)?;
385        let z1 = read_fp(&mut source, &mut buffer)?;
386
387        let x = Fp2::new(x0, x1);
388        let y = Fp2::new(y0, y1);
389        let z = Fp2::new(z0, z1);
390
391        let compressed = GtCompressed(Fp6::new(x, y, z));
392        compressed.uncompress().ok_or_else(|| {
393            std::io::Error::new(std::io::ErrorKind::InvalidData, "invalid compression point")
394        })
395    }
396}
397
398#[cfg(test)]
399mod tests {
400    use super::*;
401
402    use group::{Curve, prime::PrimeCurveAffine};
403    use pairing_lib::{Engine, MillerLoopResult, MultiMillerLoop};
404    use rand_core::SeedableRng;
405    use rand_xorshift::XorShiftRng;
406
407    use crate::{Bls12, G1Affine, G1Projective, G2Affine, G2Prepared, G2Projective, pairing};
408
409    #[test]
410    fn test_gt_generator() {
411        assert_eq!(
412            Gt::generator(),
413            pairing(&G1Affine::generator(), &G2Affine::generator()),
414        );
415    }
416
417    #[test]
418    fn test_gt_bilinearity() {
419        use crate::Scalar;
420
421        let a = Scalar::from_u64s_le(&[1, 2, 3, 4])
422            .unwrap()
423            .invert()
424            .unwrap()
425            .square();
426        let b = Scalar::from_u64s_le(&[5, 6, 7, 8])
427            .unwrap()
428            .invert()
429            .unwrap()
430            .square();
431        let c = a * b;
432
433        let g = G1Affine::from(G1Affine::generator() * a);
434        let h = G2Affine::from(G2Affine::generator() * b);
435        let p = pairing(&g, &h);
436
437        assert_ne!(p, Gt::identity());
438        assert_eq!(
439            p,
440            pairing(
441                &G1Affine::from(G1Affine::generator() * c),
442                &G2Affine::generator()
443            ),
444        );
445        assert_eq!(
446            p,
447            pairing(&G1Affine::generator(), &G2Affine::generator()) * c
448        );
449    }
450
451    #[test]
452    fn test_gt_unitary() {
453        let g = G1Affine::generator();
454        let h = G2Affine::generator();
455        let p = -pairing(&g, &h);
456        let q = pairing(&g, &-h);
457        let r = pairing(&-g, &h);
458
459        assert_eq!(p, q);
460        assert_eq!(q, r);
461    }
462
463    #[test]
464    fn test_multi_miller_loop() {
465        let a1 = G1Affine::generator();
466        let b1 = G2Affine::generator();
467
468        let a2 = G1Affine::from(
469            G1Affine::generator()
470                * Scalar::from_u64s_le(&[1, 2, 3, 4])
471                    .unwrap()
472                    .invert()
473                    .unwrap()
474                    .square(),
475        );
476        let b2 = G2Affine::from(
477            G2Affine::generator()
478                * Scalar::from_u64s_le(&[4, 2, 2, 4])
479                    .unwrap()
480                    .invert()
481                    .unwrap()
482                    .square(),
483        );
484
485        let a3 = G1Affine::identity();
486        let b3 = G2Affine::from(
487            G2Affine::generator()
488                * Scalar::from_u64s_le(&[9, 2, 2, 4])
489                    .unwrap()
490                    .invert()
491                    .unwrap()
492                    .square(),
493        );
494
495        let a4 = G1Affine::from(
496            G1Affine::generator()
497                * Scalar::from_u64s_le(&[5, 5, 5, 5])
498                    .unwrap()
499                    .invert()
500                    .unwrap()
501                    .square(),
502        );
503        let b4 = G2Affine::identity();
504
505        let a5 = G1Affine::from(
506            G1Affine::generator()
507                * Scalar::from_u64s_le(&[323, 32, 3, 1])
508                    .unwrap()
509                    .invert()
510                    .unwrap()
511                    .square(),
512        );
513        let b5 = G2Affine::from(
514            G2Affine::generator()
515                * Scalar::from_u64s_le(&[4, 2, 2, 9099])
516                    .unwrap()
517                    .invert()
518                    .unwrap()
519                    .square(),
520        );
521
522        let b1_prepared = G2Prepared::from(b1);
523        let b2_prepared = G2Prepared::from(b2);
524        let b3_prepared = G2Prepared::from(b3);
525        let b4_prepared = G2Prepared::from(b4);
526        let b5_prepared = G2Prepared::from(b5);
527
528        let expected = Bls12::pairing(&a1, &b1)
529            + Bls12::pairing(&a2, &b2)
530            + Bls12::pairing(&a3, &b3)
531            + Bls12::pairing(&a4, &b4)
532            + Bls12::pairing(&a5, &b5);
533
534        let test = <Bls12 as MultiMillerLoop>::multi_miller_loop(&[
535            (&a1, &b1_prepared),
536            (&a2, &b2_prepared),
537            (&a3, &b3_prepared),
538            (&a4, &b4_prepared),
539            (&a5, &b5_prepared),
540        ])
541        .final_exponentiation();
542
543        assert_eq!(expected, test);
544    }
545
546    #[test]
547    fn gt_compression() {
548        let mut rng = XorShiftRng::from_seed([
549            0x59, 0x62, 0xbe, 0x5d, 0x76, 0x3d, 0x31, 0x8d, 0x17, 0xdb, 0x37, 0x32, 0x54, 0x06,
550            0xbc, 0xe5,
551        ]);
552
553        for i in 0..100 {
554            let a = Gt::random(&mut rng);
555            // usually not cyclomatic, so not compressable
556            if let Some(b) = a.compress() {
557                let c = b.uncompress().unwrap();
558                assert_eq!(a, c, "{}", i);
559            } else {
560                println!("skipping {}", i);
561            }
562
563            // pairing result, should be compressable
564            let p = G1Projective::random(&mut rng).to_affine();
565            let q = G2Projective::random(&mut rng).to_affine();
566            let a: Gt = crate::pairing(&p, &q);
567            assert!(a.is_in_subgroup());
568
569            let b = a.compress().unwrap();
570            let c = b.uncompress().unwrap();
571            assert_eq!(a, c, "{}", i);
572
573            let mut buffer = Vec::new();
574            a.write_compressed(&mut buffer).unwrap();
575            let out = Gt::read_compressed(std::io::Cursor::new(buffer)).unwrap();
576            assert_eq!(a, out);
577        }
578    }
579
580    #[test]
581    fn gt_subgroup() {
582        let mut rng = XorShiftRng::from_seed([
583            0x59, 0x62, 0xbe, 0x5d, 0x76, 0x3d, 0x31, 0x8d, 0x17, 0xdb, 0x37, 0x32, 0x54, 0x06,
584            0xbc, 0xe5,
585        ]);
586        let p = G1Projective::random(&mut rng).to_affine();
587        let q = G2Projective::random(&mut rng).to_affine();
588        let a = crate::pairing(&p, &q);
589        assert!(a.is_in_subgroup());
590    }
591}