1
   2
   3
   4
   5
   6
   7
   8
   9
  10
  11
  12
  13
  14
  15
  16
  17
  18
  19
  20
  21
  22
  23
  24
  25
  26
  27
  28
  29
  30
  31
  32
  33
  34
  35
  36
  37
  38
  39
  40
  41
  42
  43
  44
  45
  46
  47
  48
  49
  50
  51
  52
  53
  54
  55
  56
  57
  58
  59
  60
  61
  62
  63
  64
  65
  66
  67
  68
  69
  70
  71
  72
  73
  74
  75
  76
  77
  78
  79
  80
  81
  82
  83
  84
  85
  86
  87
  88
  89
  90
  91
  92
  93
  94
  95
  96
  97
  98
  99
 100
 101
 102
 103
 104
 105
 106
 107
 108
 109
 110
 111
 112
 113
 114
 115
 116
 117
 118
 119
 120
 121
 122
 123
 124
 125
 126
 127
 128
 129
 130
 131
 132
 133
 134
 135
 136
 137
 138
 139
 140
 141
 142
 143
 144
 145
 146
 147
 148
 149
 150
 151
 152
 153
 154
 155
 156
 157
 158
 159
 160
 161
 162
 163
 164
 165
 166
 167
 168
 169
 170
 171
 172
 173
 174
 175
 176
 177
 178
 179
 180
 181
 182
 183
 184
 185
 186
 187
 188
 189
 190
 191
 192
 193
 194
 195
 196
 197
 198
 199
 200
 201
 202
 203
 204
 205
 206
 207
 208
 209
 210
 211
 212
 213
 214
 215
 216
 217
 218
 219
 220
 221
 222
 223
 224
 225
 226
 227
 228
 229
 230
 231
 232
 233
 234
 235
 236
 237
 238
 239
 240
 241
 242
 243
 244
 245
 246
 247
 248
 249
 250
 251
 252
 253
 254
 255
 256
 257
 258
 259
 260
 261
 262
 263
 264
 265
 266
 267
 268
 269
 270
 271
 272
 273
 274
 275
 276
 277
 278
 279
 280
 281
 282
 283
 284
 285
 286
 287
 288
 289
 290
 291
 292
 293
 294
 295
 296
 297
 298
 299
 300
 301
 302
 303
 304
 305
 306
 307
 308
 309
 310
 311
 312
 313
 314
 315
 316
 317
 318
 319
 320
 321
 322
 323
 324
 325
 326
 327
 328
 329
 330
 331
 332
 333
 334
 335
 336
 337
 338
 339
 340
 341
 342
 343
 344
 345
 346
 347
 348
 349
 350
 351
 352
 353
 354
 355
 356
 357
 358
 359
 360
 361
 362
 363
 364
 365
 366
 367
 368
 369
 370
 371
 372
 373
 374
 375
 376
 377
 378
 379
 380
 381
 382
 383
 384
 385
 386
 387
 388
 389
 390
 391
 392
 393
 394
 395
 396
 397
 398
 399
 400
 401
 402
 403
 404
 405
 406
 407
 408
 409
 410
 411
 412
 413
 414
 415
 416
 417
 418
 419
 420
 421
 422
 423
 424
 425
 426
 427
 428
 429
 430
 431
 432
 433
 434
 435
 436
 437
 438
 439
 440
 441
 442
 443
 444
 445
 446
 447
 448
 449
 450
 451
 452
 453
 454
 455
 456
 457
 458
 459
 460
 461
 462
 463
 464
 465
 466
 467
 468
 469
 470
 471
 472
 473
 474
 475
 476
 477
 478
 479
 480
 481
 482
 483
 484
 485
 486
 487
 488
 489
 490
 491
 492
 493
 494
 495
 496
 497
 498
 499
 500
 501
 502
 503
 504
 505
 506
 507
 508
 509
 510
 511
 512
 513
 514
 515
 516
 517
 518
 519
 520
 521
 522
 523
 524
 525
 526
 527
 528
 529
 530
 531
 532
 533
 534
 535
 536
 537
 538
 539
 540
 541
 542
 543
 544
 545
 546
 547
 548
 549
 550
 551
 552
 553
 554
 555
 556
 557
 558
 559
 560
 561
 562
 563
 564
 565
 566
 567
 568
 569
 570
 571
 572
 573
 574
 575
 576
 577
 578
 579
 580
 581
 582
 583
 584
 585
 586
 587
 588
 589
 590
 591
 592
 593
 594
 595
 596
 597
 598
 599
 600
 601
 602
 603
 604
 605
 606
 607
 608
 609
 610
 611
 612
 613
 614
 615
 616
 617
 618
 619
 620
 621
 622
 623
 624
 625
 626
 627
 628
 629
 630
 631
 632
 633
 634
 635
 636
 637
 638
 639
 640
 641
 642
 643
 644
 645
 646
 647
 648
 649
 650
 651
 652
 653
 654
 655
 656
 657
 658
 659
 660
 661
 662
 663
 664
 665
 666
 667
 668
 669
 670
 671
 672
 673
 674
 675
 676
 677
 678
 679
 680
 681
 682
 683
 684
 685
 686
 687
 688
 689
 690
 691
 692
 693
 694
 695
 696
 697
 698
 699
 700
 701
 702
 703
 704
 705
 706
 707
 708
 709
 710
 711
 712
 713
 714
 715
 716
 717
 718
 719
 720
 721
 722
 723
 724
 725
 726
 727
 728
 729
 730
 731
 732
 733
 734
 735
 736
 737
 738
 739
 740
 741
 742
 743
 744
 745
 746
 747
 748
 749
 750
 751
 752
 753
 754
 755
 756
 757
 758
 759
 760
 761
 762
 763
 764
 765
 766
 767
 768
 769
 770
 771
 772
 773
 774
 775
 776
 777
 778
 779
 780
 781
 782
 783
 784
 785
 786
 787
 788
 789
 790
 791
 792
 793
 794
 795
 796
 797
 798
 799
 800
 801
 802
 803
 804
 805
 806
 807
 808
 809
 810
 811
 812
 813
 814
 815
 816
 817
 818
 819
 820
 821
 822
 823
 824
 825
 826
 827
 828
 829
 830
 831
 832
 833
 834
 835
 836
 837
 838
 839
 840
 841
 842
 843
 844
 845
 846
 847
 848
 849
 850
 851
 852
 853
 854
 855
 856
 857
 858
 859
 860
 861
 862
 863
 864
 865
 866
 867
 868
 869
 870
 871
 872
 873
 874
 875
 876
 877
 878
 879
 880
 881
 882
 883
 884
 885
 886
 887
 888
 889
 890
 891
 892
 893
 894
 895
 896
 897
 898
 899
 900
 901
 902
 903
 904
 905
 906
 907
 908
 909
 910
 911
 912
 913
 914
 915
 916
 917
 918
 919
 920
 921
 922
 923
 924
 925
 926
 927
 928
 929
 930
 931
 932
 933
 934
 935
 936
 937
 938
 939
 940
 941
 942
 943
 944
 945
 946
 947
 948
 949
 950
 951
 952
 953
 954
 955
 956
 957
 958
 959
 960
 961
 962
 963
 964
 965
 966
 967
 968
 969
 970
 971
 972
 973
 974
 975
 976
 977
 978
 979
 980
 981
 982
 983
 984
 985
 986
 987
 988
 989
 990
 991
 992
 993
 994
 995
 996
 997
 998
 999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
use crate::fields::{const_fq, FieldElement, Fq, Fq12, Fq2, Fr, fq2_nonresidue};
use crate::arith::U256;
use core::{fmt, ops::{Add, Mul, Neg, Sub}};
use rand::Rng;
use alloc::vec::Vec;
#[cfg(test)]
use alloc::vec;

// This is the NAF version of ate_loop_count. Entries are all mod 4, so 3 = -1
// n.b. ate_loop_count = 0x19d797039be763ba8
//                     = 11001110101111001011100000011100110111110011101100011101110101000
//       (naf version) = 11010003030003010300300000100301003000030100030300100030030101000
// We skip the first element (1) as we would need to skip over it in the main loop
const ATE_LOOP_COUNT_NAF : [u8; 64] = [1,0,1,0,0,0,3,0,3,0,0,0,3,0,1,0,3,0,0,3,0,0,0,0,0,1,0,0,3,0,1,0,0,3,0,0,0,0,3,0,1,0,0,0,3,0,3,0,0,1,0,0,0,3,0,0,3,0,1,0,1,0,0,0];

pub trait GroupElement
    : Sized
    + Copy
    + Clone
    + PartialEq
    + Eq
    + fmt::Debug
    + Add<Output = Self>
    + Sub<Output = Self>
    + Neg<Output = Self>
    + Mul<Fr, Output = Self> {
    fn zero() -> Self;
    fn one() -> Self;
    fn random<R: Rng>(rng: &mut R) -> Self;
    fn is_zero(&self) -> bool;
    fn double(&self) -> Self;
}

pub trait GroupParams: Sized + fmt::Debug {
    type Base: FieldElement;

    fn name() -> &'static str;
    fn one() -> G<Self>;
    fn coeff_b() -> Self::Base;
    fn check_order() -> bool {
        false
    }
}

#[repr(C)]
pub struct G<P: GroupParams> {
    x: P::Base,
    y: P::Base,
    z: P::Base,
}

impl<P: GroupParams> G<P> {
    pub fn new(x: P::Base, y: P::Base, z: P::Base) -> Self {
        G { x: x, y: y, z: z }
    }

    pub fn x(&self) -> &P::Base {
        &self.x
    }

    pub fn x_mut(&mut self) -> &mut P::Base {
        &mut self.x
    }

    pub fn y(&self) -> &P::Base {
        &self.y
    }

    pub fn y_mut(&mut self) -> &mut P::Base {
        &mut self.y
    }

    pub fn z(&self) -> &P::Base {
        &self.z
    }

    pub fn z_mut(&mut self) -> &mut P::Base {
        &mut self.z
    }
}

#[derive(Debug)]
pub struct AffineG<P: GroupParams> {
    x: P::Base,
    y: P::Base,
}

#[derive(Debug)]
pub enum Error {
    NotOnCurve,
    NotInSubgroup,
}

impl<P: GroupParams> AffineG<P> {
    pub fn new(x: P::Base, y: P::Base) -> Result<Self, Error> {
        if y.squared() == (x.squared() * x) + P::coeff_b() {
            if P::check_order() {
                let p: G<P> = G {
                    x: x,
                    y: y,
                    z: P::Base::one(),
                };

                if (p * (-Fr::one())) + p != G::zero() {
                    return Err(Error::NotInSubgroup);
                }
            }

            Ok(AffineG { x: x, y: y })
        } else {
            Err(Error::NotOnCurve)
        }
    }

    pub fn x(&self) -> &P::Base {
        &self.x
    }

    pub fn x_mut(&mut self) -> &mut P::Base {
        &mut self.x
    }

    pub fn y(&self) -> &P::Base {
        &self.y
    }

    pub fn y_mut(&mut self) -> &mut P::Base {
        &mut self.y
    }
}

impl<P: GroupParams> PartialEq for AffineG<P> {
    fn eq(&self, other: &Self) -> bool {
        self.x == other.x && self.y == other.y
    }
}

impl<P: GroupParams> Eq for AffineG<P> {}

impl<P: GroupParams> fmt::Debug for G<P> {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{}({:?}, {:?}, {:?})", P::name(), self.x, self.y, self.z)
    }
}

impl<P: GroupParams> Clone for G<P> {
    fn clone(&self) -> Self {
        G {
            x: self.x,
            y: self.y,
            z: self.z,
        }
    }
}

impl<P: GroupParams> Copy for G<P> {}

impl<P: GroupParams> Clone for AffineG<P> {
    fn clone(&self) -> Self {
        AffineG {
            x: self.x,
            y: self.y,
        }
    }
}

impl<P: GroupParams> Copy for AffineG<P> {}

impl<P: GroupParams> PartialEq for G<P> {
    fn eq(&self, other: &Self) -> bool {
        if self.is_zero() {
            return other.is_zero();
        }

        if other.is_zero() {
            return false;
        }

        let z1_squared = self.z.squared();
        let z2_squared = other.z.squared();

        if self.x * z2_squared != other.x * z1_squared {
            return false;
        }

        let z1_cubed = self.z * z1_squared;
        let z2_cubed = other.z * z2_squared;

        if self.y * z2_cubed != other.y * z1_cubed {
            return false;
        }

        return true;
    }
}
impl<P: GroupParams> Eq for G<P> {}

impl<P: GroupParams> G<P> {
    pub fn to_affine(&self) -> Option<AffineG<P>> {
        if self.z.is_zero() {
            None
        } else if self.z == P::Base::one() {
            Some(AffineG {
                x: self.x,
                y: self.y,
            })
        } else {
            let zinv = self.z.inverse().unwrap();
            let zinv_squared = zinv.squared();

            Some(AffineG {
                x: self.x * zinv_squared,
                y: self.y * (zinv_squared * zinv),
            })
        }
    }
}

impl<P: GroupParams> AffineG<P> {
    pub fn to_jacobian(&self) -> G<P> {
        G {
            x: self.x,
            y: self.y,
            z: P::Base::one(),
        }
    }
}

impl<P: GroupParams> GroupElement for G<P> {
    fn zero() -> Self {
        G {
            x: P::Base::zero(),
            y: P::Base::one(),
            z: P::Base::zero(),
        }
    }

    fn one() -> Self {
        P::one()
    }

    fn random<R: Rng>(rng: &mut R) -> Self {
        P::one() * Fr::random(rng)
    }

    fn is_zero(&self) -> bool {
        self.z.is_zero()
    }

    fn double(&self) -> Self {
        let a = self.x.squared();
        let b = self.y.squared();
        let c = b.squared();
        let mut d = (self.x + b).squared() - a - c;
        d = d + d;
        let e = a + a + a;
        let f = e.squared();
        let x3 = f - (d + d);
        let mut eight_c = c + c;
        eight_c = eight_c + eight_c;
        eight_c = eight_c + eight_c;
        let y1z1 = self.y * self.z;

        G {
            x: x3,
            y: e * (d - x3) - eight_c,
            z: y1z1 + y1z1,
        }
    }
}

impl<P: GroupParams> Mul<Fr> for G<P> {
    type Output = G<P>;

    fn mul(self, other: Fr) -> G<P> {
        let mut res = G::zero();
        let mut found_one = false;

        for i in U256::from(other).bits() {
            if found_one {
                res = res.double();
            }

            if i {
                found_one = true;
                res = res + self;
            }
        }

        res
    }
}

impl<P: GroupParams> Add<G<P>> for G<P> {
    type Output = G<P>;

    fn add(self, other: G<P>) -> G<P> {
        if self.is_zero() {
            return other;
        }

        if other.is_zero() {
            return self;
        }

        let z1_squared = self.z.squared();
        let z2_squared = other.z.squared();
        let u1 = self.x * z2_squared;
        let u2 = other.x * z1_squared;
        let z1_cubed = self.z * z1_squared;
        let z2_cubed = other.z * z2_squared;
        let s1 = self.y * z2_cubed;
        let s2 = other.y * z1_cubed;

        if u1 == u2 && s1 == s2 {
            self.double()
        } else {
            let h = u2 - u1;
            let s2_minus_s1 = s2 - s1;
            let i = (h + h).squared();
            let j = h * i;
            let r = s2_minus_s1 + s2_minus_s1;
            let v = u1 * i;
            let s1_j = s1 * j;
            let x3 = r.squared() - j - (v + v);

            G {
                x: x3,
                y: r * (v - x3) - (s1_j + s1_j),
                z: ((self.z + other.z).squared() - z1_squared - z2_squared) * h,
            }
        }
    }
}

impl<P: GroupParams> Neg for G<P> {
    type Output = G<P>;

    fn neg(self) -> G<P> {
        if self.is_zero() {
            self
        } else {
            G {
                x: self.x,
                y: -self.y,
                z: self.z,
            }
        }
    }
}

impl<P: GroupParams> Neg for AffineG<P> {
    type Output = AffineG<P>;

    fn neg(self) -> AffineG<P> {
        AffineG {
            x: self.x,
            y: -self.y,
        }
    }
}

impl<P: GroupParams> Sub<G<P>> for G<P> {
    type Output = G<P>;

    fn sub(self, other: G<P>) -> G<P> {
        self + (-other)
    }
}

#[derive(Debug)]
pub struct G1Params;

impl GroupParams for G1Params {
    type Base = Fq;

    fn name() -> &'static str {
        "G1"
    }

    fn one() -> G<Self> {
        G {
            x: Fq::one(),
            y: const_fq([
                0xa6ba871b8b1e1b3a,
                0x14f1d651eb8e167b,
                0xccdd46def0f28c58,
                0x1c14ef83340fbe5e,
            ]),
            z: Fq::one(),
        }
    }

    fn coeff_b() -> Fq {
        const_fq([
            0x7a17caa950ad28d7,
            0x1f6ac17ae15521b9,
            0x334bea4e696bd284,
            0x2a1f6744ce179d8e,
        ])
    }
}

pub type G1 = G<G1Params>;

pub type AffineG1 = AffineG<G1Params>;

#[derive(Debug)]
pub struct G2Params;

impl GroupParams for G2Params {
    type Base = Fq2;

    fn name() -> &'static str {
        "G2"
    }

    fn one() -> G<Self> {
        G {
            x: Fq2::new(
                const_fq([
                    0x8e83b5d102bc2026,
                    0xdceb1935497b0172,
                    0xfbb8264797811adf,
                    0x19573841af96503b,
                ]),
                const_fq([
                    0xafb4737da84c6140,
                    0x6043dd5a5802d8c4,
                    0x09e950fc52a02f86,
                    0x14fef0833aea7b6b,
                ]),
            ),
            y: Fq2::new(
                const_fq([
                    0x619dfa9d886be9f6,
                    0xfe7fd297f59e9b78,
                    0xff9e1a62231b7dfe,
                    0x28fd7eebae9e4206,
                ]),
                const_fq([
                    0x64095b56c71856ee,
                    0xdc57f922327d3cbb,
                    0x55f935be33351076,
                    0x0da4a0e693fd6482,
                ]),
            ),
            z: Fq2::one(),
        }
    }

    fn coeff_b() -> Fq2 {
        Fq2::new(
            const_fq([
                0x3bf938e377b802a8,
                0x020b1b273633535d,
                0x26b7edf049755260,
                0x2514c6324384a86d,
            ]),
            const_fq([
                0x38e7ecccd1dcff67,
                0x65f0b37d93ce0d3e,
                0xd749d0dd22ac00aa,
                0x0141b9ce4a688d4d,
            ]),
        )
    }

    fn check_order() -> bool {
        true
    }
}

pub type G2 = G<G2Params>;

pub type AffineG2 = AffineG<G2Params>;

#[cfg(test)]
mod tests;

#[test]
fn test_g1() {
    tests::group_trials::<G1>();
}

#[test]
fn test_g2() {
    tests::group_trials::<G2>();
}

#[test]
fn test_affine_jacobian_conversion() {
    let rng = &mut ::rand::thread_rng();

    assert!(G1::zero().to_affine().is_none());
    assert!(G2::zero().to_affine().is_none());

    for _ in 0..1000 {
        let a = G1::one() * Fr::random(rng);
        let b = a.to_affine().unwrap();
        let c = b.to_jacobian();

        assert_eq!(a, c);
    }

    for _ in 0..1000 {
        let a = G2::one() * Fr::random(rng);
        let b = a.to_affine().unwrap();
        let c = b.to_jacobian();

        assert_eq!(a, c);
    }
}

#[inline]
fn twist() -> Fq2 {
    fq2_nonresidue()
}

#[inline]
fn two_inv() -> Fq {
    const_fq([
        9781510331150239090,
        15059239858463337189,
        10331104244869713732,
        2249375503248834476,
    ])
}

#[inline]
fn twist_mul_by_q_x() -> Fq2 {
    Fq2::new(
        const_fq([
            13075984984163199792,
            3782902503040509012,
            8791150885551868305,
            1825854335138010348,
        ]),
        const_fq([
            7963664994991228759,
            12257807996192067905,
            13179524609921305146,
            2767831111890561987,
        ]),
    )
}

#[inline]
fn twist_mul_by_q_y() -> Fq2 {
    Fq2::new(
        const_fq([
            16482010305593259561,
            13488546290961988299,
            3578621962720924518,
            2681173117283399901,
        ]),
        const_fq([
            11661927080404088775,
            553939530661941723,
            7860678177968807019,
            3208568454732775116,
        ]),
    )
}

#[derive(PartialEq, Eq)]
pub struct EllCoeffs {
    pub ell_0: Fq2,
    pub ell_vw: Fq2,
    pub ell_vv: Fq2,
}

#[derive(PartialEq, Eq)]
pub struct G2Precomp {
    pub q: AffineG<G2Params>,
    pub coeffs: Vec<EllCoeffs>,
}

impl G2Precomp {
    pub fn miller_loop(&self, g1: &AffineG<G1Params>) -> Fq12 {
        let mut f = Fq12::one();

        let mut idx = 0;

        for i in ATE_LOOP_COUNT_NAF.iter() {
            let c = &self.coeffs[idx];
            idx += 1;
            f = f.squared()
                .mul_by_024(c.ell_0, c.ell_vw.scale(g1.y), c.ell_vv.scale(g1.x));

            if *i != 0 {
                let c = &self.coeffs[idx];
                idx += 1;
                f = f.mul_by_024(c.ell_0, c.ell_vw.scale(g1.y), c.ell_vv.scale(g1.x));
            }
        }

        let c = &self.coeffs[idx];
        idx += 1;
        f = f.mul_by_024(c.ell_0, c.ell_vw.scale(g1.y), c.ell_vv.scale(g1.x));

        let c = &self.coeffs[idx];
        f = f.mul_by_024(c.ell_0, c.ell_vw.scale(g1.y), c.ell_vv.scale(g1.x));

        f
    }
}

pub fn miller_loop_batch(g2_precomputes: &Vec<G2Precomp>, g1_vec: &Vec<AffineG<G1Params>>) -> Fq12 {
    let mut f = Fq12::one();

    let mut idx = 0;

    for i in ATE_LOOP_COUNT_NAF.iter() {
        f = f.squared();
        for (g2_precompute, g1) in g2_precomputes.iter().zip(g1_vec.iter()) {
            let c = &g2_precompute.coeffs[idx];
            f = f.mul_by_024(c.ell_0, c.ell_vw.scale(g1.y), c.ell_vv.scale(g1.x));
        }
        idx += 1;
        if *i != 0 {
            for (g2_precompute, g1) in g2_precomputes.iter().zip(g1_vec.iter()) {
                let c = &g2_precompute.coeffs[idx];
                f = f.mul_by_024(c.ell_0, c.ell_vw.scale(g1.y), c.ell_vv.scale(g1.x));
            }
            idx += 1;
        }
    }

    for (g2_precompute, g1) in g2_precomputes.iter().zip(g1_vec.iter()) {
        let c = &g2_precompute.coeffs[idx];
        f = f.mul_by_024(c.ell_0, c.ell_vw.scale(g1.y), c.ell_vv.scale(g1.x));
    }
    idx += 1;
    for (g2_precompute, g1) in g2_precomputes.iter().zip(g1_vec.iter()) {
        let c = &g2_precompute.coeffs[idx];
        f = f.mul_by_024(c.ell_0, c.ell_vw.scale(g1.y), c.ell_vv.scale(g1.x));
    }
    f
}

#[test]
fn test_miller_loop() {
    use crate::fields::Fq6;

    let g1 = G1::one()
        * Fr::from_str(
            "18097487326282793650237947474982649264364522469319914492172746413872781676",
        ).unwrap();
    let g2 = G2::one()
        * Fr::from_str(
            "20390255904278144451778773028944684152769293537511418234311120800877067946",
        ).unwrap();

    let g1_pre = g1.to_affine().unwrap();
    let g2_pre = g2.to_affine().unwrap().precompute();

    let gt = g2_pre.miller_loop(&g1_pre);

    let expected: Fq12 = Fq12::new(
            Fq6::new(
                Fq2::new(
                    Fq::new(U256([51910954035973319022896381997847359481, 49070349125448662928383548013678560320])).unwrap(),
                    Fq::new(U256([150594250655925940766158230906714822921, 45067780486977162411874315270532662559])).unwrap(),
                ),
                Fq2::new(
                    Fq::new(U256([293313211826787380313097274184299135668, 28033688961864567415258173424862015279])).unwrap(),
                    Fq::new(U256([167463228417728651969785007140185669229, 7077084888790581611350259269763958251])).unwrap(),
                ),
                Fq2::new(
                    Fq::new(U256([166574695108782631900870170909221310910, 36301755601680728879208628452507017454])).unwrap(),
                    Fq::new(U256([61790765844042689836493058059938629070, 8459680572251855304146082351314233167])).unwrap(),
                )
            ),
            Fq6::new(
                Fq2::new(
                    Fq::new(U256([274725556782132290265566702453516000786, 47645385003117491559484060631887523335])).unwrap(),
                    Fq::new(U256([218741759704184655717004970623859820160, 5768209145436844234600983552836237590])).unwrap(),
                ),
                Fq2::new(
                    Fq::new(U256([166365676746880051357185694330614395245, 44422629177536239628987108174157680084])).unwrap(),
                    Fq::new(U256([188797990739833756731082975171894736944, 643465180603364587407484249282263717])).unwrap(),
                ),
                Fq2::new(
                    Fq::new(U256([271144479861903489720584548513988144824, 10463758518630442972881156820224659715])).unwrap(),
                    Fq::new(U256([214759070354702766397810519515686065785, 63150584453541665372008601383729030318])).unwrap(),
                )
            )
        );
    assert_eq!(gt, expected);
}

impl AffineG<G2Params> {
    fn mul_by_q(&self) -> Self {
        AffineG {
            x: twist_mul_by_q_x() * self.x.frobenius_map(1),
            y: twist_mul_by_q_y() * self.y.frobenius_map(1),
        }
    }

    pub fn precompute(&self) -> G2Precomp {
        let mut r = self.to_jacobian();

        let mut coeffs = Vec::with_capacity(102);

        let q_neg = self.neg();
        for i in ATE_LOOP_COUNT_NAF.iter() {
            coeffs.push(r.doubling_step_for_flipped_miller_loop());

            if *i == 1 {
                coeffs.push(r.mixed_addition_step_for_flipped_miller_loop(self));
            }
            if *i == 3 {
                coeffs.push(r.mixed_addition_step_for_flipped_miller_loop(&q_neg));
            }
        }
        let q1 = self.mul_by_q();
        let q2 = -(q1.mul_by_q());

        coeffs.push(r.mixed_addition_step_for_flipped_miller_loop(&q1));
        coeffs.push(r.mixed_addition_step_for_flipped_miller_loop(&q2));

        G2Precomp {
            q: *self,
            coeffs: coeffs,
        }
    }
}

impl G2 {
    fn mixed_addition_step_for_flipped_miller_loop(
        &mut self,
        base: &AffineG<G2Params>,
    ) -> EllCoeffs {
        let d = self.x - self.z * base.x;
        let e = self.y - self.z * base.y;
        let f = d.squared();
        let g = e.squared();
        let h = d * f;
        let i = self.x * f;
        let j = self.z * g + h - (i + i);

        self.x = d * j;
        self.y = e * (i - j) - h * self.y;
        self.z = self.z * h;

        EllCoeffs {
            ell_0: twist() * (e * base.x - d * base.y),
            ell_vv: e.neg(),
            ell_vw: d,
        }
    }

    fn doubling_step_for_flipped_miller_loop(&mut self) -> EllCoeffs {
        let a = (self.x * self.y).scale(two_inv());
        let b = self.y.squared();
        let c = self.z.squared();
        let d = c + c + c;
        let e = G2Params::coeff_b() * d;
        let f = e + e + e;
        let g = (b + f).scale(two_inv());
        let h = (self.y + self.z).squared() - (b + c);
        let i = e - b;
        let j = self.x.squared();
        let e_sq = e.squared();

        self.x = a * (b - f);
        self.y = g.squared() - (e_sq + e_sq + e_sq);
        self.z = b * h;

        EllCoeffs {
            ell_0: twist() * i,
            ell_vw: h.neg(),
            ell_vv: j + j + j,
        }
    }
}

#[test]
fn test_prepared_g2() {
    let g2 = G2::one()
        * Fr::from_str(
            "20390255904278144451778773028944684152769293537511418234311120800877067946",
        ).unwrap();

    let g2_p = g2.to_affine().unwrap().precompute();

    let expected_g2_p = G2Precomp {
        q: AffineG {
            x: Fq2::new(
                Fq::new(U256([286108132425575157823044300810193365120, 40955922372263072279965766273066553545])).unwrap(),
                Fq::new(U256([51787456028377068413742525949644831103, 18727496177066613612143648473641354138])).unwrap()
            ),
            y: Fq2::new(
                Fq::new(U256([105235307093009526756443741536710213857, 56697136982397507595538316605420403515])).unwrap(),
                Fq::new(U256([329285813328264858787093963282305459902, 40620233227497131789363095249389429612])).unwrap()
            )
        },
        coeffs: vec![
            EllCoeffs { ell_0: Fq2::new(Fq::new(U256([145915152615018094207274265949237364577, 7720188347992263845704223037750674843])).unwrap(), Fq::new(U256([1642602221754736777334297091439332137, 43455737427254701713230610624368790806])).unwrap()), ell_vw: Fq2::new(Fq::new(U256([192300176042178641247789718482757908684, 15253255261571338892647481759611271748])).unwrap(), Fq::new(U256([84481530492606440649863882423335628050, 47407062771372090504997924471673219553])).unwrap()), ell_vv: Fq2::new(Fq::new(U256([232941999867492381013751617901190279960, 33161335727531633874118709394498248694])).unwrap(), Fq::new(U256([205159091107726234051689046255658875895, 18784742195738106358087607099254122817])).unwrap()) },
            EllCoeffs { ell_0: Fq2::new(Fq::new(U256([44903717410722925336080339155272113598, 2432148164440313442445265360131108750])).unwrap(), Fq::new(U256([115058944839679151514931430187558137193, 19913547532675326995005163334665928687])).unwrap()), ell_vw: Fq2::new(Fq::new(U256([162225342243516818284353803749727397649, 2902812998987289816105769597190405892])).unwrap(), Fq::new(U256([280882305470180330735174149091034017688, 45000755139204939118391532933575113126])).unwrap()), ell_vv: Fq2::new(Fq::new(U256([81439170073299243743458979294198176188, 8269567577349210062003854855333109246])).unwrap(), Fq::new(U256([229588642195598894684486216729209468602, 50120631775179825949138513731773141671])).unwrap()) },
            EllCoeffs { ell_0: Fq2::new(Fq::new(U256([62571086408084349816772432183764211635, 50233214695378430835319156584725532968])).unwrap(), Fq::new(U256([187531012616569709709565459550531626708, 25399966597901061874032352675540749982])).unwrap()), ell_vw: Fq2::new(Fq::new(U256([125202410210363842487043120679844927964, 32132029049461004568346717732357115368])).unwrap(), Fq::new(U256([40047990517661970904320150752358301419, 29547330482153702904408059941549019061])).unwrap()), ell_vv: Fq2::new(Fq::new(U256([72725441636162608820485881978479394755, 4335829118654814821890758647864621059])).unwrap(), Fq::new(U256([3808530996119731737716737121624391430, 14840119309897919310042357130477699760])).unwrap()) },
            EllCoeffs { ell_0: Fq2::new(Fq::new(U256([230923838046707456761202857863657685779, 30663581424308030115949734185266015957])).unwrap(), Fq::new(U256([75804784823545649980980649059385057066, 40783805432413650791639865054248573254])).unwrap()), ell_vw: Fq2::new(Fq::new(U256([220403174695454806701510356109644664970, 61694366506643321007589345694791625235])).unwrap(), Fq::new(U256([111050616308506297168851165107648527276, 29953858315513175963460036136567176100])).unwrap()), ell_vv: Fq2::new(Fq::new(U256([339360714692284035291265180647668192278, 51066683198194726198561068977822786062])).unwrap(), Fq::new(U256([20532264324124291215869821260466642056, 53772344434441015474139364759811842825])).unwrap()) },
            EllCoeffs { ell_0: Fq2::new(Fq::new(U256([53390632401876602131528763251836289052, 7039696239984002955384050250007606842])).unwrap(), Fq::new(U256([120756148133186395786471763371515340851, 62560956052144901655506280432592733452])).unwrap()), ell_vw: Fq2::new(Fq::new(U256([308576049186755490218939550330371718607, 60158766280438165965298754059752443327])).unwrap(), Fq::new(U256([36519660971413271753359371158311247925, 33166369001790145692925310192829466902])).unwrap()), ell_vv: Fq2::new(Fq::new(U256([178902296336418821251231963767960641976, 54039139529656231144797458475123200306])).unwrap(), Fq::new(U256([327709063289359470903820254941214044104, 29921523827667065809753199129798632038])).unwrap()) },
            EllCoeffs { ell_0: Fq2::new(Fq::new(U256([293947345738284310028696202770439717312, 23629199470233866978708839571836323911])).unwrap(), Fq::new(U256([270255405451020462647135094276398320650, 10214498461032217021941670870340839535])).unwrap()), ell_vw: Fq2::new(Fq::new(U256([159737597162744224577379609113239040296, 42414292048147664393527940182644080820])).unwrap(), Fq::new(U256([319166235401593060755720596880844332847, 23942967675619615172318074973192451617])).unwrap()), ell_vv: Fq2::new(Fq::new(U256([322582854267025287594765649802803266076, 43815838906866149475744535388480851056])).unwrap(), Fq::new(U256([194731262617323725381582694441938082298, 6318043913263416116808511289860376787])).unwrap()) },
            EllCoeffs { ell_0: Fq2::new(Fq::new(U256([340258202167813930211448229264749672038, 49309561646299510355653200516855272117])).unwrap(), Fq::new(U256([314009511129121341897071879080163927420, 46733418159091100849346822998375177924])).unwrap()), ell_vw: Fq2::new(Fq::new(U256([296127326349185873181435771941022502705, 27092339993712929447599322475752229815])).unwrap(), Fq::new(U256([14090774041595571810302865485069892149, 9416959189726308679959984066033622764])).unwrap()), ell_vv: Fq2::new(Fq::new(U256([331192186309261416893996962356905314610, 6133490180262551399580874298514378708])).unwrap(), Fq::new(U256([52146008553438513232574426823996261862, 51692389190081578424424830745703412620])).unwrap()) },
            EllCoeffs { ell_0: Fq2::new(Fq::new(U256([304886724958645105936240400377782311329, 24452949520921718326419149662293231897])).unwrap(), Fq::new(U256([191644523888771552477578834463319391872, 21762407072532056308389337796258587948])).unwrap()), ell_vw: Fq2::new(Fq::new(U256([131875657381595539531276734057336373872, 46145756403556903236477737806904884925])).unwrap(), Fq::new(U256([15513558811446045405402043661936649706, 6164660913794769127674854991137805768])).unwrap()), ell_vv: Fq2::new(Fq::new(U256([37628908878688989915703006228108158588, 32333568458099455743134394992114761192])).unwrap(), Fq::new(U256([310028653933656868572053471966984499925, 50927285178735796712220443510555951806])).unwrap()) },
            EllCoeffs { ell_0: Fq2::new(Fq::new(U256([323579290279633699483736616036574636320, 26462458255852549637810201830373030787])).unwrap(), Fq::new(U256([212356178148004645362110693008071812466, 7147771462841335914859391157142419270])).unwrap()), ell_vw: Fq2::new(Fq::new(U256([33793486114682505251748396905247663179, 12971208030293312519168903516345112146])).unwrap(), Fq::new(U256([105470215179403498779276452125929471612, 13243999928262666388833873861601164323])).unwrap()), ell_vv: Fq2::new(Fq::new(U256([336774965397927402479055329499391144750, 14092422716202439890370586466555281245])).unwrap(), Fq::new(U256([128524990965480598411024491536330888971, 16339349487558512101009117692791080358])).unwrap()) },
            EllCoeffs { ell_0: Fq2::new(Fq::new(U256([308807709363499650659552856561459119304, 20549512613498060437905021698882254003])).unwrap(), Fq::new(U256([296831825878801592442122280385770051837, 12999356604250035352468537408632919011])).unwrap()), ell_vw: Fq2::new(Fq::new(U256([6416952549311962005690662486801715420, 6562024922514861810686738146979657369])).unwrap(), Fq::new(U256([208034701293282569885312328681897330015, 32565296199581814532888690261728578093])).unwrap()), ell_vv: Fq2::new(Fq::new(U256([158821090683358871468006667057662171383, 43639936137708212270934870661514052516])).unwrap(), Fq::new(U256([273462267971100585683746592136866263017, 33686004986452254374790215788772597950])).unwrap()) },
            EllCoeffs { ell_0: Fq2::new(Fq::new(U256([239050458738191424179259026508283200850, 59033062634877072212375745660456082761])).unwrap(), Fq::new(U256([42735232027681689333255156554135804585, 3645324355274008091830665999900460711])).unwrap()), ell_vw: Fq2::new(Fq::new(U256([108736755942765297679539162065532976047, 53005795603772553534072950762621113576])).unwrap(), Fq::new(U256([161143215360647768117453097687908448132, 47995551574176504820342605188594172079])).unwrap()), ell_vv: Fq2::new(Fq::new(U256([6657347986892301669776633258074701543, 11902481233202623619409687758723915636])).unwrap(), Fq::new(U256([222956595276083444090603007056080975801, 36382879863546362219365726153085767156])).unwrap()) },
            EllCoeffs { ell_0: Fq2::new(Fq::new(U256([313682093040115002688525404999246872446, 42956501626298358982264420981703353471])).unwrap(), Fq::new(U256([16910327688907897736932577003378075753, 1359671211117678708781834746493106650])).unwrap()), ell_vw: Fq2::new(Fq::new(U256([6643221945930263241861939865579251413, 53936065521860576156419486432192485437])).unwrap(), Fq::new(U256([309056291241266457640134736864478816427, 37247957207987268075661977484645707642])).unwrap()), ell_vv: Fq2::new(Fq::new(U256([222157301260856896664619946160442713805, 49516872999212369072183141842522894943])).unwrap(), Fq::new(U256([205513791444704603844975928062124661076, 57468270224470125473802906477068197830])).unwrap()) },
            EllCoeffs { ell_0: Fq2::new(Fq::new(U256([8285963000933560101573593109316673578, 14786122523410515396563958444109027710])).unwrap(), Fq::new(U256([122158657553570962061566147876423950513, 52139940048286310424786112221915773798])).unwrap()), ell_vw: Fq2::new(Fq::new(U256([334881138980927901869290199603379387015, 25426740963889929124688873103192284934])).unwrap(), Fq::new(U256([15041987314727689102877759056668987416, 53279076961030137249765180937167164331])).unwrap()), ell_vv: Fq2::new(Fq::new(U256([219851721339103004718273461131600868626, 36871882814964809764289908414024625695])).unwrap(), Fq::new(U256([131269942067631553063094618567097759738, 53556533347945713183394135477679452243])).unwrap()) },
            EllCoeffs { ell_0: Fq2::new(Fq::new(U256([303322002694907044040904386256578801369, 47818402373691165133570276489205539247])).unwrap(), Fq::new(U256([23840328058016663665484464556433078317, 57137757685631244994429644684135564308])).unwrap()), ell_vw: Fq2::new(Fq::new(U256([313347268925459844355339673983448992252, 63059125438530014597733264556246943889])).unwrap(), Fq::new(U256([332951624867081114956044501931839262097, 21446965953220512177611603903918604526])).unwrap()), ell_vv: Fq2::new(Fq::new(U256([43308962429154740283739513584277671586, 36726788540078024416606994278261486896])).unwrap(), Fq::new(U256([201462004284693135670392448782496773481, 37361169637624833127302583281977615660])).unwrap()) },
            EllCoeffs { ell_0: Fq2::new(Fq::new(U256([23765318454509223469921591210222962901, 44990033695398054388153021046567582164])).unwrap(), Fq::new(U256([31305053791054889490552967007711680166, 27103171064041391806294729422128985527])).unwrap()), ell_vw: Fq2::new(Fq::new(U256([276126480562835962332188871122724756846, 16736136826306680287985573076016202896])).unwrap(), Fq::new(U256([4414319697794720805306194236550991584, 62019193108561650400059767099940339027])).unwrap()), ell_vv: Fq2::new(Fq::new(U256([264396677944255651896935058261233808058, 56759185289081036894005736531633938393])).unwrap(), Fq::new(U256([203628704629834831927134686796555271069, 48471469187143841926703236705753907861])).unwrap()) },
            EllCoeffs { ell_0: Fq2::new(Fq::new(U256([239115594351165984647685996782641428320, 44545149465284571473177119694335581490])).unwrap(), Fq::new(U256([213078788930221853484251159402089402698, 16763897914971151155224113448725254022])).unwrap()), ell_vw: Fq2::new(Fq::new(U256([114008097138551618719252989046711208100, 59881147407771812558336453348641148192])).unwrap(), Fq::new(U256([73126778233281675579715222201222232223, 38826300453213723195010574430400377747])).unwrap()), ell_vv: Fq2::new(Fq::new(U256([241042448915505783580706972981273226980, 56417003146210087305012985177072964806])).unwrap(), Fq::new(U256([169177487653248888684114813249577243086, 32380354283087538368804702239086564787])).unwrap()) },
            EllCoeffs { ell_0: Fq2::new(Fq::new(U256([158019800920508883600584012748066500495, 26898142140797843980908437809386681141])).unwrap(), Fq::new(U256([306502797874464726523541130582372927512, 24906313146242057911294566839234262300])).unwrap()), ell_vw: Fq2::new(Fq::new(U256([285760763932996537001129740504018931266, 61805280449958461312688755177468072593])).unwrap(), Fq::new(U256([135545131264687967800977791145788692523, 58139772512775079850670752006123037712])).unwrap()), ell_vv: Fq2::new(Fq::new(U256([70329035009259499088740735594160571447, 39714506367478198523957872987679138518])).unwrap(), Fq::new(U256([140879819390078281356249388675648473246, 43854973736398482797037510623636581696])).unwrap()) },
            EllCoeffs { ell_0: Fq2::new(Fq::new(U256([38618198701391964654224360704900902096, 38909748519501972274789640400408937483])).unwrap(), Fq::new(U256([180923031305898977193921630254659948144, 27460338595509200370239569387614912648])).unwrap()), ell_vw: Fq2::new(Fq::new(U256([70057505024221161194312385639309591716, 18962666975131076741596858262806841593])).unwrap(), Fq::new(U256([214987899942827030987983709303642547609, 7354730872853772995022889424236171557])).unwrap()), ell_vv: Fq2::new(Fq::new(U256([915509610432328048899540473086774484, 34629556430715177067961193484853677166])).unwrap(), Fq::new(U256([210548802885276746342162088554780886285, 59946451357630926783981469963615555141])).unwrap()) },
            EllCoeffs { ell_0: Fq2::new(Fq::new(U256([192587880731165909190755427373555634019, 10976459312692912490911357520717353871])).unwrap(), Fq::new(U256([167196051583193420579377374296283755878, 49401460467894024879692901111127305932])).unwrap()), ell_vw: Fq2::new(Fq::new(U256([280565789968090605174946518022513606221, 37294483328987043033011583201807512274])).unwrap(), Fq::new(U256([43162969061744994465193287354736460751, 22121767444104992380234797550707180209])).unwrap()), ell_vv: Fq2::new(Fq::new(U256([253802905125777094707425424576407998829, 61686094369188045360896737944218403088])).unwrap(), Fq::new(U256([182974256593102716284343824242596548794, 7848028085342627691056691434137748535])).unwrap()) },
            EllCoeffs { ell_0: Fq2::new(Fq::new(U256([84344031984214336466355469526466525167, 27093607600401090432285884248864703045])).unwrap(), Fq::new(U256([149328956099164822232925589118520984554, 2238105695079930209978670615986667957])).unwrap()), ell_vw: Fq2::new(Fq::new(U256([164524345257317963111443719298495629688, 60976929030698527695079882388987310770])).unwrap(), Fq::new(U256([306918226890341242065585311833805224781, 55722896155980110701397483880645840750])).unwrap()), ell_vv: Fq2::new(Fq::new(U256([93128913154066328529254602049518310767, 33148637643432199376041913783211199036])).unwrap(), Fq::new(U256([232426184416524951250597496934702636450, 43720696322889678655980069182616949616])).unwrap()) },
            EllCoeffs { ell_0: Fq2::new(Fq::new(U256([102654491688396809766739755399745558766, 38605113922396003219627575078289843591])).unwrap(), Fq::new(U256([19593962899181063954599229676070513364, 60350965404501231355169027639339453296])).unwrap()), ell_vw: Fq2::new(Fq::new(U256([159636507003615123705820979675398042179, 54313579941440479276773225578829814974])).unwrap(), Fq::new(U256([119356164563490256235392395335237959869, 39953340372362730589797197026608570557])).unwrap()), ell_vv: Fq2::new(Fq::new(U256([63485981942258213001157530320762394972, 31200716913018312733656963741875248687])).unwrap(), Fq::new(U256([12902614517986449375080893856817237357, 18985985729109591831690040180758616083])).unwrap()) },
            EllCoeffs { ell_0: Fq2::new(Fq::new(U256([42308655177239821867892255001741444297, 48752963962065714616191625548108808])).unwrap(), Fq::new(U256([128521931437576558829282023068349026555, 41085812991756679343188097975462933419])).unwrap()), ell_vw: Fq2::new(Fq::new(U256([10035402786640614748259945360125292132, 38557266307605033249489727552659930268])).unwrap(), Fq::new(U256([261287166540169381393014929830322479654, 42514049292646924562828996896910004962])).unwrap()), ell_vv: Fq2::new(Fq::new(U256([56319885731071444245573249522306017699, 53577564523791470572591472840806985932])).unwrap(), Fq::new(U256([124520094647080351085028512262335797477, 34908241061842840630524194497782012074])).unwrap()) },
            EllCoeffs { ell_0: Fq2::new(Fq::new(U256([126634618253919797596379978688751079772, 52145187142657016821034369446677208400])).unwrap(), Fq::new(U256([331622287147942589908508350657315302543, 33514242158733995981408830992771427380])).unwrap()), ell_vw: Fq2::new(Fq::new(U256([120940515437555419164907643421042686361, 17509271053436215565086784812667241751])).unwrap(), Fq::new(U256([115780741523467210253488502554959480585, 32352495928341192491861819481034439523])).unwrap()), ell_vv: Fq2::new(Fq::new(U256([3619315753849520875056567968398971518, 24276420029996570458336249792816120452])).unwrap(), Fq::new(U256([211494135803191326215038551543926917828, 43184746342435212339590913052197238451])).unwrap()) },
            EllCoeffs { ell_0: Fq2::new(Fq::new(U256([299074873552726234274845052736068685064, 17100980781941753342716208137755315141])).unwrap(), Fq::new(U256([132717845842422445158092928854959930670, 44970515850781790514922204901459656647])).unwrap()), ell_vw: Fq2::new(Fq::new(U256([216867730679061124743178802618784487478, 57695717306148677588162001948381364803])).unwrap(), Fq::new(U256([270218131380365097755408360718075538527, 57477676726794026534017337223493668494])).unwrap()), ell_vv: Fq2::new(Fq::new(U256([165279544895097377741304748618110436848, 20425223618131822879513743747480509436])).unwrap(), Fq::new(U256([74939006713979852471760437945055491248, 13009460391442509409368588074771283312])).unwrap()) },
            EllCoeffs { ell_0: Fq2::new(Fq::new(U256([66759827770349370888034213457290956508, 9005955800938032302448930728853089346])).unwrap(), Fq::new(U256([132741330683696165661450722498530220360, 7366906791443646993778243377846220425])).unwrap()), ell_vw: Fq2::new(Fq::new(U256([253540438895289112523278690834655986794, 6116090159416232554407405788526490758])).unwrap(), Fq::new(U256([30829083910163512740218467627901622426, 55620601051021580444864016142195538972])).unwrap()), ell_vv: Fq2::new(Fq::new(U256([250454368142865329167214266233567636554, 62357304312342518984982585510065736692])).unwrap(), Fq::new(U256([188759902529624961292182168219378957599, 46090313440960353890072197786322881372])).unwrap()) },
            EllCoeffs { ell_0: Fq2::new(Fq::new(U256([273524456438634570870477083331972978477, 9134704483036112456254510849911524401])).unwrap(), Fq::new(U256([67478468272116511526567438494812739067, 25741563822514337717816912706935692500])).unwrap()), ell_vw: Fq2::new(Fq::new(U256([116593352960394465976476340555861894686, 41112534697095493718894382902662572752])).unwrap(), Fq::new(U256([120743240887113383410964567068981375379, 38598788085595749947162720656886407615])).unwrap()), ell_vv: Fq2::new(Fq::new(U256([312187803926537773957821532171857044863, 19370576499152094014450666672812249236])).unwrap(), Fq::new(U256([240827398452930199439299722869808431504, 49509218268486878713361004562421178491])).unwrap()) },
            EllCoeffs { ell_0: Fq2::new(Fq::new(U256([290888165778751654611450374067314903262, 37392757737237359968235356696866333927])).unwrap(), Fq::new(U256([252204440608865557722718990121112495320, 55975286037277175922442767802285953079])).unwrap()), ell_vw: Fq2::new(Fq::new(U256([270039432299546232993592658461203399951, 60656209400809596126720306545905143183])).unwrap(), Fq::new(U256([285724268091089729569589544862374658568, 31963711474983717334851248248346512047])).unwrap()), ell_vv: Fq2::new(Fq::new(U256([3884505529011790540935411500477268151, 13223063167340752683657032423528371760])).unwrap(), Fq::new(U256([159305752483642939978115090933392332739, 7151624608160356980135319367119889070])).unwrap()) },
            EllCoeffs { ell_0: Fq2::new(Fq::new(U256([272676514700352082082833454466828720931, 25063412692149354657238579769317314943])).unwrap(), Fq::new(U256([293621566718932528694833049559922653569, 45585304922174719737960365050252747127])).unwrap()), ell_vw: Fq2::new(Fq::new(U256([54835393883782456964222308618682542993, 37980634195943561261187934261099825702])).unwrap(), Fq::new(U256([59974402342646963556531661011524247325, 35696051147487701670582425632191066303])).unwrap()), ell_vv: Fq2::new(Fq::new(U256([259456005420154607862207518414346338959, 26130920677564288823895676956971235383])).unwrap(), Fq::new(U256([116130095060524137327607346021153250193, 10807286360635090367088053443993301396])).unwrap()) },
            EllCoeffs { ell_0: Fq2::new(Fq::new(U256([49392952615759746255132704409838115781, 47072336582571607445080612120532993304])).unwrap(), Fq::new(U256([176567394438796320529775330793638953568, 28322815468520297894419530294472748019])).unwrap()), ell_vw: Fq2::new(Fq::new(U256([80922979608932498062547214350892680237, 39454405473363814575369344229244849847])).unwrap(), Fq::new(U256([91401220192762005776280059066692026476, 15657465297119889794043496656394015039])).unwrap()), ell_vv: Fq2::new(Fq::new(U256([66039352326985904039959490473173702089, 53019684969492620390541557616482571566])).unwrap(), Fq::new(U256([241714503488139707849386627025007386925, 26075944201145781355709956722513636060])).unwrap()) },
            EllCoeffs { ell_0: Fq2::new(Fq::new(U256([282595284318374809009793390206732488298, 45748314157085313197379566607025256839])).unwrap(), Fq::new(U256([270827203295353120306891953779231995484, 2061164657869992110070755346638481586])).unwrap()), ell_vw: Fq2::new(Fq::new(U256([122772591180024083766405253289036218458, 22573428243213343555967649475581629995])).unwrap(), Fq::new(U256([335389040951271989935196787085438096490, 19801736168278388699669751501679386348])).unwrap()), ell_vv: Fq2::new(Fq::new(U256([339013185026928567100232707644090060309, 19772463825247091662201186215246641510])).unwrap(), Fq::new(U256([4804384610649622501550615795150497154, 23912076036945346828195974346279787457])).unwrap()) },
            EllCoeffs { ell_0: Fq2::new(Fq::new(U256([214353258010296277687883233632193811097, 5219474783704014986415232032947372339])).unwrap(), Fq::new(U256([79624479932762034850342328421303227111, 56787485322265889115274972394242117773])).unwrap()), ell_vw: Fq2::new(Fq::new(U256([184200025840089548499823017798379240877, 48381517011877532244879858559402925202])).unwrap(), Fq::new(U256([107565933978602112957892502247036378430, 45988606090744912079186414974711188065])).unwrap()), ell_vv: Fq2::new(Fq::new(U256([110593764674444376346243358175616189408, 35712801833060284055338218769830573236])).unwrap(), Fq::new(U256([151909254176585476474604484796159407774, 47158276852552318249500199498589330637])).unwrap()) },
            EllCoeffs { ell_0: Fq2::new(Fq::new(U256([322742519015404082194067614300530448854, 59892171075434557254836830922854923608])).unwrap(), Fq::new(U256([32065386578298932256366582168915019527, 26752577181504287044232901657950904669])).unwrap()), ell_vw: Fq2::new(Fq::new(U256([174803672897784440584915958306641383601, 41091446500573741637560530719439634516])).unwrap(), Fq::new(U256([209867082646016733058415245870200692364, 62329211607238784395268250532556435532])).unwrap()), ell_vv: Fq2::new(Fq::new(U256([306258598889945544178464082933019504953, 54721033386841216374878966418312958051])).unwrap(), Fq::new(U256([60603024880089835430212078876452798438, 46149383487322374768616169869877411190])).unwrap()) },
            EllCoeffs { ell_0: Fq2::new(Fq::new(U256([217026022228859342220342856144348485905, 57413523264998851221438276699831981764])).unwrap(), Fq::new(U256([124187198815039194427087238512103681551, 5443344443330946400667796667492661757])).unwrap()), ell_vw: Fq2::new(Fq::new(U256([329826946228202959132718054863501702187, 48520872455619090037740041830322500851])).unwrap(), Fq::new(U256([84830400552123989859098307569998867522, 48734171266537512281517863759978814030])).unwrap()), ell_vv: Fq2::new(Fq::new(U256([223121539165880683751973996773385673012, 25883364325220384256766392563193579368])).unwrap(), Fq::new(U256([299825902106679285294937343573961127742, 60789713632874624396137039249203910802])).unwrap()) },
            EllCoeffs { ell_0: Fq2::new(Fq::new(U256([256516074149138488025765885988857744378, 14065986710590565469822003697004376208])).unwrap(), Fq::new(U256([234079806620020815932181140203001517322, 23367862088314049165195572485764983176])).unwrap()), ell_vw: Fq2::new(Fq::new(U256([196437028129330799836797952742061629213, 48672430434915053819319219832418834574])).unwrap(), Fq::new(U256([325437437809364731749456582922626432887, 48166506837989600793252437510600468487])).unwrap()), ell_vv: Fq2::new(Fq::new(U256([159308630334191903727834955555702331135, 62436879262294424125843661083177310796])).unwrap(), Fq::new(U256([36766573815532717355926225449599862176, 45422312033048180700369981630678147962])).unwrap()) },
            EllCoeffs { ell_0: Fq2::new(Fq::new(U256([242582781609674647644149440448686667005, 38749506523024653240132899426202698768])).unwrap(), Fq::new(U256([175781266358339061379292613713370680613, 18670420110220807116103915283744134837])).unwrap()), ell_vw: Fq2::new(Fq::new(U256([330308428247032904787707230285980997411, 57858504610626027326513601505745660271])).unwrap(), Fq::new(U256([137930876960001856905683673437505764610, 51655613850377410017968900339454298922])).unwrap()), ell_vv: Fq2::new(Fq::new(U256([313120701922067853802383887994976410392, 45975781155280265622735990880466050470])).unwrap(), Fq::new(U256([335042362116388769047290965043326509112, 60559196975175152299532260702584038813])).unwrap()) },
            EllCoeffs { ell_0: Fq2::new(Fq::new(U256([168129439507943874458360855948848921748, 49297302702020077848733359456178701712])).unwrap(), Fq::new(U256([269089028431861389215030548077466532939, 39860705713699087717989916919780377864])).unwrap()), ell_vw: Fq2::new(Fq::new(U256([13752419440807207645064267622022228455, 58622973202646179187733854793315255353])).unwrap(), Fq::new(U256([214305013520593573583121266790878302778, 38633624705329558986475404541639527124])).unwrap()), ell_vv: Fq2::new(Fq::new(U256([149534517263071862076005511513725705381, 33923419970735724678578488530397069094])).unwrap(), Fq::new(U256([52240222008682291290000393139971676649, 13553337484321113195355299279586301175])).unwrap()) },
            EllCoeffs { ell_0: Fq2::new(Fq::new(U256([272900340425393991345320871434898164480, 38006376657960211098766747755417992280])).unwrap(), Fq::new(U256([261987172512287267237176115671442326513, 8128050373266945066047548013741537435])).unwrap()), ell_vw: Fq2::new(Fq::new(U256([262663552198367871113333688806502970707, 16999213382693802141971886256404278345])).unwrap(), Fq::new(U256([82246501115823748400993076962512494264, 9513280785331020628797944368204762745])).unwrap()), ell_vv: Fq2::new(Fq::new(U256([68225301292307439157467124956340428751, 23984526331358356113154550150873972672])).unwrap(), Fq::new(U256([322267803776402446716363035932937663522, 7643551605057856674267205902314118400])).unwrap()) },
            EllCoeffs { ell_0: Fq2::new(Fq::new(U256([226851502497426762485425012577563658166, 33899046058167810318161894461593654070])).unwrap(), Fq::new(U256([106137231667287271021611800141669940221, 38201180579053106362613109983520968531])).unwrap()), ell_vw: Fq2::new(Fq::new(U256([75276265667487657641526123222570637963, 18790008468742777802887427569490235644])).unwrap(), Fq::new(U256([236348579709234796216705203173042603149, 51488118889105930334369568118204095717])).unwrap()), ell_vv: Fq2::new(Fq::new(U256([70847827090593073015786004614483673839, 3475230874891506896203634678143642692])).unwrap(), Fq::new(U256([8314731801106602343291488634630259831, 3521008420700564106163167660007668706])).unwrap()) },
            EllCoeffs { ell_0: Fq2::new(Fq::new(U256([92354268952660546820822603858288251059, 19778715140389467112147527814081030657])).unwrap(), Fq::new(U256([329385425284714431570594794971018721246, 60587846563454266929636649850787073194])).unwrap()), ell_vw: Fq2::new(Fq::new(U256([288633840471857542459144118014949000217, 51605528849039220650036727626483827846])).unwrap(), Fq::new(U256([251176740648089198193669671101929833609, 48200613470991338988108533743364275499])).unwrap()), ell_vv: Fq2::new(Fq::new(U256([329193234492690486040808567065220826749, 21378867659438236011740749197423248550])).unwrap(), Fq::new(U256([285725508694128806244839238836446595834, 16111887615812423492270406970991867951])).unwrap()) },
            EllCoeffs { ell_0: Fq2::new(Fq::new(U256([152431519506548878061194990588057484057, 49184327280433466363535994383523121304])).unwrap(), Fq::new(U256([176897083100713735838942116510311567986, 37010995217032206230292299420436992452])).unwrap()), ell_vw: Fq2::new(Fq::new(U256([130722401240365261778198584122678322362, 19293082368638790806988358431355209633])).unwrap(), Fq::new(U256([336032502313799023638100083369289206306, 31063427179065887680551186789065013379])).unwrap()), ell_vv: Fq2::new(Fq::new(U256([313119215174514058940660103265063450959, 25103967051083955792570154250792420707])).unwrap(), Fq::new(U256([31520718700780462452153016807890400365, 3961179668062303572693429066523266858])).unwrap()) },
            EllCoeffs { ell_0: Fq2::new(Fq::new(U256([36667789591031625960240713279402427923, 5637784774941607531109129433890086293])).unwrap(), Fq::new(U256([286537786902526732905389646221547183400, 56733382321156059969864849624810202662])).unwrap()), ell_vw: Fq2::new(Fq::new(U256([318803650206508773198520172514605937449, 26727846312556415159035648438885744059])).unwrap(), Fq::new(U256([296466727057750056890238712141155450998, 27938872020780344351123790536034462604])).unwrap()), ell_vv: Fq2::new(Fq::new(U256([324800882000687209140271724886698306645, 44039571306759586933457684739133508444])).unwrap(), Fq::new(U256([47608245752536926992583482090528259005, 47831283599118732387313994410846427415])).unwrap()) },
            EllCoeffs { ell_0: Fq2::new(Fq::new(U256([234409386597337888556308771844488457344, 12956567414802650084616113864217789370])).unwrap(), Fq::new(U256([54897214305565100948138138335662741789, 15577398627840234231085370089137424292])).unwrap()), ell_vw: Fq2::new(Fq::new(U256([133034516621699526624516176710921958663, 21900941110792896857560826682314104825])).unwrap(), Fq::new(U256([91786723574963827227124861422083359642, 15631994703072556950984703411138300334])).unwrap()), ell_vv: Fq2::new(Fq::new(U256([61994842824519647915165015802348883298, 3606506510538278544457628965895793792])).unwrap(), Fq::new(U256([176676927874382884508332121131239871282, 40115823781693004388411447312641595826])).unwrap()) },
            EllCoeffs { ell_0: Fq2::new(Fq::new(U256([16486756791893393092802670694010735350, 51213785425023838862657045286249924761])).unwrap(), Fq::new(U256([127374475998848055784786954745585192546, 61927128032062116041960763307324749312])).unwrap()), ell_vw: Fq2::new(Fq::new(U256([57390628646817442883586335851238696952, 1896752620999083872406878608493454725])).unwrap(), Fq::new(U256([221112496886805666932828220912733522283, 2796002105546343313342619098780470642])).unwrap()), ell_vv: Fq2::new(Fq::new(U256([126113866998638086000522716026151108800, 58788515162206661637618804912085907736])).unwrap(), Fq::new(U256([19079621171649876430748555077055696995, 46826087543975014201271501761421031441])).unwrap()) },
            EllCoeffs { ell_0: Fq2::new(Fq::new(U256([287527947651847089840085422221128698763, 3282429064220775329203352366444259855])).unwrap(), Fq::new(U256([307065141447870398567299540185758941637, 48193280250277647112409463651594786821])).unwrap()), ell_vw: Fq2::new(Fq::new(U256([39874018308944266937776827490734641649, 37791912819895325896728519088674532352])).unwrap(), Fq::new(U256([11619188332176047954604673895207823813, 12983193006960451462302777892755566431])).unwrap()), ell_vv: Fq2::new(Fq::new(U256([76045219079383487030610001932731241273, 16472513195230803643409094386807276517])).unwrap(), Fq::new(U256([244195048886093761135684126358283143738, 19479715688038787901591036330200598003])).unwrap()) },
            EllCoeffs { ell_0: Fq2::new(Fq::new(U256([10563934263428977131610186732868968998, 14179527485555674710937849675581267906])).unwrap(), Fq::new(U256([257836314204976892854269567329818300595, 50763620022812667001295845381267741649])).unwrap()), ell_vw: Fq2::new(Fq::new(U256([160125271831790681583846356333205971645, 38979573670559551140762745448476278558])).unwrap(), Fq::new(U256([258822435332825467396267733276444939733, 19361127335094998017975714923507638644])).unwrap()), ell_vv: Fq2::new(Fq::new(U256([5086484109635987997951722709219418410, 11993539151823720275736182499510363133])).unwrap(), Fq::new(U256([171423009691627362399764794909459189507, 20404136165000279368092981943358482669])).unwrap()) },
            EllCoeffs { ell_0: Fq2::new(Fq::new(U256([249500614137833023351305910264605008487, 42310425481082032237299673828774704531])).unwrap(), Fq::new(U256([253496613223900195266106558972296162241, 19046240361322320039639197826388768369])).unwrap()), ell_vw: Fq2::new(Fq::new(U256([72263736846331351747703353269055941107, 19969702258117494907412570199791763548])).unwrap(), Fq::new(U256([126173921786290239044307538655562024455, 18006158804314393556918633600879748967])).unwrap()), ell_vv: Fq2::new(Fq::new(U256([16201000244836956196708576962189993382, 32135871927111623648702296852432295139])).unwrap(), Fq::new(U256([186963801770492707056456887595022261993, 2392014174637078162701730926122882282])).unwrap()) },
            EllCoeffs { ell_0: Fq2::new(Fq::new(U256([8922998024015454766590940715881630670, 55567280047347356367358986337109964651])).unwrap(), Fq::new(U256([313173491437683437471834863194199707492, 3438176908844635046641649505356812159])).unwrap()), ell_vw: Fq2::new(Fq::new(U256([276513541262028309851175912267910407247, 11212571122829858982221117734299851084])).unwrap(), Fq::new(U256([259509606552160006313745402323745013955, 43114059504717558659877851383190095613])).unwrap()), ell_vv: Fq2::new(Fq::new(U256([28098698558455079499553496395853067214, 2999821831816440733771768849050005165])).unwrap(), Fq::new(U256([334260605709723946550826958885726232933, 11843826148467149350077605819950881153])).unwrap()) },
            EllCoeffs { ell_0: Fq2::new(Fq::new(U256([283455530950531157108702282345810919101, 52521643621580203161269969006017546759])).unwrap(), Fq::new(U256([118200123115287742628646618978307449884, 14795263733303838767894982171654042612])).unwrap()), ell_vw: Fq2::new(Fq::new(U256([132641709504834974317245159550618583253, 28570895840581787308435502085893978558])).unwrap(), Fq::new(U256([229949535634549796967397304164923056337, 42352202734287711800564772982472784839])).unwrap()), ell_vv: Fq2::new(Fq::new(U256([128199924683494278990710814519750517837, 58638050185358930355922065483565476451])).unwrap(), Fq::new(U256([4192016302378839786647752861109219112, 61114929591783829703998838288847230811])).unwrap()) },
            EllCoeffs { ell_0: Fq2::new(Fq::new(U256([113754079486581376183180167710451964178, 25998717875687967363117010408717972365])).unwrap(), Fq::new(U256([133697001740616163002906224555636440440, 23546896556625148713807775573505304385])).unwrap()), ell_vw: Fq2::new(Fq::new(U256([227150749988479615370826356819617461792, 41368715815045829029754124051658999245])).unwrap(), Fq::new(U256([222514455614853854819390285868145864687, 27579121989432504016710179821357885225])).unwrap()), ell_vv: Fq2::new(Fq::new(U256([128433415390196263739951350076595027296, 55636438757722652255615208065796848545])).unwrap(), Fq::new(U256([94671524825895380554049635623924644741, 9791488862788864605119131922310440121])).unwrap()) },
            EllCoeffs { ell_0: Fq2::new(Fq::new(U256([153881172058568095361353223261899310845, 17156484263879687903526208855029043640])).unwrap(), Fq::new(U256([192077265925835218260989605323461142787, 32318605747689071742310867751273886645])).unwrap()), ell_vw: Fq2::new(Fq::new(U256([317408271159096505221916390262196469787, 49717575675851229633286204770028665263])).unwrap(), Fq::new(U256([230095008714744652782426759714312292445, 34768179553549400629211217553610554586])).unwrap()), ell_vv: Fq2::new(Fq::new(U256([259609023305192462223688164733847327699, 55498873698881710203435569648274404478])).unwrap(), Fq::new(U256([256325550082466559964506871761831536884, 46357653853657184428683736043713888406])).unwrap()) },
            EllCoeffs { ell_0: Fq2::new(Fq::new(U256([174577378801081039438668831531067795857, 53157547858752109585449689851969780390])).unwrap(), Fq::new(U256([82151408345597494862089436182070514285, 16466308473185906030465208465158400278])).unwrap()), ell_vw: Fq2::new(Fq::new(U256([245839213042247079917412337301921026922, 22457019238909031929740892448482221879])).unwrap(), Fq::new(U256([109513130235370254599453293800543710331, 47936140417415756744920707247895308699])).unwrap()), ell_vv: Fq2::new(Fq::new(U256([10776161146456696103487000724300150234, 21638563281306406703950273508675616623])).unwrap(), Fq::new(U256([207085916216710302318982426148546767798, 7534717897684081051796825732586695012])).unwrap()) },
            EllCoeffs { ell_0: Fq2::new(Fq::new(U256([155690435801319682239901337205468120840, 57696234800836220244083348626584391674])).unwrap(), Fq::new(U256([126757665472044690642504190674454727065, 27689844381974512633117281405461511493])).unwrap()), ell_vw: Fq2::new(Fq::new(U256([111371524430469273469505773333501881358, 41915381107432591576214180621073813739])).unwrap(), Fq::new(U256([8591307386286176660318558848651389629, 19322060932256059989553597776338707006])).unwrap()), ell_vv: Fq2::new(Fq::new(U256([34843642275668259465089406915935512474, 25199006933559492770352887141455099490])).unwrap(), Fq::new(U256([10645844381812821922686446509476566858, 35688555865712593739042131731320400759])).unwrap()) },
            EllCoeffs { ell_0: Fq2::new(Fq::new(U256([189947043026848705532544415056376972054, 16798314122488336019972329871173393651])).unwrap(), Fq::new(U256([308591971026665862143792543801958855960, 31580607213628921122716343407923691193])).unwrap()), ell_vw: Fq2::new(Fq::new(U256([163093806147198705684115256712150030567, 61044717475711501197927838344963107641])).unwrap(), Fq::new(U256([307478741843426518207523544103444625011, 32813498165003357356133773951397928550])).unwrap()), ell_vv: Fq2::new(Fq::new(U256([95291548361985054080716711033104720817, 32740343556226865497613504752972186774])).unwrap(), Fq::new(U256([99656074525833092595857684423204299816, 52556239373424263650635019541397500598])).unwrap()) },
            EllCoeffs { ell_0: Fq2::new(Fq::new(U256([131443621763425639580622258813874957855, 17694455882904396881053896673232601550])).unwrap(), Fq::new(U256([57787363176533727883752817750279248594, 1322856562657561354884935596241784834])).unwrap()), ell_vw: Fq2::new(Fq::new(U256([41788280636884607828680584839495219344, 51762903848994175839707598487572362464])).unwrap(), Fq::new(U256([327908654890111464339978492292915303796, 47344360741808824501224988176645616993])).unwrap()), ell_vv: Fq2::new(Fq::new(U256([263873593879772810642440946076120656306, 60210233792451977377881584251511840173])).unwrap(), Fq::new(U256([77405826008973124400288647208797749719, 53019697808811506999139211676029205579])).unwrap()) },
            EllCoeffs { ell_0: Fq2::new(Fq::new(U256([254578463202394703187741702356839561011, 21096342408485278014732435848864790919])).unwrap(), Fq::new(U256([230473548197181635567135409745417796610, 22147822186055995413422331229074559071])).unwrap()), ell_vw: Fq2::new(Fq::new(U256([336878015160301245042116518989872047941, 3472301140759760061593082376347546380])).unwrap(), Fq::new(U256([16684778019642037346902548590480489922, 63174559216409847644418505532384053845])).unwrap()), ell_vv: Fq2::new(Fq::new(U256([7942031074055265340884029455787976403, 46511448608441944387405531466341650044])).unwrap(), Fq::new(U256([201615416576767918339868727158350994412, 64255187168173681561527383354144167567])).unwrap()) },
            EllCoeffs { ell_0: Fq2::new(Fq::new(U256([243308496406996226443557446925779452296, 45849597498959240205941430238383669623])).unwrap(), Fq::new(U256([48147614977633577164856356428453305437, 24480032415168951986931189038013914221])).unwrap()), ell_vw: Fq2::new(Fq::new(U256([74830884455458736654394458059429787198, 19093844097308332947749289150298249126])).unwrap(), Fq::new(U256([33643899737753431509498018706471379301, 1054576208141392093174173023229894557])).unwrap()), ell_vv: Fq2::new(Fq::new(U256([25010096200987042473393926604632958161, 36666296164531908118513449509789076579])).unwrap(), Fq::new(U256([33471717379111372311065514803349918045, 39155528164590001712332400960607038189])).unwrap()) },
            EllCoeffs { ell_0: Fq2::new(Fq::new(U256([130247467451091212947023092388792369109, 5581090585326037371051920053369586280])).unwrap(), Fq::new(U256([322360327312842929444256001314179239601, 39584478520709259399389327249557202594])).unwrap()), ell_vw: Fq2::new(Fq::new(U256([109138162516199992788447200019766811785, 47525457821584643247627414608651841972])).unwrap(), Fq::new(U256([76309968110600274074720854748516338453, 54739068402347163521228676077893779893])).unwrap()), ell_vv: Fq2::new(Fq::new(U256([42180409310074737345249452446852603163, 4374831491626595184699821621506201010])).unwrap(), Fq::new(U256([130057601620482713644106051600317093239, 12010679381934251249551375242240386988])).unwrap()) },
            EllCoeffs { ell_0: Fq2::new(Fq::new(U256([203271863519350650976092478448210829293, 52275506975977654460248547356331731574])).unwrap(), Fq::new(U256([87019891938199989232789187109647269328, 44030460834732836505939430240249354505])).unwrap()), ell_vw: Fq2::new(Fq::new(U256([110171920943726136564495503552694726540, 18110786866783187141705660513534498930])).unwrap(), Fq::new(U256([86561692963802460817471572747090778194, 16449770553500671507593674575394564582])).unwrap()), ell_vv: Fq2::new(Fq::new(U256([221876651023252602014468911202182579931, 20555077542463658872780769250687782117])).unwrap(), Fq::new(U256([121263539082468326970413896423548540112, 61907715120666399953423277772754945448])).unwrap()) },
            EllCoeffs { ell_0: Fq2::new(Fq::new(U256([290357192776207049084050762947655748975, 57738753410091871200747406135207764151])).unwrap(), Fq::new(U256([135358190114239175182066780052123869069, 56081507988571629614406051210751609147])).unwrap()), ell_vw: Fq2::new(Fq::new(U256([195622544431419990666685034787625747326, 54874770374981417815900652767969731611])).unwrap(), Fq::new(U256([6243075893789260529022348241785972764, 56521196313521047823704039910006760241])).unwrap()), ell_vv: Fq2::new(Fq::new(U256([243495103440406073534289072917982313090, 57589106164518822864160786869003317813])).unwrap(), Fq::new(U256([235848100009165241948327269818774314127, 37511141642844469126769577281189903937])).unwrap()) },
            EllCoeffs { ell_0: Fq2::new(Fq::new(U256([78592775623001882471215007061407141105, 34941191764695857355641077076235001298])).unwrap(), Fq::new(U256([267356444067983260493350102628306009780, 28391440678450825886400816194304466425])).unwrap()), ell_vw: Fq2::new(Fq::new(U256([315751120363918319477358949808863604966, 2531298718421943078485957285990819438])).unwrap(), Fq::new(U256([242692025957487623878146464744369629015, 52949313651228011397330521709355671746])).unwrap()), ell_vv: Fq2::new(Fq::new(U256([223701370913329345484604486557612973051, 34658421329859066183602046886271845301])).unwrap(), Fq::new(U256([239281725801587867757289553693045231533, 14055606950515704853984812857124180593])).unwrap()) },
            EllCoeffs { ell_0: Fq2::new(Fq::new(U256([321292144065398009067550865125623060600, 18828918489183113618746569726487918435])).unwrap(), Fq::new(U256([44953266506250560322305758112518632685, 12818035770624926325618902943072758802])).unwrap()), ell_vw: Fq2::new(Fq::new(U256([198712001500603483304766131655796332238, 37755493054877351200992330081944773222])).unwrap(), Fq::new(U256([203494901390506520787336076745404879355, 9719695048988798612029815523720059188])).unwrap()), ell_vv: Fq2::new(Fq::new(U256([324082713664478232334150476671698670935, 57968755532718074766231332303127747698])).unwrap(), Fq::new(U256([172967914786650717580471091827836918517, 39836011047291872323399953583350854813])).unwrap()) },
            EllCoeffs { ell_0: Fq2::new(Fq::new(U256([104693003047177326696685168159983773290, 21362453846755563999603040010467525220])).unwrap(), Fq::new(U256([56374029098225126238873495696162227687, 61374137561202742141169238350854689065])).unwrap()), ell_vw: Fq2::new(Fq::new(U256([158941255788517659316631775720407312680, 2477940982651390357755438379586767771])).unwrap(), Fq::new(U256([212374631437158170731256215648153937637, 51786402687200191668238627015343248084])).unwrap()), ell_vv: Fq2::new(Fq::new(U256([67431426417348416607065438525684723676, 59371917376780773574999923335758781547])).unwrap(), Fq::new(U256([329921395426288970367695061271959172005, 18586382421107856878975478269722973139])).unwrap()) },
            EllCoeffs { ell_0: Fq2::new(Fq::new(U256([207312856868420470351121539398556490708, 6749469271717320482346458817170377645])).unwrap(), Fq::new(U256([297463942398989836675066926378547228117, 42180709737629195771943741993025816145])).unwrap()), ell_vw: Fq2::new(Fq::new(U256([294946811578231549301710677543341218799, 20992621396953319301177509266206083412])).unwrap(), Fq::new(U256([334489506001987366917405458471890123881, 5644882162624043756459843063788196004])).unwrap()), ell_vv: Fq2::new(Fq::new(U256([339439573540481749394421794993912483199, 45246606008318294125455064627263238236])).unwrap(), Fq::new(U256([270454941633781906426514756020454116063, 13672349860189847846584888881208438638])).unwrap()) },
            EllCoeffs { ell_0: Fq2::new(Fq::new(U256([184109810967303467375292693011959132443, 22047527326459095654736206729881706277])).unwrap(), Fq::new(U256([306855976122543936818762044270256507301, 13817693020515802987708208420701920996])).unwrap()), ell_vw: Fq2::new(Fq::new(U256([18944943876649574380290951381571111173, 54681599103691370082120691137707858843])).unwrap(), Fq::new(U256([274481158071926596330090943889570839876, 27656594457831811691825162550052997369])).unwrap()), ell_vv: Fq2::new(Fq::new(U256([72068179718393541085353916977402777797, 23085120169523399820600846468009840798])).unwrap(), Fq::new(U256([39623239893391127442673085065505018616, 49714238177847283522861975755429582165])).unwrap()) },
            EllCoeffs { ell_0: Fq2::new(Fq::new(U256([326109292257788796875387755492793672492, 38017828106840559651298055936334805586])).unwrap(), Fq::new(U256([300970874956733001966255469483871058465, 61693366793354972449623979259561628170])).unwrap()), ell_vw: Fq2::new(Fq::new(U256([247117608403708963869106314083852845952, 6270392892469923670893298889464309224])).unwrap(), Fq::new(U256([85229127169148541816114014402805254942, 23692121466582475015916841187427174063])).unwrap()), ell_vv: Fq2::new(Fq::new(U256([322377160078698123442118813943355848949, 44790955831224275605828973863316334451])).unwrap(), Fq::new(U256([246121457999548771204074791900055840778, 36237050391238930724599650306620108395])).unwrap()) },
            EllCoeffs { ell_0: Fq2::new(Fq::new(U256([6479203335512729615298164829455402801, 18899857059703106109444283718983930606])).unwrap(), Fq::new(U256([307909459343553081188134777979054198524, 23104930848877581327335450138633822165])).unwrap()), ell_vw: Fq2::new(Fq::new(U256([117408687141894236273811591312978416850, 27064268635283187986411799061361560149])).unwrap(), Fq::new(U256([89156958054425667145216654832344296184, 28498155840544447742658664925302722255])).unwrap()), ell_vv: Fq2::new(Fq::new(U256([264148561516468171084137463578412456470, 56169366101528802291286278394614323282])).unwrap(), Fq::new(U256([80748178838045963455037482306103002135, 31627837956246452962075717178352705444])).unwrap()) },
            EllCoeffs { ell_0: Fq2::new(Fq::new(U256([228462396223758741175275124462032775241, 541217865154375519097785562072294622])).unwrap(), Fq::new(U256([147690209661782719555035340726175029695, 47332569057322758675859735205860987883])).unwrap()), ell_vw: Fq2::new(Fq::new(U256([332785070356059610611838282826287767824, 28411804506527712891888004500289036321])).unwrap(), Fq::new(U256([80347503246503526995863756906237840538, 40694963808412110626720215103350226072])).unwrap()), ell_vv: Fq2::new(Fq::new(U256([73246772632733278293170183313248743910, 51739954405360462951359314934075204478])).unwrap(), Fq::new(U256([323288654986485051471927026636004277374, 55328786519754064217639655113348868700])).unwrap()) },
            EllCoeffs { ell_0: Fq2::new(Fq::new(U256([107183915141555030394480811055835932411, 28743337205734819613101267731330261274])).unwrap(), Fq::new(U256([265627562010734120334801493421007962860, 21113833505065027987742094370311235794])).unwrap()), ell_vw: Fq2::new(Fq::new(U256([145390576010968264278778301175950417032, 25603093564950634850451680305056487937])).unwrap(), Fq::new(U256([171224041010792018070371064846900704811, 50628689539088695975060303147297926726])).unwrap()), ell_vv: Fq2::new(Fq::new(U256([181636860313080877557100227907605412643, 19106550849436726002022081116470620327])).unwrap(), Fq::new(U256([97037887458346176230370696042525047476, 38315199704917257726469887714210863756])).unwrap()) },
            EllCoeffs { ell_0: Fq2::new(Fq::new(U256([165755565692898412861884552154775259871, 23293168176203049828541858307098176114])).unwrap(), Fq::new(U256([111579679857180892572143097811395790681, 21526844626039124934190021704344618627])).unwrap()), ell_vw: Fq2::new(Fq::new(U256([335258764315672309726009417334449368453, 103404408330729181499563204707141002])).unwrap(), Fq::new(U256([191071126894978514432059671553769626880, 5078535341242500657674641815850178322])).unwrap()), ell_vv: Fq2::new(Fq::new(U256([72579254643315478022492189073789591450, 44167090301255006150406576419615402343])).unwrap(), Fq::new(U256([183377394594390630206602640102963629039, 23990882412966103396841140729654425350])).unwrap()) },
            EllCoeffs { ell_0: Fq2::new(Fq::new(U256([66679892701097417538832992488778691458, 58183623071000462877229489086029090922])).unwrap(), Fq::new(U256([294287188350011499287443971281382798030, 18151493196929461249661140690411179165])).unwrap()), ell_vw: Fq2::new(Fq::new(U256([74805142807574287323605988196524192535, 18100483139591925759594156937304667397])).unwrap(), Fq::new(U256([139120256621259665435015294709846943744, 33412960622211929574259219789625651855])).unwrap()), ell_vv: Fq2::new(Fq::new(U256([220224993980394484792559335583346867939, 59970476024877867633551960552156066815])).unwrap(), Fq::new(U256([165016476912320413641798011850430383400, 40087637389842239140931509158661317913])).unwrap()) },
            EllCoeffs { ell_0: Fq2::new(Fq::new(U256([26669149284356063486638395246213038984, 48932308059862148467989673144064276455])).unwrap(), Fq::new(U256([112017260916309235481572722060511554523, 1292432061742789600481111238179731344])).unwrap()), ell_vw: Fq2::new(Fq::new(U256([162967204317649679020730851544536806310, 32746563765578881184210706548921981702])).unwrap(), Fq::new(U256([337655071624981772776297963145482993016, 33145701869840355692613219913227327224])).unwrap()), ell_vv: Fq2::new(Fq::new(U256([116821007696021810040981824168902376385, 46826353416371587869648603174437608629])).unwrap(), Fq::new(U256([286148963257224477643539266261574758187, 20903220425259595580283778435979469601])).unwrap()) },
            EllCoeffs { ell_0: Fq2::new(Fq::new(U256([265822762895337622674318289864003560145, 62593533822281897460585212079268870737])).unwrap(), Fq::new(U256([180099245396770402139578639916315358651, 5289891249371566111607452004558281256])).unwrap()), ell_vw: Fq2::new(Fq::new(U256([215101732208902323596827687492609836402, 60151379040875297433910120987825047988])).unwrap(), Fq::new(U256([200376206977547885622799626268846642009, 36107786828287031009478318323095814795])).unwrap()), ell_vv: Fq2::new(Fq::new(U256([228632312969356396843036040954049410317, 29609317291386275502321244693464627541])).unwrap(), Fq::new(U256([250663071554126685512943663183005740350, 26707628083885320745488595167117920892])).unwrap()) },
            EllCoeffs { ell_0: Fq2::new(Fq::new(U256([167686658731994607869572641962208584698, 32951815034866853047881968952398296677])).unwrap(), Fq::new(U256([59732843077777643290110085014121270991, 19690792480201713304572701985281652804])).unwrap()), ell_vw: Fq2::new(Fq::new(U256([231501326469976058677041937508913323742, 58052555179693233540250119022219467367])).unwrap(), Fq::new(U256([268839178939407644460103390562050250791, 51244123519915889574699519125947948433])).unwrap()), ell_vv: Fq2::new(Fq::new(U256([15335721378364717505397406493265760457, 50543654003772338617069451450467156120])).unwrap(), Fq::new(U256([147668560194510325215853847922114742688, 63436405408432434981818242494869996954])).unwrap()) },
            EllCoeffs { ell_0: Fq2::new(Fq::new(U256([324251215335356818847634371978327094047, 53597895382917031159515977918983297046])).unwrap(), Fq::new(U256([336456284126450597413571076848285070208, 9834107003348358663468869495065030828])).unwrap()), ell_vw: Fq2::new(Fq::new(U256([4893147365574829346121011245181836636, 48500338802265501645481354399443896938])).unwrap(), Fq::new(U256([242843203647079174857073639694991742532, 39587639177217829196831837775949647839])).unwrap()), ell_vv: Fq2::new(Fq::new(U256([260649755517395052354543412976006056345, 41595453161552128661866319661777368912])).unwrap(), Fq::new(U256([186473459113497941493205881377276393540, 10232971634855792150439771169030969984])).unwrap()) },
            EllCoeffs { ell_0: Fq2::new(Fq::new(U256([153156583597535672033657311231041916285, 12870713391441384499488044032601826356])).unwrap(), Fq::new(U256([160011046903032247780660416127755862604, 59740658966179967701258870119449151446])).unwrap()), ell_vw: Fq2::new(Fq::new(U256([53781592683706300755113958689829175735, 5160246898475719650527746461036900035])).unwrap(), Fq::new(U256([127406077100448076503048857464807666048, 5027357614298608514576108334690214373])).unwrap()), ell_vv: Fq2::new(Fq::new(U256([123323183388079598137911866134337668968, 30831375761327507469744048674597209517])).unwrap(), Fq::new(U256([282757148554018853521114389661374143994, 41725892078174834143829265912887863334])).unwrap()) },
            EllCoeffs { ell_0: Fq2::new(Fq::new(U256([231142251602796484920728141361531696801, 43369687276260799996121293373394088506])).unwrap(), Fq::new(U256([193386979673076292922455602081629373614, 13517077812823754339540367661413555482])).unwrap()), ell_vw: Fq2::new(Fq::new(U256([32945933209167472962271089890685376310, 23645956766949617286909797728539496219])).unwrap(), Fq::new(U256([128873344219688070096765758438736759675, 54069131826062143440208582926638727688])).unwrap()), ell_vv: Fq2::new(Fq::new(U256([307107650784744827303698892877513591771, 60627460508970098579939906160617347629])).unwrap(), Fq::new(U256([12937320851881845879608263929438137281, 2854427512588805421842219517935335360])).unwrap()) },
            EllCoeffs { ell_0: Fq2::new(Fq::new(U256([262645642925640470181426114122199935672, 23413555848620303130105885294683582394])).unwrap(), Fq::new(U256([23944562046020585348174388998952320194, 45084472027505841447432550475379032518])).unwrap()), ell_vw: Fq2::new(Fq::new(U256([270949855981998457946423634423510659239, 52707980377915683827433393510154134085])).unwrap(), Fq::new(U256([171166926104504281831828631675145047352, 56745888676097625411354427758332611365])).unwrap()), ell_vv: Fq2::new(Fq::new(U256([185108543406354812098225836322332681649, 38838551534016041901193249287881822376])).unwrap(), Fq::new(U256([220861944829798693206584739921489268335, 13318922669002030127185287983351463417])).unwrap()) },
            EllCoeffs { ell_0: Fq2::new(Fq::new(U256([262922874647306006405720155830997014046, 60319938491997842161822791958118330349])).unwrap(), Fq::new(U256([267514688756785010198929321975858316586, 13431251784763107399729556927577885114])).unwrap()), ell_vw: Fq2::new(Fq::new(U256([305051645126359797430782278231595423685, 47480212315012773203275805598650271426])).unwrap(), Fq::new(U256([61134576323692903306240935503639110628, 44357224521089864059608299955085658945])).unwrap()), ell_vv: Fq2::new(Fq::new(U256([288996190497897556231840221598596395445, 7566958477031738465971309099100507442])).unwrap(), Fq::new(U256([133814060971580680876860604680251720534, 59538638355154696335532006425868258220])).unwrap()) },
            EllCoeffs { ell_0: Fq2::new(Fq::new(U256([255893828824714285725760712618255298338, 62054226651551009305279711173141456137])).unwrap(), Fq::new(U256([278488875745654026562827558863613836490, 21008509582420141439141693540430274581])).unwrap()), ell_vw: Fq2::new(Fq::new(U256([275369357162135051507223962619748317962, 42306683939383379782070254815636483252])).unwrap(), Fq::new(U256([33692084190064829046021773249716169044, 16341905693988596015816579527114077079])).unwrap()), ell_vv: Fq2::new(Fq::new(U256([50744916563901235043837175537752247974, 10172930551818555335663216915059687204])).unwrap(), Fq::new(U256([130434124352272975884189586048532260990, 23515228462564504103971218058319726320])).unwrap()) },
            EllCoeffs { ell_0: Fq2::new(Fq::new(U256([315580665939248091007437797584737352150, 29708290546079112870849940807626622733])).unwrap(), Fq::new(U256([74442409874403535705044740324158268395, 1505199293980260993350511103843027993])).unwrap()), ell_vw: Fq2::new(Fq::new(U256([250928935731107472086814134230515792409, 5910959187805062997477270179924851551])).unwrap(), Fq::new(U256([280398933657384735588696583124789483429, 54783439113329460653893087793006061735])).unwrap()), ell_vv: Fq2::new(Fq::new(U256([251724005393716093632013371894721049773, 58848512269437108583523559065211202801])).unwrap(), Fq::new(U256([31822195116686689031909059702809975955, 24789755710735999475756589726034740758])).unwrap()) },
            EllCoeffs { ell_0: Fq2::new(Fq::new(U256([311444467767445140793669110935330311306, 49680899512402416135008064018078765713])).unwrap(), Fq::new(U256([260075513883026034919987130520464438138, 71886459212490540714376620236740887])).unwrap()), ell_vw: Fq2::new(Fq::new(U256([218811104197169142519581806122315915406, 2003975639040616906277810278389252752])).unwrap(), Fq::new(U256([162449781759514417953768678035934414521, 20826616094394726852079185751274539294])).unwrap()), ell_vv: Fq2::new(Fq::new(U256([50122791277121629311774878848574398797, 41715208027917549489096484827508261925])).unwrap(), Fq::new(U256([61219715357759165032395798940008313361, 55168729445645977375765036089053114792])).unwrap()) },
            EllCoeffs { ell_0: Fq2::new(Fq::new(U256([79415664581913745231517567945561341244, 19961313692291707660858657690356279927])).unwrap(), Fq::new(U256([169697929611525566517558862344260578764, 32844991405556424161021671060175948696])).unwrap()), ell_vw: Fq2::new(Fq::new(U256([97951068758528710273694591785781637499, 18749313896215455583118043227454839626])).unwrap(), Fq::new(U256([86416223342992670848217929911878122430, 45508713650090104780284816480988042321])).unwrap()), ell_vv: Fq2::new(Fq::new(U256([205166366708889542887913296220698256110, 64283667930878381361054417761490454821])).unwrap(), Fq::new(U256([127337780289640176933683040648197123026, 45189848010560984741741084451460237213])).unwrap()) },
            EllCoeffs { ell_0: Fq2::new(Fq::new(U256([339284773562100413239152438122508204497, 50631498218339719105248005011418978450])).unwrap(), Fq::new(U256([33417073692493560040015373020490319634, 35461627756915585142929292352046627148])).unwrap()), ell_vw: Fq2::new(Fq::new(U256([65706590332479893819786959846945487398, 49780412881756049082474794087182699607])).unwrap(), Fq::new(U256([52838637386752308153448355705238086197, 25418435995504719731730063829115376943])).unwrap()), ell_vv: Fq2::new(Fq::new(U256([272342513391057040499052435082268704444, 40734611488359738470851428580967866388])).unwrap(), Fq::new(U256([195464905670800875597868841505577455579, 4567343913211817054294335942572391310])).unwrap()) },
            EllCoeffs { ell_0: Fq2::new(Fq::new(U256([116585277318795490267488822790585572050, 60535270002127930300661104257194060095])).unwrap(), Fq::new(U256([60938698090087736292473391678184666708, 13034546647626058553077909902189865751])).unwrap()), ell_vw: Fq2::new(Fq::new(U256([173096860849358760769153772521753734944, 46051492674049147015395144791836438516])).unwrap(), Fq::new(U256([217847244022302146556080020387033222160, 3714600728561149832835084325311638745])).unwrap()), ell_vv: Fq2::new(Fq::new(U256([86749998713441153819371623717671780463, 22072421598347798931100735874660737662])).unwrap(), Fq::new(U256([318879363093875678699747171034610586590, 20824335590094416898946169150944599166])).unwrap()) },
            EllCoeffs { ell_0: Fq2::new(Fq::new(U256([64488889092438538888826783837043953900, 37184887689753051095864638902885179863])).unwrap(), Fq::new(U256([30545663928156310707528798028105042841, 35594636889744346303301050561815356233])).unwrap()), ell_vw: Fq2::new(Fq::new(U256([25983064025798529721395241242693795182, 8818840193887973907465639723423785685])).unwrap(), Fq::new(U256([258083038864362618956338229292791076964, 59924965332411444209484507096908691451])).unwrap()), ell_vv: Fq2::new(Fq::new(U256([220038260912754873591576914680586914279, 13066051346629987308799710602199271472])).unwrap(), Fq::new(U256([145490690982645862542497983334649610263, 61905193484912278316593689230425266660])).unwrap()) },
            EllCoeffs { ell_0: Fq2::new(Fq::new(U256([91140715068468329987534450751429662322, 19286363177619277557153390898202185703])).unwrap(), Fq::new(U256([93820352751188715792914230189639637744, 34108640942617057347076989500332659863])).unwrap()), ell_vw: Fq2::new(Fq::new(U256([64028159517497609933876978072339213461, 15242545675596643010631683990284567829])).unwrap(), Fq::new(U256([196818140250582584748747602707707107768, 19674391967426090834762688793787807910])).unwrap()), ell_vv: Fq2::new(Fq::new(U256([240272916169613868056785616677104041776, 43506134861603450917738367078393340621])).unwrap(), Fq::new(U256([4195648020339163006604321850508278540, 4672904647050343041579830671683109182])).unwrap()) },
            EllCoeffs { ell_0: Fq2::new(Fq::new(U256([76224289879065773445423898190273853011, 29552147447995384759425668183293749353])).unwrap(), Fq::new(U256([131775855227043960655312514543203001277, 11525949759137095338012468817574456927])).unwrap()), ell_vw: Fq2::new(Fq::new(U256([24164999216876611682317527533876599098, 1468136824339634997687523096410027135])).unwrap(), Fq::new(U256([287955683679784091578386069448645460818, 42392463637589581183769087503942163916])).unwrap()), ell_vv: Fq2::new(Fq::new(U256([277415168387483520146499744266583303231, 43682907322954483721445867111493680565])).unwrap(), Fq::new(U256([231497467135626786731515493713118585761, 56904388641135605566397108514408579757])).unwrap()) }
        ]
    };

    assert!(expected_g2_p == g2_p);
    assert!(expected_g2_p.coeffs.len() == 87);
}

pub fn pairing(p: &G1, q: &G2) -> Fq12 {
    match (p.to_affine(), q.to_affine()) {
        (None, _) | (_, None) => Fq12::one(),
        (Some(p), Some(q)) => q.precompute()
            .miller_loop(&p)
            .final_exponentiation()
            .expect("miller loop cannot produce zero"),
    }
}

pub fn pairing_batch(ps: &[G1], qs: &[G2]) -> Fq12 {
    let mut p_affines: Vec<AffineG<G1Params>> = Vec::new();
    let mut q_precomputes: Vec<G2Precomp> = Vec::new();
    for (p, q) in ps.into_iter().zip(qs.into_iter()) {

        let p_affine = p.to_affine();
        let q_affine = q.to_affine();
        let exists = match(p_affine, q_affine)
        {
            (None, _) | (_, None) => false,
            (Some(_p_affine), Some(_q_affine)) => true,
        };

        if exists {
            p_affines.push(p.to_affine().unwrap());
            q_precomputes.push(q.to_affine().unwrap().precompute());
        }
    }
    if q_precomputes.len() == 0 {
        return Fq12::one();
    }
    miller_loop_batch(&q_precomputes, &p_affines).final_exponentiation().expect("miller loop cannot produce zero")
}

#[test]
fn test_reduced_pairing() {
    use crate::fields::Fq6;

    let g1 = G1::one()
        * Fr::from_str(
            "18097487326282793650237947474982649264364522469319914492172746413872781676",
        ).unwrap();
    let g2 = G2::one()
        * Fr::from_str(
            "20390255904278144451778773028944684152769293537511418234311120800877067946",
        ).unwrap();

    let gt = pairing(&g1, &g2);

    let expected = Fq12::new(
        Fq6::new(
            Fq2::new(
                Fq::from_str(
                    "7520311483001723614143802378045727372643587653754534704390832890681688842501",
                ).unwrap(),
                Fq::from_str(
                    "20265650864814324826731498061022229653175757397078253377158157137251452249882",
                ).unwrap(),
            ),
            Fq2::new(
                Fq::from_str(
                    "11942254371042183455193243679791334797733902728447312943687767053513298221130",
                ).unwrap(),
                Fq::from_str(
                    "759657045325139626991751731924144629256296901790485373000297868065176843620",
                ).unwrap(),
            ),
            Fq2::new(
                Fq::from_str(
                    "16045761475400271697821392803010234478356356448940805056528536884493606035236",
                ).unwrap(),
                Fq::from_str(
                    "4715626119252431692316067698189337228571577552724976915822652894333558784086",
                ).unwrap(),
            ),
        ),
        Fq6::new(
            Fq2::new(
                Fq::from_str(
                    "14901948363362882981706797068611719724999331551064314004234728272909570402962",
                ).unwrap(),
                Fq::from_str(
                    "11093203747077241090565767003969726435272313921345853819385060670210834379103",
                ).unwrap(),
            ),
            Fq2::new(
                Fq::from_str(
                    "17897835398184801202802503586172351707502775171934235751219763553166796820753",
                ).unwrap(),
                Fq::from_str(
                    "1344517825169318161285758374052722008806261739116142912817807653057880346554",
                ).unwrap(),
            ),
            Fq2::new(
                Fq::from_str(
                    "11123896897251094532909582772961906225000817992624500900708432321664085800838",
                ).unwrap(),
                Fq::from_str(
                    "17453370448280081813275586256976217762629631160552329276585874071364454854650",
                ).unwrap(),
            ),
        ),
    );

    assert_eq!(expected, gt);
}

#[test]
fn predefined_pair() {
    let g1 = AffineG1::new(
        Fq::from_str("1").expect("Fq(1) should exist"),
        Fq::from_str("2").expect("Fq(2) should exist"),
    ).expect("Point (1,2) should exist in G1")
        .to_jacobian();

    let g2 = AffineG2::new(
        Fq2::new(
            Fq::from_str("10857046999023057135944570762232829481370756359578518086990519993285655852781")
                .expect("a-coeff of g2 x generator is of the right order"),
            Fq::from_str("11559732032986387107991004021392285783925812861821192530917403151452391805634")
                .expect("b-coeff of g2 x generator is of the right order"),
        ),
        Fq2::new(
            Fq::from_str("8495653923123431417604973247489272438418190587263600148770280649306958101930")
                .expect("a-coeff of g2 y generator is of the right order"),
            Fq::from_str("4082367875863433681332203403145435568316851327593401208105741076214120093531")
                .expect("b-coeff of g2 y generator is of the right order"),
        ),
    ).expect("Point(11559732032986387107991004021392285783925812861821192530917403151452391805634 * i + 10857046999023057135944570762232829481370756359578518086990519993285655852781, 4082367875863433681332203403145435568316851327593401208105741076214120093531 * i + 8495653923123431417604973247489272438418190587263600148770280649306958101930) is a valid generator for G2")
        .to_jacobian();

    let p = pairing(&g1, &g2);

    let g1_vec : Vec<G1> = vec![g1, g1];
    let g2_vec : Vec<G2> = vec![g2, g2];
    let p2 = pairing_batch(&g1_vec, &g2_vec);
    assert!(!p2.is_zero());
    assert!(!p.is_zero());
}

#[test]
fn test_batch_bilinearity_empty() {
    let p_vec : Vec<G1> = Vec::new();
    let q_vec : Vec<G2> = Vec::new();
    let r = pairing_batch(&p_vec, &q_vec);
    assert_eq!(r, Fq12::one());
}

#[test]
fn test_batch_bilinearity_one() {
    use rand::{SeedableRng, rngs::StdRng};
    let seed = [
        0, 0, 0, 0, 0, 0, 64, 13, // 103245
        0, 0, 0, 0, 0, 0, 176, 2, // 191922
        0, 0, 0, 0, 0, 0, 0, 13, // 1293
        0, 0, 0, 0, 0, 0, 96, 7u8, // 192103
    ];
    let mut rng = StdRng::from_seed(seed);
    let p_vec : Vec<G1> = vec![G1::random(&mut rng)];
    let q_vec : Vec<G2> = vec![G2::random(&mut rng)];
    let s = Fr::random(&mut rng);
    let sp_vec : Vec<G1> = vec![p_vec[0] * s];
    let sq_vec : Vec<G2> = vec![q_vec[0] * s];
    let b = pairing_batch(&sp_vec, &q_vec);
    let c = pairing_batch(&p_vec, &sq_vec);
    assert_eq!(b, c);
}

#[test]
fn test_batch_bilinearity_fifty() {
    use rand::{SeedableRng, rngs::StdRng};
    let seed = [
        0, 0, 0, 0, 0, 0, 64, 13, // 103245
        0, 0, 0, 0, 0, 0, 176, 2, // 191922
        0, 0, 0, 0, 0, 0, 0, 13, // 1293
        0, 0, 0, 0, 0, 0, 96, 7u8, // 192103
    ];
    let mut rng = StdRng::from_seed(seed);

    let mut p_vec : Vec<G1> = Vec::new();
    let mut q_vec : Vec<G2> = Vec::new();
    let mut sp_vec : Vec<G1> = Vec::new();
    let mut sq_vec : Vec<G2> = Vec::new();

    for _ in 0..50 {
        let p = G1::random(&mut rng);
        let q = G2::random(&mut rng);
        let s = Fr::random(&mut rng);
        let sp = p * s;
        let sq = q * s;
        sp_vec.push(sp);
        q_vec.push(q);
        sq_vec.push(sq);
        p_vec.push(p);
    }
    let b_batch = pairing_batch(&sp_vec, &q_vec);
    let c_batch = pairing_batch(&p_vec, &sq_vec);
    assert_eq!(b_batch, c_batch);
}

#[test]
fn test_bilinearity() {
    use rand::{SeedableRng, rngs::StdRng};
    let seed = [
        0, 0, 0, 0, 0, 0, 64, 13, // 103245
        0, 0, 0, 0, 0, 0, 176, 2, // 191922
        0, 0, 0, 0, 0, 0, 0, 13, // 1293
        0, 0, 0, 0, 0, 0, 96, 7u8, // 192103
    ];
    let mut rng = StdRng::from_seed(seed);

    for _ in 0..50 {
        let p = G1::random(&mut rng);
        let q = G2::random(&mut rng);
        let s = Fr::random(&mut rng);
        let sp = p * s;
        let sq = q * s;

        let a = pairing(&p, &q).pow(s);
        let b = pairing(&sp, &q);
        let c = pairing(&p, &sq);

        assert_eq!(a, b);
        assert_eq!(b, c);

        let t = -Fr::one();

        assert!(a != Fq12::one());
        assert_eq!((a.pow(t)) * a, Fq12::one());
    }
}

#[test]
fn internals() {
    let test_p = G1::one();

    let val = G1::new(test_p.x().clone(), test_p.y().clone(), test_p.z().clone());

    let affine = val.to_affine()
        .expect("There should be affine coords for (0, 0)");

    assert_eq!(affine.x(), &Fq::one());
}

#[test]
fn affine_fail() {
    let res = AffineG1::new(Fq::one(), Fq::one());
    assert!(
        res.is_err(),
        "Affine initialization should fail because the point is not on curve"
    );
}

#[test]
fn affine_ok() {
    let res = AffineG1::new(Fq::one(), G1Params::coeff_b());
    assert!(
        res.is_err(),
        "Affine initialization should be ok because the point is on the curve"
    );
}

#[test]
fn test_y_at_point_at_infinity() {
    assert!(G1::zero().y == Fq::one());
    assert!((-G1::zero()).y == Fq::one());

    assert!(G2::zero().y == Fq2::one());
    assert!((-G2::zero()).y == Fq2::one());
}