diff --git a/bls12_381/.github/workflows/ci.yml b/bls12_381/.github/workflows/ci.yml
new file mode 100644
index 0000000..39066db
--- /dev/null
+++ b/bls12_381/.github/workflows/ci.yml
@@ -0,0 +1,95 @@
+name: CI checks
+
+on: [push, pull_request]
+
+jobs:
+  lint:
+    name: Lint
+    runs-on: ubuntu-latest
+
+    steps:
+      - uses: actions/checkout@v1
+      - uses: actions-rs/toolchain@v1
+        with:
+          toolchain: 1.36.0
+          override: true
+
+      # Ensure all code has been formatted with rustfmt
+      - run: rustup component add rustfmt
+      - name: Check formatting
+        uses: actions-rs/cargo@v1
+        with:
+          command: fmt
+          args: -- --check --color always
+
+  test:
+    name: Test on ${{ matrix.os }}
+    runs-on: ${{ matrix.os }}
+    strategy:
+      matrix:
+        os: [ubuntu-latest, windows-latest, macOS-latest]
+
+    steps:
+      - uses: actions/checkout@v1
+      - uses: actions-rs/toolchain@v1
+        with:
+          toolchain: 1.36.0
+          override: true
+      - name: cargo fetch
+        uses: actions-rs/cargo@v1
+        with:
+          command: fetch
+      - name: Build tests
+        uses: actions-rs/cargo@v1
+        with:
+          command: build
+          args: --verbose --release --tests
+      - name: Run tests
+        uses: actions-rs/cargo@v1
+        with:
+          command: test
+          args: --verbose --release
+
+  no-std:
+    name: Check no-std compatibility
+    runs-on: ubuntu-latest
+
+    steps:
+      - uses: actions/checkout@v1
+      - uses: actions-rs/toolchain@v1
+        with:
+          toolchain: 1.36.0
+          override: true
+      - run: rustup target add thumbv6m-none-eabi
+      - name: cargo fetch
+        uses: actions-rs/cargo@v1
+        with:
+          command: fetch
+      - name: Build
+        uses: actions-rs/cargo@v1
+        with:
+          command: build
+          args: --verbose --target thumbv6m-none-eabi --no-default-features --features groups,pairings
+
+  doc-links:
+    name: Nightly lint
+    runs-on: ubuntu-latest
+
+    steps:
+      - uses: actions/checkout@v1
+      - uses: actions-rs/toolchain@v1
+        with:
+          toolchain: nightly
+          override: true
+      - name: cargo fetch
+        uses: actions-rs/cargo@v1
+        with:
+          command: fetch
+
+      # Ensure intra-documentation links all resolve correctly
+      # Requires #![deny(intra_doc_link_resolution_failure)] in crate.
+      - name: Check intra-doc links
+        uses: actions-rs/cargo@v1
+        with:
+          command: doc
+          args: --document-private-items
diff --git a/bls12_381/.gitignore b/bls12_381/.gitignore
new file mode 100644
index 0000000..2f88dba
--- /dev/null
+++ b/bls12_381/.gitignore
@@ -0,0 +1,3 @@
+/target
+**/*.rs.bk
+Cargo.lock
\ No newline at end of file
diff --git a/bls12_381/COPYRIGHT b/bls12_381/COPYRIGHT
new file mode 100644
index 0000000..7764b86
--- /dev/null
+++ b/bls12_381/COPYRIGHT
@@ -0,0 +1,14 @@
+Copyrights in the "bls12_381" library are retained by their contributors. No
+copyright assignment is required to contribute to the "bls12_381" library.
+
+The "bls12_381" library is licensed under either of
+
+ * Apache License, Version 2.0, (see ./LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0)
+ * MIT license (see ./LICENSE-MIT or http://opensource.org/licenses/MIT)
+
+at your option.
+
+Unless you explicitly state otherwise, any contribution intentionally
+submitted for inclusion in the work by you, as defined in the Apache-2.0
+license, shall be dual licensed as above, without any additional terms or
+conditions.
diff --git a/bls12_381/Cargo.toml b/bls12_381/Cargo.toml
new file mode 100644
index 0000000..0bfb0d4
--- /dev/null
+++ b/bls12_381/Cargo.toml
@@ -0,0 +1,32 @@
+[package]
+authors = ["Sean Bowe <ewillbefull@gmail.com>"]
+description = "Implementation of the BLS12-381 pairing-friendly elliptic curve construction"
+documentation = "https://docs.rs/bls12_381/"
+homepage = "https://github.com/zkcrypto/bls12_381"
+license = "MIT/Apache-2.0"
+name = "bls12_381"
+repository = "https://github.com/zkcrypto/bls12_381"
+version = "0.1.0"
+edition = "2018"
+
+[package.metadata.docs.rs]
+rustdoc-args = [ "--html-in-header", "katex-header.html" ]
+
+[dev-dependencies]
+criterion = "0.2.11"
+
+[[bench]]
+name = "groups"
+harness = false
+required-features = ["groups"]
+
+[dependencies.subtle]
+version = "2.2.1"
+default-features = false
+
+[features]
+default = ["groups", "pairings", "alloc"]
+groups = []
+pairings = ["groups"]
+alloc = []
+nightly = ["subtle/nightly"]
diff --git a/bls12_381/LICENSE-APACHE b/bls12_381/LICENSE-APACHE
new file mode 100644
index 0000000..16fe87b
--- /dev/null
+++ b/bls12_381/LICENSE-APACHE
@@ -0,0 +1,201 @@
+                              Apache License
+                        Version 2.0, January 2004
+                     http://www.apache.org/licenses/
+
+TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+1. Definitions.
+
+   "License" shall mean the terms and conditions for use, reproduction,
+   and distribution as defined by Sections 1 through 9 of this document.
+
+   "Licensor" shall mean the copyright owner or entity authorized by
+   the copyright owner that is granting the License.
+
+   "Legal Entity" shall mean the union of the acting entity and all
+   other entities that control, are controlled by, or are under common
+   control with that entity. For the purposes of this definition,
+   "control" means (i) the power, direct or indirect, to cause the
+   direction or management of such entity, whether by contract or
+   otherwise, or (ii) ownership of fifty percent (50%) or more of the
+   outstanding shares, or (iii) beneficial ownership of such entity.
+
+   "You" (or "Your") shall mean an individual or Legal Entity
+   exercising permissions granted by this License.
+
+   "Source" form shall mean the preferred form for making modifications,
+   including but not limited to software source code, documentation
+   source, and configuration files.
+
+   "Object" form shall mean any form resulting from mechanical
+   transformation or translation of a Source form, including but
+   not limited to compiled object code, generated documentation,
+   and conversions to other media types.
+
+   "Work" shall mean the work of authorship, whether in Source or
+   Object form, made available under the License, as indicated by a
+   copyright notice that is included in or attached to the work
+   (an example is provided in the Appendix below).
+
+   "Derivative Works" shall mean any work, whether in Source or Object
+   form, that is based on (or derived from) the Work and for which the
+   editorial revisions, annotations, elaborations, or other modifications
+   represent, as a whole, an original work of authorship. For the purposes
+   of this License, Derivative Works shall not include works that remain
+   separable from, or merely link (or bind by name) to the interfaces of,
+   the Work and Derivative Works thereof.
+
+   "Contribution" shall mean any work of authorship, including
+   the original version of the Work and any modifications or additions
+   to that Work or Derivative Works thereof, that is intentionally
+   submitted to Licensor for inclusion in the Work by the copyright owner
+   or by an individual or Legal Entity authorized to submit on behalf of
+   the copyright owner. For the purposes of this definition, "submitted"
+   means any form of electronic, verbal, or written communication sent
+   to the Licensor or its representatives, including but not limited to
+   communication on electronic mailing lists, source code control systems,
+   and issue tracking systems that are managed by, or on behalf of, the
+   Licensor for the purpose of discussing and improving the Work, but
+   excluding communication that is conspicuously marked or otherwise
+   designated in writing by the copyright owner as "Not a Contribution."
+
+   "Contributor" shall mean Licensor and any individual or Legal Entity
+   on behalf of whom a Contribution has been received by Licensor and
+   subsequently incorporated within the Work.
+
+2. Grant of Copyright License. Subject to the terms and conditions of
+   this License, each Contributor hereby grants to You a perpetual,
+   worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+   copyright license to reproduce, prepare Derivative Works of,
+   publicly display, publicly perform, sublicense, and distribute the
+   Work and such Derivative Works in Source or Object form.
+
+3. Grant of Patent License. Subject to the terms and conditions of
+   this License, each Contributor hereby grants to You a perpetual,
+   worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+   (except as stated in this section) patent license to make, have made,
+   use, offer to sell, sell, import, and otherwise transfer the Work,
+   where such license applies only to those patent claims licensable
+   by such Contributor that are necessarily infringed by their
+   Contribution(s) alone or by combination of their Contribution(s)
+   with the Work to which such Contribution(s) was submitted. If You
+   institute patent litigation against any entity (including a
+   cross-claim or counterclaim in a lawsuit) alleging that the Work
+   or a Contribution incorporated within the Work constitutes direct
+   or contributory patent infringement, then any patent licenses
+   granted to You under this License for that Work shall terminate
+   as of the date such litigation is filed.
+
+4. Redistribution. You may reproduce and distribute copies of the
+   Work or Derivative Works thereof in any medium, with or without
+   modifications, and in Source or Object form, provided that You
+   meet the following conditions:
+
+   (a) You must give any other recipients of the Work or
+       Derivative Works a copy of this License; and
+
+   (b) You must cause any modified files to carry prominent notices
+       stating that You changed the files; and
+
+   (c) You must retain, in the Source form of any Derivative Works
+       that You distribute, all copyright, patent, trademark, and
+       attribution notices from the Source form of the Work,
+       excluding those notices that do not pertain to any part of
+       the Derivative Works; and
+
+   (d) If the Work includes a "NOTICE" text file as part of its
+       distribution, then any Derivative Works that You distribute must
+       include a readable copy of the attribution notices contained
+       within such NOTICE file, excluding those notices that do not
+       pertain to any part of the Derivative Works, in at least one
+       of the following places: within a NOTICE text file distributed
+       as part of the Derivative Works; within the Source form or
+       documentation, if provided along with the Derivative Works; or,
+       within a display generated by the Derivative Works, if and
+       wherever such third-party notices normally appear. The contents
+       of the NOTICE file are for informational purposes only and
+       do not modify the License. You may add Your own attribution
+       notices within Derivative Works that You distribute, alongside
+       or as an addendum to the NOTICE text from the Work, provided
+       that such additional attribution notices cannot be construed
+       as modifying the License.
+
+   You may add Your own copyright statement to Your modifications and
+   may provide additional or different license terms and conditions
+   for use, reproduction, or distribution of Your modifications, or
+   for any such Derivative Works as a whole, provided Your use,
+   reproduction, and distribution of the Work otherwise complies with
+   the conditions stated in this License.
+
+5. Submission of Contributions. Unless You explicitly state otherwise,
+   any Contribution intentionally submitted for inclusion in the Work
+   by You to the Licensor shall be under the terms and conditions of
+   this License, without any additional terms or conditions.
+   Notwithstanding the above, nothing herein shall supersede or modify
+   the terms of any separate license agreement you may have executed
+   with Licensor regarding such Contributions.
+
+6. Trademarks. This License does not grant permission to use the trade
+   names, trademarks, service marks, or product names of the Licensor,
+   except as required for reasonable and customary use in describing the
+   origin of the Work and reproducing the content of the NOTICE file.
+
+7. Disclaimer of Warranty. Unless required by applicable law or
+   agreed to in writing, Licensor provides the Work (and each
+   Contributor provides its Contributions) on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+   implied, including, without limitation, any warranties or conditions
+   of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+   PARTICULAR PURPOSE. You are solely responsible for determining the
+   appropriateness of using or redistributing the Work and assume any
+   risks associated with Your exercise of permissions under this License.
+
+8. Limitation of Liability. In no event and under no legal theory,
+   whether in tort (including negligence), contract, or otherwise,
+   unless required by applicable law (such as deliberate and grossly
+   negligent acts) or agreed to in writing, shall any Contributor be
+   liable to You for damages, including any direct, indirect, special,
+   incidental, or consequential damages of any character arising as a
+   result of this License or out of the use or inability to use the
+   Work (including but not limited to damages for loss of goodwill,
+   work stoppage, computer failure or malfunction, or any and all
+   other commercial damages or losses), even if such Contributor
+   has been advised of the possibility of such damages.
+
+9. Accepting Warranty or Additional Liability. While redistributing
+   the Work or Derivative Works thereof, You may choose to offer,
+   and charge a fee for, acceptance of support, warranty, indemnity,
+   or other liability obligations and/or rights consistent with this
+   License. However, in accepting such obligations, You may act only
+   on Your own behalf and on Your sole responsibility, not on behalf
+   of any other Contributor, and only if You agree to indemnify,
+   defend, and hold each Contributor harmless for any liability
+   incurred by, or claims asserted against, such Contributor by reason
+   of your accepting any such warranty or additional liability.
+
+END OF TERMS AND CONDITIONS
+
+APPENDIX: How to apply the Apache License to your work.
+
+   To apply the Apache License to your work, attach the following
+   boilerplate notice, with the fields enclosed by brackets "[]"
+   replaced with your own identifying information. (Don't include
+   the brackets!)  The text should be enclosed in the appropriate
+   comment syntax for the file format. We also recommend that a
+   file or class name and description of purpose be included on the
+   same "printed page" as the copyright notice for easier
+   identification within third-party archives.
+
+Copyright [yyyy] [name of copyright owner]
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+	http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
diff --git a/bls12_381/LICENSE-MIT b/bls12_381/LICENSE-MIT
new file mode 100644
index 0000000..31aa793
--- /dev/null
+++ b/bls12_381/LICENSE-MIT
@@ -0,0 +1,23 @@
+Permission is hereby granted, free of charge, to any
+person obtaining a copy of this software and associated
+documentation files (the "Software"), to deal in the
+Software without restriction, including without
+limitation the rights to use, copy, modify, merge,
+publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software
+is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice
+shall be included in all copies or substantial portions
+of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
+ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
+TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
+IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
diff --git a/bls12_381/README.md b/bls12_381/README.md
new file mode 100644
index 0000000..ba61f30
--- /dev/null
+++ b/bls12_381/README.md
@@ -0,0 +1,63 @@
+# bls12_381 [![Crates.io](https://img.shields.io/crates/v/bls12_381.svg)](https://crates.io/crates/bls12_381) #
+
+This crate provides an implementation of the BLS12-381 pairing-friendly elliptic curve construction.
+
+* **This implementation has not been reviewed or audited. Use at your own risk.**
+* This implementation targets Rust `1.36` or later.
+* This implementation does not require the Rust standard library.
+* All operations are constant time unless explicitly noted.
+
+## Features
+
+* `groups` (on by default): Enables APIs for performing group arithmetic with G1, G2, and GT.
+* `pairings` (on by default): Enables some APIs for performing pairings.
+* `alloc` (on by default): Enables APIs that require an allocator; these include pairing optimizations.
+* `nightly`: Enables `subtle/nightly` which tries to prevent compiler optimizations that could jeopardize constant time operations. Requires the nightly Rust compiler.
+
+## [Documentation](https://docs.rs/bls12_381)
+
+## Curve Description
+
+BLS12-381 is a pairing-friendly elliptic curve construction from the [BLS family](https://eprint.iacr.org/2002/088), with embedding degree 12. It is built over a 381-bit prime field `GF(p)` with...
+
+* z = `-0xd201000000010000`
+* p = (z - 1)<sup>2</sup>(z<sup>4</sup> - z<sup>2</sup> + 1) / 3 + z
+	* = `0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaab`
+* q = z<sup>4</sup> - z<sup>2</sup> + 1
+	* = `0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001`
+
+... yielding two **source groups** G<sub>1</sub> and G<sub>2</sub>, each of 255-bit prime order `q`, such that an efficiently computable non-degenerate bilinear pairing function `e` exists into a third **target group** G<sub>T</sub>. Specifically, G<sub>1</sub> is the `q`-order subgroup of E(F<sub>p</sub>) : y<sup>2</sup> = x<sup>3</sup> + 4 and G<sub>2</sub> is the `q`-order subgroup of E'(F<sub>p<sup>2</sup></sub>) : y<sup>2</sup> = x<sup>3</sup> + 4(u + 1) where the extention field F<sub>p<sup>2</sup></sub> is defined as F<sub>p</sub>(u) / (u<sup>2</sup> + 1).
+
+BLS12-381 is chosen so that `z` has small Hamming weight (to improve pairing performance) and also so that `GF(q)` has a large 2<sup>32</sup> primitive root of unity for performing radix-2 fast Fourier transforms for efficient multi-point evaluation and interpolation. It is also chosen so that it exists in a particularly efficient and rigid subfamily of BLS12 curves.
+
+### Curve Security
+
+Pairing-friendly elliptic curve constructions are (necessarily) less secure than conventional elliptic curves due to their small "embedding degree". Given a small enough embedding degree, the pairing function itself would allow for a break in DLP hardness if it projected into a weak target group, as weaknesses in this target group are immediately translated into weaknesses in the source group.
+
+In order to achieve reasonable security without an unreasonably expensive pairing function, a careful choice of embedding degree, base field characteristic and prime subgroup order must be made. BLS12-381 uses an embedding degree of 12 to ensure fast pairing performance but a choice of a 381-bit base field characteristic to yeild a 255-bit subgroup order (for protection against [Pollard's rho algorithm](https://en.wikipedia.org/wiki/Pollard%27s_rho_algorithm)) while reaching close to a 128-bit security level.
+
+There are [known optimizations](https://ellipticnews.wordpress.com/2016/05/02/kim-barbulescu-variant-of-the-number-field-sieve-to-compute-discrete-logarithms-in-finite-fields/) of the [Number Field Sieve algorithm](https://en.wikipedia.org/wiki/General_number_field_sieve) which could be used to weaken DLP security in the target group by taking advantage of its structure, as it is a multiplicative subgroup of a low-degree extension field. However, these attacks require an (as of yet unknown) efficient algorithm for scanning a large space of polynomials. Even if the attack were practical it would only reduce security to roughly 117 to 120 bits. (This contrasts with 254-bit BN curves which usually have less than 100 bits of security in the same situation.)
+
+### Alternative Curves
+
+Applications may wish to exchange pairing performance and/or G<sub>2</sub> performance by using BLS24 or KSS16 curves which conservatively target 128-bit security. In applications that need cycles of elliptic curves for e.g. arbitrary proof composition, MNT6/MNT4 curve cycles are known that target the 128-bit security level. In applications that only need fixed-depth proof composition, curves of this form have been constructed as part of Zexe.
+
+## Acknowledgements
+
+Please see `Cargo.toml` for a list of primary authors of this codebase.
+
+## License
+
+Licensed under either of
+
+ * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
+ * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
+
+at your option.
+
+### Contribution
+
+Unless you explicitly state otherwise, any contribution intentionally
+submitted for inclusion in the work by you, as defined in the Apache-2.0
+license, shall be dual licensed as above, without any additional terms or
+conditions.
diff --git a/bls12_381/RELEASES.md b/bls12_381/RELEASES.md
new file mode 100644
index 0000000..69afd52
--- /dev/null
+++ b/bls12_381/RELEASES.md
@@ -0,0 +1,3 @@
+# 0.1.0
+
+Initial release.
diff --git a/bls12_381/benches/groups.rs b/bls12_381/benches/groups.rs
new file mode 100644
index 0000000..87c80d0
--- /dev/null
+++ b/bls12_381/benches/groups.rs
@@ -0,0 +1,170 @@
+#[macro_use]
+extern crate criterion;
+
+extern crate bls12_381;
+use bls12_381::*;
+
+use criterion::{black_box, Criterion};
+
+fn criterion_benchmark(c: &mut Criterion) {
+    // Pairings
+    {
+        let g = G1Affine::generator();
+        let h = G2Affine::generator();
+        c.bench_function("full pairing", move |b| {
+            b.iter(|| pairing(black_box(&g), black_box(&h)))
+        });
+        c.bench_function("G2 preparation for pairing", move |b| {
+            b.iter(|| G2Prepared::from(h))
+        });
+        let prep = G2Prepared::from(h);
+        c.bench_function("miller loop for pairing", move |b| {
+            b.iter(|| multi_miller_loop(&[(&g, &prep)]))
+        });
+        let prep = G2Prepared::from(h);
+        let r = multi_miller_loop(&[(&g, &prep)]);
+        c.bench_function("final exponentiation for pairing", move |b| {
+            b.iter(|| r.final_exponentiation())
+        });
+    }
+    // G1Affine
+    {
+        let name = "G1Affine";
+        let a = G1Affine::generator();
+        let s = Scalar::from_raw([1, 2, 3, 4]);
+        let compressed = [0u8; 48];
+        let uncompressed = [0u8; 96];
+        c.bench_function(&format!("{} check on curve", name), move |b| {
+            b.iter(|| black_box(a).is_on_curve())
+        });
+        c.bench_function(&format!("{} check equality", name), move |b| {
+            b.iter(|| black_box(a) == black_box(a))
+        });
+        c.bench_function(&format!("{} scalar multiplication", name), move |b| {
+            b.iter(|| black_box(a) * black_box(s))
+        });
+        c.bench_function(&format!("{} subgroup check", name), move |b| {
+            b.iter(|| black_box(a).is_torsion_free())
+        });
+        c.bench_function(
+            &format!("{} deserialize compressed point", name),
+            move |b| b.iter(|| G1Affine::from_compressed(black_box(&compressed))),
+        );
+        c.bench_function(
+            &format!("{} deserialize uncompressed point", name),
+            move |b| b.iter(|| G1Affine::from_uncompressed(black_box(&uncompressed))),
+        );
+    }
+
+    // G1Projective
+    {
+        let name = "G1Projective";
+        let a = G1Projective::generator();
+        let a_affine = G1Affine::generator();
+        let s = Scalar::from_raw([1, 2, 3, 4]);
+
+        const N: usize = 10000;
+        let v = vec![G1Projective::generator(); N];
+        let mut q = vec![G1Affine::identity(); N];
+
+        c.bench_function(&format!("{} check on curve", name), move |b| {
+            b.iter(|| black_box(a).is_on_curve())
+        });
+        c.bench_function(&format!("{} check equality", name), move |b| {
+            b.iter(|| black_box(a) == black_box(a))
+        });
+        c.bench_function(&format!("{} to affine", name), move |b| {
+            b.iter(|| G1Affine::from(black_box(a)))
+        });
+        c.bench_function(&format!("{} doubling", name), move |b| {
+            b.iter(|| black_box(a).double())
+        });
+        c.bench_function(&format!("{} addition", name), move |b| {
+            b.iter(|| black_box(a).add(&a))
+        });
+        c.bench_function(&format!("{} mixed addition", name), move |b| {
+            b.iter(|| black_box(a).add_mixed(&a_affine))
+        });
+        c.bench_function(&format!("{} scalar multiplication", name), move |b| {
+            b.iter(|| black_box(a) * black_box(s))
+        });
+        c.bench_function(&format!("{} batch to affine n={}", name, N), move |b| {
+            b.iter(|| {
+                G1Projective::batch_normalize(black_box(&v), black_box(&mut q));
+                black_box(&q)[0]
+            })
+        });
+    }
+
+    // G2Affine
+    {
+        let name = "G2Affine";
+        let a = G2Affine::generator();
+        let s = Scalar::from_raw([1, 2, 3, 4]);
+        let compressed = [0u8; 96];
+        let uncompressed = [0u8; 192];
+        c.bench_function(&format!("{} check on curve", name), move |b| {
+            b.iter(|| black_box(a).is_on_curve())
+        });
+        c.bench_function(&format!("{} check equality", name), move |b| {
+            b.iter(|| black_box(a) == black_box(a))
+        });
+        c.bench_function(&format!("{} scalar multiplication", name), move |b| {
+            b.iter(|| black_box(a) * black_box(s))
+        });
+        c.bench_function(&format!("{} subgroup check", name), move |b| {
+            b.iter(|| black_box(a).is_torsion_free())
+        });
+        c.bench_function(
+            &format!("{} deserialize compressed point", name),
+            move |b| b.iter(|| G2Affine::from_compressed(black_box(&compressed))),
+        );
+        c.bench_function(
+            &format!("{} deserialize uncompressed point", name),
+            move |b| b.iter(|| G2Affine::from_uncompressed(black_box(&uncompressed))),
+        );
+    }
+
+    // G2Projective
+    {
+        let name = "G2Projective";
+        let a = G2Projective::generator();
+        let a_affine = G2Affine::generator();
+        let s = Scalar::from_raw([1, 2, 3, 4]);
+
+        const N: usize = 10000;
+        let v = vec![G2Projective::generator(); N];
+        let mut q = vec![G2Affine::identity(); N];
+
+        c.bench_function(&format!("{} check on curve", name), move |b| {
+            b.iter(|| black_box(a).is_on_curve())
+        });
+        c.bench_function(&format!("{} check equality", name), move |b| {
+            b.iter(|| black_box(a) == black_box(a))
+        });
+        c.bench_function(&format!("{} to affine", name), move |b| {
+            b.iter(|| G2Affine::from(black_box(a)))
+        });
+        c.bench_function(&format!("{} doubling", name), move |b| {
+            b.iter(|| black_box(a).double())
+        });
+        c.bench_function(&format!("{} addition", name), move |b| {
+            b.iter(|| black_box(a).add(&a))
+        });
+        c.bench_function(&format!("{} mixed addition", name), move |b| {
+            b.iter(|| black_box(a).add_mixed(&a_affine))
+        });
+        c.bench_function(&format!("{} scalar multiplication", name), move |b| {
+            b.iter(|| black_box(a) * black_box(s))
+        });
+        c.bench_function(&format!("{} batch to affine n={}", name, N), move |b| {
+            b.iter(|| {
+                G2Projective::batch_normalize(black_box(&v), black_box(&mut q));
+                black_box(&q)[0]
+            })
+        });
+    }
+}
+
+criterion_group!(benches, criterion_benchmark);
+criterion_main!(benches);
diff --git a/bls12_381/katex-header.html b/bls12_381/katex-header.html
new file mode 100644
index 0000000..98e8590
--- /dev/null
+++ b/bls12_381/katex-header.html
@@ -0,0 +1,15 @@
+<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.10.0/dist/katex.min.css" integrity="sha384-9eLZqc9ds8eNjO3TmqPeYcDj8n+Qfa4nuSiGYa6DjLNcv9BtN69ZIulL9+8CqC9Y" crossorigin="anonymous">
+<script src="https://cdn.jsdelivr.net/npm/katex@0.10.0/dist/katex.min.js"                  integrity="sha384-K3vbOmF2BtaVai+Qk37uypf7VrgBubhQreNQe9aGsz9lB63dIFiQVlJbr92dw2Lx" crossorigin="anonymous"></script>
+<script src="https://cdn.jsdelivr.net/npm/katex@0.10.0/dist/contrib/auto-render.min.js"    integrity="sha384-kmZOZB5ObwgQnS/DuDg6TScgOiWWBiVt0plIRkZCmE6rDZGrEOQeHM5PcHi+nyqe" crossorigin="anonymous"></script>
+<script>
+    document.addEventListener("DOMContentLoaded", function() {
+        renderMathInElement(document.body, {
+            delimiters: [
+                {left: "$$", right: "$$", display: true},
+                {left: "\\(", right: "\\)", display: false},
+                {left: "$", right: "$", display: false},
+                {left: "\\[", right: "\\]", display: true}
+            ]
+        });
+    });
+</script>
\ No newline at end of file
diff --git a/bls12_381/rust-toolchain b/bls12_381/rust-toolchain
new file mode 100644
index 0000000..d70132e
--- /dev/null
+++ b/bls12_381/rust-toolchain
@@ -0,0 +1 @@
+1.36.0
\ No newline at end of file
diff --git a/bls12_381/src/fp.rs b/bls12_381/src/fp.rs
new file mode 100644
index 0000000..1a25fdf
--- /dev/null
+++ b/bls12_381/src/fp.rs
@@ -0,0 +1,859 @@
+//! This module provides an implementation of the BLS12-381 base field `GF(p)`
+//! where `p = 0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaab`
+
+use core::convert::TryFrom;
+use core::fmt;
+use core::ops::{Add, AddAssign, Mul, MulAssign, Neg, Sub, SubAssign};
+
+use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption};
+
+use crate::util::{adc, mac, sbb};
+
+// The internal representation of this type is six 64-bit unsigned
+// integers in little-endian order. `Fp` values are always in
+// Montgomery form; i.e., Scalar(a) = aR mod p, with R = 2^384.
+#[derive(Copy, Clone)]
+pub struct Fp([u64; 6]);
+
+impl fmt::Debug for Fp {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        let tmp = self.to_bytes();
+        write!(f, "0x")?;
+        for &b in tmp.iter() {
+            write!(f, "{:02x}", b)?;
+        }
+        Ok(())
+    }
+}
+
+impl Default for Fp {
+    fn default() -> Self {
+        Fp::zero()
+    }
+}
+
+impl ConstantTimeEq for Fp {
+    fn ct_eq(&self, other: &Self) -> Choice {
+        self.0[0].ct_eq(&other.0[0])
+            & self.0[1].ct_eq(&other.0[1])
+            & self.0[2].ct_eq(&other.0[2])
+            & self.0[3].ct_eq(&other.0[3])
+            & self.0[4].ct_eq(&other.0[4])
+            & self.0[5].ct_eq(&other.0[5])
+    }
+}
+
+impl Eq for Fp {}
+impl PartialEq for Fp {
+    #[inline]
+    fn eq(&self, other: &Self) -> bool {
+        self.ct_eq(other).unwrap_u8() == 1
+    }
+}
+
+impl ConditionallySelectable for Fp {
+    fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self {
+        Fp([
+            u64::conditional_select(&a.0[0], &b.0[0], choice),
+            u64::conditional_select(&a.0[1], &b.0[1], choice),
+            u64::conditional_select(&a.0[2], &b.0[2], choice),
+            u64::conditional_select(&a.0[3], &b.0[3], choice),
+            u64::conditional_select(&a.0[4], &b.0[4], choice),
+            u64::conditional_select(&a.0[5], &b.0[5], choice),
+        ])
+    }
+}
+
+/// p = 4002409555221667393417789825735904156556882819939007885332058136124031650490837864442687629129015664037894272559787
+const MODULUS: [u64; 6] = [
+    0xb9feffffffffaaab,
+    0x1eabfffeb153ffff,
+    0x6730d2a0f6b0f624,
+    0x64774b84f38512bf,
+    0x4b1ba7b6434bacd7,
+    0x1a0111ea397fe69a,
+];
+
+/// INV = -(p^{-1} mod 2^64) mod 2^64
+const INV: u64 = 0x89f3fffcfffcfffd;
+
+/// R = 2^384 mod p
+const R: Fp = Fp([
+    0x760900000002fffd,
+    0xebf4000bc40c0002,
+    0x5f48985753c758ba,
+    0x77ce585370525745,
+    0x5c071a97a256ec6d,
+    0x15f65ec3fa80e493,
+]);
+
+/// R2 = 2^(384*2) mod p
+const R2: Fp = Fp([
+    0xf4df1f341c341746,
+    0xa76e6a609d104f1,
+    0x8de5476c4c95b6d5,
+    0x67eb88a9939d83c0,
+    0x9a793e85b519952d,
+    0x11988fe592cae3aa,
+]);
+
+impl<'a> Neg for &'a Fp {
+    type Output = Fp;
+
+    #[inline]
+    fn neg(self) -> Fp {
+        self.neg()
+    }
+}
+
+impl Neg for Fp {
+    type Output = Fp;
+
+    #[inline]
+    fn neg(self) -> Fp {
+        -&self
+    }
+}
+
+impl<'a, 'b> Sub<&'b Fp> for &'a Fp {
+    type Output = Fp;
+
+    #[inline]
+    fn sub(self, rhs: &'b Fp) -> Fp {
+        self.sub(rhs)
+    }
+}
+
+impl<'a, 'b> Add<&'b Fp> for &'a Fp {
+    type Output = Fp;
+
+    #[inline]
+    fn add(self, rhs: &'b Fp) -> Fp {
+        self.add(rhs)
+    }
+}
+
+impl<'a, 'b> Mul<&'b Fp> for &'a Fp {
+    type Output = Fp;
+
+    #[inline]
+    fn mul(self, rhs: &'b Fp) -> Fp {
+        self.mul(rhs)
+    }
+}
+
+impl_binops_additive!(Fp, Fp);
+impl_binops_multiplicative!(Fp, Fp);
+
+impl Fp {
+    /// Returns zero, the additive identity.
+    #[inline]
+    pub const fn zero() -> Fp {
+        Fp([0, 0, 0, 0, 0, 0])
+    }
+
+    /// Returns one, the multiplicative identity.
+    #[inline]
+    pub const fn one() -> Fp {
+        R
+    }
+
+    pub fn is_zero(&self) -> Choice {
+        self.ct_eq(&Fp::zero())
+    }
+
+    /// Attempts to convert a little-endian byte representation of
+    /// a scalar into an `Fp`, failing if the input is not canonical.
+    pub fn from_bytes(bytes: &[u8; 48]) -> CtOption<Fp> {
+        let mut tmp = Fp([0, 0, 0, 0, 0, 0]);
+
+        tmp.0[5] = u64::from_be_bytes(<[u8; 8]>::try_from(&bytes[0..8]).unwrap());
+        tmp.0[4] = u64::from_be_bytes(<[u8; 8]>::try_from(&bytes[8..16]).unwrap());
+        tmp.0[3] = u64::from_be_bytes(<[u8; 8]>::try_from(&bytes[16..24]).unwrap());
+        tmp.0[2] = u64::from_be_bytes(<[u8; 8]>::try_from(&bytes[24..32]).unwrap());
+        tmp.0[1] = u64::from_be_bytes(<[u8; 8]>::try_from(&bytes[32..40]).unwrap());
+        tmp.0[0] = u64::from_be_bytes(<[u8; 8]>::try_from(&bytes[40..48]).unwrap());
+
+        // Try to subtract the modulus
+        let (_, borrow) = sbb(tmp.0[0], MODULUS[0], 0);
+        let (_, borrow) = sbb(tmp.0[1], MODULUS[1], borrow);
+        let (_, borrow) = sbb(tmp.0[2], MODULUS[2], borrow);
+        let (_, borrow) = sbb(tmp.0[3], MODULUS[3], borrow);
+        let (_, borrow) = sbb(tmp.0[4], MODULUS[4], borrow);
+        let (_, borrow) = sbb(tmp.0[5], MODULUS[5], borrow);
+
+        // If the element is smaller than MODULUS then the
+        // subtraction will underflow, producing a borrow value
+        // of 0xffff...ffff. Otherwise, it'll be zero.
+        let is_some = (borrow as u8) & 1;
+
+        // Convert to Montgomery form by computing
+        // (a.R^0 * R^2) / R = a.R
+        tmp *= &R2;
+
+        CtOption::new(tmp, Choice::from(is_some))
+    }
+
+    /// Converts an element of `Fp` into a byte representation in
+    /// big-endian byte order.
+    pub fn to_bytes(&self) -> [u8; 48] {
+        // Turn into canonical form by computing
+        // (a.R) / R = a
+        let tmp = Fp::montgomery_reduce(
+            self.0[0], self.0[1], self.0[2], self.0[3], self.0[4], self.0[5], 0, 0, 0, 0, 0, 0,
+        );
+
+        let mut res = [0; 48];
+        res[0..8].copy_from_slice(&tmp.0[5].to_be_bytes());
+        res[8..16].copy_from_slice(&tmp.0[4].to_be_bytes());
+        res[16..24].copy_from_slice(&tmp.0[3].to_be_bytes());
+        res[24..32].copy_from_slice(&tmp.0[2].to_be_bytes());
+        res[32..40].copy_from_slice(&tmp.0[1].to_be_bytes());
+        res[40..48].copy_from_slice(&tmp.0[0].to_be_bytes());
+
+        res
+    }
+
+    /// Returns whether or not this element is strictly lexicographically
+    /// larger than its negation.
+    pub fn lexicographically_largest(&self) -> Choice {
+        // This can be determined by checking to see if the element is
+        // larger than (p - 1) // 2. If we subtract by ((p - 1) // 2) + 1
+        // and there is no underflow, then the element must be larger than
+        // (p - 1) // 2.
+
+        // First, because self is in Montgomery form we need to reduce it
+        let tmp = Fp::montgomery_reduce(
+            self.0[0], self.0[1], self.0[2], self.0[3], self.0[4], self.0[5], 0, 0, 0, 0, 0, 0,
+        );
+
+        let (_, borrow) = sbb(tmp.0[0], 0xdcff7fffffffd556, 0);
+        let (_, borrow) = sbb(tmp.0[1], 0x0f55ffff58a9ffff, borrow);
+        let (_, borrow) = sbb(tmp.0[2], 0xb39869507b587b12, borrow);
+        let (_, borrow) = sbb(tmp.0[3], 0xb23ba5c279c2895f, borrow);
+        let (_, borrow) = sbb(tmp.0[4], 0x258dd3db21a5d66b, borrow);
+        let (_, borrow) = sbb(tmp.0[5], 0x0d0088f51cbff34d, borrow);
+
+        // If the element was smaller, the subtraction will underflow
+        // producing a borrow value of 0xffff...ffff, otherwise it will
+        // be zero. We create a Choice representing true if there was
+        // overflow (and so this element is not lexicographically larger
+        // than its negation) and then negate it.
+
+        !Choice::from((borrow as u8) & 1)
+    }
+
+    /// Constructs an element of `Fp` without checking that it is
+    /// canonical.
+    pub const fn from_raw_unchecked(v: [u64; 6]) -> Fp {
+        Fp(v)
+    }
+
+    /// Although this is labeled "vartime", it is only
+    /// variable time with respect to the exponent. It
+    /// is also not exposed in the public API.
+    pub fn pow_vartime(&self, by: &[u64; 6]) -> Self {
+        let mut res = Self::one();
+        for e in by.iter().rev() {
+            for i in (0..64).rev() {
+                res = res.square();
+
+                if ((*e >> i) & 1) == 1 {
+                    res *= self;
+                }
+            }
+        }
+        res
+    }
+
+    #[inline]
+    pub fn sqrt(&self) -> CtOption<Self> {
+        // We use Shank's method, as p = 3 (mod 4). This means
+        // we only need to exponentiate by (p+1)/4. This only
+        // works for elements that are actually quadratic residue,
+        // so we check that we got the correct result at the end.
+
+        let sqrt = self.pow_vartime(&[
+            0xee7fbfffffffeaab,
+            0x7aaffffac54ffff,
+            0xd9cc34a83dac3d89,
+            0xd91dd2e13ce144af,
+            0x92c6e9ed90d2eb35,
+            0x680447a8e5ff9a6,
+        ]);
+
+        CtOption::new(sqrt, sqrt.square().ct_eq(self))
+    }
+
+    #[inline]
+    /// Computes the multiplicative inverse of this field
+    /// element, returning None in the case that this element
+    /// is zero.
+    pub fn invert(&self) -> CtOption<Self> {
+        // Exponentiate by p - 2
+        let t = self.pow_vartime(&[
+            0xb9feffffffffaaa9,
+            0x1eabfffeb153ffff,
+            0x6730d2a0f6b0f624,
+            0x64774b84f38512bf,
+            0x4b1ba7b6434bacd7,
+            0x1a0111ea397fe69a,
+        ]);
+
+        CtOption::new(t, !self.is_zero())
+    }
+
+    #[inline]
+    const fn subtract_p(&self) -> Fp {
+        let (r0, borrow) = sbb(self.0[0], MODULUS[0], 0);
+        let (r1, borrow) = sbb(self.0[1], MODULUS[1], borrow);
+        let (r2, borrow) = sbb(self.0[2], MODULUS[2], borrow);
+        let (r3, borrow) = sbb(self.0[3], MODULUS[3], borrow);
+        let (r4, borrow) = sbb(self.0[4], MODULUS[4], borrow);
+        let (r5, borrow) = sbb(self.0[5], MODULUS[5], borrow);
+
+        // If underflow occurred on the final limb, borrow = 0xfff...fff, otherwise
+        // borrow = 0x000...000. Thus, we use it as a mask!
+        let r0 = (self.0[0] & borrow) | (r0 & !borrow);
+        let r1 = (self.0[1] & borrow) | (r1 & !borrow);
+        let r2 = (self.0[2] & borrow) | (r2 & !borrow);
+        let r3 = (self.0[3] & borrow) | (r3 & !borrow);
+        let r4 = (self.0[4] & borrow) | (r4 & !borrow);
+        let r5 = (self.0[5] & borrow) | (r5 & !borrow);
+
+        Fp([r0, r1, r2, r3, r4, r5])
+    }
+
+    #[inline]
+    pub const fn add(&self, rhs: &Fp) -> Fp {
+        let (d0, carry) = adc(self.0[0], rhs.0[0], 0);
+        let (d1, carry) = adc(self.0[1], rhs.0[1], carry);
+        let (d2, carry) = adc(self.0[2], rhs.0[2], carry);
+        let (d3, carry) = adc(self.0[3], rhs.0[3], carry);
+        let (d4, carry) = adc(self.0[4], rhs.0[4], carry);
+        let (d5, _) = adc(self.0[5], rhs.0[5], carry);
+
+        // Attempt to subtract the modulus, to ensure the value
+        // is smaller than the modulus.
+        (&Fp([d0, d1, d2, d3, d4, d5])).subtract_p()
+    }
+
+    #[inline]
+    pub const fn neg(&self) -> Fp {
+        let (d0, borrow) = sbb(MODULUS[0], self.0[0], 0);
+        let (d1, borrow) = sbb(MODULUS[1], self.0[1], borrow);
+        let (d2, borrow) = sbb(MODULUS[2], self.0[2], borrow);
+        let (d3, borrow) = sbb(MODULUS[3], self.0[3], borrow);
+        let (d4, borrow) = sbb(MODULUS[4], self.0[4], borrow);
+        let (d5, _) = sbb(MODULUS[5], self.0[5], borrow);
+
+        // Let's use a mask if `self` was zero, which would mean
+        // the result of the subtraction is p.
+        let mask = (((self.0[0] | self.0[1] | self.0[2] | self.0[3] | self.0[4] | self.0[5]) == 0)
+            as u64)
+            .wrapping_sub(1);
+
+        Fp([
+            d0 & mask,
+            d1 & mask,
+            d2 & mask,
+            d3 & mask,
+            d4 & mask,
+            d5 & mask,
+        ])
+    }
+
+    #[inline]
+    pub const fn sub(&self, rhs: &Fp) -> Fp {
+        (&rhs.neg()).add(self)
+    }
+
+    #[inline(always)]
+    const fn montgomery_reduce(
+        t0: u64,
+        t1: u64,
+        t2: u64,
+        t3: u64,
+        t4: u64,
+        t5: u64,
+        t6: u64,
+        t7: u64,
+        t8: u64,
+        t9: u64,
+        t10: u64,
+        t11: u64,
+    ) -> Self {
+        // The Montgomery reduction here is based on Algorithm 14.32 in
+        // Handbook of Applied Cryptography
+        // <http://cacr.uwaterloo.ca/hac/about/chap14.pdf>.
+
+        let k = t0.wrapping_mul(INV);
+        let (_, carry) = mac(t0, k, MODULUS[0], 0);
+        let (r1, carry) = mac(t1, k, MODULUS[1], carry);
+        let (r2, carry) = mac(t2, k, MODULUS[2], carry);
+        let (r3, carry) = mac(t3, k, MODULUS[3], carry);
+        let (r4, carry) = mac(t4, k, MODULUS[4], carry);
+        let (r5, carry) = mac(t5, k, MODULUS[5], carry);
+        let (r6, r7) = adc(t6, 0, carry);
+
+        let k = r1.wrapping_mul(INV);
+        let (_, carry) = mac(r1, k, MODULUS[0], 0);
+        let (r2, carry) = mac(r2, k, MODULUS[1], carry);
+        let (r3, carry) = mac(r3, k, MODULUS[2], carry);
+        let (r4, carry) = mac(r4, k, MODULUS[3], carry);
+        let (r5, carry) = mac(r5, k, MODULUS[4], carry);
+        let (r6, carry) = mac(r6, k, MODULUS[5], carry);
+        let (r7, r8) = adc(t7, r7, carry);
+
+        let k = r2.wrapping_mul(INV);
+        let (_, carry) = mac(r2, k, MODULUS[0], 0);
+        let (r3, carry) = mac(r3, k, MODULUS[1], carry);
+        let (r4, carry) = mac(r4, k, MODULUS[2], carry);
+        let (r5, carry) = mac(r5, k, MODULUS[3], carry);
+        let (r6, carry) = mac(r6, k, MODULUS[4], carry);
+        let (r7, carry) = mac(r7, k, MODULUS[5], carry);
+        let (r8, r9) = adc(t8, r8, carry);
+
+        let k = r3.wrapping_mul(INV);
+        let (_, carry) = mac(r3, k, MODULUS[0], 0);
+        let (r4, carry) = mac(r4, k, MODULUS[1], carry);
+        let (r5, carry) = mac(r5, k, MODULUS[2], carry);
+        let (r6, carry) = mac(r6, k, MODULUS[3], carry);
+        let (r7, carry) = mac(r7, k, MODULUS[4], carry);
+        let (r8, carry) = mac(r8, k, MODULUS[5], carry);
+        let (r9, r10) = adc(t9, r9, carry);
+
+        let k = r4.wrapping_mul(INV);
+        let (_, carry) = mac(r4, k, MODULUS[0], 0);
+        let (r5, carry) = mac(r5, k, MODULUS[1], carry);
+        let (r6, carry) = mac(r6, k, MODULUS[2], carry);
+        let (r7, carry) = mac(r7, k, MODULUS[3], carry);
+        let (r8, carry) = mac(r8, k, MODULUS[4], carry);
+        let (r9, carry) = mac(r9, k, MODULUS[5], carry);
+        let (r10, r11) = adc(t10, r10, carry);
+
+        let k = r5.wrapping_mul(INV);
+        let (_, carry) = mac(r5, k, MODULUS[0], 0);
+        let (r6, carry) = mac(r6, k, MODULUS[1], carry);
+        let (r7, carry) = mac(r7, k, MODULUS[2], carry);
+        let (r8, carry) = mac(r8, k, MODULUS[3], carry);
+        let (r9, carry) = mac(r9, k, MODULUS[4], carry);
+        let (r10, carry) = mac(r10, k, MODULUS[5], carry);
+        let (r11, _) = adc(t11, r11, carry);
+
+        // Attempt to subtract the modulus, to ensure the value
+        // is smaller than the modulus.
+        (&Fp([r6, r7, r8, r9, r10, r11])).subtract_p()
+    }
+
+    #[inline]
+    pub const fn mul(&self, rhs: &Fp) -> Fp {
+        let (t0, carry) = mac(0, self.0[0], rhs.0[0], 0);
+        let (t1, carry) = mac(0, self.0[0], rhs.0[1], carry);
+        let (t2, carry) = mac(0, self.0[0], rhs.0[2], carry);
+        let (t3, carry) = mac(0, self.0[0], rhs.0[3], carry);
+        let (t4, carry) = mac(0, self.0[0], rhs.0[4], carry);
+        let (t5, t6) = mac(0, self.0[0], rhs.0[5], carry);
+
+        let (t1, carry) = mac(t1, self.0[1], rhs.0[0], 0);
+        let (t2, carry) = mac(t2, self.0[1], rhs.0[1], carry);
+        let (t3, carry) = mac(t3, self.0[1], rhs.0[2], carry);
+        let (t4, carry) = mac(t4, self.0[1], rhs.0[3], carry);
+        let (t5, carry) = mac(t5, self.0[1], rhs.0[4], carry);
+        let (t6, t7) = mac(t6, self.0[1], rhs.0[5], carry);
+
+        let (t2, carry) = mac(t2, self.0[2], rhs.0[0], 0);
+        let (t3, carry) = mac(t3, self.0[2], rhs.0[1], carry);
+        let (t4, carry) = mac(t4, self.0[2], rhs.0[2], carry);
+        let (t5, carry) = mac(t5, self.0[2], rhs.0[3], carry);
+        let (t6, carry) = mac(t6, self.0[2], rhs.0[4], carry);
+        let (t7, t8) = mac(t7, self.0[2], rhs.0[5], carry);
+
+        let (t3, carry) = mac(t3, self.0[3], rhs.0[0], 0);
+        let (t4, carry) = mac(t4, self.0[3], rhs.0[1], carry);
+        let (t5, carry) = mac(t5, self.0[3], rhs.0[2], carry);
+        let (t6, carry) = mac(t6, self.0[3], rhs.0[3], carry);
+        let (t7, carry) = mac(t7, self.0[3], rhs.0[4], carry);
+        let (t8, t9) = mac(t8, self.0[3], rhs.0[5], carry);
+
+        let (t4, carry) = mac(t4, self.0[4], rhs.0[0], 0);
+        let (t5, carry) = mac(t5, self.0[4], rhs.0[1], carry);
+        let (t6, carry) = mac(t6, self.0[4], rhs.0[2], carry);
+        let (t7, carry) = mac(t7, self.0[4], rhs.0[3], carry);
+        let (t8, carry) = mac(t8, self.0[4], rhs.0[4], carry);
+        let (t9, t10) = mac(t9, self.0[4], rhs.0[5], carry);
+
+        let (t5, carry) = mac(t5, self.0[5], rhs.0[0], 0);
+        let (t6, carry) = mac(t6, self.0[5], rhs.0[1], carry);
+        let (t7, carry) = mac(t7, self.0[5], rhs.0[2], carry);
+        let (t8, carry) = mac(t8, self.0[5], rhs.0[3], carry);
+        let (t9, carry) = mac(t9, self.0[5], rhs.0[4], carry);
+        let (t10, t11) = mac(t10, self.0[5], rhs.0[5], carry);
+
+        Self::montgomery_reduce(t0, t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11)
+    }
+
+    /// Squares this element.
+    #[inline]
+    pub const fn square(&self) -> Self {
+        let (t1, carry) = mac(0, self.0[0], self.0[1], 0);
+        let (t2, carry) = mac(0, self.0[0], self.0[2], carry);
+        let (t3, carry) = mac(0, self.0[0], self.0[3], carry);
+        let (t4, carry) = mac(0, self.0[0], self.0[4], carry);
+        let (t5, t6) = mac(0, self.0[0], self.0[5], carry);
+
+        let (t3, carry) = mac(t3, self.0[1], self.0[2], 0);
+        let (t4, carry) = mac(t4, self.0[1], self.0[3], carry);
+        let (t5, carry) = mac(t5, self.0[1], self.0[4], carry);
+        let (t6, t7) = mac(t6, self.0[1], self.0[5], carry);
+
+        let (t5, carry) = mac(t5, self.0[2], self.0[3], 0);
+        let (t6, carry) = mac(t6, self.0[2], self.0[4], carry);
+        let (t7, t8) = mac(t7, self.0[2], self.0[5], carry);
+
+        let (t7, carry) = mac(t7, self.0[3], self.0[4], 0);
+        let (t8, t9) = mac(t8, self.0[3], self.0[5], carry);
+
+        let (t9, t10) = mac(t9, self.0[4], self.0[5], 0);
+
+        let t11 = t10 >> 63;
+        let t10 = (t10 << 1) | (t9 >> 63);
+        let t9 = (t9 << 1) | (t8 >> 63);
+        let t8 = (t8 << 1) | (t7 >> 63);
+        let t7 = (t7 << 1) | (t6 >> 63);
+        let t6 = (t6 << 1) | (t5 >> 63);
+        let t5 = (t5 << 1) | (t4 >> 63);
+        let t4 = (t4 << 1) | (t3 >> 63);
+        let t3 = (t3 << 1) | (t2 >> 63);
+        let t2 = (t2 << 1) | (t1 >> 63);
+        let t1 = t1 << 1;
+
+        let (t0, carry) = mac(0, self.0[0], self.0[0], 0);
+        let (t1, carry) = adc(t1, 0, carry);
+        let (t2, carry) = mac(t2, self.0[1], self.0[1], carry);
+        let (t3, carry) = adc(t3, 0, carry);
+        let (t4, carry) = mac(t4, self.0[2], self.0[2], carry);
+        let (t5, carry) = adc(t5, 0, carry);
+        let (t6, carry) = mac(t6, self.0[3], self.0[3], carry);
+        let (t7, carry) = adc(t7, 0, carry);
+        let (t8, carry) = mac(t8, self.0[4], self.0[4], carry);
+        let (t9, carry) = adc(t9, 0, carry);
+        let (t10, carry) = mac(t10, self.0[5], self.0[5], carry);
+        let (t11, _) = adc(t11, 0, carry);
+
+        Self::montgomery_reduce(t0, t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11)
+    }
+}
+
+#[test]
+fn test_conditional_selection() {
+    let a = Fp([1, 2, 3, 4, 5, 6]);
+    let b = Fp([7, 8, 9, 10, 11, 12]);
+
+    assert_eq!(
+        ConditionallySelectable::conditional_select(&a, &b, Choice::from(0u8)),
+        a
+    );
+    assert_eq!(
+        ConditionallySelectable::conditional_select(&a, &b, Choice::from(1u8)),
+        b
+    );
+}
+
+#[test]
+fn test_equality() {
+    fn is_equal(a: &Fp, b: &Fp) -> bool {
+        let eq = a == b;
+        let ct_eq = a.ct_eq(&b);
+
+        assert_eq!(eq, ct_eq.unwrap_u8() == 1);
+
+        eq
+    }
+
+    assert!(is_equal(&Fp([1, 2, 3, 4, 5, 6]), &Fp([1, 2, 3, 4, 5, 6])));
+
+    assert!(!is_equal(&Fp([7, 2, 3, 4, 5, 6]), &Fp([1, 2, 3, 4, 5, 6])));
+    assert!(!is_equal(&Fp([1, 7, 3, 4, 5, 6]), &Fp([1, 2, 3, 4, 5, 6])));
+    assert!(!is_equal(&Fp([1, 2, 7, 4, 5, 6]), &Fp([1, 2, 3, 4, 5, 6])));
+    assert!(!is_equal(&Fp([1, 2, 3, 7, 5, 6]), &Fp([1, 2, 3, 4, 5, 6])));
+    assert!(!is_equal(&Fp([1, 2, 3, 4, 7, 6]), &Fp([1, 2, 3, 4, 5, 6])));
+    assert!(!is_equal(&Fp([1, 2, 3, 4, 5, 7]), &Fp([1, 2, 3, 4, 5, 6])));
+}
+
+#[test]
+fn test_squaring() {
+    let a = Fp([
+        0xd215d2768e83191b,
+        0x5085d80f8fb28261,
+        0xce9a032ddf393a56,
+        0x3e9c4fff2ca0c4bb,
+        0x6436b6f7f4d95dfb,
+        0x10606628ad4a4d90,
+    ]);
+    let b = Fp([
+        0x33d9c42a3cb3e235,
+        0xdad11a094c4cd455,
+        0xa2f144bd729aaeba,
+        0xd4150932be9ffeac,
+        0xe27bc7c47d44ee50,
+        0x14b6a78d3ec7a560,
+    ]);
+
+    assert_eq!(a.square(), b);
+}
+
+#[test]
+fn test_multiplication() {
+    let a = Fp([
+        0x397a38320170cd4,
+        0x734c1b2c9e761d30,
+        0x5ed255ad9a48beb5,
+        0x95a3c6b22a7fcfc,
+        0x2294ce75d4e26a27,
+        0x13338bd870011ebb,
+    ]);
+    let b = Fp([
+        0xb9c3c7c5b1196af7,
+        0x2580e2086ce335c1,
+        0xf49aed3d8a57ef42,
+        0x41f281e49846e878,
+        0xe0762346c38452ce,
+        0x652e89326e57dc0,
+    ]);
+    let c = Fp([
+        0xf96ef3d711ab5355,
+        0xe8d459ea00f148dd,
+        0x53f7354a5f00fa78,
+        0x9e34a4f3125c5f83,
+        0x3fbe0c47ca74c19e,
+        0x1b06a8bbd4adfe4,
+    ]);
+
+    assert_eq!(a * b, c);
+}
+
+#[test]
+fn test_addition() {
+    let a = Fp([
+        0x5360bb5978678032,
+        0x7dd275ae799e128e,
+        0x5c5b5071ce4f4dcf,
+        0xcdb21f93078dbb3e,
+        0xc32365c5e73f474a,
+        0x115a2a5489babe5b,
+    ]);
+    let b = Fp([
+        0x9fd287733d23dda0,
+        0xb16bf2af738b3554,
+        0x3e57a75bd3cc6d1d,
+        0x900bc0bd627fd6d6,
+        0xd319a080efb245fe,
+        0x15fdcaa4e4bb2091,
+    ]);
+    let c = Fp([
+        0x393442ccb58bb327,
+        0x1092685f3bd547e3,
+        0x3382252cab6ac4c9,
+        0xf94694cb76887f55,
+        0x4b215e9093a5e071,
+        0xd56e30f34f5f853,
+    ]);
+
+    assert_eq!(a + b, c);
+}
+
+#[test]
+fn test_subtraction() {
+    let a = Fp([
+        0x5360bb5978678032,
+        0x7dd275ae799e128e,
+        0x5c5b5071ce4f4dcf,
+        0xcdb21f93078dbb3e,
+        0xc32365c5e73f474a,
+        0x115a2a5489babe5b,
+    ]);
+    let b = Fp([
+        0x9fd287733d23dda0,
+        0xb16bf2af738b3554,
+        0x3e57a75bd3cc6d1d,
+        0x900bc0bd627fd6d6,
+        0xd319a080efb245fe,
+        0x15fdcaa4e4bb2091,
+    ]);
+    let c = Fp([
+        0x6d8d33e63b434d3d,
+        0xeb1282fdb766dd39,
+        0x85347bb6f133d6d5,
+        0xa21daa5a9892f727,
+        0x3b256cfb3ad8ae23,
+        0x155d7199de7f8464,
+    ]);
+
+    assert_eq!(a - b, c);
+}
+
+#[test]
+fn test_negation() {
+    let a = Fp([
+        0x5360bb5978678032,
+        0x7dd275ae799e128e,
+        0x5c5b5071ce4f4dcf,
+        0xcdb21f93078dbb3e,
+        0xc32365c5e73f474a,
+        0x115a2a5489babe5b,
+    ]);
+    let b = Fp([
+        0x669e44a687982a79,
+        0xa0d98a5037b5ed71,
+        0xad5822f2861a854,
+        0x96c52bf1ebf75781,
+        0x87f841f05c0c658c,
+        0x8a6e795afc5283e,
+    ]);
+
+    assert_eq!(-a, b);
+}
+
+#[test]
+fn test_debug() {
+    assert_eq!(
+        format!(
+            "{:?}",
+            Fp([0x5360bb5978678032, 0x7dd275ae799e128e, 0x5c5b5071ce4f4dcf, 0xcdb21f93078dbb3e, 0xc32365c5e73f474a, 0x115a2a5489babe5b])
+        ),
+        "0x104bf052ad3bc99bcb176c24a06a6c3aad4eaf2308fc4d282e106c84a757d061052630515305e59bdddf8111bfdeb704"
+    );
+}
+
+#[test]
+fn test_from_bytes() {
+    let mut a = Fp([
+        0xdc906d9be3f95dc8,
+        0x8755caf7459691a1,
+        0xcff1a7f4e9583ab3,
+        0x9b43821f849e2284,
+        0xf57554f3a2974f3f,
+        0x85dbea84ed47f79,
+    ]);
+
+    for _ in 0..100 {
+        a = a.square();
+        let tmp = a.to_bytes();
+        let b = Fp::from_bytes(&tmp).unwrap();
+
+        assert_eq!(a, b);
+    }
+
+    assert_eq!(
+        -Fp::one(),
+        Fp::from_bytes(&[
+            26, 1, 17, 234, 57, 127, 230, 154, 75, 27, 167, 182, 67, 75, 172, 215, 100, 119, 75,
+            132, 243, 133, 18, 191, 103, 48, 210, 160, 246, 176, 246, 36, 30, 171, 255, 254, 177,
+            83, 255, 255, 185, 254, 255, 255, 255, 255, 170, 170
+        ])
+        .unwrap()
+    );
+
+    assert!(
+        Fp::from_bytes(&[
+            27, 1, 17, 234, 57, 127, 230, 154, 75, 27, 167, 182, 67, 75, 172, 215, 100, 119, 75,
+            132, 243, 133, 18, 191, 103, 48, 210, 160, 246, 176, 246, 36, 30, 171, 255, 254, 177,
+            83, 255, 255, 185, 254, 255, 255, 255, 255, 170, 170
+        ])
+        .is_none()
+        .unwrap_u8()
+            == 1
+    );
+
+    assert!(Fp::from_bytes(&[0xff; 48]).is_none().unwrap_u8() == 1);
+}
+
+#[test]
+fn test_sqrt() {
+    // a = 4
+    let a = Fp::from_raw_unchecked([
+        0xaa270000000cfff3,
+        0x53cc0032fc34000a,
+        0x478fe97a6b0a807f,
+        0xb1d37ebee6ba24d7,
+        0x8ec9733bbf78ab2f,
+        0x9d645513d83de7e,
+    ]);
+
+    assert_eq!(
+        // sqrt(4) = -2
+        -a.sqrt().unwrap(),
+        // 2
+        Fp::from_raw_unchecked([
+            0x321300000006554f,
+            0xb93c0018d6c40005,
+            0x57605e0db0ddbb51,
+            0x8b256521ed1f9bcb,
+            0x6cf28d7901622c03,
+            0x11ebab9dbb81e28c
+        ])
+    );
+}
+
+#[test]
+fn test_inversion() {
+    let a = Fp([
+        0x43b43a5078ac2076,
+        0x1ce0763046f8962b,
+        0x724a5276486d735c,
+        0x6f05c2a6282d48fd,
+        0x2095bd5bb4ca9331,
+        0x3b35b3894b0f7da,
+    ]);
+    let b = Fp([
+        0x69ecd7040952148f,
+        0x985ccc2022190f55,
+        0xe19bba36a9ad2f41,
+        0x19bb16c95219dbd8,
+        0x14dcacfdfb478693,
+        0x115ff58afff9a8e1,
+    ]);
+
+    assert_eq!(a.invert().unwrap(), b);
+    assert!(Fp::zero().invert().is_none().unwrap_u8() == 1);
+}
+
+#[test]
+fn test_lexicographic_largest() {
+    assert!(!bool::from(Fp::zero().lexicographically_largest()));
+    assert!(!bool::from(Fp::one().lexicographically_largest()));
+    assert!(!bool::from(
+        Fp::from_raw_unchecked([
+            0xa1fafffffffe5557,
+            0x995bfff976a3fffe,
+            0x3f41d24d174ceb4,
+            0xf6547998c1995dbd,
+            0x778a468f507a6034,
+            0x20559931f7f8103
+        ])
+        .lexicographically_largest()
+    ));
+    assert!(bool::from(
+        Fp::from_raw_unchecked([
+            0x1804000000015554,
+            0x855000053ab00001,
+            0x633cb57c253c276f,
+            0x6e22d1ec31ebb502,
+            0xd3916126f2d14ca2,
+            0x17fbb8571a006596
+        ])
+        .lexicographically_largest()
+    ));
+    assert!(bool::from(
+        Fp::from_raw_unchecked([
+            0x43f5fffffffcaaae,
+            0x32b7fff2ed47fffd,
+            0x7e83a49a2e99d69,
+            0xeca8f3318332bb7a,
+            0xef148d1ea0f4c069,
+            0x40ab3263eff0206
+        ])
+        .lexicographically_largest()
+    ));
+}
diff --git a/bls12_381/src/fp12.rs b/bls12_381/src/fp12.rs
new file mode 100644
index 0000000..de9b540
--- /dev/null
+++ b/bls12_381/src/fp12.rs
@@ -0,0 +1,638 @@
+use crate::fp::*;
+use crate::fp2::*;
+use crate::fp6::*;
+
+use core::fmt;
+use core::ops::{Add, AddAssign, Mul, MulAssign, Neg, Sub, SubAssign};
+use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption};
+
+/// This represents an element $c_0 + c_1 w$ of $\mathbb{F}_{p^12} = \mathbb{F}_{p^6} / w^2 - v$.
+pub struct Fp12 {
+    pub c0: Fp6,
+    pub c1: Fp6,
+}
+
+impl From<Fp> for Fp12 {
+    fn from(f: Fp) -> Fp12 {
+        Fp12 {
+            c0: Fp6::from(f),
+            c1: Fp6::zero(),
+        }
+    }
+}
+
+impl From<Fp2> for Fp12 {
+    fn from(f: Fp2) -> Fp12 {
+        Fp12 {
+            c0: Fp6::from(f),
+            c1: Fp6::zero(),
+        }
+    }
+}
+
+impl From<Fp6> for Fp12 {
+    fn from(f: Fp6) -> Fp12 {
+        Fp12 {
+            c0: f,
+            c1: Fp6::zero(),
+        }
+    }
+}
+
+impl PartialEq for Fp12 {
+    fn eq(&self, other: &Fp12) -> bool {
+        self.ct_eq(other).into()
+    }
+}
+
+impl Copy for Fp12 {}
+impl Clone for Fp12 {
+    #[inline]
+    fn clone(&self) -> Self {
+        *self
+    }
+}
+
+impl Default for Fp12 {
+    fn default() -> Self {
+        Fp12::zero()
+    }
+}
+
+impl fmt::Debug for Fp12 {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        write!(f, "{:?} + ({:?})*w", self.c0, self.c1)
+    }
+}
+
+impl ConditionallySelectable for Fp12 {
+    #[inline(always)]
+    fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self {
+        Fp12 {
+            c0: Fp6::conditional_select(&a.c0, &b.c0, choice),
+            c1: Fp6::conditional_select(&a.c1, &b.c1, choice),
+        }
+    }
+}
+
+impl ConstantTimeEq for Fp12 {
+    #[inline(always)]
+    fn ct_eq(&self, other: &Self) -> Choice {
+        self.c0.ct_eq(&other.c0) & self.c1.ct_eq(&other.c1)
+    }
+}
+
+impl Fp12 {
+    #[inline]
+    pub fn zero() -> Self {
+        Fp12 {
+            c0: Fp6::zero(),
+            c1: Fp6::zero(),
+        }
+    }
+
+    #[inline]
+    pub fn one() -> Self {
+        Fp12 {
+            c0: Fp6::one(),
+            c1: Fp6::zero(),
+        }
+    }
+
+    pub fn mul_by_014(&self, c0: &Fp2, c1: &Fp2, c4: &Fp2) -> Fp12 {
+        let aa = self.c0.mul_by_01(c0, c1);
+        let bb = self.c1.mul_by_1(c4);
+        let o = c1 + c4;
+        let c1 = self.c1 + self.c0;
+        let c1 = c1.mul_by_01(c0, &o);
+        let c1 = c1 - aa - bb;
+        let c0 = bb;
+        let c0 = c0.mul_by_nonresidue();
+        let c0 = c0 + aa;
+
+        Fp12 { c0, c1 }
+    }
+
+    #[inline(always)]
+    pub fn is_zero(&self) -> Choice {
+        self.c0.is_zero() & self.c1.is_zero()
+    }
+
+    #[inline(always)]
+    pub fn conjugate(&self) -> Self {
+        Fp12 {
+            c0: self.c0,
+            c1: -self.c1,
+        }
+    }
+
+    /// Raises this element to p.
+    #[inline(always)]
+    pub fn frobenius_map(&self) -> Self {
+        let c0 = self.c0.frobenius_map();
+        let c1 = self.c1.frobenius_map();
+
+        // c1 = c1 * (u + 1)^((p - 1) / 6)
+        let c1 = c1
+            * Fp6::from(Fp2 {
+                c0: Fp::from_raw_unchecked([
+                    0x7089552b319d465,
+                    0xc6695f92b50a8313,
+                    0x97e83cccd117228f,
+                    0xa35baecab2dc29ee,
+                    0x1ce393ea5daace4d,
+                    0x8f2220fb0fb66eb,
+                ]),
+                c1: Fp::from_raw_unchecked([
+                    0xb2f66aad4ce5d646,
+                    0x5842a06bfc497cec,
+                    0xcf4895d42599d394,
+                    0xc11b9cba40a8e8d0,
+                    0x2e3813cbe5a0de89,
+                    0x110eefda88847faf,
+                ]),
+            });
+
+        Fp12 { c0, c1 }
+    }
+
+    #[inline]
+    pub fn square(&self) -> Self {
+        let ab = self.c0 * self.c1;
+        let c0c1 = self.c0 + self.c1;
+        let c0 = self.c1.mul_by_nonresidue();
+        let c0 = c0 + self.c0;
+        let c0 = c0 * c0c1;
+        let c0 = c0 - ab;
+        let c1 = ab + ab;
+        let c0 = c0 - ab.mul_by_nonresidue();
+
+        Fp12 { c0, c1 }
+    }
+
+    pub fn invert(&self) -> CtOption<Self> {
+        (self.c0.square() - self.c1.square().mul_by_nonresidue())
+            .invert()
+            .map(|t| Fp12 {
+                c0: self.c0 * t,
+                c1: self.c1 * -t,
+            })
+    }
+}
+
+impl<'a, 'b> Mul<&'b Fp12> for &'a Fp12 {
+    type Output = Fp12;
+
+    #[inline]
+    fn mul(self, other: &'b Fp12) -> Self::Output {
+        let aa = self.c0 * other.c0;
+        let bb = self.c1 * other.c1;
+        let o = other.c0 + other.c1;
+        let c1 = self.c1 + self.c0;
+        let c1 = c1 * o;
+        let c1 = c1 - aa;
+        let c1 = c1 - bb;
+        let c0 = bb.mul_by_nonresidue();
+        let c0 = c0 + aa;
+
+        Fp12 { c0, c1 }
+    }
+}
+
+impl<'a, 'b> Add<&'b Fp12> for &'a Fp12 {
+    type Output = Fp12;
+
+    #[inline]
+    fn add(self, rhs: &'b Fp12) -> Self::Output {
+        Fp12 {
+            c0: self.c0 + rhs.c0,
+            c1: self.c1 + rhs.c1,
+        }
+    }
+}
+
+impl<'a> Neg for &'a Fp12 {
+    type Output = Fp12;
+
+    #[inline]
+    fn neg(self) -> Self::Output {
+        Fp12 {
+            c0: -self.c0,
+            c1: -self.c1,
+        }
+    }
+}
+
+impl Neg for Fp12 {
+    type Output = Fp12;
+
+    #[inline]
+    fn neg(self) -> Self::Output {
+        -&self
+    }
+}
+
+impl<'a, 'b> Sub<&'b Fp12> for &'a Fp12 {
+    type Output = Fp12;
+
+    #[inline]
+    fn sub(self, rhs: &'b Fp12) -> Self::Output {
+        Fp12 {
+            c0: self.c0 - rhs.c0,
+            c1: self.c1 - rhs.c1,
+        }
+    }
+}
+
+impl_binops_additive!(Fp12, Fp12);
+impl_binops_multiplicative!(Fp12, Fp12);
+
+#[test]
+fn test_arithmetic() {
+    use crate::fp::*;
+    use crate::fp2::*;
+
+    let a = Fp12 {
+        c0: Fp6 {
+            c0: Fp2 {
+                c0: Fp::from_raw_unchecked([
+                    0x47f9cb98b1b82d58,
+                    0x5fe911eba3aa1d9d,
+                    0x96bf1b5f4dd81db3,
+                    0x8100d27cc9259f5b,
+                    0xafa20b9674640eab,
+                    0x9bbcea7d8d9497d,
+                ]),
+                c1: Fp::from_raw_unchecked([
+                    0x303cb98b1662daa,
+                    0xd93110aa0a621d5a,
+                    0xbfa9820c5be4a468,
+                    0xba3643ecb05a348,
+                    0xdc3534bb1f1c25a6,
+                    0x6c305bb19c0e1c1,
+                ]),
+            },
+            c1: Fp2 {
+                c0: Fp::from_raw_unchecked([
+                    0x46f9cb98b162d858,
+                    0xbe9109cf7aa1d57,
+                    0xc791bc55fece41d2,
+                    0xf84c57704e385ec2,
+                    0xcb49c1d9c010e60f,
+                    0xacdb8e158bfe3c8,
+                ]),
+                c1: Fp::from_raw_unchecked([
+                    0x8aefcb98b15f8306,
+                    0x3ea1108fe4f21d54,
+                    0xcf79f69fa1b7df3b,
+                    0xe4f54aa1d16b1a3c,
+                    0xba5e4ef86105a679,
+                    0xed86c0797bee5cf,
+                ]),
+            },
+            c2: Fp2 {
+                c0: Fp::from_raw_unchecked([
+                    0xcee5cb98b15c2db4,
+                    0x71591082d23a1d51,
+                    0xd76230e944a17ca4,
+                    0xd19e3dd3549dd5b6,
+                    0xa972dc1701fa66e3,
+                    0x12e31f2dd6bde7d6,
+                ]),
+                c1: Fp::from_raw_unchecked([
+                    0xad2acb98b1732d9d,
+                    0x2cfd10dd06961d64,
+                    0x7396b86c6ef24e8,
+                    0xbd76e2fdb1bfc820,
+                    0x6afea7f6de94d0d5,
+                    0x10994b0c5744c040,
+                ]),
+            },
+        },
+        c1: Fp6 {
+            c0: Fp2 {
+                c0: Fp::from_raw_unchecked([
+                    0x47f9cb98b1b82d58,
+                    0x5fe911eba3aa1d9d,
+                    0x96bf1b5f4dd81db3,
+                    0x8100d27cc9259f5b,
+                    0xafa20b9674640eab,
+                    0x9bbcea7d8d9497d,
+                ]),
+                c1: Fp::from_raw_unchecked([
+                    0x303cb98b1662daa,
+                    0xd93110aa0a621d5a,
+                    0xbfa9820c5be4a468,
+                    0xba3643ecb05a348,
+                    0xdc3534bb1f1c25a6,
+                    0x6c305bb19c0e1c1,
+                ]),
+            },
+            c1: Fp2 {
+                c0: Fp::from_raw_unchecked([
+                    0x46f9cb98b162d858,
+                    0xbe9109cf7aa1d57,
+                    0xc791bc55fece41d2,
+                    0xf84c57704e385ec2,
+                    0xcb49c1d9c010e60f,
+                    0xacdb8e158bfe3c8,
+                ]),
+                c1: Fp::from_raw_unchecked([
+                    0x8aefcb98b15f8306,
+                    0x3ea1108fe4f21d54,
+                    0xcf79f69fa1b7df3b,
+                    0xe4f54aa1d16b1a3c,
+                    0xba5e4ef86105a679,
+                    0xed86c0797bee5cf,
+                ]),
+            },
+            c2: Fp2 {
+                c0: Fp::from_raw_unchecked([
+                    0xcee5cb98b15c2db4,
+                    0x71591082d23a1d51,
+                    0xd76230e944a17ca4,
+                    0xd19e3dd3549dd5b6,
+                    0xa972dc1701fa66e3,
+                    0x12e31f2dd6bde7d6,
+                ]),
+                c1: Fp::from_raw_unchecked([
+                    0xad2acb98b1732d9d,
+                    0x2cfd10dd06961d64,
+                    0x7396b86c6ef24e8,
+                    0xbd76e2fdb1bfc820,
+                    0x6afea7f6de94d0d5,
+                    0x10994b0c5744c040,
+                ]),
+            },
+        },
+    };
+
+    let b = Fp12 {
+        c0: Fp6 {
+            c0: Fp2 {
+                c0: Fp::from_raw_unchecked([
+                    0x47f9cb98b1b82d58,
+                    0x5fe911eba3aa1d9d,
+                    0x96bf1b5f4dd81db3,
+                    0x8100d272c9259f5b,
+                    0xafa20b9674640eab,
+                    0x9bbcea7d8d9497d,
+                ]),
+                c1: Fp::from_raw_unchecked([
+                    0x303cb98b1662daa,
+                    0xd93110aa0a621d5a,
+                    0xbfa9820c5be4a468,
+                    0xba3643ecb05a348,
+                    0xdc3534bb1f1c25a6,
+                    0x6c305bb19c0e1c1,
+                ]),
+            },
+            c1: Fp2 {
+                c0: Fp::from_raw_unchecked([
+                    0x46f9cb98b162d858,
+                    0xbe9109cf7aa1d57,
+                    0xc791bc55fece41d2,
+                    0xf84c57704e385ec2,
+                    0xcb49c1d9c010e60f,
+                    0xacdb8e158bfe348,
+                ]),
+                c1: Fp::from_raw_unchecked([
+                    0x8aefcb98b15f8306,
+                    0x3ea1108fe4f21d54,
+                    0xcf79f69fa1b7df3b,
+                    0xe4f54aa1d16b1a3c,
+                    0xba5e4ef86105a679,
+                    0xed86c0797bee5cf,
+                ]),
+            },
+            c2: Fp2 {
+                c0: Fp::from_raw_unchecked([
+                    0xcee5cb98b15c2db4,
+                    0x71591082d23a1d51,
+                    0xd76230e944a17ca4,
+                    0xd19e3dd3549dd5b6,
+                    0xa972dc1701fa66e3,
+                    0x12e31f2dd6bde7d6,
+                ]),
+                c1: Fp::from_raw_unchecked([
+                    0xad2acb98b1732d9d,
+                    0x2cfd10dd06961d64,
+                    0x7396b86c6ef24e8,
+                    0xbd76e2fdb1bfc820,
+                    0x6afea7f6de94d0d5,
+                    0x10994b0c5744c040,
+                ]),
+            },
+        },
+        c1: Fp6 {
+            c0: Fp2 {
+                c0: Fp::from_raw_unchecked([
+                    0x47f9cb98b1b82d58,
+                    0x5fe911eba3aa1d9d,
+                    0x96bf1b5f4dd21db3,
+                    0x8100d27cc9259f5b,
+                    0xafa20b9674640eab,
+                    0x9bbcea7d8d9497d,
+                ]),
+                c1: Fp::from_raw_unchecked([
+                    0x303cb98b1662daa,
+                    0xd93110aa0a621d5a,
+                    0xbfa9820c5be4a468,
+                    0xba3643ecb05a348,
+                    0xdc3534bb1f1c25a6,
+                    0x6c305bb19c0e1c1,
+                ]),
+            },
+            c1: Fp2 {
+                c0: Fp::from_raw_unchecked([
+                    0x46f9cb98b162d858,
+                    0xbe9109cf7aa1d57,
+                    0xc791bc55fece41d2,
+                    0xf84c57704e385ec2,
+                    0xcb49c1d9c010e60f,
+                    0xacdb8e158bfe3c8,
+                ]),
+                c1: Fp::from_raw_unchecked([
+                    0x8aefcb98b15f8306,
+                    0x3ea1108fe4f21d54,
+                    0xcf79f69fa117df3b,
+                    0xe4f54aa1d16b1a3c,
+                    0xba5e4ef86105a679,
+                    0xed86c0797bee5cf,
+                ]),
+            },
+            c2: Fp2 {
+                c0: Fp::from_raw_unchecked([
+                    0xcee5cb98b15c2db4,
+                    0x71591082d23a1d51,
+                    0xd76230e944a17ca4,
+                    0xd19e3dd3549dd5b6,
+                    0xa972dc1701fa66e3,
+                    0x12e31f2dd6bde7d6,
+                ]),
+                c1: Fp::from_raw_unchecked([
+                    0xad2acb98b1732d9d,
+                    0x2cfd10dd06961d64,
+                    0x7396b86c6ef24e8,
+                    0xbd76e2fdb1bfc820,
+                    0x6afea7f6de94d0d5,
+                    0x10994b0c5744c040,
+                ]),
+            },
+        },
+    };
+
+    let c = Fp12 {
+        c0: Fp6 {
+            c0: Fp2 {
+                c0: Fp::from_raw_unchecked([
+                    0x47f9cb9871b82d58,
+                    0x5fe911eba3aa1d9d,
+                    0x96bf1b5f4dd81db3,
+                    0x8100d27cc9259f5b,
+                    0xafa20b9674640eab,
+                    0x9bbcea7d8d9497d,
+                ]),
+                c1: Fp::from_raw_unchecked([
+                    0x303cb98b1662daa,
+                    0xd93110aa0a621d5a,
+                    0xbfa9820c5be4a468,
+                    0xba3643ecb05a348,
+                    0xdc3534bb1f1c25a6,
+                    0x6c305bb19c0e1c1,
+                ]),
+            },
+            c1: Fp2 {
+                c0: Fp::from_raw_unchecked([
+                    0x46f9cb98b162d858,
+                    0xbe9109cf7aa1d57,
+                    0x7791bc55fece41d2,
+                    0xf84c57704e385ec2,
+                    0xcb49c1d9c010e60f,
+                    0xacdb8e158bfe3c8,
+                ]),
+                c1: Fp::from_raw_unchecked([
+                    0x8aefcb98b15f8306,
+                    0x3ea1108fe4f21d54,
+                    0xcf79f69fa1b7df3b,
+                    0xe4f54aa1d16b133c,
+                    0xba5e4ef86105a679,
+                    0xed86c0797bee5cf,
+                ]),
+            },
+            c2: Fp2 {
+                c0: Fp::from_raw_unchecked([
+                    0xcee5cb98b15c2db4,
+                    0x71591082d23a1d51,
+                    0xd76240e944a17ca4,
+                    0xd19e3dd3549dd5b6,
+                    0xa972dc1701fa66e3,
+                    0x12e31f2dd6bde7d6,
+                ]),
+                c1: Fp::from_raw_unchecked([
+                    0xad2acb98b1732d9d,
+                    0x2cfd10dd06961d64,
+                    0x7396b86c6ef24e8,
+                    0xbd76e2fdb1bfc820,
+                    0x6afea7f6de94d0d5,
+                    0x10994b0c1744c040,
+                ]),
+            },
+        },
+        c1: Fp6 {
+            c0: Fp2 {
+                c0: Fp::from_raw_unchecked([
+                    0x47f9cb98b1b82d58,
+                    0x5fe911eba3aa1d9d,
+                    0x96bf1b5f4dd81db3,
+                    0x8100d27cc9259f5b,
+                    0xafa20b9674640eab,
+                    0x9bbcea7d8d9497d,
+                ]),
+                c1: Fp::from_raw_unchecked([
+                    0x303cb98b1662daa,
+                    0xd93110aa0a621d5a,
+                    0xbfa9820c5be4a468,
+                    0xba3643ecb05a348,
+                    0xdc3534bb1f1c25a6,
+                    0x6c305bb19c0e1c1,
+                ]),
+            },
+            c1: Fp2 {
+                c0: Fp::from_raw_unchecked([
+                    0x46f9cb98b162d858,
+                    0xbe9109cf7aa1d57,
+                    0xc791bc55fece41d2,
+                    0xf84c57704e385ec2,
+                    0xcb49c1d3c010e60f,
+                    0xacdb8e158bfe3c8,
+                ]),
+                c1: Fp::from_raw_unchecked([
+                    0x8aefcb98b15f8306,
+                    0x3ea1108fe4f21d54,
+                    0xcf79f69fa1b7df3b,
+                    0xe4f54aa1d16b1a3c,
+                    0xba5e4ef86105a679,
+                    0xed86c0797bee5cf,
+                ]),
+            },
+            c2: Fp2 {
+                c0: Fp::from_raw_unchecked([
+                    0xcee5cb98b15c2db4,
+                    0x71591082d23a1d51,
+                    0xd76230e944a17ca4,
+                    0xd19e3dd3549dd5b6,
+                    0xa972dc1701fa66e3,
+                    0x12e31f2dd6bde7d6,
+                ]),
+                c1: Fp::from_raw_unchecked([
+                    0xad2acb98b1732d9d,
+                    0x2cfd10dd06961d64,
+                    0x7396b86c6ef24e8,
+                    0xbd76e2fdb1bfc820,
+                    0x6afea7f6de94d0d5,
+                    0x10994b0c57441040,
+                ]),
+            },
+        },
+    };
+
+    // because a and b and c are similar to each other and
+    // I was lazy, this is just some arbitrary way to make
+    // them a little more different
+    let a = &a.square().invert().unwrap().square() + &c;
+    let b = &b.square().invert().unwrap().square() + &a;
+    let c = &c.square().invert().unwrap().square() + &b;
+
+    assert_eq!(a.square(), &a * &a);
+    assert_eq!(b.square(), &b * &b);
+    assert_eq!(c.square(), &c * &c);
+
+    assert_eq!(
+        (a + b) * c.square(),
+        &(&(&c * &c) * &a) + &(&(&c * &c) * &b)
+    );
+
+    assert_eq!(
+        &a.invert().unwrap() * &b.invert().unwrap(),
+        (&a * &b).invert().unwrap()
+    );
+    assert_eq!(&a.invert().unwrap() * &a, Fp12::one());
+
+    assert!(a != a.frobenius_map());
+    assert_eq!(
+        a,
+        a.frobenius_map()
+            .frobenius_map()
+            .frobenius_map()
+            .frobenius_map()
+            .frobenius_map()
+            .frobenius_map()
+            .frobenius_map()
+            .frobenius_map()
+            .frobenius_map()
+            .frobenius_map()
+            .frobenius_map()
+            .frobenius_map()
+    );
+}
diff --git a/bls12_381/src/fp2.rs b/bls12_381/src/fp2.rs
new file mode 100644
index 0000000..4cd0a23
--- /dev/null
+++ b/bls12_381/src/fp2.rs
@@ -0,0 +1,868 @@
+//! This module implements arithmetic over the quadratic extension field Fp2.
+
+use core::fmt;
+use core::ops::{Add, AddAssign, Mul, MulAssign, Neg, Sub, SubAssign};
+
+use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption};
+
+use crate::fp::Fp;
+
+#[derive(Copy, Clone)]
+pub struct Fp2 {
+    pub c0: Fp,
+    pub c1: Fp,
+}
+
+impl fmt::Debug for Fp2 {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        write!(f, "{:?} + {:?}*u", self.c0, self.c1)
+    }
+}
+
+impl Default for Fp2 {
+    fn default() -> Self {
+        Fp2::zero()
+    }
+}
+
+impl From<Fp> for Fp2 {
+    fn from(f: Fp) -> Fp2 {
+        Fp2 {
+            c0: f,
+            c1: Fp::zero(),
+        }
+    }
+}
+
+impl ConstantTimeEq for Fp2 {
+    fn ct_eq(&self, other: &Self) -> Choice {
+        self.c0.ct_eq(&other.c0) & self.c1.ct_eq(&other.c1)
+    }
+}
+
+impl Eq for Fp2 {}
+impl PartialEq for Fp2 {
+    #[inline]
+    fn eq(&self, other: &Self) -> bool {
+        self.ct_eq(other).unwrap_u8() == 1
+    }
+}
+
+impl ConditionallySelectable for Fp2 {
+    fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self {
+        Fp2 {
+            c0: Fp::conditional_select(&a.c0, &b.c0, choice),
+            c1: Fp::conditional_select(&a.c1, &b.c1, choice),
+        }
+    }
+}
+
+impl<'a> Neg for &'a Fp2 {
+    type Output = Fp2;
+
+    #[inline]
+    fn neg(self) -> Fp2 {
+        self.neg()
+    }
+}
+
+impl Neg for Fp2 {
+    type Output = Fp2;
+
+    #[inline]
+    fn neg(self) -> Fp2 {
+        -&self
+    }
+}
+
+impl<'a, 'b> Sub<&'b Fp2> for &'a Fp2 {
+    type Output = Fp2;
+
+    #[inline]
+    fn sub(self, rhs: &'b Fp2) -> Fp2 {
+        self.sub(rhs)
+    }
+}
+
+impl<'a, 'b> Add<&'b Fp2> for &'a Fp2 {
+    type Output = Fp2;
+
+    #[inline]
+    fn add(self, rhs: &'b Fp2) -> Fp2 {
+        self.add(rhs)
+    }
+}
+
+impl<'a, 'b> Mul<&'b Fp2> for &'a Fp2 {
+    type Output = Fp2;
+
+    #[inline]
+    fn mul(self, rhs: &'b Fp2) -> Fp2 {
+        self.mul(rhs)
+    }
+}
+
+impl_binops_additive!(Fp2, Fp2);
+impl_binops_multiplicative!(Fp2, Fp2);
+
+impl Fp2 {
+    #[inline]
+    pub const fn zero() -> Fp2 {
+        Fp2 {
+            c0: Fp::zero(),
+            c1: Fp::zero(),
+        }
+    }
+
+    #[inline]
+    pub const fn one() -> Fp2 {
+        Fp2 {
+            c0: Fp::one(),
+            c1: Fp::zero(),
+        }
+    }
+
+    pub fn is_zero(&self) -> Choice {
+        self.c0.is_zero() & self.c1.is_zero()
+    }
+
+    /// Raises this element to p.
+    #[inline(always)]
+    pub fn frobenius_map(&self) -> Self {
+        // This is always just a conjugation. If you're curious why, here's
+        // an article about it: https://alicebob.cryptoland.net/the-frobenius-endomorphism-with-finite-fields/
+        self.conjugate()
+    }
+
+    #[inline(always)]
+    pub fn conjugate(&self) -> Self {
+        Fp2 {
+            c0: self.c0,
+            c1: -self.c1,
+        }
+    }
+
+    #[inline(always)]
+    pub fn mul_by_nonresidue(&self) -> Fp2 {
+        // Multiply a + bu by u + 1, getting
+        // au + a + bu^2 + bu
+        // and because u^2 = -1, we get
+        // (a - b) + (a + b)u
+
+        Fp2 {
+            c0: self.c0 - self.c1,
+            c1: self.c0 + self.c1,
+        }
+    }
+
+    /// Returns whether or not this element is strictly lexicographically
+    /// larger than its negation.
+    #[inline]
+    pub fn lexicographically_largest(&self) -> Choice {
+        // If this element's c1 coefficient is lexicographically largest
+        // then it is lexicographically largest. Otherwise, in the event
+        // the c1 coefficient is zero and the c0 coefficient is
+        // lexicographically largest, then this element is lexicographically
+        // largest.
+
+        self.c1.lexicographically_largest()
+            | (self.c1.is_zero() & self.c0.lexicographically_largest())
+    }
+
+    pub const fn square(&self) -> Fp2 {
+        // Complex squaring:
+        //
+        // v0  = c0 * c1
+        // c0' = (c0 + c1) * (c0 + \beta*c1) - v0 - \beta * v0
+        // c1' = 2 * v0
+        //
+        // In BLS12-381's F_{p^2}, our \beta is -1 so we
+        // can modify this formula:
+        //
+        // c0' = (c0 + c1) * (c0 - c1)
+        // c1' = 2 * c0 * c1
+
+        let a = (&self.c0).add(&self.c1);
+        let b = (&self.c0).sub(&self.c1);
+        let c = (&self.c0).add(&self.c0);
+
+        Fp2 {
+            c0: (&a).mul(&b),
+            c1: (&c).mul(&self.c1),
+        }
+    }
+
+    pub const fn mul(&self, rhs: &Fp2) -> Fp2 {
+        // Karatsuba multiplication:
+        //
+        // v0  = a0 * b0
+        // v1  = a1 * b1
+        // c0 = v0 + \beta * v1
+        // c1 = (a0 + a1) * (b0 + b1) - v0 - v1
+        //
+        // In BLS12-381's F_{p^2}, our \beta is -1 so we
+        // can modify this formula. (Also, since we always
+        // subtract v1, we can compute v1 = -a1 * b1.)
+        //
+        // v0  = a0 * b0
+        // v1  = (-a1) * b1
+        // c0 = v0 + v1
+        // c1 = (a0 + a1) * (b0 + b1) - v0 + v1
+
+        let v0 = (&self.c0).mul(&rhs.c0);
+        let v1 = (&(&self.c1).neg()).mul(&rhs.c1);
+        let c0 = (&v0).add(&v1);
+        let c1 = (&(&self.c0).add(&self.c1)).mul(&(&rhs.c0).add(&rhs.c1));
+        let c1 = (&c1).sub(&v0);
+        let c1 = (&c1).add(&v1);
+
+        Fp2 { c0, c1 }
+    }
+
+    pub const fn add(&self, rhs: &Fp2) -> Fp2 {
+        Fp2 {
+            c0: (&self.c0).add(&rhs.c0),
+            c1: (&self.c1).add(&rhs.c1),
+        }
+    }
+
+    pub const fn sub(&self, rhs: &Fp2) -> Fp2 {
+        Fp2 {
+            c0: (&self.c0).sub(&rhs.c0),
+            c1: (&self.c1).sub(&rhs.c1),
+        }
+    }
+
+    pub const fn neg(&self) -> Fp2 {
+        Fp2 {
+            c0: (&self.c0).neg(),
+            c1: (&self.c1).neg(),
+        }
+    }
+
+    pub fn sqrt(&self) -> CtOption<Self> {
+        // Algorithm 9, https://eprint.iacr.org/2012/685.pdf
+        // with constant time modifications.
+
+        CtOption::new(Fp2::zero(), self.is_zero()).or_else(|| {
+            // a1 = self^((p - 3) / 4)
+            let a1 = self.pow_vartime(&[
+                0xee7fbfffffffeaaa,
+                0x7aaffffac54ffff,
+                0xd9cc34a83dac3d89,
+                0xd91dd2e13ce144af,
+                0x92c6e9ed90d2eb35,
+                0x680447a8e5ff9a6,
+            ]);
+
+            // alpha = a1^2 * self = self^((p - 3) / 2 + 1) = self^((p - 1) / 2)
+            let alpha = a1.square() * self;
+
+            // x0 = self^((p + 1) / 4)
+            let x0 = a1 * self;
+
+            // In the event that alpha = -1, the element is order p - 1 and so
+            // we're just trying to get the square of an element of the subfield
+            // Fp. This is given by x0 * u, since u = sqrt(-1). Since the element
+            // x0 = a + bu has b = 0, the solution is therefore au.
+            CtOption::new(
+                Fp2 {
+                    c0: -x0.c1,
+                    c1: x0.c0,
+                },
+                alpha.ct_eq(&(&Fp2::one()).neg()),
+            )
+            // Otherwise, the correct solution is (1 + alpha)^((q - 1) // 2) * x0
+            .or_else(|| {
+                CtOption::new(
+                    (alpha + Fp2::one()).pow_vartime(&[
+                        0xdcff7fffffffd555,
+                        0xf55ffff58a9ffff,
+                        0xb39869507b587b12,
+                        0xb23ba5c279c2895f,
+                        0x258dd3db21a5d66b,
+                        0xd0088f51cbff34d,
+                    ]) * x0,
+                    Choice::from(1),
+                )
+            })
+            // Only return the result if it's really the square root (and so
+            // self is actually quadratic nonresidue)
+            .and_then(|sqrt| CtOption::new(sqrt, sqrt.square().ct_eq(self)))
+        })
+    }
+
+    /// Computes the multiplicative inverse of this field
+    /// element, returning None in the case that this element
+    /// is zero.
+    pub fn invert(&self) -> CtOption<Self> {
+        // We wish to find the multiplicative inverse of a nonzero
+        // element a + bu in Fp2. We leverage an identity
+        //
+        // (a + bu)(a - bu) = a^2 + b^2
+        //
+        // which holds because u^2 = -1. This can be rewritten as
+        //
+        // (a + bu)(a - bu)/(a^2 + b^2) = 1
+        //
+        // because a^2 + b^2 = 0 has no nonzero solutions for (a, b).
+        // This gives that (a - bu)/(a^2 + b^2) is the inverse
+        // of (a + bu). Importantly, this can be computing using
+        // only a single inversion in Fp.
+
+        (self.c0.square() + self.c1.square()).invert().map(|t| Fp2 {
+            c0: self.c0 * t,
+            c1: self.c1 * -t,
+        })
+    }
+
+    /// Although this is labeled "vartime", it is only
+    /// variable time with respect to the exponent. It
+    /// is also not exposed in the public API.
+    pub fn pow_vartime(&self, by: &[u64; 6]) -> Self {
+        let mut res = Self::one();
+        for e in by.iter().rev() {
+            for i in (0..64).rev() {
+                res = res.square();
+
+                if ((*e >> i) & 1) == 1 {
+                    res *= self;
+                }
+            }
+        }
+        res
+    }
+}
+
+#[test]
+fn test_conditional_selection() {
+    let a = Fp2 {
+        c0: Fp::from_raw_unchecked([1, 2, 3, 4, 5, 6]),
+        c1: Fp::from_raw_unchecked([7, 8, 9, 10, 11, 12]),
+    };
+    let b = Fp2 {
+        c0: Fp::from_raw_unchecked([13, 14, 15, 16, 17, 18]),
+        c1: Fp::from_raw_unchecked([19, 20, 21, 22, 23, 24]),
+    };
+
+    assert_eq!(
+        ConditionallySelectable::conditional_select(&a, &b, Choice::from(0u8)),
+        a
+    );
+    assert_eq!(
+        ConditionallySelectable::conditional_select(&a, &b, Choice::from(1u8)),
+        b
+    );
+}
+
+#[test]
+fn test_equality() {
+    fn is_equal(a: &Fp2, b: &Fp2) -> bool {
+        let eq = a == b;
+        let ct_eq = a.ct_eq(&b);
+
+        assert_eq!(eq, ct_eq.unwrap_u8() == 1);
+
+        eq
+    }
+
+    assert!(is_equal(
+        &Fp2 {
+            c0: Fp::from_raw_unchecked([1, 2, 3, 4, 5, 6]),
+            c1: Fp::from_raw_unchecked([7, 8, 9, 10, 11, 12]),
+        },
+        &Fp2 {
+            c0: Fp::from_raw_unchecked([1, 2, 3, 4, 5, 6]),
+            c1: Fp::from_raw_unchecked([7, 8, 9, 10, 11, 12]),
+        }
+    ));
+
+    assert!(!is_equal(
+        &Fp2 {
+            c0: Fp::from_raw_unchecked([2, 2, 3, 4, 5, 6]),
+            c1: Fp::from_raw_unchecked([7, 8, 9, 10, 11, 12]),
+        },
+        &Fp2 {
+            c0: Fp::from_raw_unchecked([1, 2, 3, 4, 5, 6]),
+            c1: Fp::from_raw_unchecked([7, 8, 9, 10, 11, 12]),
+        }
+    ));
+
+    assert!(!is_equal(
+        &Fp2 {
+            c0: Fp::from_raw_unchecked([1, 2, 3, 4, 5, 6]),
+            c1: Fp::from_raw_unchecked([2, 8, 9, 10, 11, 12]),
+        },
+        &Fp2 {
+            c0: Fp::from_raw_unchecked([1, 2, 3, 4, 5, 6]),
+            c1: Fp::from_raw_unchecked([7, 8, 9, 10, 11, 12]),
+        }
+    ));
+}
+
+#[test]
+fn test_squaring() {
+    let a = Fp2 {
+        c0: Fp::from_raw_unchecked([
+            0xc9a2183163ee70d4,
+            0xbc3770a7196b5c91,
+            0xa247f8c1304c5f44,
+            0xb01fc2a3726c80b5,
+            0xe1d293e5bbd919c9,
+            0x4b78e80020ef2ca,
+        ]),
+        c1: Fp::from_raw_unchecked([
+            0x952ea4460462618f,
+            0x238d5eddf025c62f,
+            0xf6c94b012ea92e72,
+            0x3ce24eac1c93808,
+            0x55950f945da483c,
+            0x10a768d0df4eabc,
+        ]),
+    };
+    let b = Fp2 {
+        c0: Fp::from_raw_unchecked([
+            0xa1e09175a4d2c1fe,
+            0x8b33acfc204eff12,
+            0xe24415a11b456e42,
+            0x61d996b1b6ee1936,
+            0x1164dbe8667c853c,
+            0x788557acc7d9c79,
+        ]),
+        c1: Fp::from_raw_unchecked([
+            0xda6a87cc6f48fa36,
+            0xfc7b488277c1903,
+            0x9445ac4adc448187,
+            0x2616d5bc9099209,
+            0xdbed46772db58d48,
+            0x11b94d5076c7b7b1,
+        ]),
+    };
+
+    assert_eq!(a.square(), b);
+}
+
+#[test]
+fn test_multiplication() {
+    let a = Fp2 {
+        c0: Fp::from_raw_unchecked([
+            0xc9a2183163ee70d4,
+            0xbc3770a7196b5c91,
+            0xa247f8c1304c5f44,
+            0xb01fc2a3726c80b5,
+            0xe1d293e5bbd919c9,
+            0x4b78e80020ef2ca,
+        ]),
+        c1: Fp::from_raw_unchecked([
+            0x952ea4460462618f,
+            0x238d5eddf025c62f,
+            0xf6c94b012ea92e72,
+            0x3ce24eac1c93808,
+            0x55950f945da483c,
+            0x10a768d0df4eabc,
+        ]),
+    };
+    let b = Fp2 {
+        c0: Fp::from_raw_unchecked([
+            0xa1e09175a4d2c1fe,
+            0x8b33acfc204eff12,
+            0xe24415a11b456e42,
+            0x61d996b1b6ee1936,
+            0x1164dbe8667c853c,
+            0x788557acc7d9c79,
+        ]),
+        c1: Fp::from_raw_unchecked([
+            0xda6a87cc6f48fa36,
+            0xfc7b488277c1903,
+            0x9445ac4adc448187,
+            0x2616d5bc9099209,
+            0xdbed46772db58d48,
+            0x11b94d5076c7b7b1,
+        ]),
+    };
+    let c = Fp2 {
+        c0: Fp::from_raw_unchecked([
+            0xf597483e27b4e0f7,
+            0x610fbadf811dae5f,
+            0x8432af917714327a,
+            0x6a9a9603cf88f09e,
+            0xf05a7bf8bad0eb01,
+            0x9549131c003ffae,
+        ]),
+        c1: Fp::from_raw_unchecked([
+            0x963b02d0f93d37cd,
+            0xc95ce1cdb30a73d4,
+            0x308725fa3126f9b8,
+            0x56da3c167fab0d50,
+            0x6b5086b5f4b6d6af,
+            0x9c39f062f18e9f2,
+        ]),
+    };
+
+    assert_eq!(a * b, c);
+}
+
+#[test]
+fn test_addition() {
+    let a = Fp2 {
+        c0: Fp::from_raw_unchecked([
+            0xc9a2183163ee70d4,
+            0xbc3770a7196b5c91,
+            0xa247f8c1304c5f44,
+            0xb01fc2a3726c80b5,
+            0xe1d293e5bbd919c9,
+            0x4b78e80020ef2ca,
+        ]),
+        c1: Fp::from_raw_unchecked([
+            0x952ea4460462618f,
+            0x238d5eddf025c62f,
+            0xf6c94b012ea92e72,
+            0x3ce24eac1c93808,
+            0x55950f945da483c,
+            0x10a768d0df4eabc,
+        ]),
+    };
+    let b = Fp2 {
+        c0: Fp::from_raw_unchecked([
+            0xa1e09175a4d2c1fe,
+            0x8b33acfc204eff12,
+            0xe24415a11b456e42,
+            0x61d996b1b6ee1936,
+            0x1164dbe8667c853c,
+            0x788557acc7d9c79,
+        ]),
+        c1: Fp::from_raw_unchecked([
+            0xda6a87cc6f48fa36,
+            0xfc7b488277c1903,
+            0x9445ac4adc448187,
+            0x2616d5bc9099209,
+            0xdbed46772db58d48,
+            0x11b94d5076c7b7b1,
+        ]),
+    };
+    let c = Fp2 {
+        c0: Fp::from_raw_unchecked([
+            0x6b82a9a708c132d2,
+            0x476b1da339ba5ba4,
+            0x848c0e624b91cd87,
+            0x11f95955295a99ec,
+            0xf3376fce22559f06,
+            0xc3fe3face8c8f43,
+        ]),
+        c1: Fp::from_raw_unchecked([
+            0x6f992c1273ab5bc5,
+            0x3355136617a1df33,
+            0x8b0ef74c0aedaff9,
+            0x62f92468ad2ca12,
+            0xe1469770738fd584,
+            0x12c3c3dd84bca26d,
+        ]),
+    };
+
+    assert_eq!(a + b, c);
+}
+
+#[test]
+fn test_subtraction() {
+    let a = Fp2 {
+        c0: Fp::from_raw_unchecked([
+            0xc9a2183163ee70d4,
+            0xbc3770a7196b5c91,
+            0xa247f8c1304c5f44,
+            0xb01fc2a3726c80b5,
+            0xe1d293e5bbd919c9,
+            0x4b78e80020ef2ca,
+        ]),
+        c1: Fp::from_raw_unchecked([
+            0x952ea4460462618f,
+            0x238d5eddf025c62f,
+            0xf6c94b012ea92e72,
+            0x3ce24eac1c93808,
+            0x55950f945da483c,
+            0x10a768d0df4eabc,
+        ]),
+    };
+    let b = Fp2 {
+        c0: Fp::from_raw_unchecked([
+            0xa1e09175a4d2c1fe,
+            0x8b33acfc204eff12,
+            0xe24415a11b456e42,
+            0x61d996b1b6ee1936,
+            0x1164dbe8667c853c,
+            0x788557acc7d9c79,
+        ]),
+        c1: Fp::from_raw_unchecked([
+            0xda6a87cc6f48fa36,
+            0xfc7b488277c1903,
+            0x9445ac4adc448187,
+            0x2616d5bc9099209,
+            0xdbed46772db58d48,
+            0x11b94d5076c7b7b1,
+        ]),
+    };
+    let c = Fp2 {
+        c0: Fp::from_raw_unchecked([
+            0xe1c086bbbf1b5981,
+            0x4fafc3a9aa705d7e,
+            0x2734b5c10bb7e726,
+            0xb2bd7776af037a3e,
+            0x1b895fb398a84164,
+            0x17304aef6f113cec,
+        ]),
+        c1: Fp::from_raw_unchecked([
+            0x74c31c7995191204,
+            0x3271aa5479fdad2b,
+            0xc9b471574915a30f,
+            0x65e40313ec44b8be,
+            0x7487b2385b7067cb,
+            0x9523b26d0ad19a4,
+        ]),
+    };
+
+    assert_eq!(a - b, c);
+}
+
+#[test]
+fn test_negation() {
+    let a = Fp2 {
+        c0: Fp::from_raw_unchecked([
+            0xc9a2183163ee70d4,
+            0xbc3770a7196b5c91,
+            0xa247f8c1304c5f44,
+            0xb01fc2a3726c80b5,
+            0xe1d293e5bbd919c9,
+            0x4b78e80020ef2ca,
+        ]),
+        c1: Fp::from_raw_unchecked([
+            0x952ea4460462618f,
+            0x238d5eddf025c62f,
+            0xf6c94b012ea92e72,
+            0x3ce24eac1c93808,
+            0x55950f945da483c,
+            0x10a768d0df4eabc,
+        ]),
+    };
+    let b = Fp2 {
+        c0: Fp::from_raw_unchecked([
+            0xf05ce7ce9c1139d7,
+            0x62748f5797e8a36d,
+            0xc4e8d9dfc66496df,
+            0xb45788e181189209,
+            0x694913d08772930d,
+            0x1549836a3770f3cf,
+        ]),
+        c1: Fp::from_raw_unchecked([
+            0x24d05bb9fb9d491c,
+            0xfb1ea120c12e39d0,
+            0x7067879fc807c7b1,
+            0x60a9269a31bbdab6,
+            0x45c256bcfd71649b,
+            0x18f69b5d2b8afbde,
+        ]),
+    };
+
+    assert_eq!(-a, b);
+}
+
+#[test]
+fn test_sqrt() {
+    // a = 1488924004771393321054797166853618474668089414631333405711627789629391903630694737978065425271543178763948256226639*u + 784063022264861764559335808165825052288770346101304131934508881646553551234697082295473567906267937225174620141295
+    let a = Fp2 {
+        c0: Fp::from_raw_unchecked([
+            0x2beed14627d7f9e9,
+            0xb6614e06660e5dce,
+            0x6c4cc7c2f91d42c,
+            0x996d78474b7a63cc,
+            0xebaebc4c820d574e,
+            0x18865e12d93fd845,
+        ]),
+        c1: Fp::from_raw_unchecked([
+            0x7d828664baf4f566,
+            0xd17e663996ec7339,
+            0x679ead55cb4078d0,
+            0xfe3b2260e001ec28,
+            0x305993d043d91b68,
+            0x626f03c0489b72d,
+        ]),
+    };
+
+    assert_eq!(a.sqrt().unwrap().square(), a);
+
+    // b = 5, which is a generator of the p - 1 order
+    // multiplicative subgroup
+    let b = Fp2 {
+        c0: Fp::from_raw_unchecked([
+            0x6631000000105545,
+            0x211400400eec000d,
+            0x3fa7af30c820e316,
+            0xc52a8b8d6387695d,
+            0x9fb4e61d1e83eac5,
+            0x5cb922afe84dc7,
+        ]),
+        c1: Fp::zero(),
+    };
+
+    assert_eq!(b.sqrt().unwrap().square(), b);
+
+    // c = 25, which is a generator of the (p - 1) / 2 order
+    // multiplicative subgroup
+    let c = Fp2 {
+        c0: Fp::from_raw_unchecked([
+            0x44f600000051ffae,
+            0x86b8014199480043,
+            0xd7159952f1f3794a,
+            0x755d6e3dfe1ffc12,
+            0xd36cd6db5547e905,
+            0x2f8c8ecbf1867bb,
+        ]),
+        c1: Fp::zero(),
+    };
+
+    assert_eq!(c.sqrt().unwrap().square(), c);
+
+    // 2155129644831861015726826462986972654175647013268275306775721078997042729172900466542651176384766902407257452753362*u + 2796889544896299244102912275102369318775038861758288697415827248356648685135290329705805931514906495247464901062529
+    // is nonsquare.
+    assert!(bool::from(
+        Fp2 {
+            c0: Fp::from_raw_unchecked([
+                0xc5fa1bc8fd00d7f6,
+                0x3830ca454606003b,
+                0x2b287f1104b102da,
+                0xa7fb30f28230f23e,
+                0x339cdb9ee953dbf0,
+                0xd78ec51d989fc57
+            ]),
+            c1: Fp::from_raw_unchecked([
+                0x27ec4898cf87f613,
+                0x9de1394e1abb05a5,
+                0x947f85dc170fc14,
+                0x586fbc696b6114b7,
+                0x2b3475a4077d7169,
+                0x13e1c895cc4b6c22
+            ])
+        }
+        .sqrt()
+        .is_none()
+    ));
+}
+
+#[test]
+fn test_inversion() {
+    let a = Fp2 {
+        c0: Fp::from_raw_unchecked([
+            0x1128ecad67549455,
+            0x9e7a1cff3a4ea1a8,
+            0xeb208d51e08bcf27,
+            0xe98ad40811f5fc2b,
+            0x736c3a59232d511d,
+            0x10acd42d29cfcbb6,
+        ]),
+        c1: Fp::from_raw_unchecked([
+            0xd328e37cc2f58d41,
+            0x948df0858a605869,
+            0x6032f9d56f93a573,
+            0x2be483ef3fffdc87,
+            0x30ef61f88f483c2a,
+            0x1333f55a35725be0,
+        ]),
+    };
+
+    let b = Fp2 {
+        c0: Fp::from_raw_unchecked([
+            0x581a1333d4f48a6,
+            0x58242f6ef0748500,
+            0x292c955349e6da5,
+            0xba37721ddd95fcd0,
+            0x70d167903aa5dfc5,
+            0x11895e118b58a9d5,
+        ]),
+        c1: Fp::from_raw_unchecked([
+            0xeda09d2d7a85d17,
+            0x8808e137a7d1a2cf,
+            0x43ae2625c1ff21db,
+            0xf85ac9fdf7a74c64,
+            0x8fccdda5b8da9738,
+            0x8e84f0cb32cd17d,
+        ]),
+    };
+
+    assert_eq!(a.invert().unwrap(), b);
+
+    assert!(Fp2::zero().invert().is_none().unwrap_u8() == 1);
+}
+
+#[test]
+fn test_lexicographic_largest() {
+    assert!(!bool::from(Fp2::zero().lexicographically_largest()));
+    assert!(!bool::from(Fp2::one().lexicographically_largest()));
+    assert!(bool::from(
+        Fp2 {
+            c0: Fp::from_raw_unchecked([
+                0x1128ecad67549455,
+                0x9e7a1cff3a4ea1a8,
+                0xeb208d51e08bcf27,
+                0xe98ad40811f5fc2b,
+                0x736c3a59232d511d,
+                0x10acd42d29cfcbb6,
+            ]),
+            c1: Fp::from_raw_unchecked([
+                0xd328e37cc2f58d41,
+                0x948df0858a605869,
+                0x6032f9d56f93a573,
+                0x2be483ef3fffdc87,
+                0x30ef61f88f483c2a,
+                0x1333f55a35725be0,
+            ]),
+        }
+        .lexicographically_largest()
+    ));
+    assert!(!bool::from(
+        Fp2 {
+            c0: -Fp::from_raw_unchecked([
+                0x1128ecad67549455,
+                0x9e7a1cff3a4ea1a8,
+                0xeb208d51e08bcf27,
+                0xe98ad40811f5fc2b,
+                0x736c3a59232d511d,
+                0x10acd42d29cfcbb6,
+            ]),
+            c1: -Fp::from_raw_unchecked([
+                0xd328e37cc2f58d41,
+                0x948df0858a605869,
+                0x6032f9d56f93a573,
+                0x2be483ef3fffdc87,
+                0x30ef61f88f483c2a,
+                0x1333f55a35725be0,
+            ]),
+        }
+        .lexicographically_largest()
+    ));
+    assert!(!bool::from(
+        Fp2 {
+            c0: Fp::from_raw_unchecked([
+                0x1128ecad67549455,
+                0x9e7a1cff3a4ea1a8,
+                0xeb208d51e08bcf27,
+                0xe98ad40811f5fc2b,
+                0x736c3a59232d511d,
+                0x10acd42d29cfcbb6,
+            ]),
+            c1: Fp::zero(),
+        }
+        .lexicographically_largest()
+    ));
+    assert!(bool::from(
+        Fp2 {
+            c0: -Fp::from_raw_unchecked([
+                0x1128ecad67549455,
+                0x9e7a1cff3a4ea1a8,
+                0xeb208d51e08bcf27,
+                0xe98ad40811f5fc2b,
+                0x736c3a59232d511d,
+                0x10acd42d29cfcbb6,
+            ]),
+            c1: Fp::zero(),
+        }
+        .lexicographically_largest()
+    ));
+}
diff --git a/bls12_381/src/fp6.rs b/bls12_381/src/fp6.rs
new file mode 100644
index 0000000..50ed2eb
--- /dev/null
+++ b/bls12_381/src/fp6.rs
@@ -0,0 +1,507 @@
+use crate::fp::*;
+use crate::fp2::*;
+
+use core::fmt;
+use core::ops::{Add, AddAssign, Mul, MulAssign, Neg, Sub, SubAssign};
+use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption};
+
+/// This represents an element $c_0 + c_1 v + c_2 v^2$ of $\mathbb{F}_{p^6} = \mathbb{F}_{p^2} / v^3 - u - 1$.
+pub struct Fp6 {
+    pub c0: Fp2,
+    pub c1: Fp2,
+    pub c2: Fp2,
+}
+
+impl From<Fp> for Fp6 {
+    fn from(f: Fp) -> Fp6 {
+        Fp6 {
+            c0: Fp2::from(f),
+            c1: Fp2::zero(),
+            c2: Fp2::zero(),
+        }
+    }
+}
+
+impl From<Fp2> for Fp6 {
+    fn from(f: Fp2) -> Fp6 {
+        Fp6 {
+            c0: f,
+            c1: Fp2::zero(),
+            c2: Fp2::zero(),
+        }
+    }
+}
+
+impl PartialEq for Fp6 {
+    fn eq(&self, other: &Fp6) -> bool {
+        self.ct_eq(other).into()
+    }
+}
+
+impl Copy for Fp6 {}
+impl Clone for Fp6 {
+    #[inline]
+    fn clone(&self) -> Self {
+        *self
+    }
+}
+
+impl Default for Fp6 {
+    fn default() -> Self {
+        Fp6::zero()
+    }
+}
+
+impl fmt::Debug for Fp6 {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        write!(f, "{:?} + ({:?})*v + ({:?})*v^2", self.c0, self.c1, self.c2)
+    }
+}
+
+impl ConditionallySelectable for Fp6 {
+    #[inline(always)]
+    fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self {
+        Fp6 {
+            c0: Fp2::conditional_select(&a.c0, &b.c0, choice),
+            c1: Fp2::conditional_select(&a.c1, &b.c1, choice),
+            c2: Fp2::conditional_select(&a.c2, &b.c2, choice),
+        }
+    }
+}
+
+impl ConstantTimeEq for Fp6 {
+    #[inline(always)]
+    fn ct_eq(&self, other: &Self) -> Choice {
+        self.c0.ct_eq(&other.c0) & self.c1.ct_eq(&other.c1) & self.c2.ct_eq(&other.c2)
+    }
+}
+
+impl Fp6 {
+    #[inline]
+    pub fn zero() -> Self {
+        Fp6 {
+            c0: Fp2::zero(),
+            c1: Fp2::zero(),
+            c2: Fp2::zero(),
+        }
+    }
+
+    #[inline]
+    pub fn one() -> Self {
+        Fp6 {
+            c0: Fp2::one(),
+            c1: Fp2::zero(),
+            c2: Fp2::zero(),
+        }
+    }
+
+    pub fn mul_by_1(&self, c1: &Fp2) -> Fp6 {
+        let b_b = self.c1 * c1;
+
+        let t1 = (self.c1 + self.c2) * c1 - b_b;
+        let t1 = t1.mul_by_nonresidue();
+
+        let t2 = (self.c0 + self.c1) * c1 - b_b;
+
+        Fp6 {
+            c0: t1,
+            c1: t2,
+            c2: b_b,
+        }
+    }
+
+    pub fn mul_by_01(&self, c0: &Fp2, c1: &Fp2) -> Fp6 {
+        let a_a = self.c0 * c0;
+        let b_b = self.c1 * c1;
+
+        let t1 = (self.c1 + self.c2) * c1 - b_b;
+        let t1 = t1.mul_by_nonresidue() + a_a;
+
+        let t2 = (c0 + c1) * (self.c0 + self.c1) - a_a - b_b;
+
+        let t3 = (self.c0 + self.c2) * c0 - a_a + b_b;
+
+        Fp6 {
+            c0: t1,
+            c1: t2,
+            c2: t3,
+        }
+    }
+
+    /// Multiply by quadratic nonresidue v.
+    pub fn mul_by_nonresidue(&self) -> Self {
+        // Given a + bv + cv^2, this produces
+        //     av + bv^2 + cv^3
+        // but because v^3 = u + 1, we have
+        //     c(u + 1) + av + v^2
+
+        Fp6 {
+            c0: self.c2.mul_by_nonresidue(),
+            c1: self.c0,
+            c2: self.c1,
+        }
+    }
+
+    /// Raises this element to p.
+    #[inline(always)]
+    pub fn frobenius_map(&self) -> Self {
+        let c0 = self.c0.frobenius_map();
+        let c1 = self.c1.frobenius_map();
+        let c2 = self.c2.frobenius_map();
+
+        // c1 = c1 * (u + 1)^((p - 1) / 3)
+        let c1 = c1
+            * Fp2 {
+                c0: Fp::zero(),
+                c1: Fp::from_raw_unchecked([
+                    0xcd03c9e48671f071,
+                    0x5dab22461fcda5d2,
+                    0x587042afd3851b95,
+                    0x8eb60ebe01bacb9e,
+                    0x3f97d6e83d050d2,
+                    0x18f0206554638741,
+                ]),
+            };
+
+        // c2 = c2 * (u + 1)^((2p - 2) / 3)
+        let c2 = c2
+            * Fp2 {
+                c0: Fp::from_raw_unchecked([
+                    0x890dc9e4867545c3,
+                    0x2af322533285a5d5,
+                    0x50880866309b7e2c,
+                    0xa20d1b8c7e881024,
+                    0x14e4f04fe2db9068,
+                    0x14e56d3f1564853a,
+                ]),
+                c1: Fp::zero(),
+            };
+
+        Fp6 { c0, c1, c2 }
+    }
+
+    #[inline(always)]
+    pub fn is_zero(&self) -> Choice {
+        self.c0.is_zero() & self.c1.is_zero() & self.c2.is_zero()
+    }
+
+    #[inline]
+    pub fn square(&self) -> Self {
+        let s0 = self.c0.square();
+        let ab = self.c0 * self.c1;
+        let s1 = ab + ab;
+        let s2 = (self.c0 - self.c1 + self.c2).square();
+        let bc = self.c1 * self.c2;
+        let s3 = bc + bc;
+        let s4 = self.c2.square();
+
+        Fp6 {
+            c0: s3.mul_by_nonresidue() + s0,
+            c1: s4.mul_by_nonresidue() + s1,
+            c2: s1 + s2 + s3 - s0 - s4,
+        }
+    }
+
+    #[inline]
+    pub fn invert(&self) -> CtOption<Self> {
+        let c0 = (self.c1 * self.c2).mul_by_nonresidue();
+        let c0 = self.c0.square() - c0;
+
+        let c1 = self.c2.square().mul_by_nonresidue();
+        let c1 = c1 - (self.c0 * self.c1);
+
+        let c2 = self.c1.square();
+        let c2 = c2 - (self.c0 * self.c2);
+
+        let tmp = ((self.c1 * c2) + (self.c2 * c1)).mul_by_nonresidue();
+        let tmp = tmp + (self.c0 * c0);
+
+        tmp.invert().map(|t| Fp6 {
+            c0: t * c0,
+            c1: t * c1,
+            c2: t * c2,
+        })
+    }
+}
+
+impl<'a, 'b> Mul<&'b Fp6> for &'a Fp6 {
+    type Output = Fp6;
+
+    #[inline]
+    fn mul(self, other: &'b Fp6) -> Self::Output {
+        let aa = self.c0 * other.c0;
+        let bb = self.c1 * other.c1;
+        let cc = self.c2 * other.c2;
+
+        let t1 = other.c1 + other.c2;
+        let tmp = self.c1 + self.c2;
+        let t1 = t1 * tmp;
+        let t1 = t1 - bb;
+        let t1 = t1 - cc;
+        let t1 = t1.mul_by_nonresidue();
+        let t1 = t1 + aa;
+
+        let t3 = other.c0 + other.c2;
+        let tmp = self.c0 + self.c2;
+        let t3 = t3 * tmp;
+        let t3 = t3 - aa;
+        let t3 = t3 + bb;
+        let t3 = t3 - cc;
+
+        let t2 = other.c0 + other.c1;
+        let tmp = self.c0 + self.c1;
+        let t2 = t2 * tmp;
+        let t2 = t2 - aa;
+        let t2 = t2 - bb;
+        let cc = cc.mul_by_nonresidue();
+        let t2 = t2 + cc;
+
+        Fp6 {
+            c0: t1,
+            c1: t2,
+            c2: t3,
+        }
+    }
+}
+
+impl<'a, 'b> Add<&'b Fp6> for &'a Fp6 {
+    type Output = Fp6;
+
+    #[inline]
+    fn add(self, rhs: &'b Fp6) -> Self::Output {
+        Fp6 {
+            c0: self.c0 + rhs.c0,
+            c1: self.c1 + rhs.c1,
+            c2: self.c2 + rhs.c2,
+        }
+    }
+}
+
+impl<'a> Neg for &'a Fp6 {
+    type Output = Fp6;
+
+    #[inline]
+    fn neg(self) -> Self::Output {
+        Fp6 {
+            c0: -self.c0,
+            c1: -self.c1,
+            c2: -self.c2,
+        }
+    }
+}
+
+impl Neg for Fp6 {
+    type Output = Fp6;
+
+    #[inline]
+    fn neg(self) -> Self::Output {
+        -&self
+    }
+}
+
+impl<'a, 'b> Sub<&'b Fp6> for &'a Fp6 {
+    type Output = Fp6;
+
+    #[inline]
+    fn sub(self, rhs: &'b Fp6) -> Self::Output {
+        Fp6 {
+            c0: self.c0 - rhs.c0,
+            c1: self.c1 - rhs.c1,
+            c2: self.c2 - rhs.c2,
+        }
+    }
+}
+
+impl_binops_additive!(Fp6, Fp6);
+impl_binops_multiplicative!(Fp6, Fp6);
+
+#[test]
+fn test_arithmetic() {
+    use crate::fp::*;
+
+    let a = Fp6 {
+        c0: Fp2 {
+            c0: Fp::from_raw_unchecked([
+                0x47f9cb98b1b82d58,
+                0x5fe911eba3aa1d9d,
+                0x96bf1b5f4dd81db3,
+                0x8100d27cc9259f5b,
+                0xafa20b9674640eab,
+                0x9bbcea7d8d9497d,
+            ]),
+            c1: Fp::from_raw_unchecked([
+                0x303cb98b1662daa,
+                0xd93110aa0a621d5a,
+                0xbfa9820c5be4a468,
+                0xba3643ecb05a348,
+                0xdc3534bb1f1c25a6,
+                0x6c305bb19c0e1c1,
+            ]),
+        },
+        c1: Fp2 {
+            c0: Fp::from_raw_unchecked([
+                0x46f9cb98b162d858,
+                0xbe9109cf7aa1d57,
+                0xc791bc55fece41d2,
+                0xf84c57704e385ec2,
+                0xcb49c1d9c010e60f,
+                0xacdb8e158bfe3c8,
+            ]),
+            c1: Fp::from_raw_unchecked([
+                0x8aefcb98b15f8306,
+                0x3ea1108fe4f21d54,
+                0xcf79f69fa1b7df3b,
+                0xe4f54aa1d16b1a3c,
+                0xba5e4ef86105a679,
+                0xed86c0797bee5cf,
+            ]),
+        },
+        c2: Fp2 {
+            c0: Fp::from_raw_unchecked([
+                0xcee5cb98b15c2db4,
+                0x71591082d23a1d51,
+                0xd76230e944a17ca4,
+                0xd19e3dd3549dd5b6,
+                0xa972dc1701fa66e3,
+                0x12e31f2dd6bde7d6,
+            ]),
+            c1: Fp::from_raw_unchecked([
+                0xad2acb98b1732d9d,
+                0x2cfd10dd06961d64,
+                0x7396b86c6ef24e8,
+                0xbd76e2fdb1bfc820,
+                0x6afea7f6de94d0d5,
+                0x10994b0c5744c040,
+            ]),
+        },
+    };
+
+    let b = Fp6 {
+        c0: Fp2 {
+            c0: Fp::from_raw_unchecked([
+                0xf120cb98b16fd84b,
+                0x5fb510cff3de1d61,
+                0xf21a5d069d8c251,
+                0xaa1fd62f34f2839a,
+                0x5a1335157f89913f,
+                0x14a3fe329643c247,
+            ]),
+            c1: Fp::from_raw_unchecked([
+                0x3516cb98b16c82f9,
+                0x926d10c2e1261d5f,
+                0x1709e01a0cc25fba,
+                0x96c8c960b8253f14,
+                0x4927c234207e51a9,
+                0x18aeb158d542c44e,
+            ]),
+        },
+        c1: Fp2 {
+            c0: Fp::from_raw_unchecked([
+                0xbf0dcb98b16982fc,
+                0xa67910b71d1a1d5c,
+                0xb7c147c2b8fb06ff,
+                0x1efa710d47d2e7ce,
+                0xed20a79c7e27653c,
+                0x2b85294dac1dfba,
+            ]),
+            c1: Fp::from_raw_unchecked([
+                0x9d52cb98b18082e5,
+                0x621d111151761d6f,
+                0xe79882603b48af43,
+                0xad31637a4f4da37,
+                0xaeac737c5ac1cf2e,
+                0x6e7e735b48b824,
+            ]),
+        },
+        c2: Fp2 {
+            c0: Fp::from_raw_unchecked([
+                0xe148cb98b17d2d93,
+                0x94d511043ebe1d6c,
+                0xef80bca9de324cac,
+                0xf77c0969282795b1,
+                0x9dc1009afbb68f97,
+                0x47931999a47ba2b,
+            ]),
+            c1: Fp::from_raw_unchecked([
+                0x253ecb98b179d841,
+                0xc78d10f72c061d6a,
+                0xf768f6f3811bea15,
+                0xe424fc9aab5a512b,
+                0x8cd58db99cab5001,
+                0x883e4bfd946bc32,
+            ]),
+        },
+    };
+
+    let c = Fp6 {
+        c0: Fp2 {
+            c0: Fp::from_raw_unchecked([
+                0x6934cb98b17682ef,
+                0xfa4510ea194e1d67,
+                0xff51313d2405877e,
+                0xd0cdefcc2e8d0ca5,
+                0x7bea1ad83da0106b,
+                0xc8e97e61845be39,
+            ]),
+            c1: Fp::from_raw_unchecked([
+                0x4779cb98b18d82d8,
+                0xb5e911444daa1d7a,
+                0x2f286bdaa6532fc2,
+                0xbca694f68baeff0f,
+                0x3d75e6b81a3a7a5d,
+                0xa44c3c498cc96a3,
+            ]),
+        },
+        c1: Fp2 {
+            c0: Fp::from_raw_unchecked([
+                0x8b6fcb98b18a2d86,
+                0xe8a111373af21d77,
+                0x3710a624493ccd2b,
+                0xa94f88280ee1ba89,
+                0x2c8a73d6bb2f3ac7,
+                0xe4f76ead7cb98aa,
+            ]),
+            c1: Fp::from_raw_unchecked([
+                0xcf65cb98b186d834,
+                0x1b59112a283a1d74,
+                0x3ef8e06dec266a95,
+                0x95f87b5992147603,
+                0x1b9f00f55c23fb31,
+                0x125a2a1116ca9ab1,
+            ]),
+        },
+        c2: Fp2 {
+            c0: Fp::from_raw_unchecked([
+                0x135bcb98b18382e2,
+                0x4e11111d15821d72,
+                0x46e11ab78f1007fe,
+                0x82a16e8b1547317d,
+                0xab38e13fd18bb9b,
+                0x1664dd3755c99cb8,
+            ]),
+            c1: Fp::from_raw_unchecked([
+                0xce65cb98b1318334,
+                0xc7590fdb7c3a1d2e,
+                0x6fcb81649d1c8eb3,
+                0xd44004d1727356a,
+                0x3746b738a7d0d296,
+                0x136c144a96b134fc,
+            ]),
+        },
+    };
+
+    assert_eq!(a.square(), &a * &a);
+    assert_eq!(b.square(), &b * &b);
+    assert_eq!(c.square(), &c * &c);
+
+    assert_eq!(
+        (a + b) * c.square(),
+        &(&(&c * &c) * &a) + &(&(&c * &c) * &b)
+    );
+
+    assert_eq!(
+        &a.invert().unwrap() * &b.invert().unwrap(),
+        (&a * &b).invert().unwrap()
+    );
+    assert_eq!(&a.invert().unwrap() * &a, Fp6::one());
+}
diff --git a/bls12_381/src/g1.rs b/bls12_381/src/g1.rs
new file mode 100644
index 0000000..aa90dc1
--- /dev/null
+++ b/bls12_381/src/g1.rs
@@ -0,0 +1,1343 @@
+//! This module provides an implementation of the $\mathbb{G}_1$ group of BLS12-381.
+
+use core::ops::{Add, AddAssign, Mul, MulAssign, Neg, Sub, SubAssign};
+
+use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption};
+
+use crate::fp::Fp;
+use crate::Scalar;
+
+/// This is an element of $\mathbb{G}_1$ represented in the affine coordinate space.
+/// It is ideal to keep elements in this representation to reduce memory usage and
+/// improve performance through the use of mixed curve model arithmetic.
+///
+/// Values of `G1Affine` are guaranteed to be in the $q$-order subgroup unless an
+/// "unchecked" API was misused.
+#[derive(Copy, Clone, Debug)]
+pub struct G1Affine {
+    pub(crate) x: Fp,
+    pub(crate) y: Fp,
+    infinity: Choice,
+}
+
+impl Default for G1Affine {
+    fn default() -> G1Affine {
+        G1Affine::identity()
+    }
+}
+
+impl<'a> From<&'a G1Projective> for G1Affine {
+    fn from(p: &'a G1Projective) -> G1Affine {
+        let zinv = p.z.invert().unwrap_or(Fp::zero());
+        let zinv2 = zinv.square();
+        let x = p.x * zinv2;
+        let zinv3 = zinv2 * zinv;
+        let y = p.y * zinv3;
+
+        let tmp = G1Affine {
+            x,
+            y,
+            infinity: Choice::from(0u8),
+        };
+
+        G1Affine::conditional_select(&tmp, &G1Affine::identity(), zinv.is_zero())
+    }
+}
+
+impl From<G1Projective> for G1Affine {
+    fn from(p: G1Projective) -> G1Affine {
+        G1Affine::from(&p)
+    }
+}
+
+impl ConstantTimeEq for G1Affine {
+    fn ct_eq(&self, other: &Self) -> Choice {
+        // The only cases in which two points are equal are
+        // 1. infinity is set on both
+        // 2. infinity is not set on both, and their coordinates are equal
+
+        (self.infinity & other.infinity)
+            | ((!self.infinity)
+                & (!other.infinity)
+                & self.x.ct_eq(&other.x)
+                & self.y.ct_eq(&other.y))
+    }
+}
+
+impl ConditionallySelectable for G1Affine {
+    fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self {
+        G1Affine {
+            x: Fp::conditional_select(&a.x, &b.x, choice),
+            y: Fp::conditional_select(&a.y, &b.y, choice),
+            infinity: Choice::conditional_select(&a.infinity, &b.infinity, choice),
+        }
+    }
+}
+
+impl Eq for G1Affine {}
+impl PartialEq for G1Affine {
+    #[inline]
+    fn eq(&self, other: &Self) -> bool {
+        bool::from(self.ct_eq(other))
+    }
+}
+
+impl<'a> Neg for &'a G1Affine {
+    type Output = G1Affine;
+
+    #[inline]
+    fn neg(self) -> G1Affine {
+        G1Affine {
+            x: self.x,
+            y: Fp::conditional_select(&-self.y, &Fp::one(), self.infinity),
+            infinity: self.infinity,
+        }
+    }
+}
+
+impl Neg for G1Affine {
+    type Output = G1Affine;
+
+    #[inline]
+    fn neg(self) -> G1Affine {
+        -&self
+    }
+}
+
+impl<'a, 'b> Add<&'b G1Projective> for &'a G1Affine {
+    type Output = G1Projective;
+
+    #[inline]
+    fn add(self, rhs: &'b G1Projective) -> G1Projective {
+        rhs.add_mixed(self)
+    }
+}
+
+impl<'a, 'b> Add<&'b G1Affine> for &'a G1Projective {
+    type Output = G1Projective;
+
+    #[inline]
+    fn add(self, rhs: &'b G1Affine) -> G1Projective {
+        self.add_mixed(rhs)
+    }
+}
+
+impl<'a, 'b> Sub<&'b G1Projective> for &'a G1Affine {
+    type Output = G1Projective;
+
+    #[inline]
+    fn sub(self, rhs: &'b G1Projective) -> G1Projective {
+        self + (-rhs)
+    }
+}
+
+impl<'a, 'b> Sub<&'b G1Affine> for &'a G1Projective {
+    type Output = G1Projective;
+
+    #[inline]
+    fn sub(self, rhs: &'b G1Affine) -> G1Projective {
+        self + (-rhs)
+    }
+}
+
+impl_binops_additive!(G1Projective, G1Affine);
+impl_binops_additive_specify_output!(G1Affine, G1Projective, G1Projective);
+
+const B: Fp = Fp::from_raw_unchecked([
+    0xaa270000000cfff3,
+    0x53cc0032fc34000a,
+    0x478fe97a6b0a807f,
+    0xb1d37ebee6ba24d7,
+    0x8ec9733bbf78ab2f,
+    0x9d645513d83de7e,
+]);
+
+impl G1Affine {
+    /// Returns the identity of the group: the point at infinity.
+    pub fn identity() -> G1Affine {
+        G1Affine {
+            x: Fp::zero(),
+            y: Fp::one(),
+            infinity: Choice::from(1u8),
+        }
+    }
+
+    /// Returns a fixed generator of the group. See [`notes::design`](notes/design/index.html#fixed-generators)
+    /// for how this generator is chosen.
+    pub fn generator() -> G1Affine {
+        G1Affine {
+            x: Fp::from_raw_unchecked([
+                0x5cb38790fd530c16,
+                0x7817fc679976fff5,
+                0x154f95c7143ba1c1,
+                0xf0ae6acdf3d0e747,
+                0xedce6ecc21dbf440,
+                0x120177419e0bfb75,
+            ]),
+            y: Fp::from_raw_unchecked([
+                0xbaac93d50ce72271,
+                0x8c22631a7918fd8e,
+                0xdd595f13570725ce,
+                0x51ac582950405194,
+                0xe1c8c3fad0059c0,
+                0xbbc3efc5008a26a,
+            ]),
+            infinity: Choice::from(0u8),
+        }
+    }
+
+    /// Serializes this element into compressed form. See [`notes::serialization`](crate::notes::serialization)
+    /// for details about how group elements are serialized.
+    pub fn to_compressed(&self) -> [u8; 48] {
+        // Strictly speaking, self.x is zero already when self.infinity is true, but
+        // to guard against implementation mistakes we do not assume this.
+        let mut res = Fp::conditional_select(&self.x, &Fp::zero(), self.infinity).to_bytes();
+
+        // This point is in compressed form, so we set the most significant bit.
+        res[0] |= 1u8 << 7;
+
+        // Is this point at infinity? If so, set the second-most significant bit.
+        res[0] |= u8::conditional_select(&0u8, &(1u8 << 6), self.infinity);
+
+        // Is the y-coordinate the lexicographically largest of the two associated with the
+        // x-coordinate? If so, set the third-most significant bit so long as this is not
+        // the point at infinity.
+        res[0] |= u8::conditional_select(
+            &0u8,
+            &(1u8 << 5),
+            (!self.infinity) & self.y.lexicographically_largest(),
+        );
+
+        res
+    }
+
+    /// Serializes this element into uncompressed form. See [`notes::serialization`](crate::notes::serialization)
+    /// for details about how group elements are serialized.
+    pub fn to_uncompressed(&self) -> [u8; 96] {
+        let mut res = [0; 96];
+
+        res[0..48].copy_from_slice(
+            &Fp::conditional_select(&self.x, &Fp::zero(), self.infinity).to_bytes()[..],
+        );
+        res[48..96].copy_from_slice(
+            &Fp::conditional_select(&self.y, &Fp::zero(), self.infinity).to_bytes()[..],
+        );
+
+        // Is this point at infinity? If so, set the second-most significant bit.
+        res[0] |= u8::conditional_select(&0u8, &(1u8 << 6), self.infinity);
+
+        res
+    }
+
+    /// Attempts to deserialize an uncompressed element. See [`notes::serialization`](crate::notes::serialization)
+    /// for details about how group elements are serialized.
+    pub fn from_uncompressed(bytes: &[u8; 96]) -> CtOption<Self> {
+        Self::from_uncompressed_unchecked(bytes)
+            .and_then(|p| CtOption::new(p, p.is_on_curve() & p.is_torsion_free()))
+    }
+
+    /// Attempts to deserialize an uncompressed element, not checking if the
+    /// element is on the curve and not checking if it is in the correct subgroup.
+    /// **This is dangerous to call unless you trust the bytes you are reading; otherwise,
+    /// API invariants may be broken.** Please consider using `from_uncompressed()` instead.
+    pub fn from_uncompressed_unchecked(bytes: &[u8; 96]) -> CtOption<Self> {
+        // Obtain the three flags from the start of the byte sequence
+        let compression_flag_set = Choice::from((bytes[0] >> 7) & 1);
+        let infinity_flag_set = Choice::from((bytes[0] >> 6) & 1);
+        let sort_flag_set = Choice::from((bytes[0] >> 5) & 1);
+
+        // Attempt to obtain the x-coordinate
+        let x = {
+            let mut tmp = [0; 48];
+            tmp.copy_from_slice(&bytes[0..48]);
+
+            // Mask away the flag bits
+            tmp[0] &= 0b0001_1111;
+
+            Fp::from_bytes(&tmp)
+        };
+
+        // Attempt to obtain the y-coordinate
+        let y = {
+            let mut tmp = [0; 48];
+            tmp.copy_from_slice(&bytes[48..96]);
+
+            Fp::from_bytes(&tmp)
+        };
+
+        x.and_then(|x| {
+            y.and_then(|y| {
+                // Create a point representing this value
+                let p = G1Affine::conditional_select(
+                    &G1Affine {
+                        x,
+                        y,
+                        infinity: infinity_flag_set,
+                    },
+                    &G1Affine::identity(),
+                    infinity_flag_set,
+                );
+
+                CtOption::new(
+                    p,
+                    // If the infinity flag is set, the x and y coordinates should have been zero.
+                    ((!infinity_flag_set) | (infinity_flag_set & x.is_zero() & y.is_zero())) &
+                    // The compression flag should not have been set, as this is an uncompressed element
+                    (!compression_flag_set) &
+                    // The sort flag should not have been set, as this is an uncompressed element
+                    (!sort_flag_set),
+                )
+            })
+        })
+    }
+
+    /// Attempts to deserialize a compressed element. See [`notes::serialization`](crate::notes::serialization)
+    /// for details about how group elements are serialized.
+    pub fn from_compressed(bytes: &[u8; 48]) -> CtOption<Self> {
+        // We already know the point is on the curve because this is established
+        // by the y-coordinate recovery procedure in from_compressed_unchecked().
+
+        Self::from_compressed_unchecked(bytes).and_then(|p| CtOption::new(p, p.is_torsion_free()))
+    }
+
+    /// Attempts to deserialize an uncompressed element, not checking if the
+    /// element is in the correct subgroup.
+    /// **This is dangerous to call unless you trust the bytes you are reading; otherwise,
+    /// API invariants may be broken.** Please consider using `from_compressed()` instead.
+    pub fn from_compressed_unchecked(bytes: &[u8; 48]) -> CtOption<Self> {
+        // Obtain the three flags from the start of the byte sequence
+        let compression_flag_set = Choice::from((bytes[0] >> 7) & 1);
+        let infinity_flag_set = Choice::from((bytes[0] >> 6) & 1);
+        let sort_flag_set = Choice::from((bytes[0] >> 5) & 1);
+
+        // Attempt to obtain the x-coordinate
+        let x = {
+            let mut tmp = [0; 48];
+            tmp.copy_from_slice(&bytes[0..48]);
+
+            // Mask away the flag bits
+            tmp[0] &= 0b0001_1111;
+
+            Fp::from_bytes(&tmp)
+        };
+
+        x.and_then(|x| {
+            // If the infinity flag is set, return the value assuming
+            // the x-coordinate is zero and the sort bit is not set.
+            //
+            // Otherwise, return a recovered point (assuming the correct
+            // y-coordinate can be found) so long as the infinity flag
+            // was not set.
+            CtOption::new(
+                G1Affine::identity(),
+                infinity_flag_set & // Infinity flag should be set
+                compression_flag_set & // Compression flag should be set
+                (!sort_flag_set) & // Sort flag should not be set
+                x.is_zero(), // The x-coordinate should be zero
+            )
+            .or_else(|| {
+                // Recover a y-coordinate given x by y = sqrt(x^3 + 4)
+                ((x.square() * x) + B).sqrt().and_then(|y| {
+                    // Switch to the correct y-coordinate if necessary.
+                    let y = Fp::conditional_select(
+                        &y,
+                        &-y,
+                        y.lexicographically_largest() ^ sort_flag_set,
+                    );
+
+                    CtOption::new(
+                        G1Affine {
+                            x,
+                            y,
+                            infinity: infinity_flag_set,
+                        },
+                        (!infinity_flag_set) & // Infinity flag should not be set
+                        compression_flag_set, // Compression flag should be set
+                    )
+                })
+            })
+        })
+    }
+
+    /// Returns true if this element is the identity (the point at infinity).
+    #[inline]
+    pub fn is_identity(&self) -> Choice {
+        self.infinity
+    }
+
+    /// Returns true if this point is free of an $h$-torsion component, and so it
+    /// exists within the $q$-order subgroup $\mathbb{G}_1$. This should always return true
+    /// unless an "unchecked" API was used.
+    pub fn is_torsion_free(&self) -> Choice {
+        const FQ_MODULUS_BYTES: [u8; 32] = [
+            1, 0, 0, 0, 255, 255, 255, 255, 254, 91, 254, 255, 2, 164, 189, 83, 5, 216, 161, 9, 8,
+            216, 57, 51, 72, 125, 157, 41, 83, 167, 237, 115,
+        ];
+
+        // Clear the r-torsion from the point and check if it is the identity
+        G1Projective::from(*self)
+            .multiply(&FQ_MODULUS_BYTES)
+            .is_identity()
+    }
+
+    /// Returns true if this point is on the curve. This should always return
+    /// true unless an "unchecked" API was used.
+    pub fn is_on_curve(&self) -> Choice {
+        // y^2 - x^3 ?= 4
+        (self.y.square() - (self.x.square() * self.x)).ct_eq(&B) | self.infinity
+    }
+}
+
+/// This is an element of $\mathbb{G}_1$ represented in the projective coordinate space.
+#[derive(Copy, Clone, Debug)]
+pub struct G1Projective {
+    x: Fp,
+    y: Fp,
+    z: Fp,
+}
+
+impl<'a> From<&'a G1Affine> for G1Projective {
+    fn from(p: &'a G1Affine) -> G1Projective {
+        G1Projective {
+            x: p.x,
+            y: p.y,
+            z: Fp::conditional_select(&Fp::one(), &Fp::zero(), p.infinity),
+        }
+    }
+}
+
+impl From<G1Affine> for G1Projective {
+    fn from(p: G1Affine) -> G1Projective {
+        G1Projective::from(&p)
+    }
+}
+
+impl ConstantTimeEq for G1Projective {
+    fn ct_eq(&self, other: &Self) -> Choice {
+        // Is (xz^2, yz^3, z) equal to (x'z'^2, yz'^3, z') when converted to affine?
+
+        let z = other.z.square();
+        let x1 = self.x * z;
+        let z = z * other.z;
+        let y1 = self.y * z;
+        let z = self.z.square();
+        let x2 = other.x * z;
+        let z = z * self.z;
+        let y2 = other.y * z;
+
+        let self_is_zero = self.z.is_zero();
+        let other_is_zero = other.z.is_zero();
+
+        (self_is_zero & other_is_zero) // Both point at infinity
+            | ((!self_is_zero) & (!other_is_zero) & x1.ct_eq(&x2) & y1.ct_eq(&y2)) // Neither point at infinity, coordinates are the same
+    }
+}
+
+impl ConditionallySelectable for G1Projective {
+    fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self {
+        G1Projective {
+            x: Fp::conditional_select(&a.x, &b.x, choice),
+            y: Fp::conditional_select(&a.y, &b.y, choice),
+            z: Fp::conditional_select(&a.z, &b.z, choice),
+        }
+    }
+}
+
+impl Eq for G1Projective {}
+impl PartialEq for G1Projective {
+    #[inline]
+    fn eq(&self, other: &Self) -> bool {
+        bool::from(self.ct_eq(other))
+    }
+}
+
+impl<'a> Neg for &'a G1Projective {
+    type Output = G1Projective;
+
+    #[inline]
+    fn neg(self) -> G1Projective {
+        G1Projective {
+            x: self.x,
+            y: -self.y,
+            z: self.z,
+        }
+    }
+}
+
+impl Neg for G1Projective {
+    type Output = G1Projective;
+
+    #[inline]
+    fn neg(self) -> G1Projective {
+        -&self
+    }
+}
+
+impl<'a, 'b> Add<&'b G1Projective> for &'a G1Projective {
+    type Output = G1Projective;
+
+    #[inline]
+    fn add(self, rhs: &'b G1Projective) -> G1Projective {
+        self.add(rhs)
+    }
+}
+
+impl<'a, 'b> Sub<&'b G1Projective> for &'a G1Projective {
+    type Output = G1Projective;
+
+    #[inline]
+    fn sub(self, rhs: &'b G1Projective) -> G1Projective {
+        self + (-rhs)
+    }
+}
+
+impl<'a, 'b> Mul<&'b Scalar> for &'a G1Projective {
+    type Output = G1Projective;
+
+    fn mul(self, other: &'b Scalar) -> Self::Output {
+        self.multiply(&other.to_bytes())
+    }
+}
+
+impl<'a, 'b> Mul<&'b Scalar> for &'a G1Affine {
+    type Output = G1Projective;
+
+    fn mul(self, other: &'b Scalar) -> Self::Output {
+        G1Projective::from(self).multiply(&other.to_bytes())
+    }
+}
+
+impl_binops_additive!(G1Projective, G1Projective);
+impl_binops_multiplicative!(G1Projective, Scalar);
+impl_binops_multiplicative_mixed!(G1Affine, Scalar, G1Projective);
+
+impl G1Projective {
+    /// Returns the identity of the group: the point at infinity.
+    pub fn identity() -> G1Projective {
+        G1Projective {
+            x: Fp::zero(),
+            y: Fp::one(),
+            z: Fp::zero(),
+        }
+    }
+
+    /// Returns a fixed generator of the group. See [`notes::design`](notes/design/index.html#fixed-generators)
+    /// for how this generator is chosen.
+    pub fn generator() -> G1Projective {
+        G1Projective {
+            x: Fp::from_raw_unchecked([
+                0x5cb38790fd530c16,
+                0x7817fc679976fff5,
+                0x154f95c7143ba1c1,
+                0xf0ae6acdf3d0e747,
+                0xedce6ecc21dbf440,
+                0x120177419e0bfb75,
+            ]),
+            y: Fp::from_raw_unchecked([
+                0xbaac93d50ce72271,
+                0x8c22631a7918fd8e,
+                0xdd595f13570725ce,
+                0x51ac582950405194,
+                0xe1c8c3fad0059c0,
+                0xbbc3efc5008a26a,
+            ]),
+            z: Fp::one(),
+        }
+    }
+
+    /// Computes the doubling of this point.
+    pub fn double(&self) -> G1Projective {
+        // http://www.hyperelliptic.org/EFD/g1p/auto-shortw-jacobian-0.html#doubling-dbl-2009-l
+        //
+        // There are no points of order 2.
+
+        let a = self.x.square();
+        let b = self.y.square();
+        let c = b.square();
+        let d = self.x + b;
+        let d = d.square();
+        let d = d - a - c;
+        let d = d + d;
+        let e = a + a + a;
+        let f = e.square();
+        let z3 = self.z * self.y;
+        let z3 = z3 + z3;
+        let x3 = f - (d + d);
+        let c = c + c;
+        let c = c + c;
+        let c = c + c;
+        let y3 = e * (d - x3) - c;
+
+        let tmp = G1Projective {
+            x: x3,
+            y: y3,
+            z: z3,
+        };
+
+        G1Projective::conditional_select(&tmp, &G1Projective::identity(), self.is_identity())
+    }
+
+    /// Adds this point to another point.
+    pub fn add(&self, rhs: &G1Projective) -> G1Projective {
+        // This Jacobian point addition technique is based on the implementation in libsecp256k1,
+        // which assumes that rhs has z=1. Let's address the case of zero z-coordinates generally.
+
+        // If self is the identity, return rhs. Otherwise, return self. The other cases will be
+        // predicated on neither self nor rhs being the identity.
+        let f1 = self.is_identity();
+        let res = G1Projective::conditional_select(self, rhs, f1);
+        let f2 = rhs.is_identity();
+
+        // If neither are the identity but x1 = x2 and y1 != y2, then return the identity
+        let z = rhs.z.square();
+        let u1 = self.x * z;
+        let z = z * rhs.z;
+        let s1 = self.y * z;
+        let z = self.z.square();
+        let u2 = rhs.x * z;
+        let z = z * self.z;
+        let s2 = rhs.y * z;
+        let f3 = u1.ct_eq(&u2) & (!s1.ct_eq(&s2));
+        let res =
+            G1Projective::conditional_select(&res, &G1Projective::identity(), (!f1) & (!f2) & f3);
+
+        let t = u1 + u2;
+        let m = s1 + s2;
+        let rr = t.square();
+        let m_alt = -u2;
+        let tt = u1 * m_alt;
+        let rr = rr + tt;
+
+        // Correct for x1 != x2 but y1 = -y2, which can occur because p - 1 is divisible by 3.
+        // libsecp256k1 does this by substituting in an alternative (defined) expression for lambda.
+        let degenerate = m.is_zero() & rr.is_zero();
+        let rr_alt = s1 + s1;
+        let m_alt = m_alt + u1;
+        let rr_alt = Fp::conditional_select(&rr_alt, &rr, !degenerate);
+        let m_alt = Fp::conditional_select(&m_alt, &m, !degenerate);
+
+        let n = m_alt.square();
+        let q = n * t;
+
+        let n = n.square();
+        let n = Fp::conditional_select(&n, &m, degenerate);
+        let t = rr_alt.square();
+        let z3 = m_alt * self.z * rhs.z; // We allow rhs.z != 1, so we must account for this.
+        let z3 = z3 + z3;
+        let q = -q;
+        let t = t + q;
+        let x3 = t;
+        let t = t + t;
+        let t = t + q;
+        let t = t * rr_alt;
+        let t = t + n;
+        let y3 = -t;
+        let x3 = x3 + x3;
+        let x3 = x3 + x3;
+        let y3 = y3 + y3;
+        let y3 = y3 + y3;
+
+        let tmp = G1Projective {
+            x: x3,
+            y: y3,
+            z: z3,
+        };
+
+        G1Projective::conditional_select(&res, &tmp, (!f1) & (!f2) & (!f3))
+    }
+
+    /// Adds this point to another point in the affine model.
+    pub fn add_mixed(&self, rhs: &G1Affine) -> G1Projective {
+        // This Jacobian point addition technique is based on the implementation in libsecp256k1,
+        // which assumes that rhs has z=1. Let's address the case of zero z-coordinates generally.
+
+        // If self is the identity, return rhs. Otherwise, return self. The other cases will be
+        // predicated on neither self nor rhs being the identity.
+        let f1 = self.is_identity();
+        let res = G1Projective::conditional_select(self, &G1Projective::from(rhs), f1);
+        let f2 = rhs.is_identity();
+
+        // If neither are the identity but x1 = x2 and y1 != y2, then return the identity
+        let u1 = self.x;
+        let s1 = self.y;
+        let z = self.z.square();
+        let u2 = rhs.x * z;
+        let z = z * self.z;
+        let s2 = rhs.y * z;
+        let f3 = u1.ct_eq(&u2) & (!s1.ct_eq(&s2));
+        let res =
+            G1Projective::conditional_select(&res, &G1Projective::identity(), (!f1) & (!f2) & f3);
+
+        let t = u1 + u2;
+        let m = s1 + s2;
+        let rr = t.square();
+        let m_alt = -u2;
+        let tt = u1 * m_alt;
+        let rr = rr + tt;
+
+        // Correct for x1 != x2 but y1 = -y2, which can occur because p - 1 is divisible by 3.
+        // libsecp256k1 does this by substituting in an alternative (defined) expression for lambda.
+        let degenerate = m.is_zero() & rr.is_zero();
+        let rr_alt = s1 + s1;
+        let m_alt = m_alt + u1;
+        let rr_alt = Fp::conditional_select(&rr_alt, &rr, !degenerate);
+        let m_alt = Fp::conditional_select(&m_alt, &m, !degenerate);
+
+        let n = m_alt.square();
+        let q = n * t;
+
+        let n = n.square();
+        let n = Fp::conditional_select(&n, &m, degenerate);
+        let t = rr_alt.square();
+        let z3 = m_alt * self.z;
+        let z3 = z3 + z3;
+        let q = -q;
+        let t = t + q;
+        let x3 = t;
+        let t = t + t;
+        let t = t + q;
+        let t = t * rr_alt;
+        let t = t + n;
+        let y3 = -t;
+        let x3 = x3 + x3;
+        let x3 = x3 + x3;
+        let y3 = y3 + y3;
+        let y3 = y3 + y3;
+
+        let tmp = G1Projective {
+            x: x3,
+            y: y3,
+            z: z3,
+        };
+
+        G1Projective::conditional_select(&res, &tmp, (!f1) & (!f2) & (!f3))
+    }
+
+    fn multiply(&self, by: &[u8; 32]) -> G1Projective {
+        let mut acc = G1Projective::identity();
+
+        // This is a simple double-and-add implementation of point
+        // multiplication, moving from most significant to least
+        // significant bit of the scalar.
+        //
+        // We skip the leading bit because it's always unset for Fq
+        // elements.
+        for bit in by
+            .iter()
+            .rev()
+            .flat_map(|byte| (0..8).rev().map(move |i| Choice::from((byte >> i) & 1u8)))
+            .skip(1)
+        {
+            acc = acc.double();
+            acc = G1Projective::conditional_select(&acc, &(acc + self), bit);
+        }
+
+        acc
+    }
+
+    /// Converts a batch of `G1Projective` elements into `G1Affine` elements. This
+    /// function will panic if `p.len() != q.len()`.
+    pub fn batch_normalize(p: &[Self], q: &mut [G1Affine]) {
+        assert_eq!(p.len(), q.len());
+
+        let mut acc = Fp::one();
+        for (p, q) in p.iter().zip(q.iter_mut()) {
+            // We use the `x` field of `G1Affine` to store the product
+            // of previous z-coordinates seen.
+            q.x = acc;
+
+            // We will end up skipping all identities in p
+            acc = Fp::conditional_select(&(acc * p.z), &acc, p.is_identity());
+        }
+
+        // This is the inverse, as all z-coordinates are nonzero and the ones
+        // that are not are skipped.
+        acc = acc.invert().unwrap();
+
+        for (p, q) in p.iter().rev().zip(q.iter_mut().rev()) {
+            let skip = p.is_identity();
+
+            // Compute tmp = 1/z
+            let tmp = q.x * acc;
+
+            // Cancel out z-coordinate in denominator of `acc`
+            acc = Fp::conditional_select(&(acc * p.z), &acc, skip);
+
+            // Set the coordinates to the correct value
+            let tmp2 = tmp.square();
+            let tmp3 = tmp2 * tmp;
+
+            q.x = p.x * tmp2;
+            q.y = p.y * tmp3;
+            q.infinity = Choice::from(0u8);
+
+            *q = G1Affine::conditional_select(&q, &G1Affine::identity(), skip);
+        }
+    }
+
+    /// Returns true if this element is the identity (the point at infinity).
+    #[inline]
+    pub fn is_identity(&self) -> Choice {
+        self.z.is_zero()
+    }
+
+    /// Returns true if this point is on the curve. This should always return
+    /// true unless an "unchecked" API was used.
+    pub fn is_on_curve(&self) -> Choice {
+        // Y^2 - X^3 = 4(Z^6)
+
+        (self.y.square() - (self.x.square() * self.x))
+            .ct_eq(&((self.z.square() * self.z).square() * B))
+            | self.z.is_zero()
+    }
+}
+
+#[test]
+fn test_is_on_curve() {
+    assert!(bool::from(G1Affine::identity().is_on_curve()));
+    assert!(bool::from(G1Affine::generator().is_on_curve()));
+    assert!(bool::from(G1Projective::identity().is_on_curve()));
+    assert!(bool::from(G1Projective::generator().is_on_curve()));
+
+    let z = Fp::from_raw_unchecked([
+        0xba7afa1f9a6fe250,
+        0xfa0f5b595eafe731,
+        0x3bdc477694c306e7,
+        0x2149be4b3949fa24,
+        0x64aa6e0649b2078c,
+        0x12b108ac33643c3e,
+    ]);
+
+    let gen = G1Affine::generator();
+    let mut test = G1Projective {
+        x: gen.x * (z.square()),
+        y: gen.y * (z.square() * z),
+        z,
+    };
+
+    assert!(bool::from(test.is_on_curve()));
+
+    test.x = z;
+    assert!(!bool::from(test.is_on_curve()));
+}
+
+#[test]
+fn test_affine_point_equality() {
+    let a = G1Affine::generator();
+    let b = G1Affine::identity();
+
+    assert!(a == a);
+    assert!(b == b);
+    assert!(a != b);
+    assert!(b != a);
+}
+
+#[test]
+fn test_projective_point_equality() {
+    let a = G1Projective::generator();
+    let b = G1Projective::identity();
+
+    assert!(a == a);
+    assert!(b == b);
+    assert!(a != b);
+    assert!(b != a);
+
+    let z = Fp::from_raw_unchecked([
+        0xba7afa1f9a6fe250,
+        0xfa0f5b595eafe731,
+        0x3bdc477694c306e7,
+        0x2149be4b3949fa24,
+        0x64aa6e0649b2078c,
+        0x12b108ac33643c3e,
+    ]);
+
+    let mut c = G1Projective {
+        x: a.x * (z.square()),
+        y: a.y * (z.square() * z),
+        z,
+    };
+    assert!(bool::from(c.is_on_curve()));
+
+    assert!(a == c);
+    assert!(b != c);
+    assert!(c == a);
+    assert!(c != b);
+
+    c.y = -c.y;
+    assert!(bool::from(c.is_on_curve()));
+
+    assert!(a != c);
+    assert!(b != c);
+    assert!(c != a);
+    assert!(c != b);
+
+    c.y = -c.y;
+    c.x = z;
+    assert!(!bool::from(c.is_on_curve()));
+    assert!(a != b);
+    assert!(a != c);
+    assert!(b != c);
+}
+
+#[test]
+fn test_conditionally_select_affine() {
+    let a = G1Affine::generator();
+    let b = G1Affine::identity();
+
+    assert_eq!(G1Affine::conditional_select(&a, &b, Choice::from(0u8)), a);
+    assert_eq!(G1Affine::conditional_select(&a, &b, Choice::from(1u8)), b);
+}
+
+#[test]
+fn test_conditionally_select_projective() {
+    let a = G1Projective::generator();
+    let b = G1Projective::identity();
+
+    assert_eq!(
+        G1Projective::conditional_select(&a, &b, Choice::from(0u8)),
+        a
+    );
+    assert_eq!(
+        G1Projective::conditional_select(&a, &b, Choice::from(1u8)),
+        b
+    );
+}
+
+#[test]
+fn test_projective_to_affine() {
+    let a = G1Projective::generator();
+    let b = G1Projective::identity();
+
+    assert!(bool::from(G1Affine::from(a).is_on_curve()));
+    assert!(!bool::from(G1Affine::from(a).is_identity()));
+    assert!(bool::from(G1Affine::from(b).is_on_curve()));
+    assert!(bool::from(G1Affine::from(b).is_identity()));
+
+    let z = Fp::from_raw_unchecked([
+        0xba7afa1f9a6fe250,
+        0xfa0f5b595eafe731,
+        0x3bdc477694c306e7,
+        0x2149be4b3949fa24,
+        0x64aa6e0649b2078c,
+        0x12b108ac33643c3e,
+    ]);
+
+    let c = G1Projective {
+        x: a.x * (z.square()),
+        y: a.y * (z.square() * z),
+        z,
+    };
+
+    assert_eq!(G1Affine::from(c), G1Affine::generator());
+}
+
+#[test]
+fn test_affine_to_projective() {
+    let a = G1Affine::generator();
+    let b = G1Affine::identity();
+
+    assert!(bool::from(G1Projective::from(a).is_on_curve()));
+    assert!(!bool::from(G1Projective::from(a).is_identity()));
+    assert!(bool::from(G1Projective::from(b).is_on_curve()));
+    assert!(bool::from(G1Projective::from(b).is_identity()));
+}
+
+#[test]
+fn test_doubling() {
+    {
+        let tmp = G1Projective::identity().double();
+        assert!(bool::from(tmp.is_identity()));
+        assert!(bool::from(tmp.is_on_curve()));
+    }
+    {
+        let tmp = G1Projective::generator().double();
+        assert!(!bool::from(tmp.is_identity()));
+        assert!(bool::from(tmp.is_on_curve()));
+
+        assert_eq!(
+            G1Affine::from(tmp),
+            G1Affine {
+                x: Fp::from_raw_unchecked([
+                    0x53e978ce58a9ba3c,
+                    0x3ea0583c4f3d65f9,
+                    0x4d20bb47f0012960,
+                    0xa54c664ae5b2b5d9,
+                    0x26b552a39d7eb21f,
+                    0x8895d26e68785
+                ]),
+                y: Fp::from_raw_unchecked([
+                    0x70110b3298293940,
+                    0xda33c5393f1f6afc,
+                    0xb86edfd16a5aa785,
+                    0xaec6d1c9e7b1c895,
+                    0x25cfc2b522d11720,
+                    0x6361c83f8d09b15
+                ]),
+                infinity: Choice::from(0u8)
+            }
+        );
+    }
+}
+
+#[test]
+fn test_projective_addition() {
+    {
+        let a = G1Projective::identity();
+        let b = G1Projective::identity();
+        let c = a + b;
+        assert!(bool::from(c.is_identity()));
+        assert!(bool::from(c.is_on_curve()));
+    }
+    {
+        let a = G1Projective::identity();
+        let mut b = G1Projective::generator();
+        {
+            let z = Fp::from_raw_unchecked([
+                0xba7afa1f9a6fe250,
+                0xfa0f5b595eafe731,
+                0x3bdc477694c306e7,
+                0x2149be4b3949fa24,
+                0x64aa6e0649b2078c,
+                0x12b108ac33643c3e,
+            ]);
+
+            b = G1Projective {
+                x: b.x * (z.square()),
+                y: b.y * (z.square() * z),
+                z,
+            };
+        }
+        let c = a + b;
+        assert!(!bool::from(c.is_identity()));
+        assert!(bool::from(c.is_on_curve()));
+        assert!(c == G1Projective::generator());
+    }
+    {
+        let a = G1Projective::identity();
+        let mut b = G1Projective::generator();
+        {
+            let z = Fp::from_raw_unchecked([
+                0xba7afa1f9a6fe250,
+                0xfa0f5b595eafe731,
+                0x3bdc477694c306e7,
+                0x2149be4b3949fa24,
+                0x64aa6e0649b2078c,
+                0x12b108ac33643c3e,
+            ]);
+
+            b = G1Projective {
+                x: b.x * (z.square()),
+                y: b.y * (z.square() * z),
+                z,
+            };
+        }
+        let c = b + a;
+        assert!(!bool::from(c.is_identity()));
+        assert!(bool::from(c.is_on_curve()));
+        assert!(c == G1Projective::generator());
+    }
+    {
+        let a = G1Projective::generator().double().double(); // 4P
+        let b = G1Projective::generator().double(); // 2P
+        let c = a + b;
+
+        let mut d = G1Projective::generator();
+        for _ in 0..5 {
+            d = d + G1Projective::generator();
+        }
+        assert!(!bool::from(c.is_identity()));
+        assert!(bool::from(c.is_on_curve()));
+        assert!(!bool::from(d.is_identity()));
+        assert!(bool::from(d.is_on_curve()));
+        assert_eq!(c, d);
+    }
+
+    // Degenerate case
+    {
+        let beta = Fp::from_raw_unchecked([
+            0xcd03c9e48671f071,
+            0x5dab22461fcda5d2,
+            0x587042afd3851b95,
+            0x8eb60ebe01bacb9e,
+            0x3f97d6e83d050d2,
+            0x18f0206554638741,
+        ]);
+        let beta = beta.square();
+        let a = G1Projective::generator().double().double();
+        let b = G1Projective {
+            x: a.x * beta,
+            y: -a.y,
+            z: a.z,
+        };
+        assert!(bool::from(a.is_on_curve()));
+        assert!(bool::from(b.is_on_curve()));
+
+        let c = a + b;
+        assert_eq!(
+            G1Affine::from(c),
+            G1Affine::from(G1Projective {
+                x: Fp::from_raw_unchecked([
+                    0x29e1e987ef68f2d0,
+                    0xc5f3ec531db03233,
+                    0xacd6c4b6ca19730f,
+                    0x18ad9e827bc2bab7,
+                    0x46e3b2c5785cc7a9,
+                    0x7e571d42d22ddd6
+                ]),
+                y: Fp::from_raw_unchecked([
+                    0x94d117a7e5a539e7,
+                    0x8e17ef673d4b5d22,
+                    0x9d746aaf508a33ea,
+                    0x8c6d883d2516c9a2,
+                    0xbc3b8d5fb0447f7,
+                    0x7bfa4c7210f4f44
+                ]),
+                z: Fp::one()
+            })
+        );
+        assert!(!bool::from(c.is_identity()));
+        assert!(bool::from(c.is_on_curve()));
+    }
+}
+
+#[test]
+fn test_mixed_addition() {
+    {
+        let a = G1Affine::identity();
+        let b = G1Projective::identity();
+        let c = a + b;
+        assert!(bool::from(c.is_identity()));
+        assert!(bool::from(c.is_on_curve()));
+    }
+    {
+        let a = G1Affine::identity();
+        let mut b = G1Projective::generator();
+        {
+            let z = Fp::from_raw_unchecked([
+                0xba7afa1f9a6fe250,
+                0xfa0f5b595eafe731,
+                0x3bdc477694c306e7,
+                0x2149be4b3949fa24,
+                0x64aa6e0649b2078c,
+                0x12b108ac33643c3e,
+            ]);
+
+            b = G1Projective {
+                x: b.x * (z.square()),
+                y: b.y * (z.square() * z),
+                z,
+            };
+        }
+        let c = a + b;
+        assert!(!bool::from(c.is_identity()));
+        assert!(bool::from(c.is_on_curve()));
+        assert!(c == G1Projective::generator());
+    }
+    {
+        let a = G1Affine::identity();
+        let mut b = G1Projective::generator();
+        {
+            let z = Fp::from_raw_unchecked([
+                0xba7afa1f9a6fe250,
+                0xfa0f5b595eafe731,
+                0x3bdc477694c306e7,
+                0x2149be4b3949fa24,
+                0x64aa6e0649b2078c,
+                0x12b108ac33643c3e,
+            ]);
+
+            b = G1Projective {
+                x: b.x * (z.square()),
+                y: b.y * (z.square() * z),
+                z,
+            };
+        }
+        let c = b + a;
+        assert!(!bool::from(c.is_identity()));
+        assert!(bool::from(c.is_on_curve()));
+        assert!(c == G1Projective::generator());
+    }
+    {
+        let a = G1Projective::generator().double().double(); // 4P
+        let b = G1Projective::generator().double(); // 2P
+        let c = a + b;
+
+        let mut d = G1Projective::generator();
+        for _ in 0..5 {
+            d = d + G1Affine::generator();
+        }
+        assert!(!bool::from(c.is_identity()));
+        assert!(bool::from(c.is_on_curve()));
+        assert!(!bool::from(d.is_identity()));
+        assert!(bool::from(d.is_on_curve()));
+        assert_eq!(c, d);
+    }
+
+    // Degenerate case
+    {
+        let beta = Fp::from_raw_unchecked([
+            0xcd03c9e48671f071,
+            0x5dab22461fcda5d2,
+            0x587042afd3851b95,
+            0x8eb60ebe01bacb9e,
+            0x3f97d6e83d050d2,
+            0x18f0206554638741,
+        ]);
+        let beta = beta.square();
+        let a = G1Projective::generator().double().double();
+        let b = G1Projective {
+            x: a.x * beta,
+            y: -a.y,
+            z: a.z,
+        };
+        let a = G1Affine::from(a);
+        assert!(bool::from(a.is_on_curve()));
+        assert!(bool::from(b.is_on_curve()));
+
+        let c = a + b;
+        assert_eq!(
+            G1Affine::from(c),
+            G1Affine::from(G1Projective {
+                x: Fp::from_raw_unchecked([
+                    0x29e1e987ef68f2d0,
+                    0xc5f3ec531db03233,
+                    0xacd6c4b6ca19730f,
+                    0x18ad9e827bc2bab7,
+                    0x46e3b2c5785cc7a9,
+                    0x7e571d42d22ddd6
+                ]),
+                y: Fp::from_raw_unchecked([
+                    0x94d117a7e5a539e7,
+                    0x8e17ef673d4b5d22,
+                    0x9d746aaf508a33ea,
+                    0x8c6d883d2516c9a2,
+                    0xbc3b8d5fb0447f7,
+                    0x7bfa4c7210f4f44
+                ]),
+                z: Fp::one()
+            })
+        );
+        assert!(!bool::from(c.is_identity()));
+        assert!(bool::from(c.is_on_curve()));
+    }
+}
+
+#[test]
+fn test_projective_negation_and_subtraction() {
+    let a = G1Projective::generator().double();
+    assert_eq!(a + (-a), G1Projective::identity());
+    assert_eq!(a + (-a), a - a);
+}
+
+#[test]
+fn test_affine_negation_and_subtraction() {
+    let a = G1Affine::generator();
+    assert_eq!(G1Projective::from(a) + (-a), G1Projective::identity());
+    assert_eq!(G1Projective::from(a) + (-a), G1Projective::from(a) - a);
+}
+
+#[test]
+fn test_projective_scalar_multiplication() {
+    let g = G1Projective::generator();
+    let a = Scalar::from_raw([
+        0x2b568297a56da71c,
+        0xd8c39ecb0ef375d1,
+        0x435c38da67bfbf96,
+        0x8088a05026b659b2,
+    ]);
+    let b = Scalar::from_raw([
+        0x785fdd9b26ef8b85,
+        0xc997f25837695c18,
+        0x4c8dbc39e7b756c1,
+        0x70d9b6cc6d87df20,
+    ]);
+    let c = a * b;
+
+    assert_eq!((g * a) * b, g * c);
+}
+
+#[test]
+fn test_affine_scalar_multiplication() {
+    let g = G1Affine::generator();
+    let a = Scalar::from_raw([
+        0x2b568297a56da71c,
+        0xd8c39ecb0ef375d1,
+        0x435c38da67bfbf96,
+        0x8088a05026b659b2,
+    ]);
+    let b = Scalar::from_raw([
+        0x785fdd9b26ef8b85,
+        0xc997f25837695c18,
+        0x4c8dbc39e7b756c1,
+        0x70d9b6cc6d87df20,
+    ]);
+    let c = a * b;
+
+    assert_eq!(G1Affine::from(g * a) * b, g * c);
+}
+
+#[test]
+fn test_is_torsion_free() {
+    let a = G1Affine {
+        x: Fp::from_raw_unchecked([
+            0xabaf895b97e43c8,
+            0xba4c6432eb9b61b0,
+            0x12506f52adfe307f,
+            0x75028c3439336b72,
+            0x84744f05b8e9bd71,
+            0x113d554fb09554f7,
+        ]),
+        y: Fp::from_raw_unchecked([
+            0x73e90e88f5cf01c0,
+            0x37007b65dd3197e2,
+            0x5cf9a1992f0d7c78,
+            0x4f83c10b9eb3330d,
+            0xf6a63f6f07f60961,
+            0xc53b5b97e634df3,
+        ]),
+        infinity: Choice::from(0u8),
+    };
+    assert!(!bool::from(a.is_torsion_free()));
+
+    assert!(bool::from(G1Affine::identity().is_torsion_free()));
+    assert!(bool::from(G1Affine::generator().is_torsion_free()));
+}
+
+#[test]
+fn test_batch_normalize() {
+    let a = G1Projective::generator().double();
+    let b = a.double();
+    let c = b.double();
+
+    for a_identity in (0..1).map(|n| n == 1) {
+        for b_identity in (0..1).map(|n| n == 1) {
+            for c_identity in (0..1).map(|n| n == 1) {
+                let mut v = [a, b, c];
+                if a_identity {
+                    v[0] = G1Projective::identity()
+                }
+                if b_identity {
+                    v[1] = G1Projective::identity()
+                }
+                if c_identity {
+                    v[2] = G1Projective::identity()
+                }
+
+                let mut t = [
+                    G1Affine::identity(),
+                    G1Affine::identity(),
+                    G1Affine::identity(),
+                ];
+                let expected = [
+                    G1Affine::from(v[0]),
+                    G1Affine::from(v[1]),
+                    G1Affine::from(v[2]),
+                ];
+
+                G1Projective::batch_normalize(&v[..], &mut t[..]);
+
+                assert_eq!(&t[..], &expected[..]);
+            }
+        }
+    }
+}
diff --git a/bls12_381/src/g2.rs b/bls12_381/src/g2.rs
new file mode 100644
index 0000000..136cd03
--- /dev/null
+++ b/bls12_381/src/g2.rs
@@ -0,0 +1,1591 @@
+//! This module provides an implementation of the $\mathbb{G}_2$ group of BLS12-381.
+
+use core::ops::{Add, AddAssign, Mul, MulAssign, Neg, Sub, SubAssign};
+
+use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption};
+
+use crate::fp::Fp;
+use crate::fp2::Fp2;
+use crate::Scalar;
+
+/// This is an element of $\mathbb{G}_2$ represented in the affine coordinate space.
+/// It is ideal to keep elements in this representation to reduce memory usage and
+/// improve performance through the use of mixed curve model arithmetic.
+///
+/// Values of `G2Affine` are guaranteed to be in the $q$-order subgroup unless an
+/// "unchecked" API was misused.
+#[derive(Copy, Clone, Debug)]
+pub struct G2Affine {
+    pub(crate) x: Fp2,
+    pub(crate) y: Fp2,
+    infinity: Choice,
+}
+
+impl Default for G2Affine {
+    fn default() -> G2Affine {
+        G2Affine::identity()
+    }
+}
+
+impl<'a> From<&'a G2Projective> for G2Affine {
+    fn from(p: &'a G2Projective) -> G2Affine {
+        let zinv = p.z.invert().unwrap_or(Fp2::zero());
+        let zinv2 = zinv.square();
+        let x = p.x * zinv2;
+        let zinv3 = zinv2 * zinv;
+        let y = p.y * zinv3;
+
+        let tmp = G2Affine {
+            x,
+            y,
+            infinity: Choice::from(0u8),
+        };
+
+        G2Affine::conditional_select(&tmp, &G2Affine::identity(), zinv.is_zero())
+    }
+}
+
+impl From<G2Projective> for G2Affine {
+    fn from(p: G2Projective) -> G2Affine {
+        G2Affine::from(&p)
+    }
+}
+
+impl ConstantTimeEq for G2Affine {
+    fn ct_eq(&self, other: &Self) -> Choice {
+        // The only cases in which two points are equal are
+        // 1. infinity is set on both
+        // 2. infinity is not set on both, and their coordinates are equal
+
+        (self.infinity & other.infinity)
+            | ((!self.infinity)
+                & (!other.infinity)
+                & self.x.ct_eq(&other.x)
+                & self.y.ct_eq(&other.y))
+    }
+}
+
+impl ConditionallySelectable for G2Affine {
+    fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self {
+        G2Affine {
+            x: Fp2::conditional_select(&a.x, &b.x, choice),
+            y: Fp2::conditional_select(&a.y, &b.y, choice),
+            infinity: Choice::conditional_select(&a.infinity, &b.infinity, choice),
+        }
+    }
+}
+
+impl Eq for G2Affine {}
+impl PartialEq for G2Affine {
+    #[inline]
+    fn eq(&self, other: &Self) -> bool {
+        bool::from(self.ct_eq(other))
+    }
+}
+
+impl<'a> Neg for &'a G2Affine {
+    type Output = G2Affine;
+
+    #[inline]
+    fn neg(self) -> G2Affine {
+        G2Affine {
+            x: self.x,
+            y: Fp2::conditional_select(&-self.y, &Fp2::one(), self.infinity),
+            infinity: self.infinity,
+        }
+    }
+}
+
+impl Neg for G2Affine {
+    type Output = G2Affine;
+
+    #[inline]
+    fn neg(self) -> G2Affine {
+        -&self
+    }
+}
+
+impl<'a, 'b> Add<&'b G2Projective> for &'a G2Affine {
+    type Output = G2Projective;
+
+    #[inline]
+    fn add(self, rhs: &'b G2Projective) -> G2Projective {
+        rhs.add_mixed(self)
+    }
+}
+
+impl<'a, 'b> Add<&'b G2Affine> for &'a G2Projective {
+    type Output = G2Projective;
+
+    #[inline]
+    fn add(self, rhs: &'b G2Affine) -> G2Projective {
+        self.add_mixed(rhs)
+    }
+}
+
+impl<'a, 'b> Sub<&'b G2Projective> for &'a G2Affine {
+    type Output = G2Projective;
+
+    #[inline]
+    fn sub(self, rhs: &'b G2Projective) -> G2Projective {
+        self + (-rhs)
+    }
+}
+
+impl<'a, 'b> Sub<&'b G2Affine> for &'a G2Projective {
+    type Output = G2Projective;
+
+    #[inline]
+    fn sub(self, rhs: &'b G2Affine) -> G2Projective {
+        self + (-rhs)
+    }
+}
+
+impl_binops_additive!(G2Projective, G2Affine);
+impl_binops_additive_specify_output!(G2Affine, G2Projective, G2Projective);
+
+const B: Fp2 = Fp2 {
+    c0: Fp::from_raw_unchecked([
+        0xaa270000000cfff3,
+        0x53cc0032fc34000a,
+        0x478fe97a6b0a807f,
+        0xb1d37ebee6ba24d7,
+        0x8ec9733bbf78ab2f,
+        0x9d645513d83de7e,
+    ]),
+    c1: Fp::from_raw_unchecked([
+        0xaa270000000cfff3,
+        0x53cc0032fc34000a,
+        0x478fe97a6b0a807f,
+        0xb1d37ebee6ba24d7,
+        0x8ec9733bbf78ab2f,
+        0x9d645513d83de7e,
+    ]),
+};
+
+impl G2Affine {
+    /// Returns the identity of the group: the point at infinity.
+    pub fn identity() -> G2Affine {
+        G2Affine {
+            x: Fp2::zero(),
+            y: Fp2::one(),
+            infinity: Choice::from(1u8),
+        }
+    }
+
+    /// Returns a fixed generator of the group. See [`notes::design`](notes/design/index.html#fixed-generators)
+    /// for how this generator is chosen.
+    pub fn generator() -> G2Affine {
+        G2Affine {
+            x: Fp2 {
+                c0: Fp::from_raw_unchecked([
+                    0xf5f28fa202940a10,
+                    0xb3f5fb2687b4961a,
+                    0xa1a893b53e2ae580,
+                    0x9894999d1a3caee9,
+                    0x6f67b7631863366b,
+                    0x58191924350bcd7,
+                ]),
+                c1: Fp::from_raw_unchecked([
+                    0xa5a9c0759e23f606,
+                    0xaaa0c59dbccd60c3,
+                    0x3bb17e18e2867806,
+                    0x1b1ab6cc8541b367,
+                    0xc2b6ed0ef2158547,
+                    0x11922a097360edf3,
+                ]),
+            },
+            y: Fp2 {
+                c0: Fp::from_raw_unchecked([
+                    0x4c730af860494c4a,
+                    0x597cfa1f5e369c5a,
+                    0xe7e6856caa0a635a,
+                    0xbbefb5e96e0d495f,
+                    0x7d3a975f0ef25a2,
+                    0x83fd8e7e80dae5,
+                ]),
+                c1: Fp::from_raw_unchecked([
+                    0xadc0fc92df64b05d,
+                    0x18aa270a2b1461dc,
+                    0x86adac6a3be4eba0,
+                    0x79495c4ec93da33a,
+                    0xe7175850a43ccaed,
+                    0xb2bc2a163de1bf2,
+                ]),
+            },
+            infinity: Choice::from(0u8),
+        }
+    }
+
+    /// Serializes this element into compressed form. See [`notes::serialization`](crate::notes::serialization)
+    /// for details about how group elements are serialized.
+    pub fn to_compressed(&self) -> [u8; 96] {
+        // Strictly speaking, self.x is zero already when self.infinity is true, but
+        // to guard against implementation mistakes we do not assume this.
+        let x = Fp2::conditional_select(&self.x, &Fp2::zero(), self.infinity);
+
+        let mut res = [0; 96];
+
+        (&mut res[0..48]).copy_from_slice(&x.c1.to_bytes()[..]);
+        (&mut res[48..96]).copy_from_slice(&x.c0.to_bytes()[..]);
+
+        // This point is in compressed form, so we set the most significant bit.
+        res[0] |= 1u8 << 7;
+
+        // Is this point at infinity? If so, set the second-most significant bit.
+        res[0] |= u8::conditional_select(&0u8, &(1u8 << 6), self.infinity);
+
+        // Is the y-coordinate the lexicographically largest of the two associated with the
+        // x-coordinate? If so, set the third-most significant bit so long as this is not
+        // the point at infinity.
+        res[0] |= u8::conditional_select(
+            &0u8,
+            &(1u8 << 5),
+            (!self.infinity) & self.y.lexicographically_largest(),
+        );
+
+        res
+    }
+
+    /// Serializes this element into uncompressed form. See [`notes::serialization`](crate::notes::serialization)
+    /// for details about how group elements are serialized.
+    pub fn to_uncompressed(&self) -> [u8; 192] {
+        let mut res = [0; 192];
+
+        let x = Fp2::conditional_select(&self.x, &Fp2::zero(), self.infinity);
+        let y = Fp2::conditional_select(&self.y, &Fp2::zero(), self.infinity);
+
+        res[0..48].copy_from_slice(&x.c1.to_bytes()[..]);
+        res[48..96].copy_from_slice(&x.c0.to_bytes()[..]);
+        res[96..144].copy_from_slice(&y.c1.to_bytes()[..]);
+        res[144..192].copy_from_slice(&y.c0.to_bytes()[..]);
+
+        // Is this point at infinity? If so, set the second-most significant bit.
+        res[0] |= u8::conditional_select(&0u8, &(1u8 << 6), self.infinity);
+
+        res
+    }
+
+    /// Attempts to deserialize an uncompressed element. See [`notes::serialization`](crate::notes::serialization)
+    /// for details about how group elements are serialized.
+    pub fn from_uncompressed(bytes: &[u8; 192]) -> CtOption<Self> {
+        Self::from_uncompressed_unchecked(bytes)
+            .and_then(|p| CtOption::new(p, p.is_on_curve() & p.is_torsion_free()))
+    }
+
+    /// Attempts to deserialize an uncompressed element, not checking if the
+    /// element is on the curve and not checking if it is in the correct subgroup.
+    /// **This is dangerous to call unless you trust the bytes you are reading; otherwise,
+    /// API invariants may be broken.** Please consider using `from_uncompressed()` instead.
+    pub fn from_uncompressed_unchecked(bytes: &[u8; 192]) -> CtOption<Self> {
+        // Obtain the three flags from the start of the byte sequence
+        let compression_flag_set = Choice::from((bytes[0] >> 7) & 1);
+        let infinity_flag_set = Choice::from((bytes[0] >> 6) & 1);
+        let sort_flag_set = Choice::from((bytes[0] >> 5) & 1);
+
+        // Attempt to obtain the x-coordinate
+        let xc1 = {
+            let mut tmp = [0; 48];
+            tmp.copy_from_slice(&bytes[0..48]);
+
+            // Mask away the flag bits
+            tmp[0] &= 0b0001_1111;
+
+            Fp::from_bytes(&tmp)
+        };
+        let xc0 = {
+            let mut tmp = [0; 48];
+            tmp.copy_from_slice(&bytes[48..96]);
+
+            Fp::from_bytes(&tmp)
+        };
+
+        // Attempt to obtain the y-coordinate
+        let yc1 = {
+            let mut tmp = [0; 48];
+            tmp.copy_from_slice(&bytes[96..144]);
+
+            Fp::from_bytes(&tmp)
+        };
+        let yc0 = {
+            let mut tmp = [0; 48];
+            tmp.copy_from_slice(&bytes[144..192]);
+
+            Fp::from_bytes(&tmp)
+        };
+
+        xc1.and_then(|xc1| {
+            xc0.and_then(|xc0| {
+                yc1.and_then(|yc1| {
+                    yc0.and_then(|yc0| {
+                        let x = Fp2 {
+                            c0: xc0,
+                            c1: xc1
+                        };
+                        let y = Fp2 {
+                            c0: yc0,
+                            c1: yc1
+                        };
+
+                        // Create a point representing this value
+                        let p = G2Affine::conditional_select(
+                            &G2Affine {
+                                x,
+                                y,
+                                infinity: infinity_flag_set,
+                            },
+                            &G2Affine::identity(),
+                            infinity_flag_set,
+                        );
+
+                        CtOption::new(
+                            p,
+                            // If the infinity flag is set, the x and y coordinates should have been zero.
+                            ((!infinity_flag_set) | (infinity_flag_set & x.is_zero() & y.is_zero())) &
+                            // The compression flag should not have been set, as this is an uncompressed element
+                            (!compression_flag_set) &
+                            // The sort flag should not have been set, as this is an uncompressed element
+                            (!sort_flag_set),
+                        )
+                    })
+                })
+            })
+        })
+    }
+
+    /// Attempts to deserialize a compressed element. See [`notes::serialization`](crate::notes::serialization)
+    /// for details about how group elements are serialized.
+    pub fn from_compressed(bytes: &[u8; 96]) -> CtOption<Self> {
+        // We already know the point is on the curve because this is established
+        // by the y-coordinate recovery procedure in from_compressed_unchecked().
+
+        Self::from_compressed_unchecked(bytes).and_then(|p| CtOption::new(p, p.is_torsion_free()))
+    }
+
+    /// Attempts to deserialize an uncompressed element, not checking if the
+    /// element is in the correct subgroup.
+    /// **This is dangerous to call unless you trust the bytes you are reading; otherwise,
+    /// API invariants may be broken.** Please consider using `from_compressed()` instead.
+    pub fn from_compressed_unchecked(bytes: &[u8; 96]) -> CtOption<Self> {
+        // Obtain the three flags from the start of the byte sequence
+        let compression_flag_set = Choice::from((bytes[0] >> 7) & 1);
+        let infinity_flag_set = Choice::from((bytes[0] >> 6) & 1);
+        let sort_flag_set = Choice::from((bytes[0] >> 5) & 1);
+
+        // Attempt to obtain the x-coordinate
+        let xc1 = {
+            let mut tmp = [0; 48];
+            tmp.copy_from_slice(&bytes[0..48]);
+
+            // Mask away the flag bits
+            tmp[0] &= 0b0001_1111;
+
+            Fp::from_bytes(&tmp)
+        };
+        let xc0 = {
+            let mut tmp = [0; 48];
+            tmp.copy_from_slice(&bytes[48..96]);
+
+            Fp::from_bytes(&tmp)
+        };
+
+        xc1.and_then(|xc1| {
+            xc0.and_then(|xc0| {
+                let x = Fp2 { c0: xc0, c1: xc1 };
+
+                // If the infinity flag is set, return the value assuming
+                // the x-coordinate is zero and the sort bit is not set.
+                //
+                // Otherwise, return a recovered point (assuming the correct
+                // y-coordinate can be found) so long as the infinity flag
+                // was not set.
+                CtOption::new(
+                    G2Affine::identity(),
+                    infinity_flag_set & // Infinity flag should be set
+                    compression_flag_set & // Compression flag should be set
+                    (!sort_flag_set) & // Sort flag should not be set
+                    x.is_zero(), // The x-coordinate should be zero
+                )
+                .or_else(|| {
+                    // Recover a y-coordinate given x by y = sqrt(x^3 + 4)
+                    ((x.square() * x) + B).sqrt().and_then(|y| {
+                        // Switch to the correct y-coordinate if necessary.
+                        let y = Fp2::conditional_select(
+                            &y,
+                            &-y,
+                            y.lexicographically_largest() ^ sort_flag_set,
+                        );
+
+                        CtOption::new(
+                            G2Affine {
+                                x,
+                                y,
+                                infinity: infinity_flag_set,
+                            },
+                            (!infinity_flag_set) & // Infinity flag should not be set
+                            compression_flag_set, // Compression flag should be set
+                        )
+                    })
+                })
+            })
+        })
+    }
+
+    /// Returns true if this element is the identity (the point at infinity).
+    #[inline]
+    pub fn is_identity(&self) -> Choice {
+        self.infinity
+    }
+
+    /// Returns true if this point is free of an $h$-torsion component, and so it
+    /// exists within the $q$-order subgroup $\mathbb{G}_2$. This should always return true
+    /// unless an "unchecked" API was used.
+    pub fn is_torsion_free(&self) -> Choice {
+        const FQ_MODULUS_BYTES: [u8; 32] = [
+            1, 0, 0, 0, 255, 255, 255, 255, 254, 91, 254, 255, 2, 164, 189, 83, 5, 216, 161, 9, 8,
+            216, 57, 51, 72, 125, 157, 41, 83, 167, 237, 115,
+        ];
+
+        // Clear the r-torsion from the point and check if it is the identity
+        G2Projective::from(*self)
+            .multiply(&FQ_MODULUS_BYTES)
+            .is_identity()
+    }
+
+    /// Returns true if this point is on the curve. This should always return
+    /// true unless an "unchecked" API was used.
+    pub fn is_on_curve(&self) -> Choice {
+        // y^2 - x^3 ?= 4(u + 1)
+        (self.y.square() - (self.x.square() * self.x)).ct_eq(&B) | self.infinity
+    }
+}
+
+/// This is an element of $\mathbb{G}_2$ represented in the projective coordinate space.
+#[derive(Copy, Clone, Debug)]
+pub struct G2Projective {
+    pub(crate) x: Fp2,
+    pub(crate) y: Fp2,
+    pub(crate) z: Fp2,
+}
+
+impl<'a> From<&'a G2Affine> for G2Projective {
+    fn from(p: &'a G2Affine) -> G2Projective {
+        G2Projective {
+            x: p.x,
+            y: p.y,
+            z: Fp2::conditional_select(&Fp2::one(), &Fp2::zero(), p.infinity),
+        }
+    }
+}
+
+impl From<G2Affine> for G2Projective {
+    fn from(p: G2Affine) -> G2Projective {
+        G2Projective::from(&p)
+    }
+}
+
+impl ConstantTimeEq for G2Projective {
+    fn ct_eq(&self, other: &Self) -> Choice {
+        // Is (xz^2, yz^3, z) equal to (x'z'^2, yz'^3, z') when converted to affine?
+
+        let z = other.z.square();
+        let x1 = self.x * z;
+        let z = z * other.z;
+        let y1 = self.y * z;
+        let z = self.z.square();
+        let x2 = other.x * z;
+        let z = z * self.z;
+        let y2 = other.y * z;
+
+        let self_is_zero = self.z.is_zero();
+        let other_is_zero = other.z.is_zero();
+
+        (self_is_zero & other_is_zero) // Both point at infinity
+            | ((!self_is_zero) & (!other_is_zero) & x1.ct_eq(&x2) & y1.ct_eq(&y2)) // Neither point at infinity, coordinates are the same
+    }
+}
+
+impl ConditionallySelectable for G2Projective {
+    fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self {
+        G2Projective {
+            x: Fp2::conditional_select(&a.x, &b.x, choice),
+            y: Fp2::conditional_select(&a.y, &b.y, choice),
+            z: Fp2::conditional_select(&a.z, &b.z, choice),
+        }
+    }
+}
+
+impl Eq for G2Projective {}
+impl PartialEq for G2Projective {
+    #[inline]
+    fn eq(&self, other: &Self) -> bool {
+        bool::from(self.ct_eq(other))
+    }
+}
+
+impl<'a> Neg for &'a G2Projective {
+    type Output = G2Projective;
+
+    #[inline]
+    fn neg(self) -> G2Projective {
+        G2Projective {
+            x: self.x,
+            y: -self.y,
+            z: self.z,
+        }
+    }
+}
+
+impl Neg for G2Projective {
+    type Output = G2Projective;
+
+    #[inline]
+    fn neg(self) -> G2Projective {
+        -&self
+    }
+}
+
+impl<'a, 'b> Add<&'b G2Projective> for &'a G2Projective {
+    type Output = G2Projective;
+
+    #[inline]
+    fn add(self, rhs: &'b G2Projective) -> G2Projective {
+        self.add(rhs)
+    }
+}
+
+impl<'a, 'b> Sub<&'b G2Projective> for &'a G2Projective {
+    type Output = G2Projective;
+
+    #[inline]
+    fn sub(self, rhs: &'b G2Projective) -> G2Projective {
+        self + (-rhs)
+    }
+}
+
+impl<'a, 'b> Mul<&'b Scalar> for &'a G2Projective {
+    type Output = G2Projective;
+
+    fn mul(self, other: &'b Scalar) -> Self::Output {
+        self.multiply(&other.to_bytes())
+    }
+}
+
+impl<'a, 'b> Mul<&'b Scalar> for &'a G2Affine {
+    type Output = G2Projective;
+
+    fn mul(self, other: &'b Scalar) -> Self::Output {
+        G2Projective::from(self).multiply(&other.to_bytes())
+    }
+}
+
+impl_binops_additive!(G2Projective, G2Projective);
+impl_binops_multiplicative!(G2Projective, Scalar);
+impl_binops_multiplicative_mixed!(G2Affine, Scalar, G2Projective);
+
+impl G2Projective {
+    /// Returns the identity of the group: the point at infinity.
+    pub fn identity() -> G2Projective {
+        G2Projective {
+            x: Fp2::zero(),
+            y: Fp2::one(),
+            z: Fp2::zero(),
+        }
+    }
+
+    /// Returns a fixed generator of the group. See [`notes::design`](notes/design/index.html#fixed-generators)
+    /// for how this generator is chosen.
+    pub fn generator() -> G2Projective {
+        G2Projective {
+            x: Fp2 {
+                c0: Fp::from_raw_unchecked([
+                    0xf5f28fa202940a10,
+                    0xb3f5fb2687b4961a,
+                    0xa1a893b53e2ae580,
+                    0x9894999d1a3caee9,
+                    0x6f67b7631863366b,
+                    0x58191924350bcd7,
+                ]),
+                c1: Fp::from_raw_unchecked([
+                    0xa5a9c0759e23f606,
+                    0xaaa0c59dbccd60c3,
+                    0x3bb17e18e2867806,
+                    0x1b1ab6cc8541b367,
+                    0xc2b6ed0ef2158547,
+                    0x11922a097360edf3,
+                ]),
+            },
+            y: Fp2 {
+                c0: Fp::from_raw_unchecked([
+                    0x4c730af860494c4a,
+                    0x597cfa1f5e369c5a,
+                    0xe7e6856caa0a635a,
+                    0xbbefb5e96e0d495f,
+                    0x7d3a975f0ef25a2,
+                    0x83fd8e7e80dae5,
+                ]),
+                c1: Fp::from_raw_unchecked([
+                    0xadc0fc92df64b05d,
+                    0x18aa270a2b1461dc,
+                    0x86adac6a3be4eba0,
+                    0x79495c4ec93da33a,
+                    0xe7175850a43ccaed,
+                    0xb2bc2a163de1bf2,
+                ]),
+            },
+            z: Fp2::one(),
+        }
+    }
+
+    /// Computes the doubling of this point.
+    pub fn double(&self) -> G2Projective {
+        // http://www.hyperelliptic.org/EFD/g2p/auto-shortw-jacobian-0.html#doubling-dbl-2009-l
+        //
+        // There are no points of order 2.
+
+        let a = self.x.square();
+        let b = self.y.square();
+        let c = b.square();
+        let d = self.x + b;
+        let d = d.square();
+        let d = d - a - c;
+        let d = d + d;
+        let e = a + a + a;
+        let f = e.square();
+        let z3 = self.z * self.y;
+        let z3 = z3 + z3;
+        let x3 = f - (d + d);
+        let c = c + c;
+        let c = c + c;
+        let c = c + c;
+        let y3 = e * (d - x3) - c;
+
+        let tmp = G2Projective {
+            x: x3,
+            y: y3,
+            z: z3,
+        };
+
+        G2Projective::conditional_select(&tmp, &G2Projective::identity(), self.is_identity())
+    }
+
+    /// Adds this point to another point.
+    pub fn add(&self, rhs: &G2Projective) -> G2Projective {
+        // This Jacobian point addition technique is based on the implementation in libsecp256k1,
+        // which assumes that rhs has z=1. Let's address the case of zero z-coordinates generally.
+
+        // If self is the identity, return rhs. Otherwise, return self. The other cases will be
+        // predicated on neither self nor rhs being the identity.
+        let f1 = self.is_identity();
+        let res = G2Projective::conditional_select(self, rhs, f1);
+        let f2 = rhs.is_identity();
+
+        // If neither are the identity but x1 = x2 and y1 != y2, then return the identity
+        let z = rhs.z.square();
+        let u1 = self.x * z;
+        let z = z * rhs.z;
+        let s1 = self.y * z;
+        let z = self.z.square();
+        let u2 = rhs.x * z;
+        let z = z * self.z;
+        let s2 = rhs.y * z;
+        let f3 = u1.ct_eq(&u2) & (!s1.ct_eq(&s2));
+        let res =
+            G2Projective::conditional_select(&res, &G2Projective::identity(), (!f1) & (!f2) & f3);
+
+        let t = u1 + u2;
+        let m = s1 + s2;
+        let rr = t.square();
+        let m_alt = -u2;
+        let tt = u1 * m_alt;
+        let rr = rr + tt;
+
+        // Correct for x1 != x2 but y1 = -y2, which can occur because p - 1 is divisible by 3.
+        // libsecp256k1 does this by substituting in an alternative (defined) expression for lambda.
+        let degenerate = m.is_zero() & rr.is_zero();
+        let rr_alt = s1 + s1;
+        let m_alt = m_alt + u1;
+        let rr_alt = Fp2::conditional_select(&rr_alt, &rr, !degenerate);
+        let m_alt = Fp2::conditional_select(&m_alt, &m, !degenerate);
+
+        let n = m_alt.square();
+        let q = n * t;
+
+        let n = n.square();
+        let n = Fp2::conditional_select(&n, &m, degenerate);
+        let t = rr_alt.square();
+        let z3 = m_alt * self.z * rhs.z; // We allow rhs.z != 1, so we must account for this.
+        let z3 = z3 + z3;
+        let q = -q;
+        let t = t + q;
+        let x3 = t;
+        let t = t + t;
+        let t = t + q;
+        let t = t * rr_alt;
+        let t = t + n;
+        let y3 = -t;
+        let x3 = x3 + x3;
+        let x3 = x3 + x3;
+        let y3 = y3 + y3;
+        let y3 = y3 + y3;
+
+        let tmp = G2Projective {
+            x: x3,
+            y: y3,
+            z: z3,
+        };
+
+        G2Projective::conditional_select(&res, &tmp, (!f1) & (!f2) & (!f3))
+    }
+
+    /// Adds this point to another point in the affine model.
+    pub fn add_mixed(&self, rhs: &G2Affine) -> G2Projective {
+        // This Jacobian point addition technique is based on the implementation in libsecp256k1,
+        // which assumes that rhs has z=1. Let's address the case of zero z-coordinates generally.
+
+        // If self is the identity, return rhs. Otherwise, return self. The other cases will be
+        // predicated on neither self nor rhs being the identity.
+        let f1 = self.is_identity();
+        let res = G2Projective::conditional_select(self, &G2Projective::from(rhs), f1);
+        let f2 = rhs.is_identity();
+
+        // If neither are the identity but x1 = x2 and y1 != y2, then return the identity
+        let u1 = self.x;
+        let s1 = self.y;
+        let z = self.z.square();
+        let u2 = rhs.x * z;
+        let z = z * self.z;
+        let s2 = rhs.y * z;
+        let f3 = u1.ct_eq(&u2) & (!s1.ct_eq(&s2));
+        let res =
+            G2Projective::conditional_select(&res, &G2Projective::identity(), (!f1) & (!f2) & f3);
+
+        let t = u1 + u2;
+        let m = s1 + s2;
+        let rr = t.square();
+        let m_alt = -u2;
+        let tt = u1 * m_alt;
+        let rr = rr + tt;
+
+        // Correct for x1 != x2 but y1 = -y2, which can occur because p - 1 is divisible by 3.
+        // libsecp256k1 does this by substituting in an alternative (defined) expression for lambda.
+        let degenerate = m.is_zero() & rr.is_zero();
+        let rr_alt = s1 + s1;
+        let m_alt = m_alt + u1;
+        let rr_alt = Fp2::conditional_select(&rr_alt, &rr, !degenerate);
+        let m_alt = Fp2::conditional_select(&m_alt, &m, !degenerate);
+
+        let n = m_alt.square();
+        let q = n * t;
+
+        let n = n.square();
+        let n = Fp2::conditional_select(&n, &m, degenerate);
+        let t = rr_alt.square();
+        let z3 = m_alt * self.z;
+        let z3 = z3 + z3;
+        let q = -q;
+        let t = t + q;
+        let x3 = t;
+        let t = t + t;
+        let t = t + q;
+        let t = t * rr_alt;
+        let t = t + n;
+        let y3 = -t;
+        let x3 = x3 + x3;
+        let x3 = x3 + x3;
+        let y3 = y3 + y3;
+        let y3 = y3 + y3;
+
+        let tmp = G2Projective {
+            x: x3,
+            y: y3,
+            z: z3,
+        };
+
+        G2Projective::conditional_select(&res, &tmp, (!f1) & (!f2) & (!f3))
+    }
+
+    fn multiply(&self, by: &[u8; 32]) -> G2Projective {
+        let mut acc = G2Projective::identity();
+
+        // This is a simple double-and-add implementation of point
+        // multiplication, moving from most significant to least
+        // significant bit of the scalar.
+        //
+        // We skip the leading bit because it's always unset for Fq
+        // elements.
+        for bit in by
+            .iter()
+            .rev()
+            .flat_map(|byte| (0..8).rev().map(move |i| Choice::from((byte >> i) & 1u8)))
+            .skip(1)
+        {
+            acc = acc.double();
+            acc = G2Projective::conditional_select(&acc, &(acc + self), bit);
+        }
+
+        acc
+    }
+
+    /// Converts a batch of `G2Projective` elements into `G2Affine` elements. This
+    /// function will panic if `p.len() != q.len()`.
+    pub fn batch_normalize(p: &[Self], q: &mut [G2Affine]) {
+        assert_eq!(p.len(), q.len());
+
+        let mut acc = Fp2::one();
+        for (p, q) in p.iter().zip(q.iter_mut()) {
+            // We use the `x` field of `G2Affine` to store the product
+            // of previous z-coordinates seen.
+            q.x = acc;
+
+            // We will end up skipping all identities in p
+            acc = Fp2::conditional_select(&(acc * p.z), &acc, p.is_identity());
+        }
+
+        // This is the inverse, as all z-coordinates are nonzero and the ones
+        // that are not are skipped.
+        acc = acc.invert().unwrap();
+
+        for (p, q) in p.iter().rev().zip(q.iter_mut().rev()) {
+            let skip = p.is_identity();
+
+            // Compute tmp = 1/z
+            let tmp = q.x * acc;
+
+            // Cancel out z-coordinate in denominator of `acc`
+            acc = Fp2::conditional_select(&(acc * p.z), &acc, skip);
+
+            // Set the coordinates to the correct value
+            let tmp2 = tmp.square();
+            let tmp3 = tmp2 * tmp;
+
+            q.x = p.x * tmp2;
+            q.y = p.y * tmp3;
+            q.infinity = Choice::from(0u8);
+
+            *q = G2Affine::conditional_select(&q, &G2Affine::identity(), skip);
+        }
+    }
+
+    /// Returns true if this element is the identity (the point at infinity).
+    #[inline]
+    pub fn is_identity(&self) -> Choice {
+        self.z.is_zero()
+    }
+
+    /// Returns true if this point is on the curve. This should always return
+    /// true unless an "unchecked" API was used.
+    pub fn is_on_curve(&self) -> Choice {
+        // Y^2 - X^3 = 4(u + 1)(Z^6)
+
+        (self.y.square() - (self.x.square() * self.x))
+            .ct_eq(&((self.z.square() * self.z).square() * B))
+            | self.z.is_zero()
+    }
+}
+
+#[test]
+fn test_is_on_curve() {
+    assert!(bool::from(G2Affine::identity().is_on_curve()));
+    assert!(bool::from(G2Affine::generator().is_on_curve()));
+    assert!(bool::from(G2Projective::identity().is_on_curve()));
+    assert!(bool::from(G2Projective::generator().is_on_curve()));
+
+    let z = Fp2 {
+        c0: Fp::from_raw_unchecked([
+            0xba7afa1f9a6fe250,
+            0xfa0f5b595eafe731,
+            0x3bdc477694c306e7,
+            0x2149be4b3949fa24,
+            0x64aa6e0649b2078c,
+            0x12b108ac33643c3e,
+        ]),
+        c1: Fp::from_raw_unchecked([
+            0x125325df3d35b5a8,
+            0xdc469ef5555d7fe3,
+            0x2d716d2443106a9,
+            0x5a1db59a6ff37d0,
+            0x7cf7784e5300bb8f,
+            0x16a88922c7a5e844,
+        ]),
+    };
+
+    let gen = G2Affine::generator();
+    let mut test = G2Projective {
+        x: gen.x * (z.square()),
+        y: gen.y * (z.square() * z),
+        z,
+    };
+
+    assert!(bool::from(test.is_on_curve()));
+
+    test.x = z;
+    assert!(!bool::from(test.is_on_curve()));
+}
+
+#[test]
+fn test_affine_point_equality() {
+    let a = G2Affine::generator();
+    let b = G2Affine::identity();
+
+    assert!(a == a);
+    assert!(b == b);
+    assert!(a != b);
+    assert!(b != a);
+}
+
+#[test]
+fn test_projective_point_equality() {
+    let a = G2Projective::generator();
+    let b = G2Projective::identity();
+
+    assert!(a == a);
+    assert!(b == b);
+    assert!(a != b);
+    assert!(b != a);
+
+    let z = Fp2 {
+        c0: Fp::from_raw_unchecked([
+            0xba7afa1f9a6fe250,
+            0xfa0f5b595eafe731,
+            0x3bdc477694c306e7,
+            0x2149be4b3949fa24,
+            0x64aa6e0649b2078c,
+            0x12b108ac33643c3e,
+        ]),
+        c1: Fp::from_raw_unchecked([
+            0x125325df3d35b5a8,
+            0xdc469ef5555d7fe3,
+            0x2d716d2443106a9,
+            0x5a1db59a6ff37d0,
+            0x7cf7784e5300bb8f,
+            0x16a88922c7a5e844,
+        ]),
+    };
+
+    let mut c = G2Projective {
+        x: a.x * (z.square()),
+        y: a.y * (z.square() * z),
+        z,
+    };
+    assert!(bool::from(c.is_on_curve()));
+
+    assert!(a == c);
+    assert!(b != c);
+    assert!(c == a);
+    assert!(c != b);
+
+    c.y = -c.y;
+    assert!(bool::from(c.is_on_curve()));
+
+    assert!(a != c);
+    assert!(b != c);
+    assert!(c != a);
+    assert!(c != b);
+
+    c.y = -c.y;
+    c.x = z;
+    assert!(!bool::from(c.is_on_curve()));
+    assert!(a != b);
+    assert!(a != c);
+    assert!(b != c);
+}
+
+#[test]
+fn test_conditionally_select_affine() {
+    let a = G2Affine::generator();
+    let b = G2Affine::identity();
+
+    assert_eq!(G2Affine::conditional_select(&a, &b, Choice::from(0u8)), a);
+    assert_eq!(G2Affine::conditional_select(&a, &b, Choice::from(1u8)), b);
+}
+
+#[test]
+fn test_conditionally_select_projective() {
+    let a = G2Projective::generator();
+    let b = G2Projective::identity();
+
+    assert_eq!(
+        G2Projective::conditional_select(&a, &b, Choice::from(0u8)),
+        a
+    );
+    assert_eq!(
+        G2Projective::conditional_select(&a, &b, Choice::from(1u8)),
+        b
+    );
+}
+
+#[test]
+fn test_projective_to_affine() {
+    let a = G2Projective::generator();
+    let b = G2Projective::identity();
+
+    assert!(bool::from(G2Affine::from(a).is_on_curve()));
+    assert!(!bool::from(G2Affine::from(a).is_identity()));
+    assert!(bool::from(G2Affine::from(b).is_on_curve()));
+    assert!(bool::from(G2Affine::from(b).is_identity()));
+
+    let z = Fp2 {
+        c0: Fp::from_raw_unchecked([
+            0xba7afa1f9a6fe250,
+            0xfa0f5b595eafe731,
+            0x3bdc477694c306e7,
+            0x2149be4b3949fa24,
+            0x64aa6e0649b2078c,
+            0x12b108ac33643c3e,
+        ]),
+        c1: Fp::from_raw_unchecked([
+            0x125325df3d35b5a8,
+            0xdc469ef5555d7fe3,
+            0x2d716d2443106a9,
+            0x5a1db59a6ff37d0,
+            0x7cf7784e5300bb8f,
+            0x16a88922c7a5e844,
+        ]),
+    };
+
+    let c = G2Projective {
+        x: a.x * (z.square()),
+        y: a.y * (z.square() * z),
+        z,
+    };
+
+    assert_eq!(G2Affine::from(c), G2Affine::generator());
+}
+
+#[test]
+fn test_affine_to_projective() {
+    let a = G2Affine::generator();
+    let b = G2Affine::identity();
+
+    assert!(bool::from(G2Projective::from(a).is_on_curve()));
+    assert!(!bool::from(G2Projective::from(a).is_identity()));
+    assert!(bool::from(G2Projective::from(b).is_on_curve()));
+    assert!(bool::from(G2Projective::from(b).is_identity()));
+}
+
+#[test]
+fn test_doubling() {
+    {
+        let tmp = G2Projective::identity().double();
+        assert!(bool::from(tmp.is_identity()));
+        assert!(bool::from(tmp.is_on_curve()));
+    }
+    {
+        let tmp = G2Projective::generator().double();
+        assert!(!bool::from(tmp.is_identity()));
+        assert!(bool::from(tmp.is_on_curve()));
+
+        assert_eq!(
+            G2Affine::from(tmp),
+            G2Affine {
+                x: Fp2 {
+                    c0: Fp::from_raw_unchecked([
+                        0xe9d9e2da9620f98b,
+                        0x54f1199346b97f36,
+                        0x3db3b820376bed27,
+                        0xcfdb31c9b0b64f4c,
+                        0x41d7c12786354493,
+                        0x5710794c255c064
+                    ]),
+                    c1: Fp::from_raw_unchecked([
+                        0xd6c1d3ca6ea0d06e,
+                        0xda0cbd905595489f,
+                        0x4f5352d43479221d,
+                        0x8ade5d736f8c97e0,
+                        0x48cc8433925ef70e,
+                        0x8d7ea71ea91ef81
+                    ]),
+                },
+                y: Fp2 {
+                    c0: Fp::from_raw_unchecked([
+                        0x15ba26eb4b0d186f,
+                        0xd086d64b7e9e01e,
+                        0xc8b848dd652f4c78,
+                        0xeecf46a6123bae4f,
+                        0x255e8dd8b6dc812a,
+                        0x164142af21dcf93f
+                    ]),
+                    c1: Fp::from_raw_unchecked([
+                        0xf9b4a1a895984db4,
+                        0xd417b114cccff748,
+                        0x6856301fc89f086e,
+                        0x41c777878931e3da,
+                        0x3556b155066a2105,
+                        0xacf7d325cb89cf
+                    ]),
+                },
+                infinity: Choice::from(0u8)
+            }
+        );
+    }
+}
+
+#[test]
+fn test_projective_addition() {
+    {
+        let a = G2Projective::identity();
+        let b = G2Projective::identity();
+        let c = a + b;
+        assert!(bool::from(c.is_identity()));
+        assert!(bool::from(c.is_on_curve()));
+    }
+    {
+        let a = G2Projective::identity();
+        let mut b = G2Projective::generator();
+        {
+            let z = Fp2 {
+                c0: Fp::from_raw_unchecked([
+                    0xba7afa1f9a6fe250,
+                    0xfa0f5b595eafe731,
+                    0x3bdc477694c306e7,
+                    0x2149be4b3949fa24,
+                    0x64aa6e0649b2078c,
+                    0x12b108ac33643c3e,
+                ]),
+                c1: Fp::from_raw_unchecked([
+                    0x125325df3d35b5a8,
+                    0xdc469ef5555d7fe3,
+                    0x2d716d2443106a9,
+                    0x5a1db59a6ff37d0,
+                    0x7cf7784e5300bb8f,
+                    0x16a88922c7a5e844,
+                ]),
+            };
+
+            b = G2Projective {
+                x: b.x * (z.square()),
+                y: b.y * (z.square() * z),
+                z,
+            };
+        }
+        let c = a + b;
+        assert!(!bool::from(c.is_identity()));
+        assert!(bool::from(c.is_on_curve()));
+        assert!(c == G2Projective::generator());
+    }
+    {
+        let a = G2Projective::identity();
+        let mut b = G2Projective::generator();
+        {
+            let z = Fp2 {
+                c0: Fp::from_raw_unchecked([
+                    0xba7afa1f9a6fe250,
+                    0xfa0f5b595eafe731,
+                    0x3bdc477694c306e7,
+                    0x2149be4b3949fa24,
+                    0x64aa6e0649b2078c,
+                    0x12b108ac33643c3e,
+                ]),
+                c1: Fp::from_raw_unchecked([
+                    0x125325df3d35b5a8,
+                    0xdc469ef5555d7fe3,
+                    0x2d716d2443106a9,
+                    0x5a1db59a6ff37d0,
+                    0x7cf7784e5300bb8f,
+                    0x16a88922c7a5e844,
+                ]),
+            };
+
+            b = G2Projective {
+                x: b.x * (z.square()),
+                y: b.y * (z.square() * z),
+                z,
+            };
+        }
+        let c = b + a;
+        assert!(!bool::from(c.is_identity()));
+        assert!(bool::from(c.is_on_curve()));
+        assert!(c == G2Projective::generator());
+    }
+    {
+        let a = G2Projective::generator().double().double(); // 4P
+        let b = G2Projective::generator().double(); // 2P
+        let c = a + b;
+
+        let mut d = G2Projective::generator();
+        for _ in 0..5 {
+            d = d + G2Projective::generator();
+        }
+        assert!(!bool::from(c.is_identity()));
+        assert!(bool::from(c.is_on_curve()));
+        assert!(!bool::from(d.is_identity()));
+        assert!(bool::from(d.is_on_curve()));
+        assert_eq!(c, d);
+    }
+
+    // Degenerate case
+    {
+        let beta = Fp2 {
+            c0: Fp::from_raw_unchecked([
+                0xcd03c9e48671f071,
+                0x5dab22461fcda5d2,
+                0x587042afd3851b95,
+                0x8eb60ebe01bacb9e,
+                0x3f97d6e83d050d2,
+                0x18f0206554638741,
+            ]),
+            c1: Fp::zero(),
+        };
+        let beta = beta.square();
+        let a = G2Projective::generator().double().double();
+        let b = G2Projective {
+            x: a.x * beta,
+            y: -a.y,
+            z: a.z,
+        };
+        assert!(bool::from(a.is_on_curve()));
+        assert!(bool::from(b.is_on_curve()));
+
+        let c = a + b;
+        assert_eq!(
+            G2Affine::from(c),
+            G2Affine::from(G2Projective {
+                x: Fp2 {
+                    c0: Fp::from_raw_unchecked([
+                        0x705abc799ca773d3,
+                        0xfe132292c1d4bf08,
+                        0xf37ece3e07b2b466,
+                        0x887e1c43f447e301,
+                        0x1e0970d033bc77e8,
+                        0x1985c81e20a693f2
+                    ]),
+                    c1: Fp::from_raw_unchecked([
+                        0x1d79b25db36ab924,
+                        0x23948e4d529639d3,
+                        0x471ba7fb0d006297,
+                        0x2c36d4b4465dc4c0,
+                        0x82bbc3cfec67f538,
+                        0x51d2728b67bf952
+                    ])
+                },
+                y: Fp2 {
+                    c0: Fp::from_raw_unchecked([
+                        0x41b1bbf6576c0abf,
+                        0xb6cc93713f7a0f9a,
+                        0x6b65b43e48f3f01f,
+                        0xfb7a4cfcaf81be4f,
+                        0x3e32dadc6ec22cb6,
+                        0xbb0fc49d79807e3
+                    ]),
+                    c1: Fp::from_raw_unchecked([
+                        0x7d1397788f5f2ddf,
+                        0xab2907144ff0d8e8,
+                        0x5b7573e0cdb91f92,
+                        0x4cb8932dd31daf28,
+                        0x62bbfac6db052a54,
+                        0x11f95c16d14c3bbe
+                    ])
+                },
+                z: Fp2::one()
+            })
+        );
+        assert!(!bool::from(c.is_identity()));
+        assert!(bool::from(c.is_on_curve()));
+    }
+}
+
+#[test]
+fn test_mixed_addition() {
+    {
+        let a = G2Affine::identity();
+        let b = G2Projective::identity();
+        let c = a + b;
+        assert!(bool::from(c.is_identity()));
+        assert!(bool::from(c.is_on_curve()));
+    }
+    {
+        let a = G2Affine::identity();
+        let mut b = G2Projective::generator();
+        {
+            let z = Fp2 {
+                c0: Fp::from_raw_unchecked([
+                    0xba7afa1f9a6fe250,
+                    0xfa0f5b595eafe731,
+                    0x3bdc477694c306e7,
+                    0x2149be4b3949fa24,
+                    0x64aa6e0649b2078c,
+                    0x12b108ac33643c3e,
+                ]),
+                c1: Fp::from_raw_unchecked([
+                    0x125325df3d35b5a8,
+                    0xdc469ef5555d7fe3,
+                    0x2d716d2443106a9,
+                    0x5a1db59a6ff37d0,
+                    0x7cf7784e5300bb8f,
+                    0x16a88922c7a5e844,
+                ]),
+            };
+
+            b = G2Projective {
+                x: b.x * (z.square()),
+                y: b.y * (z.square() * z),
+                z,
+            };
+        }
+        let c = a + b;
+        assert!(!bool::from(c.is_identity()));
+        assert!(bool::from(c.is_on_curve()));
+        assert!(c == G2Projective::generator());
+    }
+    {
+        let a = G2Affine::identity();
+        let mut b = G2Projective::generator();
+        {
+            let z = Fp2 {
+                c0: Fp::from_raw_unchecked([
+                    0xba7afa1f9a6fe250,
+                    0xfa0f5b595eafe731,
+                    0x3bdc477694c306e7,
+                    0x2149be4b3949fa24,
+                    0x64aa6e0649b2078c,
+                    0x12b108ac33643c3e,
+                ]),
+                c1: Fp::from_raw_unchecked([
+                    0x125325df3d35b5a8,
+                    0xdc469ef5555d7fe3,
+                    0x2d716d2443106a9,
+                    0x5a1db59a6ff37d0,
+                    0x7cf7784e5300bb8f,
+                    0x16a88922c7a5e844,
+                ]),
+            };
+
+            b = G2Projective {
+                x: b.x * (z.square()),
+                y: b.y * (z.square() * z),
+                z,
+            };
+        }
+        let c = b + a;
+        assert!(!bool::from(c.is_identity()));
+        assert!(bool::from(c.is_on_curve()));
+        assert!(c == G2Projective::generator());
+    }
+    {
+        let a = G2Projective::generator().double().double(); // 4P
+        let b = G2Projective::generator().double(); // 2P
+        let c = a + b;
+
+        let mut d = G2Projective::generator();
+        for _ in 0..5 {
+            d = d + G2Affine::generator();
+        }
+        assert!(!bool::from(c.is_identity()));
+        assert!(bool::from(c.is_on_curve()));
+        assert!(!bool::from(d.is_identity()));
+        assert!(bool::from(d.is_on_curve()));
+        assert_eq!(c, d);
+    }
+
+    // Degenerate case
+    {
+        let beta = Fp2 {
+            c0: Fp::from_raw_unchecked([
+                0xcd03c9e48671f071,
+                0x5dab22461fcda5d2,
+                0x587042afd3851b95,
+                0x8eb60ebe01bacb9e,
+                0x3f97d6e83d050d2,
+                0x18f0206554638741,
+            ]),
+            c1: Fp::zero(),
+        };
+        let beta = beta.square();
+        let a = G2Projective::generator().double().double();
+        let b = G2Projective {
+            x: a.x * beta,
+            y: -a.y,
+            z: a.z,
+        };
+        let a = G2Affine::from(a);
+        assert!(bool::from(a.is_on_curve()));
+        assert!(bool::from(b.is_on_curve()));
+
+        let c = a + b;
+        assert_eq!(
+            G2Affine::from(c),
+            G2Affine::from(G2Projective {
+                x: Fp2 {
+                    c0: Fp::from_raw_unchecked([
+                        0x705abc799ca773d3,
+                        0xfe132292c1d4bf08,
+                        0xf37ece3e07b2b466,
+                        0x887e1c43f447e301,
+                        0x1e0970d033bc77e8,
+                        0x1985c81e20a693f2
+                    ]),
+                    c1: Fp::from_raw_unchecked([
+                        0x1d79b25db36ab924,
+                        0x23948e4d529639d3,
+                        0x471ba7fb0d006297,
+                        0x2c36d4b4465dc4c0,
+                        0x82bbc3cfec67f538,
+                        0x51d2728b67bf952
+                    ])
+                },
+                y: Fp2 {
+                    c0: Fp::from_raw_unchecked([
+                        0x41b1bbf6576c0abf,
+                        0xb6cc93713f7a0f9a,
+                        0x6b65b43e48f3f01f,
+                        0xfb7a4cfcaf81be4f,
+                        0x3e32dadc6ec22cb6,
+                        0xbb0fc49d79807e3
+                    ]),
+                    c1: Fp::from_raw_unchecked([
+                        0x7d1397788f5f2ddf,
+                        0xab2907144ff0d8e8,
+                        0x5b7573e0cdb91f92,
+                        0x4cb8932dd31daf28,
+                        0x62bbfac6db052a54,
+                        0x11f95c16d14c3bbe
+                    ])
+                },
+                z: Fp2::one()
+            })
+        );
+        assert!(!bool::from(c.is_identity()));
+        assert!(bool::from(c.is_on_curve()));
+    }
+}
+
+#[test]
+fn test_projective_negation_and_subtraction() {
+    let a = G2Projective::generator().double();
+    assert_eq!(a + (-a), G2Projective::identity());
+    assert_eq!(a + (-a), a - a);
+}
+
+#[test]
+fn test_affine_negation_and_subtraction() {
+    let a = G2Affine::generator();
+    assert_eq!(G2Projective::from(a) + (-a), G2Projective::identity());
+    assert_eq!(G2Projective::from(a) + (-a), G2Projective::from(a) - a);
+}
+
+#[test]
+fn test_projective_scalar_multiplication() {
+    let g = G2Projective::generator();
+    let a = Scalar::from_raw([
+        0x2b568297a56da71c,
+        0xd8c39ecb0ef375d1,
+        0x435c38da67bfbf96,
+        0x8088a05026b659b2,
+    ]);
+    let b = Scalar::from_raw([
+        0x785fdd9b26ef8b85,
+        0xc997f25837695c18,
+        0x4c8dbc39e7b756c1,
+        0x70d9b6cc6d87df20,
+    ]);
+    let c = a * b;
+
+    assert_eq!((g * a) * b, g * c);
+}
+
+#[test]
+fn test_affine_scalar_multiplication() {
+    let g = G2Affine::generator();
+    let a = Scalar::from_raw([
+        0x2b568297a56da71c,
+        0xd8c39ecb0ef375d1,
+        0x435c38da67bfbf96,
+        0x8088a05026b659b2,
+    ]);
+    let b = Scalar::from_raw([
+        0x785fdd9b26ef8b85,
+        0xc997f25837695c18,
+        0x4c8dbc39e7b756c1,
+        0x70d9b6cc6d87df20,
+    ]);
+    let c = a * b;
+
+    assert_eq!(G2Affine::from(g * a) * b, g * c);
+}
+
+#[test]
+fn test_is_torsion_free() {
+    let a = G2Affine {
+        x: Fp2 {
+            c0: Fp::from_raw_unchecked([
+                0x89f550c813db6431,
+                0xa50be8c456cd8a1a,
+                0xa45b374114cae851,
+                0xbb6190f5bf7fff63,
+                0x970ca02c3ba80bc7,
+                0x2b85d24e840fbac,
+            ]),
+            c1: Fp::from_raw_unchecked([
+                0x6888bc53d70716dc,
+                0x3dea6b4117682d70,
+                0xd8f5f930500ca354,
+                0x6b5ecb6556f5c155,
+                0xc96bef0434778ab0,
+                0x5081505515006ad,
+            ]),
+        },
+        y: Fp2 {
+            c0: Fp::from_raw_unchecked([
+                0x3cf1ea0d434b0f40,
+                0x1a0dc610e603e333,
+                0x7f89956160c72fa0,
+                0x25ee03decf6431c5,
+                0xeee8e206ec0fe137,
+                0x97592b226dfef28,
+            ]),
+            c1: Fp::from_raw_unchecked([
+                0x71e8bb5f29247367,
+                0xa5fe049e211831ce,
+                0xce6b354502a3896,
+                0x93b012000997314e,
+                0x6759f3b6aa5b42ac,
+                0x156944c4dfe92bbb,
+            ]),
+        },
+        infinity: Choice::from(0u8),
+    };
+    assert!(!bool::from(a.is_torsion_free()));
+
+    assert!(bool::from(G2Affine::identity().is_torsion_free()));
+    assert!(bool::from(G2Affine::generator().is_torsion_free()));
+}
+
+#[test]
+fn test_batch_normalize() {
+    let a = G2Projective::generator().double();
+    let b = a.double();
+    let c = b.double();
+
+    for a_identity in (0..1).map(|n| n == 1) {
+        for b_identity in (0..1).map(|n| n == 1) {
+            for c_identity in (0..1).map(|n| n == 1) {
+                let mut v = [a, b, c];
+                if a_identity {
+                    v[0] = G2Projective::identity()
+                }
+                if b_identity {
+                    v[1] = G2Projective::identity()
+                }
+                if c_identity {
+                    v[2] = G2Projective::identity()
+                }
+
+                let mut t = [
+                    G2Affine::identity(),
+                    G2Affine::identity(),
+                    G2Affine::identity(),
+                ];
+                let expected = [
+                    G2Affine::from(v[0]),
+                    G2Affine::from(v[1]),
+                    G2Affine::from(v[2]),
+                ];
+
+                G2Projective::batch_normalize(&v[..], &mut t[..]);
+
+                assert_eq!(&t[..], &expected[..]);
+            }
+        }
+    }
+}
diff --git a/bls12_381/src/lib.rs b/bls12_381/src/lib.rs
new file mode 100644
index 0000000..e6c0e47
--- /dev/null
+++ b/bls12_381/src/lib.rs
@@ -0,0 +1,81 @@
+//! # `bls12_381`
+//!
+//! This crate provides an implementation of the BLS12-381 pairing-friendly elliptic
+//! curve construction.
+//!
+//! * **This implementation has not been reviewed or audited. Use at your own risk.**
+//! * This implementation targets Rust `1.36` or later.
+//! * This implementation does not require the Rust standard library.
+//! * All operations are constant time unless explicitly noted.
+
+#![no_std]
+// Catch documentation errors caused by code changes.
+#![deny(intra_doc_link_resolution_failure)]
+#![deny(missing_debug_implementations)]
+#![deny(missing_docs)]
+#![deny(unsafe_code)]
+#![allow(clippy::too_many_arguments)]
+#![allow(clippy::unreadable_literal)]
+#![allow(clippy::many_single_char_names)]
+// This lint is described at
+// https://rust-lang.github.io/rust-clippy/master/index.html#suspicious_arithmetic_impl
+// In our library, some of the arithmetic involving extension fields will necessarily
+// involve various binary operators, and so this lint is triggered unnecessarily.
+#![allow(clippy::suspicious_arithmetic_impl)]
+
+#[cfg(feature = "alloc")]
+extern crate alloc;
+
+#[cfg(test)]
+#[macro_use]
+extern crate std;
+
+#[cfg(test)]
+#[cfg(feature = "groups")]
+mod tests;
+
+#[macro_use]
+mod util;
+
+/// Notes about how the BLS12-381 elliptic curve is designed, specified
+/// and implemented by this library.
+pub mod notes {
+    pub mod design;
+    pub mod serialization;
+}
+
+mod scalar;
+
+pub use scalar::Scalar;
+
+#[cfg(feature = "groups")]
+mod fp;
+#[cfg(feature = "groups")]
+mod fp2;
+#[cfg(feature = "groups")]
+mod g1;
+#[cfg(feature = "groups")]
+mod g2;
+
+#[cfg(feature = "groups")]
+pub use g1::{G1Affine, G1Projective};
+#[cfg(feature = "groups")]
+pub use g2::{G2Affine, G2Projective};
+
+#[cfg(feature = "groups")]
+mod fp12;
+#[cfg(feature = "groups")]
+mod fp6;
+
+// The BLS parameter x for BLS12-381 is -0xd201000000010000
+const BLS_X: u64 = 0xd201000000010000;
+const BLS_X_IS_NEGATIVE: bool = true;
+
+#[cfg(feature = "pairings")]
+mod pairings;
+
+#[cfg(feature = "pairings")]
+pub use pairings::{pairing, Gt, MillerLoopResult};
+
+#[cfg(all(feature = "pairings", feature = "alloc"))]
+pub use pairings::{multi_miller_loop, G2Prepared};
diff --git a/bls12_381/src/notes/design.rs b/bls12_381/src/notes/design.rs
new file mode 100644
index 0000000..d245260
--- /dev/null
+++ b/bls12_381/src/notes/design.rs
@@ -0,0 +1,63 @@
+//! # Design of BLS12-381
+//! ## Fixed Generators
+//!
+//! Although any generator produced by hashing to $\mathbb{G}_1$ or $\mathbb{G}_2$ is
+//! safe to use in a cryptographic protocol, we specify some simple, fixed generators.
+//!
+//! In order to derive these generators, we select the lexicographically smallest
+//! valid $x$-coordinate and the lexicographically smallest corresponding $y$-coordinate,
+//! and then scale the resulting point by the cofactor, such that the result is not the
+//! identity. This results in the following fixed generators:
+//!
+//! 1. $\mathbb{G}_1$
+//!     * $x = 3685416753713387016781088315183077757961620795782546409894578378688607592378376318836054947676345821548104185464507$
+//!     * $y = 1339506544944476473020471379941921221584933875938349620426543736416511423956333506472724655353366534992391756441569$
+//! 2. $\mathbb{G}_2$
+//!     * $x = 352701069587466618187139116011060144890029952792775240219908644239793785735715026873347600343865175952761926303160 + 3059144344244213709971259814753781636986470325476647558659373206291635324768958432433509563104347017837885763365758 u$
+//!     * $y = 1985150602287291935568054521177171638300868978215655730859378665066344726373823718423869104263333984641494340347905 + 927553665492332455747201965776037880757740193453592970025027978793976877002675564980949289727957565575433344219582 u$
+//!
+//! This can be derived using the following sage script:
+//!
+//! ```norun
+//! param = -0xd201000000010000
+//! def r(x):
+//!     return (x**4) - (x**2) + 1
+//! def q(x):
+//!     return (((x - 1) ** 2) * ((x**4) - (x**2) + 1) // 3) + x
+//! def g1_h(x):
+//! 	return ((x-1)**2) // 3
+//! def g2_h(x):
+//!     return ((x**8) - (4 * (x**7)) + (5 * (x**6)) - (4 * (x**4)) + (6 * (x**3)) - (4 * (x**2)) - (4*x) + 13) // 9
+//! q = q(param)
+//! r = r(param)
+//! Fq = GF(q)
+//! ec = EllipticCurve(Fq, [0, 4])
+//! def psqrt(v):
+//! 	assert(not v.is_zero())
+//! 	a = sqrt(v)
+//! 	b = -a
+//! 	if a < b:
+//! 		return a
+//! 	else:
+//! 		return b
+//! for x in range(0,100):
+//! 	rhs = Fq(x)^3 + 4
+//! 	if rhs.is_square():
+//! 		y = psqrt(rhs)
+//! 		p = ec(x, y) * g1_h(param)
+//! 		if (not p.is_zero()) and (p * r).is_zero():
+//! 			print "g1 generator: %s" % p
+//! 			break
+//! Fqx.<j> = PolynomialRing(Fq, 'j')
+//! Fq2.<i> = GF(q^2, modulus=j^2 + 1)
+//! ec2 = EllipticCurve(Fq2, [0, (4 * (1 + i))])
+//! assert(ec2.order() == (r * g2_h(param)))
+//! for x in range(0,100):
+//! 	rhs = (Fq2(x))^3 + (4 * (1 + i))
+//! 	if rhs.is_square():
+//! 		y = psqrt(rhs)
+//! 		p = ec2(Fq2(x), y) * g2_h(param)
+//! 		if (not p.is_zero()) and (p * r).is_zero():
+//! 			print "g2 generator: %s" % p
+//! 			break
+//! ```
diff --git a/bls12_381/src/notes/serialization.rs b/bls12_381/src/notes/serialization.rs
new file mode 100644
index 0000000..ded752e
--- /dev/null
+++ b/bls12_381/src/notes/serialization.rs
@@ -0,0 +1,29 @@
+//! # BLS12-381 serialization
+//!
+//! * $\mathbb{F}\_p$ elements are encoded in big-endian form. They occupy 48
+//!   bytes in this form.
+//! * $\mathbb{F}\_{p^2}$ elements are encoded in big-endian form, meaning that
+//!   the $\mathbb{F}\_{p^2}$ element $c\_0 + c\_1 \cdot u$ is represented by the
+//!   $\mathbb{F}\_p$ element $c\_1$ followed by the $\mathbb{F}\_p$ element $c\_0$.
+//!   This means $\mathbb{F}_{p^2}$ elements occupy 96 bytes in this form.
+//! * The group $\mathbb{G}\_1$ uses $\mathbb{F}\_p$ elements for coordinates. The
+//!   group $\mathbb{G}\_2$ uses $\mathbb{F}_{p^2}$ elements for coordinates.
+//! * $\mathbb{G}\_1$ and $\mathbb{G}\_2$ elements can be encoded in uncompressed
+//!   form (the x-coordinate followed by the y-coordinate) or in compressed form
+//!   (just the x-coordinate). $\mathbb{G}\_1$ elements occupy 96 bytes in
+//!   uncompressed form, and 48 bytes in compressed form. $\mathbb{G}\_2$
+//!   elements occupy 192 bytes in uncompressed form, and 96 bytes in compressed
+//!   form.
+//!
+//! The most-significant three bits of a $\mathbb{G}\_1$ or $\mathbb{G}\_2$
+//!   encoding should be masked away before the coordinate(s) are interpreted.
+//!   These bits are used to unambiguously represent the underlying element:
+//! * The most significant bit, when set, indicates that the point is in
+//!   compressed form. Otherwise, the point is in uncompressed form.
+//! * The second-most significant bit indicates that the point is at infinity.
+//!   If this bit is set, the remaining bits of the group element's encoding
+//!   should be set to zero.
+//! * The third-most significant bit is set if (and only if) this point is in
+//!   compressed form _and_ it is not the point at infinity _and_ its
+//!   y-coordinate is the lexicographically largest of the two associated with
+//!   the encoded x-coordinate.
diff --git a/bls12_381/src/pairings.rs b/bls12_381/src/pairings.rs
new file mode 100644
index 0000000..459d501
--- /dev/null
+++ b/bls12_381/src/pairings.rs
@@ -0,0 +1,654 @@
+use crate::fp12::Fp12;
+use crate::fp2::Fp2;
+use crate::fp6::Fp6;
+use crate::{G1Affine, G2Affine, G2Projective, Scalar, BLS_X, BLS_X_IS_NEGATIVE};
+
+use core::ops::{Add, AddAssign, Mul, MulAssign, Neg, Sub, SubAssign};
+
+use subtle::{Choice, ConditionallySelectable, ConstantTimeEq};
+
+#[cfg(feature = "alloc")]
+use alloc::vec::Vec;
+
+/// Represents results of a Miller loop, one of the most expensive portions
+/// of the pairing function. `MillerLoopResult`s cannot be compared with each
+/// other until `.final_exponentiation()` is called, which is also expensive.
+#[derive(Copy, Clone, Debug)]
+pub struct MillerLoopResult(pub(crate) Fp12);
+
+impl ConditionallySelectable for MillerLoopResult {
+    fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self {
+        MillerLoopResult(Fp12::conditional_select(&a.0, &b.0, choice))
+    }
+}
+
+impl MillerLoopResult {
+    /// This performs a "final exponentiation" routine to convert the result
+    /// of a Miller loop into an element of `Gt` with help of efficient squaring
+    /// operation in the so-called `cyclotomic subgroup` of `Fq6` so that
+    /// it can be compared with other elements of `Gt`.
+    pub fn final_exponentiation(&self) -> Gt {
+        #[must_use]
+        fn fp4_square(a: Fp2, b: Fp2) -> (Fp2, Fp2) {
+            let t0 = a.square();
+            let t1 = b.square();
+            let mut t2 = t1.mul_by_nonresidue();
+            let c0 = t2 + t0;
+            t2 = a + b;
+            t2 = t2.square();
+            t2 -= t0;
+            let c1 = t2 - t1;
+
+            (c0, c1)
+        }
+        // Adaptation of Algorithm 5.5.4, Guide to Pairing-Based Cryptography
+        // Faster Squaring in the Cyclotomic Subgroup of Sixth Degree Extensions
+        // https://eprint.iacr.org/2009/565.pdf
+        #[must_use]
+        fn cyclotomic_square(f: Fp12) -> Fp12 {
+            let mut z0 = f.c0.c0.clone();
+            let mut z4 = f.c0.c1.clone();
+            let mut z3 = f.c0.c2.clone();
+            let mut z2 = f.c1.c0.clone();
+            let mut z1 = f.c1.c1.clone();
+            let mut z5 = f.c1.c2.clone();
+
+            let (t0, t1) = fp4_square(z0, z1);
+
+            // For A
+            z0 = t0 - z0;
+            z0 += z0 + t0;
+
+            z1 = t1 + z1;
+            z1 += z1 + t1;
+
+            let (mut t0, t1) = fp4_square(z2, z3);
+            let (t2, t3) = fp4_square(z4, z5);
+
+            // For C
+            z4 = t0 - z4;
+            z4 += z4 + t0;
+
+            z5 = t1 + z5;
+            z5 += z5 + t1;
+
+            // For B
+            t0 = t3.mul_by_nonresidue();
+            z2 = t0 + z2;
+            z2 += z2 + t0;
+
+            z3 = t2 - z3;
+            z3 += z3 + t2;
+
+            Fp12 {
+                c0: Fp6 {
+                    c0: z0,
+                    c1: z4,
+                    c2: z3,
+                },
+                c1: Fp6 {
+                    c0: z2,
+                    c1: z1,
+                    c2: z5,
+                },
+            }
+        }
+        #[must_use]
+        fn cycolotomic_exp(f: Fp12) -> Fp12 {
+            let x = BLS_X;
+            let mut tmp = Fp12::one();
+            let mut found_one = false;
+            for i in (0..64).rev().map(|b| ((x >> b) & 1) == 1) {
+                if found_one {
+                    tmp = cyclotomic_square(tmp)
+                } else {
+                    found_one = i;
+                }
+
+                if i {
+                    tmp *= f;
+                }
+            }
+
+            tmp.conjugate()
+        }
+
+        let mut f = self.0.clone();
+        let mut t0 = f
+            .frobenius_map()
+            .frobenius_map()
+            .frobenius_map()
+            .frobenius_map()
+            .frobenius_map()
+            .frobenius_map();
+        Gt(f.invert()
+            .map(|mut t1| {
+                let mut t2 = t0 * t1;
+                t1 = t2.clone();
+                t2 = t2.frobenius_map().frobenius_map();
+                t2 *= t1;
+                t1 = cyclotomic_square(t2).conjugate();
+                let mut t3 = cycolotomic_exp(t2);
+                let mut t4 = cyclotomic_square(t3);
+                let mut t5 = t1 * t3;
+                t1 = cycolotomic_exp(t5);
+                t0 = cycolotomic_exp(t1);
+                let mut t6 = cycolotomic_exp(t0);
+                t6 *= t4;
+                t4 = cycolotomic_exp(t6);
+                t5 = t5.conjugate();
+                t4 *= t5 * t2;
+                t5 = t2.conjugate();
+                t1 *= t2;
+                t1 = t1.frobenius_map().frobenius_map().frobenius_map();
+                t6 *= t5;
+                t6 = t6.frobenius_map();
+                t3 *= t0;
+                t3 = t3.frobenius_map().frobenius_map();
+                t3 *= t1;
+                t3 *= t6;
+                f = t3 * t4;
+
+                f
+            })
+            // We unwrap() because `MillerLoopResult` can only be constructed
+            // by a function within this crate, and we uphold the invariant
+            // that the enclosed value is nonzero.
+            .unwrap())
+    }
+}
+
+impl<'a, 'b> Add<&'b MillerLoopResult> for &'a MillerLoopResult {
+    type Output = MillerLoopResult;
+
+    #[inline]
+    fn add(self, rhs: &'b MillerLoopResult) -> MillerLoopResult {
+        MillerLoopResult(self.0 * rhs.0)
+    }
+}
+
+impl_add_binop_specify_output!(MillerLoopResult, MillerLoopResult, MillerLoopResult);
+
+/// This is an element of $\mathbb{G}_T$, the target group of the pairing function. As with
+/// $\mathbb{G}_1$ and $\mathbb{G}_2$ this group has order $q$.
+///
+/// Typically, $\mathbb{G}_T$ is written multiplicatively but we will write it additively to
+/// keep code and abstractions consistent.
+#[derive(Copy, Clone, Debug)]
+pub struct Gt(pub(crate) Fp12);
+
+impl ConstantTimeEq for Gt {
+    fn ct_eq(&self, other: &Self) -> Choice {
+        self.0.ct_eq(&other.0)
+    }
+}
+
+impl ConditionallySelectable for Gt {
+    fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self {
+        Gt(Fp12::conditional_select(&a.0, &b.0, choice))
+    }
+}
+
+impl Eq for Gt {}
+impl PartialEq for Gt {
+    #[inline]
+    fn eq(&self, other: &Self) -> bool {
+        bool::from(self.ct_eq(other))
+    }
+}
+
+impl Gt {
+    /// Returns the group identity, which is $1$.
+    pub fn identity() -> Gt {
+        Gt(Fp12::one())
+    }
+
+    /// Doubles this group element.
+    pub fn double(&self) -> Gt {
+        Gt(self.0.square())
+    }
+}
+
+impl<'a> Neg for &'a Gt {
+    type Output = Gt;
+
+    #[inline]
+    fn neg(self) -> Gt {
+        // The element is unitary, so we just conjugate.
+        Gt(self.0.conjugate())
+    }
+}
+
+impl Neg for Gt {
+    type Output = Gt;
+
+    #[inline]
+    fn neg(self) -> Gt {
+        -&self
+    }
+}
+
+impl<'a, 'b> Add<&'b Gt> for &'a Gt {
+    type Output = Gt;
+
+    #[inline]
+    fn add(self, rhs: &'b Gt) -> Gt {
+        Gt(self.0 * rhs.0)
+    }
+}
+
+impl<'a, 'b> Sub<&'b Gt> for &'a Gt {
+    type Output = Gt;
+
+    #[inline]
+    fn sub(self, rhs: &'b Gt) -> Gt {
+        self + (-rhs)
+    }
+}
+
+impl<'a, 'b> Mul<&'b Scalar> for &'a Gt {
+    type Output = Gt;
+
+    fn mul(self, other: &'b Scalar) -> Self::Output {
+        let mut acc = Gt::identity();
+
+        // This is a simple double-and-add implementation of group element
+        // multiplication, moving from most significant to least
+        // significant bit of the scalar.
+        //
+        // We skip the leading bit because it's always unset for Fq
+        // elements.
+        for bit in other
+            .to_bytes()
+            .iter()
+            .rev()
+            .flat_map(|byte| (0..8).rev().map(move |i| Choice::from((byte >> i) & 1u8)))
+            .skip(1)
+        {
+            acc = acc.double();
+            acc = Gt::conditional_select(&acc, &(acc + self), bit);
+        }
+
+        acc
+    }
+}
+
+impl_binops_additive!(Gt, Gt);
+impl_binops_multiplicative!(Gt, Scalar);
+
+#[cfg(feature = "alloc")]
+#[derive(Clone, Debug)]
+/// This structure contains cached computations pertaining to a $\mathbb{G}_2$
+/// element as part of the pairing function (specifically, the Miller loop) and
+/// so should be computed whenever a $\mathbb{G}_2$ element is being used in
+/// multiple pairings or is otherwise known in advance. This should be used in
+/// conjunction with the [`multi_miller_loop`](crate::multi_miller_loop)
+/// function provided by this crate.
+///
+/// Requires the `alloc` and `pairing` crate features to be enabled.
+pub struct G2Prepared {
+    infinity: Choice,
+    coeffs: Vec<(Fp2, Fp2, Fp2)>,
+}
+
+#[cfg(feature = "alloc")]
+impl From<G2Affine> for G2Prepared {
+    fn from(q: G2Affine) -> G2Prepared {
+        struct Adder {
+            cur: G2Projective,
+            base: G2Affine,
+            coeffs: Vec<(Fp2, Fp2, Fp2)>,
+        }
+
+        impl MillerLoopDriver for Adder {
+            type Output = ();
+
+            fn doubling_step(&mut self, _: Self::Output) -> Self::Output {
+                let coeffs = doubling_step(&mut self.cur);
+                self.coeffs.push(coeffs);
+            }
+            fn addition_step(&mut self, _: Self::Output) -> Self::Output {
+                let coeffs = addition_step(&mut self.cur, &self.base);
+                self.coeffs.push(coeffs);
+            }
+            fn square_output(_: Self::Output) -> Self::Output {
+                ()
+            }
+            fn conjugate(_: Self::Output) -> Self::Output {
+                ()
+            }
+            fn one() -> Self::Output {
+                ()
+            }
+        }
+
+        let is_identity = q.is_identity();
+        let q = G2Affine::conditional_select(&q, &G2Affine::generator(), is_identity);
+
+        let mut adder = Adder {
+            cur: G2Projective::from(q),
+            base: q,
+            coeffs: Vec::with_capacity(68),
+        };
+
+        miller_loop(&mut adder);
+
+        assert_eq!(adder.coeffs.len(), 68);
+
+        G2Prepared {
+            infinity: is_identity,
+            coeffs: adder.coeffs,
+        }
+    }
+}
+
+#[cfg(feature = "alloc")]
+/// Computes $$\sum_{i=1}^n \textbf{ML}(a_i, b_i)$$ given a series of terms
+/// $$(a_1, b_1), (a_2, b_2), ..., (a_n, b_n).$$
+///
+/// Requires the `alloc` and `pairing` crate features to be enabled.
+pub fn multi_miller_loop(terms: &[(&G1Affine, &G2Prepared)]) -> MillerLoopResult {
+    struct Adder<'a, 'b, 'c> {
+        terms: &'c [(&'a G1Affine, &'b G2Prepared)],
+        index: usize,
+    }
+
+    impl<'a, 'b, 'c> MillerLoopDriver for Adder<'a, 'b, 'c> {
+        type Output = Fp12;
+
+        fn doubling_step(&mut self, mut f: Self::Output) -> Self::Output {
+            let index = self.index;
+            for term in self.terms {
+                let either_identity = term.0.is_identity() | term.1.infinity;
+
+                let new_f = ell(f, &term.1.coeffs[index], term.0);
+                f = Fp12::conditional_select(&new_f, &f, either_identity);
+            }
+            self.index += 1;
+
+            f
+        }
+        fn addition_step(&mut self, mut f: Self::Output) -> Self::Output {
+            let index = self.index;
+            for term in self.terms {
+                let either_identity = term.0.is_identity() | term.1.infinity;
+
+                let new_f = ell(f, &term.1.coeffs[index], term.0);
+                f = Fp12::conditional_select(&new_f, &f, either_identity);
+            }
+            self.index += 1;
+
+            f
+        }
+        fn square_output(f: Self::Output) -> Self::Output {
+            f.square()
+        }
+        fn conjugate(f: Self::Output) -> Self::Output {
+            f.conjugate()
+        }
+        fn one() -> Self::Output {
+            Fp12::one()
+        }
+    }
+
+    let mut adder = Adder { terms, index: 0 };
+
+    let tmp = miller_loop(&mut adder);
+
+    MillerLoopResult(tmp)
+}
+
+/// Invoke the pairing function without the use of precomputation and other optimizations.
+pub fn pairing(p: &G1Affine, q: &G2Affine) -> Gt {
+    struct Adder {
+        cur: G2Projective,
+        base: G2Affine,
+        p: G1Affine,
+    }
+
+    impl MillerLoopDriver for Adder {
+        type Output = Fp12;
+
+        fn doubling_step(&mut self, f: Self::Output) -> Self::Output {
+            let coeffs = doubling_step(&mut self.cur);
+            ell(f, &coeffs, &self.p)
+        }
+        fn addition_step(&mut self, f: Self::Output) -> Self::Output {
+            let coeffs = addition_step(&mut self.cur, &self.base);
+            ell(f, &coeffs, &self.p)
+        }
+        fn square_output(f: Self::Output) -> Self::Output {
+            f.square()
+        }
+        fn conjugate(f: Self::Output) -> Self::Output {
+            f.conjugate()
+        }
+        fn one() -> Self::Output {
+            Fp12::one()
+        }
+    }
+
+    let either_identity = p.is_identity() | q.is_identity();
+    let p = G1Affine::conditional_select(&p, &G1Affine::generator(), either_identity);
+    let q = G2Affine::conditional_select(&q, &G2Affine::generator(), either_identity);
+
+    let mut adder = Adder {
+        cur: G2Projective::from(q),
+        base: q,
+        p,
+    };
+
+    let tmp = miller_loop(&mut adder);
+    let tmp = MillerLoopResult(Fp12::conditional_select(
+        &tmp,
+        &Fp12::one(),
+        either_identity,
+    ));
+    tmp.final_exponentiation()
+}
+
+trait MillerLoopDriver {
+    type Output;
+
+    fn doubling_step(&mut self, f: Self::Output) -> Self::Output;
+    fn addition_step(&mut self, f: Self::Output) -> Self::Output;
+    fn square_output(f: Self::Output) -> Self::Output;
+    fn conjugate(f: Self::Output) -> Self::Output;
+    fn one() -> Self::Output;
+}
+
+/// This is a "generic" implementation of the Miller loop to avoid duplicating code
+/// structure elsewhere; instead, we'll write concrete instantiations of
+/// `MillerLoopDriver` for whatever purposes we need (such as caching modes).
+fn miller_loop<D: MillerLoopDriver>(driver: &mut D) -> D::Output {
+    let mut f = D::one();
+
+    let mut found_one = false;
+    for i in (0..64).rev().map(|b| (((BLS_X >> 1) >> b) & 1) == 1) {
+        if !found_one {
+            found_one = i;
+            continue;
+        }
+
+        f = driver.doubling_step(f);
+
+        if i {
+            f = driver.addition_step(f);
+        }
+
+        f = D::square_output(f);
+    }
+
+    f = driver.doubling_step(f);
+
+    if BLS_X_IS_NEGATIVE {
+        f = D::conjugate(f);
+    }
+
+    f
+}
+
+fn ell(f: Fp12, coeffs: &(Fp2, Fp2, Fp2), p: &G1Affine) -> Fp12 {
+    let mut c0 = coeffs.0;
+    let mut c1 = coeffs.1;
+
+    c0.c0 *= p.y;
+    c0.c1 *= p.y;
+
+    c1.c0 *= p.x;
+    c1.c1 *= p.x;
+
+    f.mul_by_014(&coeffs.2, &c1, &c0)
+}
+
+fn doubling_step(r: &mut G2Projective) -> (Fp2, Fp2, Fp2) {
+    // Adaptation of Algorithm 26, https://eprint.iacr.org/2010/354.pdf
+    let tmp0 = r.x.square();
+    let tmp1 = r.y.square();
+    let tmp2 = tmp1.square();
+    let tmp3 = (tmp1 + r.x).square() - tmp0 - tmp2;
+    let tmp3 = tmp3 + tmp3;
+    let tmp4 = tmp0 + tmp0 + tmp0;
+    let tmp6 = r.x + tmp4;
+    let tmp5 = tmp4.square();
+    let zsquared = r.z.square();
+    r.x = tmp5 - tmp3 - tmp3;
+    r.z = (r.z + r.y).square() - tmp1 - zsquared;
+    r.y = (tmp3 - r.x) * tmp4;
+    let tmp2 = tmp2 + tmp2;
+    let tmp2 = tmp2 + tmp2;
+    let tmp2 = tmp2 + tmp2;
+    r.y -= tmp2;
+    let tmp3 = tmp4 * zsquared;
+    let tmp3 = tmp3 + tmp3;
+    let tmp3 = -tmp3;
+    let tmp6 = tmp6.square() - tmp0 - tmp5;
+    let tmp1 = tmp1 + tmp1;
+    let tmp1 = tmp1 + tmp1;
+    let tmp6 = tmp6 - tmp1;
+    let tmp0 = r.z * zsquared;
+    let tmp0 = tmp0 + tmp0;
+
+    (tmp0, tmp3, tmp6)
+}
+
+fn addition_step(r: &mut G2Projective, q: &G2Affine) -> (Fp2, Fp2, Fp2) {
+    // Adaptation of Algorithm 27, https://eprint.iacr.org/2010/354.pdf
+    let zsquared = r.z.square();
+    let ysquared = q.y.square();
+    let t0 = zsquared * q.x;
+    let t1 = ((q.y + r.z).square() - ysquared - zsquared) * zsquared;
+    let t2 = t0 - r.x;
+    let t3 = t2.square();
+    let t4 = t3 + t3;
+    let t4 = t4 + t4;
+    let t5 = t4 * t2;
+    let t6 = t1 - r.y - r.y;
+    let t9 = t6 * q.x;
+    let t7 = t4 * r.x;
+    r.x = t6.square() - t5 - t7 - t7;
+    r.z = (r.z + t2).square() - zsquared - t3;
+    let t10 = q.y + r.z;
+    let t8 = (t7 - r.x) * t6;
+    let t0 = r.y * t5;
+    let t0 = t0 + t0;
+    r.y = t8 - t0;
+    let t10 = t10.square() - ysquared;
+    let ztsquared = r.z.square();
+    let t10 = t10 - ztsquared;
+    let t9 = t9 + t9 - t10;
+    let t10 = r.z + r.z;
+    let t6 = -t6;
+    let t1 = t6 + t6;
+
+    (t10, t1, t9)
+}
+
+#[test]
+fn test_bilinearity() {
+    use crate::Scalar;
+
+    let a = Scalar::from_raw([1, 2, 3, 4]).invert().unwrap().square();
+    let b = Scalar::from_raw([5, 6, 7, 8]).invert().unwrap().square();
+    let c = a * b;
+
+    let g = G1Affine::from(G1Affine::generator() * a);
+    let h = G2Affine::from(G2Affine::generator() * b);
+    let p = pairing(&g, &h);
+
+    assert!(p != Gt::identity());
+
+    let expected = G1Affine::from(G1Affine::generator() * c);
+
+    assert_eq!(p, pairing(&expected, &G2Affine::generator()));
+    assert_eq!(
+        p,
+        pairing(&G1Affine::generator(), &G2Affine::generator()) * c
+    );
+}
+
+#[test]
+fn test_unitary() {
+    let g = G1Affine::generator();
+    let h = G2Affine::generator();
+    let p = -pairing(&g, &h);
+    let q = pairing(&g, &-h);
+    let r = pairing(&-g, &h);
+
+    assert_eq!(p, q);
+    assert_eq!(q, r);
+}
+
+#[cfg(feature = "alloc")]
+#[test]
+fn test_multi_miller_loop() {
+    let a1 = G1Affine::generator();
+    let b1 = G2Affine::generator();
+
+    let a2 = G1Affine::from(
+        G1Affine::generator() * Scalar::from_raw([1, 2, 3, 4]).invert().unwrap().square(),
+    );
+    let b2 = G2Affine::from(
+        G2Affine::generator() * Scalar::from_raw([4, 2, 2, 4]).invert().unwrap().square(),
+    );
+
+    let a3 = G1Affine::identity();
+    let b3 = G2Affine::from(
+        G2Affine::generator() * Scalar::from_raw([9, 2, 2, 4]).invert().unwrap().square(),
+    );
+
+    let a4 = G1Affine::from(
+        G1Affine::generator() * Scalar::from_raw([5, 5, 5, 5]).invert().unwrap().square(),
+    );
+    let b4 = G2Affine::identity();
+
+    let a5 = G1Affine::from(
+        G1Affine::generator() * Scalar::from_raw([323, 32, 3, 1]).invert().unwrap().square(),
+    );
+    let b5 = G2Affine::from(
+        G2Affine::generator() * Scalar::from_raw([4, 2, 2, 9099]).invert().unwrap().square(),
+    );
+
+    let b1_prepared = G2Prepared::from(b1);
+    let b2_prepared = G2Prepared::from(b2);
+    let b3_prepared = G2Prepared::from(b3);
+    let b4_prepared = G2Prepared::from(b4);
+    let b5_prepared = G2Prepared::from(b5);
+
+    let expected = pairing(&a1, &b1)
+        + pairing(&a2, &b2)
+        + pairing(&a3, &b3)
+        + pairing(&a4, &b4)
+        + pairing(&a5, &b5);
+
+    let test = multi_miller_loop(&[
+        (&a1, &b1_prepared),
+        (&a2, &b2_prepared),
+        (&a3, &b3_prepared),
+        (&a4, &b4_prepared),
+        (&a5, &b5_prepared),
+    ])
+    .final_exponentiation();
+
+    assert_eq!(expected, test);
+}
diff --git a/bls12_381/src/scalar.rs b/bls12_381/src/scalar.rs
new file mode 100644
index 0000000..d4a7ab2
--- /dev/null
+++ b/bls12_381/src/scalar.rs
@@ -0,0 +1,1076 @@
+//! This module provides an implementation of the BLS12-381 scalar field $\mathbb{F}_q$
+//! where `q = 0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001`
+
+use core::convert::TryFrom;
+use core::fmt;
+use core::ops::{Add, AddAssign, Mul, MulAssign, Neg, Sub, SubAssign};
+
+use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption};
+
+use crate::util::{adc, mac, sbb};
+
+/// Represents an element of the scalar field $\mathbb{F}_q$ of the BLS12-381 elliptic
+/// curve construction.
+// The internal representation of this type is four 64-bit unsigned
+// integers in little-endian order. `Scalar` values are always in
+// Montgomery form; i.e., Scalar(a) = aR mod q, with R = 2^256.
+#[derive(Clone, Copy, Eq)]
+pub struct Scalar(pub(crate) [u64; 4]);
+
+impl fmt::Debug for Scalar {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        let tmp = self.to_bytes();
+        write!(f, "0x")?;
+        for &b in tmp.iter().rev() {
+            write!(f, "{:02x}", b)?;
+        }
+        Ok(())
+    }
+}
+
+impl From<u64> for Scalar {
+    fn from(val: u64) -> Scalar {
+        Scalar([val, 0, 0, 0]) * R2
+    }
+}
+
+impl ConstantTimeEq for Scalar {
+    fn ct_eq(&self, other: &Self) -> Choice {
+        self.0[0].ct_eq(&other.0[0])
+            & self.0[1].ct_eq(&other.0[1])
+            & self.0[2].ct_eq(&other.0[2])
+            & self.0[3].ct_eq(&other.0[3])
+    }
+}
+
+impl PartialEq for Scalar {
+    #[inline]
+    fn eq(&self, other: &Self) -> bool {
+        self.ct_eq(other).unwrap_u8() == 1
+    }
+}
+
+impl ConditionallySelectable for Scalar {
+    fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self {
+        Scalar([
+            u64::conditional_select(&a.0[0], &b.0[0], choice),
+            u64::conditional_select(&a.0[1], &b.0[1], choice),
+            u64::conditional_select(&a.0[2], &b.0[2], choice),
+            u64::conditional_select(&a.0[3], &b.0[3], choice),
+        ])
+    }
+}
+
+/// Constant representing the modulus
+/// q = 0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001
+const MODULUS: Scalar = Scalar([
+    0xffffffff00000001,
+    0x53bda402fffe5bfe,
+    0x3339d80809a1d805,
+    0x73eda753299d7d48,
+]);
+
+impl<'a> Neg for &'a Scalar {
+    type Output = Scalar;
+
+    #[inline]
+    fn neg(self) -> Scalar {
+        self.neg()
+    }
+}
+
+impl Neg for Scalar {
+    type Output = Scalar;
+
+    #[inline]
+    fn neg(self) -> Scalar {
+        -&self
+    }
+}
+
+impl<'a, 'b> Sub<&'b Scalar> for &'a Scalar {
+    type Output = Scalar;
+
+    #[inline]
+    fn sub(self, rhs: &'b Scalar) -> Scalar {
+        self.sub(rhs)
+    }
+}
+
+impl<'a, 'b> Add<&'b Scalar> for &'a Scalar {
+    type Output = Scalar;
+
+    #[inline]
+    fn add(self, rhs: &'b Scalar) -> Scalar {
+        self.add(rhs)
+    }
+}
+
+impl<'a, 'b> Mul<&'b Scalar> for &'a Scalar {
+    type Output = Scalar;
+
+    #[inline]
+    fn mul(self, rhs: &'b Scalar) -> Scalar {
+        self.mul(rhs)
+    }
+}
+
+impl_binops_additive!(Scalar, Scalar);
+impl_binops_multiplicative!(Scalar, Scalar);
+
+/// INV = -(q^{-1} mod 2^64) mod 2^64
+const INV: u64 = 0xfffffffeffffffff;
+
+/// R = 2^256 mod q
+const R: Scalar = Scalar([
+    0x00000001fffffffe,
+    0x5884b7fa00034802,
+    0x998c4fefecbc4ff5,
+    0x1824b159acc5056f,
+]);
+
+/// R^2 = 2^512 mod q
+const R2: Scalar = Scalar([
+    0xc999e990f3f29c6d,
+    0x2b6cedcb87925c23,
+    0x05d314967254398f,
+    0x0748d9d99f59ff11,
+]);
+
+/// R^3 = 2^768 mod q
+const R3: Scalar = Scalar([
+    0xc62c1807439b73af,
+    0x1b3e0d188cf06990,
+    0x73d13c71c7b5f418,
+    0x6e2a5bb9c8db33e9,
+]);
+
+const S: u32 = 32;
+
+/// GENERATOR^t where t * 2^s + 1 = q
+/// with t odd. In other words, this
+/// is a 2^s root of unity.
+///
+/// `GENERATOR = 7 mod q` is a generator
+/// of the q - 1 order multiplicative
+/// subgroup.
+const ROOT_OF_UNITY: Scalar = Scalar([
+    0xb9b58d8c5f0e466a,
+    0x5b1b4c801819d7ec,
+    0x0af53ae352a31e64,
+    0x5bf3adda19e9b27b,
+]);
+
+impl Default for Scalar {
+    #[inline]
+    fn default() -> Self {
+        Self::zero()
+    }
+}
+
+impl Scalar {
+    /// Returns zero, the additive identity.
+    #[inline]
+    pub const fn zero() -> Scalar {
+        Scalar([0, 0, 0, 0])
+    }
+
+    /// Returns one, the multiplicative identity.
+    #[inline]
+    pub const fn one() -> Scalar {
+        R
+    }
+
+    /// Doubles this field element.
+    #[inline]
+    pub const fn double(&self) -> Scalar {
+        // TODO: This can be achieved more efficiently with a bitshift.
+        self.add(self)
+    }
+
+    /// Attempts to convert a little-endian byte representation of
+    /// a scalar into a `Scalar`, failing if the input is not canonical.
+    pub fn from_bytes(bytes: &[u8; 32]) -> CtOption<Scalar> {
+        let mut tmp = Scalar([0, 0, 0, 0]);
+
+        tmp.0[0] = u64::from_le_bytes(<[u8; 8]>::try_from(&bytes[0..8]).unwrap());
+        tmp.0[1] = u64::from_le_bytes(<[u8; 8]>::try_from(&bytes[8..16]).unwrap());
+        tmp.0[2] = u64::from_le_bytes(<[u8; 8]>::try_from(&bytes[16..24]).unwrap());
+        tmp.0[3] = u64::from_le_bytes(<[u8; 8]>::try_from(&bytes[24..32]).unwrap());
+
+        // Try to subtract the modulus
+        let (_, borrow) = sbb(tmp.0[0], MODULUS.0[0], 0);
+        let (_, borrow) = sbb(tmp.0[1], MODULUS.0[1], borrow);
+        let (_, borrow) = sbb(tmp.0[2], MODULUS.0[2], borrow);
+        let (_, borrow) = sbb(tmp.0[3], MODULUS.0[3], borrow);
+
+        // If the element is smaller than MODULUS then the
+        // subtraction will underflow, producing a borrow value
+        // of 0xffff...ffff. Otherwise, it'll be zero.
+        let is_some = (borrow as u8) & 1;
+
+        // Convert to Montgomery form by computing
+        // (a.R^0 * R^2) / R = a.R
+        tmp *= &R2;
+
+        CtOption::new(tmp, Choice::from(is_some))
+    }
+
+    /// Converts an element of `Scalar` into a byte representation in
+    /// little-endian byte order.
+    pub fn to_bytes(&self) -> [u8; 32] {
+        // Turn into canonical form by computing
+        // (a.R) / R = a
+        let tmp = Scalar::montgomery_reduce(self.0[0], self.0[1], self.0[2], self.0[3], 0, 0, 0, 0);
+
+        let mut res = [0; 32];
+        res[0..8].copy_from_slice(&tmp.0[0].to_le_bytes());
+        res[8..16].copy_from_slice(&tmp.0[1].to_le_bytes());
+        res[16..24].copy_from_slice(&tmp.0[2].to_le_bytes());
+        res[24..32].copy_from_slice(&tmp.0[3].to_le_bytes());
+
+        res
+    }
+
+    /// Converts a 512-bit little endian integer into
+    /// a `Scalar` by reducing by the modulus.
+    pub fn from_bytes_wide(bytes: &[u8; 64]) -> Scalar {
+        Scalar::from_u512([
+            u64::from_le_bytes(<[u8; 8]>::try_from(&bytes[0..8]).unwrap()),
+            u64::from_le_bytes(<[u8; 8]>::try_from(&bytes[8..16]).unwrap()),
+            u64::from_le_bytes(<[u8; 8]>::try_from(&bytes[16..24]).unwrap()),
+            u64::from_le_bytes(<[u8; 8]>::try_from(&bytes[24..32]).unwrap()),
+            u64::from_le_bytes(<[u8; 8]>::try_from(&bytes[32..40]).unwrap()),
+            u64::from_le_bytes(<[u8; 8]>::try_from(&bytes[40..48]).unwrap()),
+            u64::from_le_bytes(<[u8; 8]>::try_from(&bytes[48..56]).unwrap()),
+            u64::from_le_bytes(<[u8; 8]>::try_from(&bytes[56..64]).unwrap()),
+        ])
+    }
+
+    fn from_u512(limbs: [u64; 8]) -> Scalar {
+        // We reduce an arbitrary 512-bit number by decomposing it into two 256-bit digits
+        // with the higher bits multiplied by 2^256. Thus, we perform two reductions
+        //
+        // 1. the lower bits are multiplied by R^2, as normal
+        // 2. the upper bits are multiplied by R^2 * 2^256 = R^3
+        //
+        // and computing their sum in the field. It remains to see that arbitrary 256-bit
+        // numbers can be placed into Montgomery form safely using the reduction. The
+        // reduction works so long as the product is less than R=2^256 multipled by
+        // the modulus. This holds because for any `c` smaller than the modulus, we have
+        // that (2^256 - 1)*c is an acceptable product for the reduction. Therefore, the
+        // reduction always works so long as `c` is in the field; in this case it is either the
+        // constant `R2` or `R3`.
+        let d0 = Scalar([limbs[0], limbs[1], limbs[2], limbs[3]]);
+        let d1 = Scalar([limbs[4], limbs[5], limbs[6], limbs[7]]);
+        // Convert to Montgomery form
+        d0 * R2 + d1 * R3
+    }
+
+    /// Converts from an integer represented in little endian
+    /// into its (congruent) `Scalar` representation.
+    pub const fn from_raw(val: [u64; 4]) -> Self {
+        (&Scalar(val)).mul(&R2)
+    }
+
+    /// Squares this element.
+    #[inline]
+    pub const fn square(&self) -> Scalar {
+        let (r1, carry) = mac(0, self.0[0], self.0[1], 0);
+        let (r2, carry) = mac(0, self.0[0], self.0[2], carry);
+        let (r3, r4) = mac(0, self.0[0], self.0[3], carry);
+
+        let (r3, carry) = mac(r3, self.0[1], self.0[2], 0);
+        let (r4, r5) = mac(r4, self.0[1], self.0[3], carry);
+
+        let (r5, r6) = mac(r5, self.0[2], self.0[3], 0);
+
+        let r7 = r6 >> 63;
+        let r6 = (r6 << 1) | (r5 >> 63);
+        let r5 = (r5 << 1) | (r4 >> 63);
+        let r4 = (r4 << 1) | (r3 >> 63);
+        let r3 = (r3 << 1) | (r2 >> 63);
+        let r2 = (r2 << 1) | (r1 >> 63);
+        let r1 = r1 << 1;
+
+        let (r0, carry) = mac(0, self.0[0], self.0[0], 0);
+        let (r1, carry) = adc(0, r1, carry);
+        let (r2, carry) = mac(r2, self.0[1], self.0[1], carry);
+        let (r3, carry) = adc(0, r3, carry);
+        let (r4, carry) = mac(r4, self.0[2], self.0[2], carry);
+        let (r5, carry) = adc(0, r5, carry);
+        let (r6, carry) = mac(r6, self.0[3], self.0[3], carry);
+        let (r7, _) = adc(0, r7, carry);
+
+        Scalar::montgomery_reduce(r0, r1, r2, r3, r4, r5, r6, r7)
+    }
+
+    /// Computes the square root of this element, if it exists.
+    pub fn sqrt(&self) -> CtOption<Self> {
+        // Tonelli-Shank's algorithm for q mod 16 = 1
+        // https://eprint.iacr.org/2012/685.pdf (page 12, algorithm 5)
+
+        // w = self^((t - 1) // 2)
+        //   = self^6104339283789297388802252303364915521546564123189034618274734669823
+        let w = self.pow_vartime(&[
+            0x7fff2dff7fffffff,
+            0x04d0ec02a9ded201,
+            0x94cebea4199cec04,
+            0x0000000039f6d3a9,
+        ]);
+
+        let mut v = S;
+        let mut x = self * w;
+        let mut b = x * w;
+
+        // Initialize z as the 2^S root of unity.
+        let mut z = ROOT_OF_UNITY;
+
+        for max_v in (1..=S).rev() {
+            let mut k = 1;
+            let mut tmp = b.square();
+            let mut j_less_than_v: Choice = 1.into();
+
+            for j in 2..max_v {
+                let tmp_is_one = tmp.ct_eq(&Scalar::one());
+                let squared = Scalar::conditional_select(&tmp, &z, tmp_is_one).square();
+                tmp = Scalar::conditional_select(&squared, &tmp, tmp_is_one);
+                let new_z = Scalar::conditional_select(&z, &squared, tmp_is_one);
+                j_less_than_v &= !j.ct_eq(&v);
+                k = u32::conditional_select(&j, &k, tmp_is_one);
+                z = Scalar::conditional_select(&z, &new_z, j_less_than_v);
+            }
+
+            let result = x * z;
+            x = Scalar::conditional_select(&result, &x, b.ct_eq(&Scalar::one()));
+            z = z.square();
+            b *= z;
+            v = k;
+        }
+
+        CtOption::new(
+            x,
+            (x * x).ct_eq(self), // Only return Some if it's the square root.
+        )
+    }
+
+    /// Exponentiates `self` by `by`, where `by` is a
+    /// little-endian order integer exponent.
+    pub fn pow(&self, by: &[u64; 4]) -> Self {
+        let mut res = Self::one();
+        for e in by.iter().rev() {
+            for i in (0..64).rev() {
+                res = res.square();
+                let mut tmp = res;
+                tmp *= self;
+                res.conditional_assign(&tmp, (((*e >> i) & 0x1) as u8).into());
+            }
+        }
+        res
+    }
+
+    /// Exponentiates `self` by `by`, where `by` is a
+    /// little-endian order integer exponent.
+    ///
+    /// **This operation is variable time with respect
+    /// to the exponent.** If the exponent is fixed,
+    /// this operation is effectively constant time.
+    pub fn pow_vartime(&self, by: &[u64; 4]) -> Self {
+        let mut res = Self::one();
+        for e in by.iter().rev() {
+            for i in (0..64).rev() {
+                res = res.square();
+
+                if ((*e >> i) & 1) == 1 {
+                    res.mul_assign(self);
+                }
+            }
+        }
+        res
+    }
+
+    /// Computes the multiplicative inverse of this element,
+    /// failing if the element is zero.
+    pub fn invert(&self) -> CtOption<Self> {
+        #[inline(always)]
+        fn square_assign_multi(n: &mut Scalar, num_times: usize) {
+            for _ in 0..num_times {
+                *n = n.square();
+            }
+        }
+        // found using https://github.com/kwantam/addchain
+        let mut t0 = self.square();
+        let mut t1 = t0 * self;
+        let mut t16 = t0.square();
+        let mut t6 = t16.square();
+        let mut t5 = t6 * t0;
+        t0 = t6 * t16;
+        let mut t12 = t5 * t16;
+        let mut t2 = t6.square();
+        let mut t7 = t5 * t6;
+        let mut t15 = t0 * t5;
+        let mut t17 = t12.square();
+        t1 *= t17;
+        let mut t3 = t7 * t2;
+        let t8 = t1 * t17;
+        let t4 = t8 * t2;
+        let t9 = t8 * t7;
+        t7 = t4 * t5;
+        let t11 = t4 * t17;
+        t5 = t9 * t17;
+        let t14 = t7 * t15;
+        let t13 = t11 * t12;
+        t12 = t11 * t17;
+        t15 *= &t12;
+        t16 *= &t15;
+        t3 *= &t16;
+        t17 *= &t3;
+        t0 *= &t17;
+        t6 *= &t0;
+        t2 *= &t6;
+        square_assign_multi(&mut t0, 8);
+        t0 *= &t17;
+        square_assign_multi(&mut t0, 9);
+        t0 *= &t16;
+        square_assign_multi(&mut t0, 9);
+        t0 *= &t15;
+        square_assign_multi(&mut t0, 9);
+        t0 *= &t15;
+        square_assign_multi(&mut t0, 7);
+        t0 *= &t14;
+        square_assign_multi(&mut t0, 7);
+        t0 *= &t13;
+        square_assign_multi(&mut t0, 10);
+        t0 *= &t12;
+        square_assign_multi(&mut t0, 9);
+        t0 *= &t11;
+        square_assign_multi(&mut t0, 8);
+        t0 *= &t8;
+        square_assign_multi(&mut t0, 8);
+        t0 *= self;
+        square_assign_multi(&mut t0, 14);
+        t0 *= &t9;
+        square_assign_multi(&mut t0, 10);
+        t0 *= &t8;
+        square_assign_multi(&mut t0, 15);
+        t0 *= &t7;
+        square_assign_multi(&mut t0, 10);
+        t0 *= &t6;
+        square_assign_multi(&mut t0, 8);
+        t0 *= &t5;
+        square_assign_multi(&mut t0, 16);
+        t0 *= &t3;
+        square_assign_multi(&mut t0, 8);
+        t0 *= &t2;
+        square_assign_multi(&mut t0, 7);
+        t0 *= &t4;
+        square_assign_multi(&mut t0, 9);
+        t0 *= &t2;
+        square_assign_multi(&mut t0, 8);
+        t0 *= &t3;
+        square_assign_multi(&mut t0, 8);
+        t0 *= &t2;
+        square_assign_multi(&mut t0, 8);
+        t0 *= &t2;
+        square_assign_multi(&mut t0, 8);
+        t0 *= &t2;
+        square_assign_multi(&mut t0, 8);
+        t0 *= &t3;
+        square_assign_multi(&mut t0, 8);
+        t0 *= &t2;
+        square_assign_multi(&mut t0, 8);
+        t0 *= &t2;
+        square_assign_multi(&mut t0, 5);
+        t0 *= &t1;
+        square_assign_multi(&mut t0, 5);
+        t0 *= &t1;
+
+        CtOption::new(t0, !self.ct_eq(&Self::zero()))
+    }
+
+    #[inline(always)]
+    const fn montgomery_reduce(
+        r0: u64,
+        r1: u64,
+        r2: u64,
+        r3: u64,
+        r4: u64,
+        r5: u64,
+        r6: u64,
+        r7: u64,
+    ) -> Self {
+        // The Montgomery reduction here is based on Algorithm 14.32 in
+        // Handbook of Applied Cryptography
+        // <http://cacr.uwaterloo.ca/hac/about/chap14.pdf>.
+
+        let k = r0.wrapping_mul(INV);
+        let (_, carry) = mac(r0, k, MODULUS.0[0], 0);
+        let (r1, carry) = mac(r1, k, MODULUS.0[1], carry);
+        let (r2, carry) = mac(r2, k, MODULUS.0[2], carry);
+        let (r3, carry) = mac(r3, k, MODULUS.0[3], carry);
+        let (r4, carry2) = adc(r4, 0, carry);
+
+        let k = r1.wrapping_mul(INV);
+        let (_, carry) = mac(r1, k, MODULUS.0[0], 0);
+        let (r2, carry) = mac(r2, k, MODULUS.0[1], carry);
+        let (r3, carry) = mac(r3, k, MODULUS.0[2], carry);
+        let (r4, carry) = mac(r4, k, MODULUS.0[3], carry);
+        let (r5, carry2) = adc(r5, carry2, carry);
+
+        let k = r2.wrapping_mul(INV);
+        let (_, carry) = mac(r2, k, MODULUS.0[0], 0);
+        let (r3, carry) = mac(r3, k, MODULUS.0[1], carry);
+        let (r4, carry) = mac(r4, k, MODULUS.0[2], carry);
+        let (r5, carry) = mac(r5, k, MODULUS.0[3], carry);
+        let (r6, carry2) = adc(r6, carry2, carry);
+
+        let k = r3.wrapping_mul(INV);
+        let (_, carry) = mac(r3, k, MODULUS.0[0], 0);
+        let (r4, carry) = mac(r4, k, MODULUS.0[1], carry);
+        let (r5, carry) = mac(r5, k, MODULUS.0[2], carry);
+        let (r6, carry) = mac(r6, k, MODULUS.0[3], carry);
+        let (r7, _) = adc(r7, carry2, carry);
+
+        // Result may be within MODULUS of the correct value
+        (&Scalar([r4, r5, r6, r7])).sub(&MODULUS)
+    }
+
+    /// Multiplies `rhs` by `self`, returning the result.
+    #[inline]
+    pub const fn mul(&self, rhs: &Self) -> Self {
+        // Schoolbook multiplication
+
+        let (r0, carry) = mac(0, self.0[0], rhs.0[0], 0);
+        let (r1, carry) = mac(0, self.0[0], rhs.0[1], carry);
+        let (r2, carry) = mac(0, self.0[0], rhs.0[2], carry);
+        let (r3, r4) = mac(0, self.0[0], rhs.0[3], carry);
+
+        let (r1, carry) = mac(r1, self.0[1], rhs.0[0], 0);
+        let (r2, carry) = mac(r2, self.0[1], rhs.0[1], carry);
+        let (r3, carry) = mac(r3, self.0[1], rhs.0[2], carry);
+        let (r4, r5) = mac(r4, self.0[1], rhs.0[3], carry);
+
+        let (r2, carry) = mac(r2, self.0[2], rhs.0[0], 0);
+        let (r3, carry) = mac(r3, self.0[2], rhs.0[1], carry);
+        let (r4, carry) = mac(r4, self.0[2], rhs.0[2], carry);
+        let (r5, r6) = mac(r5, self.0[2], rhs.0[3], carry);
+
+        let (r3, carry) = mac(r3, self.0[3], rhs.0[0], 0);
+        let (r4, carry) = mac(r4, self.0[3], rhs.0[1], carry);
+        let (r5, carry) = mac(r5, self.0[3], rhs.0[2], carry);
+        let (r6, r7) = mac(r6, self.0[3], rhs.0[3], carry);
+
+        Scalar::montgomery_reduce(r0, r1, r2, r3, r4, r5, r6, r7)
+    }
+
+    /// Subtracts `rhs` from `self`, returning the result.
+    #[inline]
+    pub const fn sub(&self, rhs: &Self) -> Self {
+        let (d0, borrow) = sbb(self.0[0], rhs.0[0], 0);
+        let (d1, borrow) = sbb(self.0[1], rhs.0[1], borrow);
+        let (d2, borrow) = sbb(self.0[2], rhs.0[2], borrow);
+        let (d3, borrow) = sbb(self.0[3], rhs.0[3], borrow);
+
+        // If underflow occurred on the final limb, borrow = 0xfff...fff, otherwise
+        // borrow = 0x000...000. Thus, we use it as a mask to conditionally add the modulus.
+        let (d0, carry) = adc(d0, MODULUS.0[0] & borrow, 0);
+        let (d1, carry) = adc(d1, MODULUS.0[1] & borrow, carry);
+        let (d2, carry) = adc(d2, MODULUS.0[2] & borrow, carry);
+        let (d3, _) = adc(d3, MODULUS.0[3] & borrow, carry);
+
+        Scalar([d0, d1, d2, d3])
+    }
+
+    /// Adds `rhs` to `self`, returning the result.
+    #[inline]
+    pub const fn add(&self, rhs: &Self) -> Self {
+        let (d0, carry) = adc(self.0[0], rhs.0[0], 0);
+        let (d1, carry) = adc(self.0[1], rhs.0[1], carry);
+        let (d2, carry) = adc(self.0[2], rhs.0[2], carry);
+        let (d3, _) = adc(self.0[3], rhs.0[3], carry);
+
+        // Attempt to subtract the modulus, to ensure the value
+        // is smaller than the modulus.
+        (&Scalar([d0, d1, d2, d3])).sub(&MODULUS)
+    }
+
+    /// Negates `self`.
+    #[inline]
+    pub const fn neg(&self) -> Self {
+        // Subtract `self` from `MODULUS` to negate. Ignore the final
+        // borrow because it cannot underflow; self is guaranteed to
+        // be in the field.
+        let (d0, borrow) = sbb(MODULUS.0[0], self.0[0], 0);
+        let (d1, borrow) = sbb(MODULUS.0[1], self.0[1], borrow);
+        let (d2, borrow) = sbb(MODULUS.0[2], self.0[2], borrow);
+        let (d3, _) = sbb(MODULUS.0[3], self.0[3], borrow);
+
+        // `tmp` could be `MODULUS` if `self` was zero. Create a mask that is
+        // zero if `self` was zero, and `u64::max_value()` if self was nonzero.
+        let mask = (((self.0[0] | self.0[1] | self.0[2] | self.0[3]) == 0) as u64).wrapping_sub(1);
+
+        Scalar([d0 & mask, d1 & mask, d2 & mask, d3 & mask])
+    }
+}
+
+impl<'a> From<&'a Scalar> for [u8; 32] {
+    fn from(value: &'a Scalar) -> [u8; 32] {
+        value.to_bytes()
+    }
+}
+
+#[test]
+fn test_inv() {
+    // Compute -(q^{-1} mod 2^64) mod 2^64 by exponentiating
+    // by totient(2**64) - 1
+
+    let mut inv = 1u64;
+    for _ in 0..63 {
+        inv = inv.wrapping_mul(inv);
+        inv = inv.wrapping_mul(MODULUS.0[0]);
+    }
+    inv = inv.wrapping_neg();
+
+    assert_eq!(inv, INV);
+}
+
+#[cfg(feature = "std")]
+#[test]
+fn test_debug() {
+    assert_eq!(
+        format!("{:?}", Scalar::zero()),
+        "0x0000000000000000000000000000000000000000000000000000000000000000"
+    );
+    assert_eq!(
+        format!("{:?}", Scalar::one()),
+        "0x0000000000000000000000000000000000000000000000000000000000000001"
+    );
+    assert_eq!(
+        format!("{:?}", R2),
+        "0x1824b159acc5056f998c4fefecbc4ff55884b7fa0003480200000001fffffffe"
+    );
+}
+
+#[test]
+fn test_equality() {
+    assert_eq!(Scalar::zero(), Scalar::zero());
+    assert_eq!(Scalar::one(), Scalar::one());
+    assert_eq!(R2, R2);
+
+    assert!(Scalar::zero() != Scalar::one());
+    assert!(Scalar::one() != R2);
+}
+
+#[test]
+fn test_to_bytes() {
+    assert_eq!(
+        Scalar::zero().to_bytes(),
+        [
+            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+            0, 0, 0
+        ]
+    );
+
+    assert_eq!(
+        Scalar::one().to_bytes(),
+        [
+            1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+            0, 0, 0
+        ]
+    );
+
+    assert_eq!(
+        R2.to_bytes(),
+        [
+            254, 255, 255, 255, 1, 0, 0, 0, 2, 72, 3, 0, 250, 183, 132, 88, 245, 79, 188, 236, 239,
+            79, 140, 153, 111, 5, 197, 172, 89, 177, 36, 24
+        ]
+    );
+
+    assert_eq!(
+        (-&Scalar::one()).to_bytes(),
+        [
+            0, 0, 0, 0, 255, 255, 255, 255, 254, 91, 254, 255, 2, 164, 189, 83, 5, 216, 161, 9, 8,
+            216, 57, 51, 72, 125, 157, 41, 83, 167, 237, 115
+        ]
+    );
+}
+
+#[test]
+fn test_from_bytes() {
+    assert_eq!(
+        Scalar::from_bytes(&[
+            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+            0, 0, 0
+        ])
+        .unwrap(),
+        Scalar::zero()
+    );
+
+    assert_eq!(
+        Scalar::from_bytes(&[
+            1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+            0, 0, 0
+        ])
+        .unwrap(),
+        Scalar::one()
+    );
+
+    assert_eq!(
+        Scalar::from_bytes(&[
+            254, 255, 255, 255, 1, 0, 0, 0, 2, 72, 3, 0, 250, 183, 132, 88, 245, 79, 188, 236, 239,
+            79, 140, 153, 111, 5, 197, 172, 89, 177, 36, 24
+        ])
+        .unwrap(),
+        R2
+    );
+
+    // -1 should work
+    assert!(
+        Scalar::from_bytes(&[
+            0, 0, 0, 0, 255, 255, 255, 255, 254, 91, 254, 255, 2, 164, 189, 83, 5, 216, 161, 9, 8,
+            216, 57, 51, 72, 125, 157, 41, 83, 167, 237, 115
+        ])
+        .is_some()
+        .unwrap_u8()
+            == 1
+    );
+
+    // modulus is invalid
+    assert!(
+        Scalar::from_bytes(&[
+            1, 0, 0, 0, 255, 255, 255, 255, 254, 91, 254, 255, 2, 164, 189, 83, 5, 216, 161, 9, 8,
+            216, 57, 51, 72, 125, 157, 41, 83, 167, 237, 115
+        ])
+        .is_none()
+        .unwrap_u8()
+            == 1
+    );
+
+    // Anything larger than the modulus is invalid
+    assert!(
+        Scalar::from_bytes(&[
+            2, 0, 0, 0, 255, 255, 255, 255, 254, 91, 254, 255, 2, 164, 189, 83, 5, 216, 161, 9, 8,
+            216, 57, 51, 72, 125, 157, 41, 83, 167, 237, 115
+        ])
+        .is_none()
+        .unwrap_u8()
+            == 1
+    );
+    assert!(
+        Scalar::from_bytes(&[
+            1, 0, 0, 0, 255, 255, 255, 255, 254, 91, 254, 255, 2, 164, 189, 83, 5, 216, 161, 9, 8,
+            216, 58, 51, 72, 125, 157, 41, 83, 167, 237, 115
+        ])
+        .is_none()
+        .unwrap_u8()
+            == 1
+    );
+    assert!(
+        Scalar::from_bytes(&[
+            1, 0, 0, 0, 255, 255, 255, 255, 254, 91, 254, 255, 2, 164, 189, 83, 5, 216, 161, 9, 8,
+            216, 57, 51, 72, 125, 157, 41, 83, 167, 237, 116
+        ])
+        .is_none()
+        .unwrap_u8()
+            == 1
+    );
+}
+
+#[test]
+fn test_from_u512_zero() {
+    assert_eq!(
+        Scalar::zero(),
+        Scalar::from_u512([
+            MODULUS.0[0],
+            MODULUS.0[1],
+            MODULUS.0[2],
+            MODULUS.0[3],
+            0,
+            0,
+            0,
+            0
+        ])
+    );
+}
+
+#[test]
+fn test_from_u512_r() {
+    assert_eq!(R, Scalar::from_u512([1, 0, 0, 0, 0, 0, 0, 0]));
+}
+
+#[test]
+fn test_from_u512_r2() {
+    assert_eq!(R2, Scalar::from_u512([0, 0, 0, 0, 1, 0, 0, 0]));
+}
+
+#[test]
+fn test_from_u512_max() {
+    let max_u64 = 0xffffffffffffffff;
+    assert_eq!(
+        R3 - R,
+        Scalar::from_u512([max_u64, max_u64, max_u64, max_u64, max_u64, max_u64, max_u64, max_u64])
+    );
+}
+
+#[test]
+fn test_from_bytes_wide_r2() {
+    assert_eq!(
+        R2,
+        Scalar::from_bytes_wide(&[
+            254, 255, 255, 255, 1, 0, 0, 0, 2, 72, 3, 0, 250, 183, 132, 88, 245, 79, 188, 236, 239,
+            79, 140, 153, 111, 5, 197, 172, 89, 177, 36, 24, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+        ])
+    );
+}
+
+#[test]
+fn test_from_bytes_wide_negative_one() {
+    assert_eq!(
+        -&Scalar::one(),
+        Scalar::from_bytes_wide(&[
+            0, 0, 0, 0, 255, 255, 255, 255, 254, 91, 254, 255, 2, 164, 189, 83, 5, 216, 161, 9, 8,
+            216, 57, 51, 72, 125, 157, 41, 83, 167, 237, 115, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+        ])
+    );
+}
+
+#[test]
+fn test_from_bytes_wide_maximum() {
+    assert_eq!(
+        Scalar([
+            0xc62c1805439b73b1,
+            0xc2b9551e8ced218e,
+            0xda44ec81daf9a422,
+            0x5605aa601c162e79
+        ]),
+        Scalar::from_bytes_wide(&[0xff; 64])
+    );
+}
+
+#[test]
+fn test_zero() {
+    assert_eq!(Scalar::zero(), -&Scalar::zero());
+    assert_eq!(Scalar::zero(), Scalar::zero() + Scalar::zero());
+    assert_eq!(Scalar::zero(), Scalar::zero() - Scalar::zero());
+    assert_eq!(Scalar::zero(), Scalar::zero() * Scalar::zero());
+}
+
+#[cfg(test)]
+const LARGEST: Scalar = Scalar([
+    0xffffffff00000000,
+    0x53bda402fffe5bfe,
+    0x3339d80809a1d805,
+    0x73eda753299d7d48,
+]);
+
+#[test]
+fn test_addition() {
+    let mut tmp = LARGEST;
+    tmp += &LARGEST;
+
+    assert_eq!(
+        tmp,
+        Scalar([
+            0xfffffffeffffffff,
+            0x53bda402fffe5bfe,
+            0x3339d80809a1d805,
+            0x73eda753299d7d48
+        ])
+    );
+
+    let mut tmp = LARGEST;
+    tmp += &Scalar([1, 0, 0, 0]);
+
+    assert_eq!(tmp, Scalar::zero());
+}
+
+#[test]
+fn test_negation() {
+    let tmp = -&LARGEST;
+
+    assert_eq!(tmp, Scalar([1, 0, 0, 0]));
+
+    let tmp = -&Scalar::zero();
+    assert_eq!(tmp, Scalar::zero());
+    let tmp = -&Scalar([1, 0, 0, 0]);
+    assert_eq!(tmp, LARGEST);
+}
+
+#[test]
+fn test_subtraction() {
+    let mut tmp = LARGEST;
+    tmp -= &LARGEST;
+
+    assert_eq!(tmp, Scalar::zero());
+
+    let mut tmp = Scalar::zero();
+    tmp -= &LARGEST;
+
+    let mut tmp2 = MODULUS;
+    tmp2 -= &LARGEST;
+
+    assert_eq!(tmp, tmp2);
+}
+
+#[test]
+fn test_multiplication() {
+    let mut cur = LARGEST;
+
+    for _ in 0..100 {
+        let mut tmp = cur;
+        tmp *= &cur;
+
+        let mut tmp2 = Scalar::zero();
+        for b in cur
+            .to_bytes()
+            .iter()
+            .rev()
+            .flat_map(|byte| (0..8).rev().map(move |i| ((byte >> i) & 1u8) == 1u8))
+        {
+            let tmp3 = tmp2;
+            tmp2.add_assign(&tmp3);
+
+            if b {
+                tmp2.add_assign(&cur);
+            }
+        }
+
+        assert_eq!(tmp, tmp2);
+
+        cur.add_assign(&LARGEST);
+    }
+}
+
+#[test]
+fn test_squaring() {
+    let mut cur = LARGEST;
+
+    for _ in 0..100 {
+        let mut tmp = cur;
+        tmp = tmp.square();
+
+        let mut tmp2 = Scalar::zero();
+        for b in cur
+            .to_bytes()
+            .iter()
+            .rev()
+            .flat_map(|byte| (0..8).rev().map(move |i| ((byte >> i) & 1u8) == 1u8))
+        {
+            let tmp3 = tmp2;
+            tmp2.add_assign(&tmp3);
+
+            if b {
+                tmp2.add_assign(&cur);
+            }
+        }
+
+        assert_eq!(tmp, tmp2);
+
+        cur.add_assign(&LARGEST);
+    }
+}
+
+#[test]
+fn test_inversion() {
+    assert_eq!(Scalar::zero().invert().is_none().unwrap_u8(), 1);
+    assert_eq!(Scalar::one().invert().unwrap(), Scalar::one());
+    assert_eq!((-&Scalar::one()).invert().unwrap(), -&Scalar::one());
+
+    let mut tmp = R2;
+
+    for _ in 0..100 {
+        let mut tmp2 = tmp.invert().unwrap();
+        tmp2.mul_assign(&tmp);
+
+        assert_eq!(tmp2, Scalar::one());
+
+        tmp.add_assign(&R2);
+    }
+}
+
+#[test]
+fn test_invert_is_pow() {
+    let q_minus_2 = [
+        0xfffffffeffffffff,
+        0x53bda402fffe5bfe,
+        0x3339d80809a1d805,
+        0x73eda753299d7d48,
+    ];
+
+    let mut r1 = R;
+    let mut r2 = R;
+    let mut r3 = R;
+
+    for _ in 0..100 {
+        r1 = r1.invert().unwrap();
+        r2 = r2.pow_vartime(&q_minus_2);
+        r3 = r3.pow(&q_minus_2);
+
+        assert_eq!(r1, r2);
+        assert_eq!(r2, r3);
+        // Add R so we check something different next time around
+        r1.add_assign(&R);
+        r2 = r1;
+        r3 = r1;
+    }
+}
+
+#[test]
+fn test_sqrt() {
+    {
+        assert_eq!(Scalar::zero().sqrt().unwrap(), Scalar::zero());
+    }
+
+    let mut square = Scalar([
+        0x46cd85a5f273077e,
+        0x1d30c47dd68fc735,
+        0x77f656f60beca0eb,
+        0x494aa01bdf32468d,
+    ]);
+
+    let mut none_count = 0;
+
+    for _ in 0..100 {
+        let square_root = square.sqrt();
+        if square_root.is_none().unwrap_u8() == 1 {
+            none_count += 1;
+        } else {
+            assert_eq!(square_root.unwrap() * square_root.unwrap(), square);
+        }
+        square -= Scalar::one();
+    }
+
+    assert_eq!(49, none_count);
+}
+
+#[test]
+fn test_from_raw() {
+    assert_eq!(
+        Scalar::from_raw([
+            0x1fffffffd,
+            0x5884b7fa00034802,
+            0x998c4fefecbc4ff5,
+            0x1824b159acc5056f
+        ]),
+        Scalar::from_raw([0xffffffffffffffff; 4])
+    );
+
+    assert_eq!(Scalar::from_raw(MODULUS.0), Scalar::zero());
+
+    assert_eq!(Scalar::from_raw([1, 0, 0, 0]), R);
+}
+
+#[test]
+fn test_double() {
+    let a = Scalar::from_raw([
+        0x1fff3231233ffffd,
+        0x4884b7fa00034802,
+        0x998c4fefecbc4ff3,
+        0x1824b159acc50562,
+    ]);
+
+    assert_eq!(a.double(), a + a);
+}
diff --git a/bls12_381/src/tests/g1_compressed_valid_test_vectors.dat b/bls12_381/src/tests/g1_compressed_valid_test_vectors.dat
new file mode 100644
index 0000000..ea8cd67
Binary files /dev/null and b/bls12_381/src/tests/g1_compressed_valid_test_vectors.dat differ
diff --git a/bls12_381/src/tests/g1_uncompressed_valid_test_vectors.dat b/bls12_381/src/tests/g1_uncompressed_valid_test_vectors.dat
new file mode 100644
index 0000000..86abfba
Binary files /dev/null and b/bls12_381/src/tests/g1_uncompressed_valid_test_vectors.dat differ
diff --git a/bls12_381/src/tests/g2_compressed_valid_test_vectors.dat b/bls12_381/src/tests/g2_compressed_valid_test_vectors.dat
new file mode 100644
index 0000000..a40bbe2
Binary files /dev/null and b/bls12_381/src/tests/g2_compressed_valid_test_vectors.dat differ
diff --git a/bls12_381/src/tests/g2_uncompressed_valid_test_vectors.dat b/bls12_381/src/tests/g2_uncompressed_valid_test_vectors.dat
new file mode 100644
index 0000000..92e4bc5
Binary files /dev/null and b/bls12_381/src/tests/g2_uncompressed_valid_test_vectors.dat differ
diff --git a/bls12_381/src/tests/mod.rs b/bls12_381/src/tests/mod.rs
new file mode 100644
index 0000000..125321b
--- /dev/null
+++ b/bls12_381/src/tests/mod.rs
@@ -0,0 +1,230 @@
+use super::*;
+
+macro_rules! test_vectors {
+    ($projective:ident, $affine:ident, $serialize:ident, $deserialize:ident, $expected:ident) => {
+        let mut e = $projective::identity();
+
+        let mut v = vec![];
+        {
+            let mut expected = $expected;
+            for _ in 0..1000 {
+                let e_affine = $affine::from(e);
+                let encoded = e_affine.$serialize();
+                v.extend_from_slice(&encoded[..]);
+
+                let mut decoded = encoded;
+                let len_of_encoding = decoded.len();
+                (&mut decoded[..]).copy_from_slice(&expected[0..len_of_encoding]);
+                expected = &expected[len_of_encoding..];
+                let decoded = $affine::$deserialize(&decoded).unwrap();
+                assert_eq!(e_affine, decoded);
+
+                e = &e + &$projective::generator();
+            }
+        }
+
+        assert_eq!(&v[..], $expected);
+    };
+}
+
+#[test]
+fn g1_uncompressed_valid_test_vectors() {
+    let bytes: &'static [u8] = include_bytes!("g1_uncompressed_valid_test_vectors.dat");
+    test_vectors!(
+        G1Projective,
+        G1Affine,
+        to_uncompressed,
+        from_uncompressed,
+        bytes
+    );
+}
+
+#[test]
+fn g1_compressed_valid_test_vectors() {
+    let bytes: &'static [u8] = include_bytes!("g1_compressed_valid_test_vectors.dat");
+    test_vectors!(
+        G1Projective,
+        G1Affine,
+        to_compressed,
+        from_compressed,
+        bytes
+    );
+}
+
+#[test]
+fn g2_uncompressed_valid_test_vectors() {
+    let bytes: &'static [u8] = include_bytes!("g2_uncompressed_valid_test_vectors.dat");
+    test_vectors!(
+        G2Projective,
+        G2Affine,
+        to_uncompressed,
+        from_uncompressed,
+        bytes
+    );
+}
+
+#[test]
+fn g2_compressed_valid_test_vectors() {
+    let bytes: &'static [u8] = include_bytes!("g2_compressed_valid_test_vectors.dat");
+    test_vectors!(
+        G2Projective,
+        G2Affine,
+        to_compressed,
+        from_compressed,
+        bytes
+    );
+}
+
+#[test]
+fn test_pairing_result_against_relic() {
+    /*
+    Sent to me from Diego Aranha (author of RELIC library):
+    1250EBD871FC0A92 A7B2D83168D0D727 272D441BEFA15C50 3DD8E90CE98DB3E7 B6D194F60839C508 A84305AACA1789B6
+    089A1C5B46E5110B 86750EC6A5323488 68A84045483C92B7 AF5AF689452EAFAB F1A8943E50439F1D 59882A98EAA0170F
+    1368BB445C7C2D20 9703F239689CE34C 0378A68E72A6B3B2 16DA0E22A5031B54 DDFF57309396B38C 881C4C849EC23E87
+    193502B86EDB8857 C273FA075A505129 37E0794E1E65A761 7C90D8BD66065B1F FFE51D7A579973B1 315021EC3C19934F
+    01B2F522473D1713 91125BA84DC4007C FBF2F8DA752F7C74 185203FCCA589AC7 19C34DFFBBAAD843 1DAD1C1FB597AAA5
+    018107154F25A764 BD3C79937A45B845 46DA634B8F6BE14A 8061E55CCEBA478B 23F7DACAA35C8CA7 8BEAE9624045B4B6
+    19F26337D205FB46 9CD6BD15C3D5A04D C88784FBB3D0B2DB DEA54D43B2B73F2C BB12D58386A8703E 0F948226E47EE89D
+    06FBA23EB7C5AF0D 9F80940CA771B6FF D5857BAAF222EB95 A7D2809D61BFE02E 1BFD1B68FF02F0B8 102AE1C2D5D5AB1A
+    11B8B424CD48BF38 FCEF68083B0B0EC5 C81A93B330EE1A67 7D0D15FF7B984E89 78EF48881E32FAC9 1B93B47333E2BA57
+    03350F55A7AEFCD3 C31B4FCB6CE5771C C6A0E9786AB59733 20C806AD36082910 7BA810C5A09FFDD9 BE2291A0C25A99A2
+    04C581234D086A99 02249B64728FFD21 A189E87935A95405 1C7CDBA7B3872629 A4FAFC05066245CB 9108F0242D0FE3EF
+    0F41E58663BF08CF 068672CBD01A7EC7 3BACA4D72CA93544 DEFF686BFD6DF543 D48EAA24AFE47E1E FDE449383B676631
+    */
+
+    let a = G1Affine::generator();
+    let b = G2Affine::generator();
+
+    use super::fp::Fp;
+    use super::fp12::Fp12;
+    use super::fp2::Fp2;
+    use super::fp6::Fp6;
+
+    let res = pairing(&a, &b);
+
+    let prep = G2Prepared::from(b);
+
+    assert_eq!(
+        res,
+        multi_miller_loop(&[(&a, &prep)]).final_exponentiation()
+    );
+
+    assert_eq!(
+        res.0,
+        Fp12 {
+            c0: Fp6 {
+                c0: Fp2 {
+                    c0: Fp::from_raw_unchecked([
+                        0x1972e433a01f85c5,
+                        0x97d32b76fd772538,
+                        0xc8ce546fc96bcdf9,
+                        0xcef63e7366d40614,
+                        0xa611342781843780,
+                        0x13f3448a3fc6d825
+                    ]),
+                    c1: Fp::from_raw_unchecked([
+                        0xd26331b02e9d6995,
+                        0x9d68a482f7797e7d,
+                        0x9c9b29248d39ea92,
+                        0xf4801ca2e13107aa,
+                        0xa16c0732bdbcb066,
+                        0x83ca4afba360478
+                    ])
+                },
+                c1: Fp2 {
+                    c0: Fp::from_raw_unchecked([
+                        0x59e261db0916b641,
+                        0x2716b6f4b23e960d,
+                        0xc8e55b10a0bd9c45,
+                        0xbdb0bd99c4deda8,
+                        0x8cf89ebf57fdaac5,
+                        0x12d6b7929e777a5e
+                    ]),
+                    c1: Fp::from_raw_unchecked([
+                        0x5fc85188b0e15f35,
+                        0x34a06e3a8f096365,
+                        0xdb3126a6e02ad62c,
+                        0xfc6f5aa97d9a990b,
+                        0xa12f55f5eb89c210,
+                        0x1723703a926f8889
+                    ])
+                },
+                c2: Fp2 {
+                    c0: Fp::from_raw_unchecked([
+                        0x93588f2971828778,
+                        0x43f65b8611ab7585,
+                        0x3183aaf5ec279fdf,
+                        0xfa73d7e18ac99df6,
+                        0x64e176a6a64c99b0,
+                        0x179fa78c58388f1f
+                    ]),
+                    c1: Fp::from_raw_unchecked([
+                        0x672a0a11ca2aef12,
+                        0xd11b9b52aa3f16b,
+                        0xa44412d0699d056e,
+                        0xc01d0177221a5ba5,
+                        0x66e0cede6c735529,
+                        0x5f5a71e9fddc339
+                    ])
+                }
+            },
+            c1: Fp6 {
+                c0: Fp2 {
+                    c0: Fp::from_raw_unchecked([
+                        0xd30a88a1b062c679,
+                        0x5ac56a5d35fc8304,
+                        0xd0c834a6a81f290d,
+                        0xcd5430c2da3707c7,
+                        0xf0c27ff780500af0,
+                        0x9245da6e2d72eae
+                    ]),
+                    c1: Fp::from_raw_unchecked([
+                        0x9f2e0676791b5156,
+                        0xe2d1c8234918fe13,
+                        0x4c9e459f3c561bf4,
+                        0xa3e85e53b9d3e3c1,
+                        0x820a121e21a70020,
+                        0x15af618341c59acc
+                    ])
+                },
+                c1: Fp2 {
+                    c0: Fp::from_raw_unchecked([
+                        0x7c95658c24993ab1,
+                        0x73eb38721ca886b9,
+                        0x5256d749477434bc,
+                        0x8ba41902ea504a8b,
+                        0x4a3d3f80c86ce6d,
+                        0x18a64a87fb686eaa
+                    ]),
+                    c1: Fp::from_raw_unchecked([
+                        0xbb83e71bb920cf26,
+                        0x2a5277ac92a73945,
+                        0xfc0ee59f94f046a0,
+                        0x7158cdf3786058f7,
+                        0x7cc1061b82f945f6,
+                        0x3f847aa9fdbe567
+                    ])
+                },
+                c2: Fp2 {
+                    c0: Fp::from_raw_unchecked([
+                        0x8078dba56134e657,
+                        0x1cd7ec9a43998a6e,
+                        0xb1aa599a1a993766,
+                        0xc9a0f62f0842ee44,
+                        0x8e159be3b605dffa,
+                        0xc86ba0d4af13fc2
+                    ]),
+                    c1: Fp::from_raw_unchecked([
+                        0xe80ff2a06a52ffb1,
+                        0x7694ca48721a906c,
+                        0x7583183e03b08514,
+                        0xf567afdd40cee4e2,
+                        0x9a6d96d2e526a5fc,
+                        0x197e9f49861f2242
+                    ])
+                }
+            }
+        }
+    );
+}
diff --git a/bls12_381/src/util.rs b/bls12_381/src/util.rs
new file mode 100644
index 0000000..bd25dd0
--- /dev/null
+++ b/bls12_381/src/util.rs
@@ -0,0 +1,174 @@
+/// Compute a + b + carry, returning the result and the new carry over.
+#[inline(always)]
+pub const fn adc(a: u64, b: u64, carry: u64) -> (u64, u64) {
+    let ret = (a as u128) + (b as u128) + (carry as u128);
+    (ret as u64, (ret >> 64) as u64)
+}
+
+/// Compute a - (b + borrow), returning the result and the new borrow.
+#[inline(always)]
+pub const fn sbb(a: u64, b: u64, borrow: u64) -> (u64, u64) {
+    let ret = (a as u128).wrapping_sub((b as u128) + ((borrow >> 63) as u128));
+    (ret as u64, (ret >> 64) as u64)
+}
+
+/// Compute a + (b * c) + carry, returning the result and the new carry over.
+#[inline(always)]
+pub const fn mac(a: u64, b: u64, c: u64, carry: u64) -> (u64, u64) {
+    let ret = (a as u128) + ((b as u128) * (c as u128)) + (carry as u128);
+    (ret as u64, (ret >> 64) as u64)
+}
+
+macro_rules! impl_add_binop_specify_output {
+    ($lhs:ident, $rhs:ident, $output:ident) => {
+        impl<'b> Add<&'b $rhs> for $lhs {
+            type Output = $output;
+
+            #[inline]
+            fn add(self, rhs: &'b $rhs) -> $output {
+                &self + rhs
+            }
+        }
+
+        impl<'a> Add<$rhs> for &'a $lhs {
+            type Output = $output;
+
+            #[inline]
+            fn add(self, rhs: $rhs) -> $output {
+                self + &rhs
+            }
+        }
+
+        impl Add<$rhs> for $lhs {
+            type Output = $output;
+
+            #[inline]
+            fn add(self, rhs: $rhs) -> $output {
+                &self + &rhs
+            }
+        }
+    };
+}
+
+macro_rules! impl_sub_binop_specify_output {
+    ($lhs:ident, $rhs:ident, $output:ident) => {
+        impl<'b> Sub<&'b $rhs> for $lhs {
+            type Output = $output;
+
+            #[inline]
+            fn sub(self, rhs: &'b $rhs) -> $output {
+                &self - rhs
+            }
+        }
+
+        impl<'a> Sub<$rhs> for &'a $lhs {
+            type Output = $output;
+
+            #[inline]
+            fn sub(self, rhs: $rhs) -> $output {
+                self - &rhs
+            }
+        }
+
+        impl Sub<$rhs> for $lhs {
+            type Output = $output;
+
+            #[inline]
+            fn sub(self, rhs: $rhs) -> $output {
+                &self - &rhs
+            }
+        }
+    };
+}
+
+macro_rules! impl_binops_additive_specify_output {
+    ($lhs:ident, $rhs:ident, $output:ident) => {
+        impl_add_binop_specify_output!($lhs, $rhs, $output);
+        impl_sub_binop_specify_output!($lhs, $rhs, $output);
+    };
+}
+
+macro_rules! impl_binops_multiplicative_mixed {
+    ($lhs:ident, $rhs:ident, $output:ident) => {
+        impl<'b> Mul<&'b $rhs> for $lhs {
+            type Output = $output;
+
+            #[inline]
+            fn mul(self, rhs: &'b $rhs) -> $output {
+                &self * rhs
+            }
+        }
+
+        impl<'a> Mul<$rhs> for &'a $lhs {
+            type Output = $output;
+
+            #[inline]
+            fn mul(self, rhs: $rhs) -> $output {
+                self * &rhs
+            }
+        }
+
+        impl Mul<$rhs> for $lhs {
+            type Output = $output;
+
+            #[inline]
+            fn mul(self, rhs: $rhs) -> $output {
+                &self * &rhs
+            }
+        }
+    };
+}
+
+macro_rules! impl_binops_additive {
+    ($lhs:ident, $rhs:ident) => {
+        impl_binops_additive_specify_output!($lhs, $rhs, $lhs);
+
+        impl SubAssign<$rhs> for $lhs {
+            #[inline]
+            fn sub_assign(&mut self, rhs: $rhs) {
+                *self = &*self - &rhs;
+            }
+        }
+
+        impl AddAssign<$rhs> for $lhs {
+            #[inline]
+            fn add_assign(&mut self, rhs: $rhs) {
+                *self = &*self + &rhs;
+            }
+        }
+
+        impl<'b> SubAssign<&'b $rhs> for $lhs {
+            #[inline]
+            fn sub_assign(&mut self, rhs: &'b $rhs) {
+                *self = &*self - rhs;
+            }
+        }
+
+        impl<'b> AddAssign<&'b $rhs> for $lhs {
+            #[inline]
+            fn add_assign(&mut self, rhs: &'b $rhs) {
+                *self = &*self + rhs;
+            }
+        }
+    };
+}
+
+macro_rules! impl_binops_multiplicative {
+    ($lhs:ident, $rhs:ident) => {
+        impl_binops_multiplicative_mixed!($lhs, $rhs, $lhs);
+
+        impl MulAssign<$rhs> for $lhs {
+            #[inline]
+            fn mul_assign(&mut self, rhs: $rhs) {
+                *self = &*self * &rhs;
+            }
+        }
+
+        impl<'b> MulAssign<&'b $rhs> for $lhs {
+            #[inline]
+            fn mul_assign(&mut self, rhs: &'b $rhs) {
+                *self = &*self * rhs;
+            }
+        }
+    };
+}