Repository: dusk-network/bls12_381 (language: rust)

Issue:
Merge our implementation with upstream implementation
#### Summary
Our implementation has diverted significantly from the zkcrypto upstream original. In order to get the upstream improvements it is good to have them merged.



Discussion / hints:

Fix patch:
diff --git a/.github/workflows/dusk_ci.yml b/.github/workflows/dusk_ci.yml
index fdac3e2a..7627ed62 100644
--- a/.github/workflows/dusk_ci.yml
+++ b/.github/workflows/dusk_ci.yml
@@ -17,15 +17,11 @@ jobs:
   test_nightly_std:
     name: Nightly tests std
     uses: dusk-network/.github/.github/workflows/run-tests.yml@main
+    with:
+      test_flags: --all-features
 
   test_nightly_no_std:
     name: Nightly tests no_std
     uses: dusk-network/.github/.github/workflows/run-tests.yml@main
     with:
       test_flags: --no-default-features
-
-  test_nightly_serde:
-    name: Nightly tests serde
-    uses: dusk-network/.github/.github/workflows/run-tests.yml@main
-    with:
-      test_flags: --features serde_req
diff --git a/CHANGELOG.md b/CHANGELOG.md
index d2672485..4f7d77e2 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 ## [Unreleased]
 
+### Added
+
+- Align crate with upstream `zkcrypto/bls12_381` [#117]
+
 ### Removed
 
 - Remove scalar field generator [#100]
@@ -14,6 +18,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 ### Changed
 
+- Rename `serde_req` feature to `serde` [#117]
 - Separate Dusk's additions from the original crate [#109]
 
 ## [0.11.3] - 2023-05-17
@@ -190,6 +195,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 - Rename `S` to `TWO_ADACITY` and export it
 
 <!-- Issues -->
+[#117]: (https://github.com/dusk-network/bls12_381/issues/117)
 [#109]: (https://github.com/dusk-network/bls12_381/issues/109)
 [#108]: (https://github.com/dusk-network/bls12_381/issues/108)
 [#100]: (https://github.com/dusk-network/bls12_381/issues/100)
diff --git a/Cargo.toml b/Cargo.toml
index 5aa321d1..bd6abc41 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,32 +1,36 @@
 [package]
-authors = ["Sean Bowe <ewillbefull@gmail.com>, CPerezz <carlos@dusk.network>"]
+authors = [
+    "Sean Bowe <ewillbefull@gmail.com>",
+    "Jack Grigg <thestr4d@gmail.com>",
+    "CPerezz <carlos@dusk.network>",
+]
 description = "Fork of the implementation of the BLS12-381 pairing-friendly elliptic curve construction with some extra tooling needed by the Dusk team"
+documentation = "https://docs.rs/dusk-bls12_381/"
 homepage = "https://github.com/dusk-network/bls12_381"
-license = "MIT/Apache-2.0"
+license = "MIT/Apache-2.0/MPL-2.0"
 name = "dusk-bls12_381"
 repository = "https://github.com/dusk-network/bls12_381"
 version = "0.11.3"
+edition = "2021"
+
 keywords = ["cryptography", "bls12_381", "zk-snarks", "ecc", "elliptic-curve"]
 categories =["algorithms", "cryptography", "development-tools"]
-edition = "2021"
-exclude = [".github/workflows/ci.yml",
-".gitignore", 
+exclude = [
+    ".github/workflows/ci.yml",
+    ".gitignore",
 ]
 
-[dependencies]
-byteorder = {version = "1.0", default-features = false, optional = true}
-rayon = {version = "1.3", optional = true}
-serde = {version = "1.0", optional = true}
-rand_core = {version = "0.6", default-features = false}
-rkyv = {version = "0.7", optional = true, default-features = false}
-bytecheck = {version = "0.6", optional = true, default-features = false}
-dusk-bytes = "0.1"
-
 [package.metadata.docs.rs]
 rustdoc-args = [ "--html-in-header", "katex-header.html" ]
 
 [dev-dependencies]
-criterion = "0.2.11"
+csv = ">= 1.0, < 1.2" # csv 1.2 has MSRV 1.60
+criterion = "0.3"
+hex = "0.4"
+rand_xorshift = "0.3"
+sha2 = "0.9"
+sha3 = "0.9"
+# Dev-dependencies added by Dusk
 bincode = "1"
 rkyv = {version = "0.7", default-features = false, features = ["size_32"]}
 
@@ -35,26 +39,77 @@ name = "groups"
 harness = false
 required-features = ["groups"]
 
+[[bench]]
+name = "hash_to_curve"
+harness = false
+required-features = ["experimental"]
+
+[dependencies.digest]
+version = "0.9"
+optional = true
+
+[dependencies.ff]
+version = "0.13"
+default-features = false
+
+[dependencies.group]
+version = "0.13"
+default-features = false
+optional = true
+
+[dependencies.pairing]
+version = "0.23"
+optional = true
+
+[dependencies.rand_core]
+version = "0.6"
+default-features = false
+
 [dependencies.subtle]
-version = "2.0"
+version = "2"
+default-features = false
+
+[dependencies.zeroize]
+version = "1"
+default-features = false
+optional = true
+
+# Start of dependencies added by Dusk
+[dependencies.byteorder]
+version = "1"
 default-features = false
+optional = true
+
+[dependencies.rayon]
+version = "1"
+optional = true
+
+[dependencies.serde]
+version = "1"
+optional = true
+
+[dependencies.rkyv]
+version = "0.7"
+default-features = false
+optional = true
+
+[dependencies.bytecheck]
+version = "0.6"
+default-features = false
+optional = true
+
+[dependencies.dusk-bytes]
+version = "0.1"
+# End
 
 [features]
-default = ["std"]
-std = [
-    "alloc", 
-    "parallel", 
-    "groups", 
-    "pairings", 
-    "endo", 
-    "byteorder", 
-    "rand_core/std"
-]
-groups = []
-parallel = ["rayon"]
-pairings = ["groups"]
-alloc = []
+default = ["groups", "pairings", "alloc", "bits", "parallel", "byteorder"]
+bits = ["ff/bits"]
+groups = ["group"]
+pairings = ["groups", "pairing"]
+alloc = ["group/alloc"]
+experimental = ["digest"]
 nightly = ["subtle/nightly"]
+# Features added by Dusk
+parallel = ["rayon"]
 rkyv-impl = ["rkyv", "rkyv/alloc", "bytecheck"]
-serde_req = ["serde", "std"]
-endo = []
diff --git a/README.md b/README.md
index 90d96f70..e67c59f5 100644
--- a/README.md
+++ b/README.md
@@ -2,7 +2,6 @@
 [![Repository](https://img.shields.io/badge/github-dusk--bls12_381-blueviolet?logo=github)](https://github.com/dusk-network/bls12_381)
 [![Documentation](https://img.shields.io/badge/docs-dusk--bls12_381-blue?logo=rust)](https://docs.rs/dusk-bls12_381/)
 
-
 **THIS CRATE IS A FORK OF [https://github.com/zkcrypto/bls12_381](https://github.com/zkcrypto/bls12_381/) where the Dusk-Network team has added a variety of tools required by other libraries built on the top of this one.
 The 99% of the library stills being done by @ebfull and you SHOULD NOT use this one unless you need a specific tool that we've 
 implemented and it's not on the original library.**
@@ -23,18 +22,20 @@ implemented and it's not on the original library.**
 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 targets Rust `1.56` or later.
 * This implementation does not require the Rust standard library.
 * All operations are constant time unless explicitly noted.
 
 ## Features
 
+* `bits` (on by default): Enables APIs for obtaining bit iterators for scalars.
 * `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.
-* `endo`: Enables optimizations that leverage curve endomorphisms, which may run foul of patents US7110538B2 and US7995752B2 set to expire in September 2020.
-* `parallel` (on by default): Enables `rayon` usage for higly parallelizable ops such as multiscalar multiplication.
+* `experimental`: Enables experimental features. These features have no backwards-compatibility guarantees and may change at any time; users that depend on specific behaviour should pin an exact version of this crate. The current list of experimental features:
+  * Hashing to curves ([Internet Draft v12](https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-12))
+* `parallel` (on by default): Enables `rayon` usage for highly parallelizable ops such as multiscalar multiplication.
 
 ## [Documentation](https://docs.rs/dusk-bls12_381)
 
diff --git a/benches/hash_to_curve.rs b/benches/hash_to_curve.rs
new file mode 100644
index 00000000..28bd15c6
--- /dev/null
+++ b/benches/hash_to_curve.rs
@@ -0,0 +1,68 @@
+#[macro_use]
+extern crate criterion;
+
+extern crate dusk_bls12_381;
+use dusk_bls12_381::hash_to_curve::*;
+use dusk_bls12_381::*;
+
+use criterion::{black_box, Criterion};
+
+fn criterion_benchmark(c: &mut Criterion) {
+    // G1Projective
+    {
+        let name = "G1Projective";
+
+        let message: &[u8] = b"test message";
+        let dst: &[u8] = b"test DST";
+
+        c.bench_function(
+            &format!("{} encode_to_curve SSWU SHA-256", name),
+            move |b| {
+                b.iter(|| {
+                    <G1Projective as HashToCurve<ExpandMsgXmd<sha2::Sha256>>>::encode_to_curve(
+                        black_box(message),
+                        black_box(dst),
+                    )
+                })
+            },
+        );
+        c.bench_function(&format!("{} hash_to_curve SSWU SHA-256", name), move |b| {
+            b.iter(|| {
+                <G1Projective as HashToCurve<ExpandMsgXmd<sha2::Sha256>>>::hash_to_curve(
+                    black_box(message),
+                    black_box(dst),
+                )
+            })
+        });
+    }
+    // G2Projective
+    {
+        let name = "G2Projective";
+
+        let message: &[u8] = b"test message";
+        let dst: &[u8] = b"test DST";
+
+        c.bench_function(
+            &format!("{} encode_to_curve SSWU SHA-256", name),
+            move |b| {
+                b.iter(|| {
+                    <G2Projective as HashToCurve<ExpandMsgXmd<sha2::Sha256>>>::encode_to_curve(
+                        black_box(message),
+                        black_box(dst),
+                    )
+                })
+            },
+        );
+        c.bench_function(&format!("{} hash_to_curve SSWU SHA-256", name), move |b| {
+            b.iter(|| {
+                <G2Projective as HashToCurve<ExpandMsgXmd<sha2::Sha256>>>::hash_to_curve(
+                    black_box(message),
+                    black_box(dst),
+                )
+            })
+        });
+    }
+}
+
+criterion_group!(benches, criterion_benchmark);
+criterion_main!(benches);
diff --git a/rust-toolchain.toml b/rust-toolchain.toml
index 8e275b74..a2d375e9 100644
--- a/rust-toolchain.toml
+++ b/rust-toolchain.toml
@@ -1,3 +1,3 @@
 [toolchain]
 channel = "nightly"
-components = ["rustfmt", "clippy"]
+components = ["clippy", "rustfmt"]
diff --git a/src/dusk/choice.rs b/src/dusk/choice.rs
index 1016a36d..c8133da9 100644
--- a/src/dusk/choice.rs
+++ b/src/dusk/choice.rs
@@ -1,6 +1,6 @@
 use core::convert::Infallible;
 
-use dusk_bytes::{HexDebug, Serializable};
+use dusk_bytes::Serializable;
 use subtle::ConditionallySelectable;
 
 #[cfg(feature = "rkyv-impl")]
@@ -9,9 +9,12 @@ use bytecheck::CheckBytes;
 use rkyv::{Archive, Deserialize as RkyvDeserialize, Serialize as RkyvSerialize};
 
 /// Wrapper for a [`subtle::Choice`]
-#[derive(Copy, Clone, HexDebug)]
-#[cfg_attr(feature = "rkyv-impl", derive(Archive, RkyvSerialize, RkyvDeserialize))]
-#[cfg_attr(feature = "rkyv-impl", archive_attr(derive(CheckBytes)))]
+#[derive(Copy, Clone, Debug)]
+#[cfg_attr(
+    feature = "rkyv-impl",
+    derive(Archive, RkyvSerialize, RkyvDeserialize),
+    archive_attr(derive(CheckBytes))
+)]
 pub struct Choice(u8);
 
 impl Choice {
diff --git a/src/dusk/multiscalar_mul.rs b/src/dusk/multiscalar_mul.rs
index 0363b526..472450e9 100644
--- a/src/dusk/multiscalar_mul.rs
+++ b/src/dusk/multiscalar_mul.rs
@@ -3,14 +3,13 @@ use crate::{
     g1::{G1Affine, G1Projective},
     scalar::Scalar,
 };
-use dusk_bytes::Serializable;
 
 use alloc::vec::*;
 
-#[cfg(feature = "std")]
 /// Performs multiscalar multiplication reliying on Pippenger's algorithm.
 /// This method was taken from `curve25519-dalek` and was originally made by
 /// Oleg Andreev <oleganza@gmail.com>.
+#[cfg(feature = "byteorder")]
 pub fn pippenger<P, I>(points: P, scalars: I) -> G1Projective
 where
     P: Iterator<Item = G1Projective>,
@@ -91,8 +90,8 @@ where
     columns.fold(hi_column, |total, p| mul_by_pow_2(&total, w as u32) + p)
 }
 
-#[cfg(feature = "std")]
 /// Compute \\([2\^k] P \\) by successive doublings. Requires \\( k > 0 \\).
+#[cfg(feature = "byteorder")]
 pub(crate) fn mul_by_pow_2(point: &G1Projective, k: u32) -> G1Projective {
     debug_assert!(k > 0);
     let mut r: G1Projective;
@@ -105,9 +104,9 @@ pub(crate) fn mul_by_pow_2(point: &G1Projective, k: u32) -> G1Projective {
     s.double()
 }
 
-#[cfg(feature = "std")]
 /// Returns a size hint indicating how many entries of the return
 /// value of `to_radix_2w` are nonzero.
+#[cfg(feature = "byteorder")]
 fn to_radix_2w_size_hint(w: usize) -> usize {
     debug_assert!(w >= 6);
     debug_assert!(w <= 8);
@@ -124,7 +123,7 @@ fn to_radix_2w_size_hint(w: usize) -> usize {
     digits_count
 }
 
-#[cfg(feature = "std")]
+#[cfg(feature = "byteorder")]
 fn to_radix_2w(scalar: &Scalar, w: usize) -> [i8; 43] {
     debug_assert!(w >= 6);
     debug_assert!(w <= 8);
@@ -286,6 +285,7 @@ mod tests {
     #[allow(unused_imports)]
     use super::*;
 
+    #[cfg(feature = "byteorder")]
     #[test]
     fn pippenger_test() {
         // Reuse points across different tests
@@ -307,10 +307,7 @@ mod tests {
             let scalars = &scalars[0..n];
             let points = &points[0..n];
             let control: G1Projective = premultiplied[0..n].iter().sum();
-            let subject = pippenger(
-                points.to_owned().into_iter(),
-                scalars.to_owned().into_iter(),
-            );
+            let subject = pippenger(points.to_vec().into_iter(), scalars.to_vec().into_iter());
             assert_eq!(subject, control);
             n = n / 2;
         }
diff --git a/src/fp.rs b/src/fp.rs
index 1513defd..16ccd277 100644
--- a/src/fp.rs
+++ b/src/fp.rs
@@ -1,9 +1,9 @@
 //! 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 rand_core::RngCore;
 use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption};
 
 use crate::util::{adc, mac, sbb};
@@ -17,9 +17,12 @@ use rkyv::{Archive, Deserialize as RkyvDeserialize, Serialize as RkyvSerialize};
 // 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)]
-#[cfg_attr(feature = "rkyv-impl", derive(Archive, RkyvSerialize, RkyvDeserialize))]
-#[cfg_attr(feature = "rkyv-impl", archive_attr(derive(CheckBytes)))]
-pub struct Fp([u64; 6]);
+#[cfg_attr(
+    feature = "rkyv-impl",
+    derive(Archive, RkyvSerialize, RkyvDeserialize),
+    archive_attr(derive(CheckBytes))
+)]
+pub struct Fp(pub(crate) [u64; 6]);
 
 impl fmt::Debug for Fp {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
@@ -38,6 +41,9 @@ impl Default for Fp {
     }
 }
 
+#[cfg(feature = "zeroize")]
+impl zeroize::DefaultIsZeroes for Fp {}
+
 impl ConstantTimeEq for Fp {
     fn ct_eq(&self, other: &Self) -> Choice {
         self.0[0].ct_eq(&other.0[0])
@@ -103,6 +109,16 @@ const R2: Fp = Fp([
     0x1198_8fe5_92ca_e3aa,
 ]);
 
+/// R3 = 2^(384*3) mod p
+const R3: Fp = Fp([
+    0xed48_ac6b_d94c_a1e0,
+    0x315f_831e_03a7_adf8,
+    0x9a53_352a_615e_29dd,
+    0x34c0_4e5e_921e_1761,
+    0x2512_d435_6572_4728,
+    0x0aa6_3460_9175_5d4d,
+]);
+
 impl<'a> Neg for &'a Fp {
     type Output = Fp;
 
@@ -202,7 +218,7 @@ impl Fp {
 
     /// Converts an element of `Fp` into a byte representation in
     /// big-endian byte order.
-    pub fn to_bytes(&self) -> [u8; 48] {
+    pub fn to_bytes(self) -> [u8; 48] {
         // Turn into canonical form by computing
         // (a.R) / R = a
         let tmp = Fp::montgomery_reduce(
@@ -220,6 +236,48 @@ impl Fp {
         res
     }
 
+    pub(crate) fn random(mut rng: impl RngCore) -> Fp {
+        let mut bytes = [0u8; 96];
+        rng.fill_bytes(&mut bytes);
+
+        // Parse the random bytes as a big-endian number, to match Fp encoding order.
+        Fp::from_u768([
+            u64::from_be_bytes(<[u8; 8]>::try_from(&bytes[0..8]).unwrap()),
+            u64::from_be_bytes(<[u8; 8]>::try_from(&bytes[8..16]).unwrap()),
+            u64::from_be_bytes(<[u8; 8]>::try_from(&bytes[16..24]).unwrap()),
+            u64::from_be_bytes(<[u8; 8]>::try_from(&bytes[24..32]).unwrap()),
+            u64::from_be_bytes(<[u8; 8]>::try_from(&bytes[32..40]).unwrap()),
+            u64::from_be_bytes(<[u8; 8]>::try_from(&bytes[40..48]).unwrap()),
+            u64::from_be_bytes(<[u8; 8]>::try_from(&bytes[48..56]).unwrap()),
+            u64::from_be_bytes(<[u8; 8]>::try_from(&bytes[56..64]).unwrap()),
+            u64::from_be_bytes(<[u8; 8]>::try_from(&bytes[64..72]).unwrap()),
+            u64::from_be_bytes(<[u8; 8]>::try_from(&bytes[72..80]).unwrap()),
+            u64::from_be_bytes(<[u8; 8]>::try_from(&bytes[80..88]).unwrap()),
+            u64::from_be_bytes(<[u8; 8]>::try_from(&bytes[88..96]).unwrap()),
+        ])
+    }
+
+    /// Reduces a big-endian 64-bit limb representation of a 768-bit number.
+    fn from_u768(limbs: [u64; 12]) -> Fp {
+        // We reduce an arbitrary 768-bit number by decomposing it into two 384-bit digits
+        // with the higher bits multiplied by 2^384. 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^384 = R^3
+        //
+        // and computing their sum in the field. It remains to see that arbitrary 384-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^384 multiplied by
+        // the modulus. This holds because for any `c` smaller than the modulus, we have
+        // that (2^384 - 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 d1 = Fp([limbs[11], limbs[10], limbs[9], limbs[8], limbs[7], limbs[6]]);
+        let d0 = Fp([limbs[5], limbs[4], limbs[3], limbs[2], limbs[1], limbs[0]]);
+        // Convert to Montgomery form
+        d0 * R2 + d1 * R3
+    }
+
     /// Returns whether or not this element is strictly lexicographically
     /// larger than its negation.
     pub fn lexicographically_largest(&self) -> Choice {
@@ -436,7 +494,7 @@ impl Fp {
     }
 
     #[inline(always)]
-    const fn montgomery_reduce(
+    pub(crate) const fn montgomery_reduce(
         t0: u64,
         t1: u64,
         t2: u64,
@@ -787,7 +845,7 @@ fn test_debug() {
     assert_eq!(
         format!(
             "{:?}",
-             Fp([
+            Fp([
                 0x5360_bb59_7867_8032,
                 0x7dd2_75ae_799e_128e,
                 0x5c5b_5071_ce4f_4dcf,
@@ -930,6 +988,16 @@ fn test_lexicographic_largest() {
     ));
 }
 
+#[cfg(feature = "zeroize")]
+#[test]
+fn test_zeroize() {
+    use zeroize::Zeroize;
+
+    let mut a = Fp::one();
+    a.zeroize();
+    assert!(bool::from(a.is_zero()));
+}
+
 mod dusk {
     use super::*;
 
@@ -940,12 +1008,12 @@ mod dusk {
         }
     }
 
-    #[cfg(feature = "serde_req")]
+    #[cfg(feature = "serde")]
     use serde::{
         self, de::Visitor, ser::SerializeSeq, Deserialize, Deserializer, Serialize, Serializer,
     };
 
-    #[cfg(feature = "serde_req")]
+    #[cfg(feature = "serde")]
     impl Serialize for Fp {
         fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
         where
@@ -960,7 +1028,7 @@ mod dusk {
         }
     }
 
-    #[cfg(feature = "serde_req")]
+    #[cfg(feature = "serde")]
     impl<'de> Deserialize<'de> for Fp {
         fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
         where
@@ -999,7 +1067,7 @@ mod dusk {
     }
 
     #[test]
-    #[cfg(feature = "serde_req")]
+    #[cfg(feature = "serde")]
     fn fp_serde_roundtrip() {
         use bincode;
         let fp = Fp::one();
diff --git a/src/fp12.rs b/src/fp12.rs
index 2336bbdf..116684ea 100644
--- a/src/fp12.rs
+++ b/src/fp12.rs
@@ -6,14 +6,20 @@ use core::fmt;
 use core::ops::{Add, AddAssign, Mul, MulAssign, Neg, Sub, SubAssign};
 use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption};
 
+#[cfg(feature = "pairings")]
+use rand_core::RngCore;
+
 #[cfg(feature = "rkyv-impl")]
 use bytecheck::CheckBytes;
 #[cfg(feature = "rkyv-impl")]
 use rkyv::{Archive, Deserialize as RkyvDeserialize, Serialize as RkyvSerialize};
 
 /// This represents an element $c_0 + c_1 w$ of $\mathbb{F}_{p^12} = \mathbb{F}_{p^6} / w^2 - v$.
-#[cfg_attr(feature = "rkyv-impl", derive(Archive, RkyvSerialize, RkyvDeserialize))]
-#[cfg_attr(feature = "rkyv-impl", archive_attr(derive(CheckBytes)))]
+#[cfg_attr(
+    feature = "rkyv-impl",
+    derive(Archive, RkyvSerialize, RkyvDeserialize),
+    archive_attr(derive(CheckBytes))
+)]
 pub struct Fp12 {
     pub c0: Fp6,
     pub c1: Fp6,
@@ -66,6 +72,9 @@ impl Default for Fp12 {
     }
 }
 
+#[cfg(feature = "zeroize")]
+impl zeroize::DefaultIsZeroes for Fp12 {}
+
 impl fmt::Debug for Fp12 {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
         write!(f, "{:?} + ({:?})*w", self.c0, self.c1)
@@ -106,6 +115,14 @@ impl Fp12 {
         }
     }
 
+    #[cfg(feature = "pairings")]
+    pub(crate) fn random(mut rng: impl RngCore) -> Self {
+        Fp12 {
+            c0: Fp6::random(&mut rng),
+            c1: Fp6::random(&mut rng),
+        }
+    }
+
     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);
@@ -607,24 +624,21 @@ fn test_arithmetic() {
     // 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;
+    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.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 + b) * c.square(), (c * c * a) + (c * c * b));
 
     assert_eq!(
-        &a.invert().unwrap() * &b.invert().unwrap(),
-        (&a * &b).invert().unwrap()
+        a.invert().unwrap() * b.invert().unwrap(),
+        (a * b).invert().unwrap()
     );
-    assert_eq!(&a.invert().unwrap() * &a, Fp12::one());
+    assert_eq!(a.invert().unwrap() * a, Fp12::one());
 
     assert!(a != a.frobenius_map());
     assert_eq!(
@@ -643,3 +657,13 @@ fn test_arithmetic() {
             .frobenius_map()
     );
 }
+
+#[cfg(feature = "zeroize")]
+#[test]
+fn test_zeroize() {
+    use zeroize::Zeroize;
+
+    let mut a = Fp12::one();
+    a.zeroize();
+    assert!(bool::from(a.is_zero()));
+}
diff --git a/src/fp2.rs b/src/fp2.rs
index f1facc32..45c8bbe5 100644
--- a/src/fp2.rs
+++ b/src/fp2.rs
@@ -2,6 +2,8 @@
 
 use core::fmt;
 use core::ops::{Add, AddAssign, Mul, MulAssign, Neg, Sub, SubAssign};
+
+use rand_core::RngCore;
 use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption};
 
 #[cfg(feature = "rkyv-impl")]
@@ -12,8 +14,11 @@ use rkyv::{Archive, Deserialize as RkyvDeserialize, Serialize as RkyvSerialize};
 use crate::fp::Fp;
 
 #[derive(Copy, Clone)]
-#[cfg_attr(feature = "rkyv-impl", derive(Archive, RkyvSerialize, RkyvDeserialize))]
-#[cfg_attr(feature = "rkyv-impl", archive_attr(derive(CheckBytes)))]
+#[cfg_attr(
+    feature = "rkyv-impl",
+    derive(Archive, RkyvSerialize, RkyvDeserialize),
+    archive_attr(derive(CheckBytes))
+)]
 pub struct Fp2 {
     pub c0: Fp,
     pub c1: Fp,
@@ -31,6 +36,9 @@ impl Default for Fp2 {
     }
 }
 
+#[cfg(feature = "zeroize")]
+impl zeroize::DefaultIsZeroes for Fp2 {}
+
 impl From<Fp> for Fp2 {
     fn from(f: Fp) -> Fp2 {
         Fp2 {
@@ -50,7 +58,7 @@ impl Eq for Fp2 {}
 impl PartialEq for Fp2 {
     #[inline]
     fn eq(&self, other: &Self) -> bool {
-        self.ct_eq(other).unwrap_u8() == 1
+        bool::from(self.ct_eq(other))
     }
 }
 
@@ -132,6 +140,13 @@ impl Fp2 {
         self.c0.is_zero() & self.c1.is_zero()
     }
 
+    pub(crate) fn random(mut rng: impl RngCore) -> Fp2 {
+        Fp2 {
+            c0: Fp::random(&mut rng),
+            c1: Fp::random(&mut rng),
+        }
+    }
+
     /// Raises this element to p.
     #[inline(always)]
     pub fn frobenius_map(&self) -> Self {
@@ -330,6 +345,23 @@ impl Fp2 {
         }
         res
     }
+
+    /// Vartime exponentiation for larger exponents, only
+    /// used in testing and not exposed through the public API.
+    #[cfg(all(test, feature = "experimental"))]
+    pub(crate) fn pow_vartime_extended(&self, by: &[u64]) -> 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]
@@ -359,7 +391,7 @@ fn test_equality() {
         let eq = a == b;
         let ct_eq = a.ct_eq(&b);
 
-        assert_eq!(eq, ct_eq.unwrap_u8() == 1);
+        assert_eq!(eq, bool::from(ct_eq));
 
         eq
     }
@@ -865,7 +897,17 @@ fn test_lexicographic_largest() {
     ));
 }
 
-#[cfg(feature = "serde_req")]
+#[cfg(feature = "zeroize")]
+#[test]
+fn test_zeroize() {
+    use zeroize::Zeroize;
+
+    let mut a = Fp2::one();
+    a.zeroize();
+    assert!(bool::from(a.is_zero()));
+}
+
+#[cfg(feature = "serde")]
 mod dusk {
     use super::*;
 
diff --git a/src/fp6.rs b/src/fp6.rs
index 1112f8d6..9985f78d 100644
--- a/src/fp6.rs
+++ b/src/fp6.rs
@@ -5,14 +5,20 @@ use core::fmt;
 use core::ops::{Add, AddAssign, Mul, MulAssign, Neg, Sub, SubAssign};
 use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption};
 
+#[cfg(feature = "pairings")]
+use rand_core::RngCore;
+
 #[cfg(feature = "rkyv-impl")]
 use bytecheck::CheckBytes;
 #[cfg(feature = "rkyv-impl")]
 use rkyv::{Archive, Deserialize as RkyvDeserialize, Serialize as RkyvSerialize};
 
 /// 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$.
-#[cfg_attr(feature = "rkyv-impl", derive(Archive, RkyvSerialize, RkyvDeserialize))]
-#[cfg_attr(feature = "rkyv-impl", archive_attr(derive(CheckBytes)))]
+#[cfg_attr(
+    feature = "rkyv-impl",
+    derive(Archive, RkyvSerialize, RkyvDeserialize),
+    archive_attr(derive(CheckBytes))
+)]
 pub struct Fp6 {
     pub c0: Fp2,
     pub c1: Fp2,
@@ -59,6 +65,9 @@ impl Default for Fp6 {
     }
 }
 
+#[cfg(feature = "zeroize")]
+impl zeroize::DefaultIsZeroes for Fp6 {}
+
 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)
@@ -102,18 +111,20 @@ impl Fp6 {
         }
     }
 
-    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;
+    #[cfg(feature = "pairings")]
+    pub(crate) fn random(mut rng: impl RngCore) -> Self {
+        Fp6 {
+            c0: Fp2::random(&mut rng),
+            c1: Fp2::random(&mut rng),
+            c2: Fp2::random(&mut rng),
+        }
+    }
 
+    pub fn mul_by_1(&self, c1: &Fp2) -> Fp6 {
         Fp6 {
-            c0: t1,
-            c1: t2,
-            c2: b_b,
+            c0: (self.c2 * c1).mul_by_nonresidue(),
+            c1: self.c0 * c1,
+            c2: self.c1 * c1,
         }
     }
 
@@ -121,12 +132,11 @@ impl 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 t1 = (self.c2 * c1).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;
+        let t3 = self.c2 * c0 + b_b;
 
         Fp6 {
             c0: t1,
@@ -161,12 +171,12 @@ impl Fp6 {
             * Fp2 {
                 c0: Fp::zero(),
                 c1: Fp::from_raw_unchecked([
-                    0xcd03c9e48671f071,
-                    0x5dab22461fcda5d2,
-                    0x587042afd3851b95,
-                    0x8eb60ebe01bacb9e,
-                    0x3f97d6e83d050d2,
-                    0x18f0206554638741,
+                    0xcd03_c9e4_8671_f071,
+                    0x5dab_2246_1fcd_a5d2,
+                    0x5870_42af_d385_1b95,
+                    0x8eb6_0ebe_01ba_cb9e,
+                    0x03f9_7d6e_83d0_50d2,
+                    0x18f0_2065_5463_8741,
                 ]),
             };
 
@@ -174,12 +184,12 @@ impl Fp6 {
         let c2 = c2
             * Fp2 {
                 c0: Fp::from_raw_unchecked([
-                    0x890dc9e4867545c3,
-                    0x2af322533285a5d5,
-                    0x50880866309b7e2c,
-                    0xa20d1b8c7e881024,
-                    0x14e4f04fe2db9068,
-                    0x14e56d3f1564853a,
+                    0x890d_c9e4_8675_45c3,
+                    0x2af3_2253_3285_a5d5,
+                    0x5088_0866_309b_7e2c,
+                    0xa20d_1b8c_7e88_1024,
+                    0x14e4_f04f_e2db_9068,
+                    0x14e5_6d3f_1564_853a,
                 ]),
                 c1: Fp::zero(),
             };
@@ -547,23 +557,30 @@ fn test_arithmetic() {
         },
     };
 
-    assert_eq!(a.square(), &a * &a);
-    assert_eq!(b.square(), &b * &b);
-    assert_eq!(c.square(), &c * &c);
+    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 + b) * c.square(), (c * c * a) + (c * c * b));
 
     assert_eq!(
-        &a.invert().unwrap() * &b.invert().unwrap(),
-        (&a * &b).invert().unwrap()
+        a.invert().unwrap() * b.invert().unwrap(),
+        (a * b).invert().unwrap()
     );
-    assert_eq!(&a.invert().unwrap() * &a, Fp6::one());
+    assert_eq!(a.invert().unwrap() * a, Fp6::one());
+}
+
+#[cfg(feature = "zeroize")]
+#[test]
+fn test_zeroize() {
+    use zeroize::Zeroize;
+
+    let mut a = Fp6::one();
+    a.zeroize();
+    assert!(bool::from(a.is_zero()));
 }
 
-#[cfg(feature = "serde_req")]
+#[cfg(feature = "serde")]
 mod dusk {
     use super::*;
     use serde::{
diff --git a/src/g1.rs b/src/g1.rs
index 61a5717a..5add6189 100644
--- a/src/g1.rs
+++ b/src/g1.rs
@@ -1,12 +1,22 @@
 //! This module provides an implementation of the $\mathbb{G}_1$ group of BLS12-381.
 
 use core::borrow::Borrow;
+use core::fmt;
 use core::iter::Sum;
 use core::ops::{Add, AddAssign, Mul, MulAssign, Neg, Sub, SubAssign};
 
-use dusk_bytes::{Error as BytesError, HexDebug, Serializable};
+use group::{
+    prime::{PrimeCurve, PrimeCurveAffine, PrimeGroup},
+    Curve, Group, GroupEncoding, UncompressedEncoding,
+};
+use rand_core::RngCore;
+
+use dusk_bytes::{Error as BytesError, Serializable};
 use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption};
 
+#[cfg(feature = "alloc")]
+use group::WnafGroup;
+
 use crate::choice;
 use crate::fp::Fp;
 use crate::BlsScalar;
@@ -22,9 +32,13 @@ use rkyv::{Archive, Deserialize as RkyvDeserialize, Serialize as RkyvSerialize};
 ///
 /// Values of `G1Affine` are guaranteed to be in the $q$-order subgroup unless an
 /// "unchecked" API was misused.
-#[derive(Copy, Clone, HexDebug)]
-#[cfg_attr(feature = "rkyv-impl", derive(Archive, RkyvSerialize, RkyvDeserialize))]
-#[cfg_attr(feature = "rkyv-impl", archive_attr(derive(CheckBytes)))]
+#[cfg_attr(docsrs, doc(cfg(feature = "groups")))]
+#[derive(Copy, Clone, Debug)]
+#[cfg_attr(
+    feature = "rkyv-impl",
+    derive(Archive, RkyvSerialize, RkyvDeserialize),
+    archive_attr(derive(CheckBytes))
+)]
 pub struct G1Affine {
     pub(crate) x: Fp,
     pub(crate) y: Fp,
@@ -37,13 +51,20 @@ impl Default for G1Affine {
     }
 }
 
+#[cfg(feature = "zeroize")]
+impl zeroize::DefaultIsZeroes for G1Affine {}
+
+impl fmt::Display for G1Affine {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        write!(f, "{:?}", self)
+    }
+}
+
 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 x = p.x * zinv;
+        let y = p.y * zinv;
 
         let tmp = G1Affine {
             x,
@@ -208,6 +229,179 @@ impl G1Affine {
         }
     }
 
+    /// 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.into()).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.into());
+
+        // 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),
+            (!Choice::from(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.into()).to_bytes()[..],
+        );
+        res[48..96].copy_from_slice(
+            &Fp::conditional_select(&self.y, &Fp::zero(), self.infinity.into()).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.into());
+
+        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.into(),
+                    },
+                    &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.into(),
+                        },
+                        (!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 {
@@ -251,19 +445,38 @@ fn endomorphism(p: &G1Affine) -> G1Affine {
     // Endomorphism of the points on the curve.
     // endomorphism_p(x,y) = (BETA * x, y)
     // where BETA is a non-trivial cubic root of unity in Fq.
-    let mut res = p.clone();
+    let mut res = *p;
     res.x *= BETA;
     res
 }
 
 /// This is an element of $\mathbb{G}_1$ represented in the projective coordinate space.
+#[cfg_attr(docsrs, doc(cfg(feature = "groups")))]
 #[derive(Copy, Clone, Debug)]
-#[cfg_attr(feature = "rkyv-impl", derive(Archive, RkyvSerialize, RkyvDeserialize))]
-#[cfg_attr(feature = "rkyv-impl", archive_attr(derive(CheckBytes)))]
+#[cfg_attr(
+    feature = "rkyv-impl",
+    derive(Archive, RkyvSerialize, RkyvDeserialize),
+    archive_attr(derive(CheckBytes))
+)]
 pub struct G1Projective {
-    x: Fp,
-    y: Fp,
-    z: Fp,
+    pub(crate) x: Fp,
+    pub(crate) y: Fp,
+    pub(crate) z: Fp,
+}
+
+impl Default for G1Projective {
+    fn default() -> G1Projective {
+        G1Projective::identity()
+    }
+}
+
+#[cfg(feature = "zeroize")]
+impl zeroize::DefaultIsZeroes for G1Projective {}
+
+impl fmt::Display for G1Projective {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        write!(f, "{:?}", self)
+    }
 }
 
 impl<'a> From<&'a G1Affine> for G1Projective {
@@ -284,16 +497,13 @@ impl From<G1Affine> for G1Projective {
 
 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?
+        // Is (xz, yz, z) equal to (x'z', y'z', z') when converted to affine?
+
+        let x1 = self.x * other.z;
+        let x2 = other.x * self.z;
 
-        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 y1 = self.y * other.z;
+        let y2 = other.y * self.z;
 
         let self_is_zero = self.z.is_zero();
         let other_is_zero = other.z.is_zero();
@@ -378,9 +588,36 @@ impl<'a, 'b> Mul<&'b BlsScalar> for &'a G1Affine {
     }
 }
 
+impl<'a, 'b> Mul<&'b G1Projective> for &'a BlsScalar {
+    type Output = G1Projective;
+
+    #[inline]
+    fn mul(self, rhs: &'b G1Projective) -> Self::Output {
+        rhs * self
+    }
+}
+
+impl<'a, 'b> Mul<&'b G1Affine> for &'a BlsScalar {
+    type Output = G1Projective;
+
+    #[inline]
+    fn mul(self, rhs: &'b G1Affine) -> Self::Output {
+        rhs * self
+    }
+}
+
 impl_binops_additive!(G1Projective, G1Projective);
 impl_binops_multiplicative!(G1Projective, BlsScalar);
 impl_binops_multiplicative_mixed!(G1Affine, BlsScalar, G1Projective);
+impl_binops_multiplicative_mixed!(BlsScalar, G1Affine, G1Projective);
+impl_binops_multiplicative_mixed!(BlsScalar, G1Projective, G1Projective);
+
+#[inline(always)]
+fn mul_by_3b(a: Fp) -> Fp {
+    let a = a + a; // 2
+    let a = a + a; // 4
+    a + a + a // 12
+}
 
 impl G1Projective {
     /// Returns the identity of the group: the point at infinity.
@@ -418,26 +655,26 @@ impl G1Projective {
 
     /// 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;
+        // Algorithm 9, https://eprint.iacr.org/2015/1060.pdf
+
+        let t0 = self.y.square();
+        let z3 = t0 + t0;
         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 z3 = z3 + z3;
+        let t1 = self.y * self.z;
+        let t2 = self.z.square();
+        let t2 = mul_by_3b(t2);
+        let x3 = t2 * z3;
+        let y3 = t0 + t2;
+        let z3 = t1 * z3;
+        let t1 = t2 + t2;
+        let t2 = t1 + t2;
+        let t0 = t0 - t2;
+        let y3 = t0 * y3;
+        let y3 = x3 + y3;
+        let t1 = self.x * self.y;
+        let x3 = t0 * t1;
+        let x3 = x3 + x3;
 
         let tmp = G1Projective {
             x: x3,
@@ -450,139 +687,79 @@ impl G1Projective {
 
     /// 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();
-        // We allow rhs.z != 1, so we must account for this.
-        let z3 = m_alt * self.z * rhs.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;
+        // Algorithm 7, https://eprint.iacr.org/2015/1060.pdf
+
+        let t0 = self.x * rhs.x;
+        let t1 = self.y * rhs.y;
+        let t2 = self.z * rhs.z;
+        let t3 = self.x + self.y;
+        let t4 = rhs.x + rhs.y;
+        let t3 = t3 * t4;
+        let t4 = t0 + t1;
+        let t3 = t3 - t4;
+        let t4 = self.y + self.z;
+        let x3 = rhs.y + rhs.z;
+        let t4 = t4 * x3;
+        let x3 = t1 + t2;
+        let t4 = t4 - x3;
+        let x3 = self.x + self.z;
+        let y3 = rhs.x + rhs.z;
+        let x3 = x3 * y3;
+        let y3 = t0 + t2;
+        let y3 = x3 - y3;
+        let x3 = t0 + t0;
+        let t0 = x3 + t0;
+        let t2 = mul_by_3b(t2);
+        let z3 = t1 + t2;
+        let t1 = t1 - t2;
+        let y3 = mul_by_3b(y3);
+        let x3 = t4 * y3;
+        let t2 = t3 * t1;
+        let x3 = t2 - x3;
+        let y3 = y3 * t0;
+        let t1 = t1 * z3;
+        let y3 = t1 + y3;
+        let t0 = t0 * t3;
+        let z3 = z3 * t4;
+        let z3 = z3 + t0;
 
-        let tmp = G1Projective {
+        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;
+        // Algorithm 8, https://eprint.iacr.org/2015/1060.pdf
+
+        let t0 = self.x * rhs.x;
+        let t1 = self.y * rhs.y;
+        let t3 = rhs.x + rhs.y;
+        let t4 = self.x + self.y;
+        let t3 = t3 * t4;
+        let t4 = t0 + t1;
+        let t3 = t3 - t4;
+        let t4 = rhs.y * self.z;
+        let t4 = t4 + self.y;
+        let y3 = rhs.x * self.z;
+        let y3 = y3 + self.x;
+        let x3 = t0 + t0;
+        let t0 = x3 + t0;
+        let t2 = mul_by_3b(self.z);
+        let z3 = t1 + t2;
+        let t1 = t1 - t2;
+        let y3 = mul_by_3b(y3);
+        let x3 = t4 * y3;
+        let t2 = t3 * t1;
+        let x3 = t2 - x3;
+        let y3 = y3 * t0;
+        let t1 = t1 * z3;
+        let y3 = t1 + y3;
+        let t0 = t0 * t3;
+        let z3 = z3 * t4;
+        let z3 = z3 + t0;
 
         let tmp = G1Projective {
             x: x3,
@@ -590,7 +767,7 @@ impl G1Projective {
             z: z3,
         };
 
-        G1Projective::conditional_select(&res, &tmp, (!f1) & (!f2) & (!f3))
+        G1Projective::conditional_select(&tmp, self, rhs.is_identity())
     }
 
     fn multiply(&self, by: &[u8; 32]) -> G1Projective {
@@ -672,14 +849,11 @@ impl G1Projective {
             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.x = p.x * tmp;
+            q.y = p.y * tmp;
             q.infinity = 0u8.into();
 
-            *q = G1Affine::conditional_select(&q, &G1Affine::identity(), skip);
+            *q = G1Affine::conditional_select(q, &G1Affine::identity(), skip);
         }
     }
 
@@ -692,14 +866,260 @@ impl G1Projective {
     /// 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)
+        // Y^2 Z = X^3 + b Z^3
 
-        (self.y.square() - (self.x.square() * self.x))
-            .ct_eq(&((self.z.square() * self.z).square() * B))
+        (self.y.square() * self.z).ct_eq(&(self.x.square() * self.x + self.z.square() * self.z * B))
             | self.z.is_zero()
     }
 }
 
+#[derive(Clone, Copy)]
+#[cfg_attr(
+    feature = "rkyv-impl",
+    derive(Archive, RkyvSerialize, RkyvDeserialize),
+    archive_attr(derive(CheckBytes))
+)]
+pub struct G1Compressed([u8; 48]);
+
+impl fmt::Debug for G1Compressed {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        self.0[..].fmt(f)
+    }
+}
+
+impl Default for G1Compressed {
+    fn default() -> Self {
+        G1Compressed([0; 48])
+    }
+}
+
+#[cfg(feature = "zeroize")]
+impl zeroize::DefaultIsZeroes for G1Compressed {}
+
+impl AsRef<[u8]> for G1Compressed {
+    fn as_ref(&self) -> &[u8] {
+        &self.0
+    }
+}
+
+impl AsMut<[u8]> for G1Compressed {
+    fn as_mut(&mut self) -> &mut [u8] {
+        &mut self.0
+    }
+}
+
+impl ConstantTimeEq for G1Compressed {
+    fn ct_eq(&self, other: &Self) -> Choice {
+        self.0.ct_eq(&other.0)
+    }
+}
+
+impl Eq for G1Compressed {}
+impl PartialEq for G1Compressed {
+    #[inline]
+    fn eq(&self, other: &Self) -> bool {
+        bool::from(self.ct_eq(other))
+    }
+}
+
+#[derive(Clone, Copy)]
+#[cfg_attr(
+    feature = "rkyv-impl",
+    derive(Archive, RkyvSerialize, RkyvDeserialize),
+    archive_attr(derive(CheckBytes))
+)]
+pub struct G1Uncompressed([u8; 96]);
+
+impl fmt::Debug for G1Uncompressed {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        self.0[..].fmt(f)
+    }
+}
+
+impl Default for G1Uncompressed {
+    fn default() -> Self {
+        G1Uncompressed([0; 96])
+    }
+}
+
+#[cfg(feature = "zeroize")]
+impl zeroize::DefaultIsZeroes for G1Uncompressed {}
+
+impl AsRef<[u8]> for G1Uncompressed {
+    fn as_ref(&self) -> &[u8] {
+        &self.0
+    }
+}
+
+impl AsMut<[u8]> for G1Uncompressed {
+    fn as_mut(&mut self) -> &mut [u8] {
+        &mut self.0
+    }
+}
+
+impl ConstantTimeEq for G1Uncompressed {
+    fn ct_eq(&self, other: &Self) -> Choice {
+        self.0.ct_eq(&other.0)
+    }
+}
+
+impl Eq for G1Uncompressed {}
+impl PartialEq for G1Uncompressed {
+    #[inline]
+    fn eq(&self, other: &Self) -> bool {
+        bool::from(self.ct_eq(other))
+    }
+}
+
+impl Group for G1Projective {
+    type Scalar = BlsScalar;
+
+    fn random(mut rng: impl RngCore) -> Self {
+        loop {
+            let x = Fp::random(&mut rng);
+            let flip_sign = rng.next_u32() % 2 != 0;
+
+            // Obtain the corresponding y-coordinate given x as y = sqrt(x^3 + 4)
+            let p = ((x.square() * x) + B).sqrt().map(|y| G1Affine {
+                x,
+                y: if flip_sign { -y } else { y },
+                infinity: 0.into(),
+            });
+
+            if p.is_some().into() {
+                let p = p.unwrap().to_curve().clear_cofactor();
+
+                if bool::from(!p.is_identity()) {
+                    return p;
+                }
+            }
+        }
+    }
+
+    fn identity() -> Self {
+        Self::identity()
+    }
+
+    fn generator() -> Self {
+        Self::generator()
+    }
+
+    fn is_identity(&self) -> Choice {
+        self.is_identity()
+    }
+
+    #[must_use]
+    fn double(&self) -> Self {
+        self.double()
+    }
+}
+
+#[cfg(feature = "alloc")]
+impl WnafGroup for G1Projective {
+    fn recommended_wnaf_for_num_scalars(num_scalars: usize) -> usize {
+        const RECOMMENDATIONS: [usize; 12] =
+            [1, 3, 7, 20, 43, 120, 273, 563, 1630, 3128, 7933, 62569];
+
+        let mut ret = 4;
+        for r in &RECOMMENDATIONS {
+            if num_scalars > *r {
+                ret += 1;
+            } else {
+                break;
+            }
+        }
+
+        ret
+    }
+}
+
+impl PrimeGroup for G1Projective {}
+
+impl Curve for G1Projective {
+    type AffineRepr = G1Affine;
+
+    fn batch_normalize(p: &[Self], q: &mut [Self::AffineRepr]) {
+        Self::batch_normalize(p, q);
+    }
+
+    fn to_affine(&self) -> Self::AffineRepr {
+        self.into()
+    }
+}
+
+impl PrimeCurve for G1Projective {
+    type Affine = G1Affine;
+}
+
+impl PrimeCurveAffine for G1Affine {
+    type Scalar = BlsScalar;
+    type Curve = G1Projective;
+
+    fn identity() -> Self {
+        Self::identity()
+    }
+
+    fn generator() -> Self {
+        Self::generator()
+    }
+
+    fn is_identity(&self) -> Choice {
+        self.is_identity()
+    }
+
+    fn to_curve(&self) -> Self::Curve {
+        self.into()
+    }
+}
+
+impl GroupEncoding for G1Projective {
+    type Repr = G1Compressed;
+
+    fn from_bytes(bytes: &Self::Repr) -> CtOption<Self> {
+        <G1Affine as GroupEncoding>::from_bytes(bytes).map(Self::from)
+    }
+
+    fn from_bytes_unchecked(bytes: &Self::Repr) -> CtOption<Self> {
+        G1Affine::from_bytes_unchecked(bytes).map(Self::from)
+    }
+
+    fn to_bytes(&self) -> Self::Repr {
+        <G1Affine as GroupEncoding>::to_bytes(&G1Affine::from(self))
+    }
+}
+
+impl GroupEncoding for G1Affine {
+    type Repr = G1Compressed;
+
+    fn from_bytes(bytes: &Self::Repr) -> CtOption<Self> {
+        Self::from_compressed(&bytes.0)
+    }
+
+    fn from_bytes_unchecked(bytes: &Self::Repr) -> CtOption<Self> {
+        Self::from_compressed_unchecked(&bytes.0)
+    }
+
+    fn to_bytes(&self) -> Self::Repr {
+        G1Compressed(self.to_compressed())
+    }
+}
+
+impl UncompressedEncoding for G1Affine {
+    type Uncompressed = G1Uncompressed;
+
+    fn from_uncompressed(bytes: &Self::Uncompressed) -> CtOption<Self> {
+        Self::from_uncompressed(&bytes.0)
+    }
+
+    fn from_uncompressed_unchecked(bytes: &Self::Uncompressed) -> CtOption<Self> {
+        Self::from_uncompressed_unchecked(&bytes.0)
+    }
+
+    fn to_uncompressed(&self) -> Self::Uncompressed {
+        G1Uncompressed(self.to_uncompressed())
+    }
+}
+
 #[test]
 fn test_beta() {
     assert_eq!(
@@ -716,7 +1136,6 @@ fn test_beta() {
     assert_ne!(BETA * BETA, Fp::one());
     assert_eq!(BETA * BETA * BETA, Fp::one());
 }
-
 #[test]
 fn test_is_on_curve() {
     assert!(bool::from(G1Affine::identity().is_on_curve()));
@@ -735,8 +1154,8 @@ fn test_is_on_curve() {
 
     let gen = G1Affine::generator();
     let mut test = G1Projective {
-        x: gen.x * (z.square()),
-        y: gen.y * (z.square() * z),
+        x: gen.x * z,
+        y: gen.y * z,
         z,
     };
 
@@ -747,6 +1166,7 @@ fn test_is_on_curve() {
 }
 
 #[test]
+#[allow(clippy::eq_op)]
 fn test_affine_point_equality() {
     let a = G1Affine::generator();
     let b = G1Affine::identity();
@@ -758,6 +1178,7 @@ fn test_affine_point_equality() {
 }
 
 #[test]
+#[allow(clippy::eq_op)]
 fn test_projective_point_equality() {
     let a = G1Projective::generator();
     let b = G1Projective::identity();
@@ -777,8 +1198,8 @@ fn test_projective_point_equality() {
     ]);
 
     let mut c = G1Projective {
-        x: a.x * (z.square()),
-        y: a.y * (z.square() * z),
+        x: a.x * z,
+        y: a.y * z,
         z,
     };
     assert!(bool::from(c.is_on_curve()));
@@ -848,8 +1269,8 @@ fn test_projective_to_affine() {
     ]);
 
     let c = G1Projective {
-        x: a.x * (z.square()),
-        y: a.y * (z.square() * z),
+        x: a.x * z,
+        y: a.y * z,
         z,
     };
 
@@ -927,8 +1348,8 @@ fn test_projective_addition() {
             ]);
 
             b = G1Projective {
-                x: b.x * (z.square()),
-                y: b.y * (z.square() * z),
+                x: b.x * z,
+                y: b.y * z,
                 z,
             };
         }
@@ -951,8 +1372,8 @@ fn test_projective_addition() {
             ]);
 
             b = G1Projective {
-                x: b.x * (z.square()),
-                y: b.y * (z.square() * z),
+                x: b.x * z,
+                y: b.y * z,
                 z,
             };
         }
@@ -968,7 +1389,7 @@ fn test_projective_addition() {
 
         let mut d = G1Projective::generator();
         for _ in 0..5 {
-            d = d + G1Projective::generator();
+            d += G1Projective::generator();
         }
         assert!(!bool::from(c.is_identity()));
         assert!(bool::from(c.is_on_curve()));
@@ -1048,8 +1469,8 @@ fn test_mixed_addition() {
             ]);
 
             b = G1Projective {
-                x: b.x * (z.square()),
-                y: b.y * (z.square() * z),
+                x: b.x * z,
+                y: b.y * z,
                 z,
             };
         }
@@ -1072,8 +1493,8 @@ fn test_mixed_addition() {
             ]);
 
             b = G1Projective {
-                x: b.x * (z.square()),
-                y: b.y * (z.square() * z),
+                x: b.x * z,
+                y: b.y * z,
                 z,
             };
         }
@@ -1089,7 +1510,7 @@ fn test_mixed_addition() {
 
         let mut d = G1Projective::generator();
         for _ in 0..5 {
-            d = d + G1Affine::generator();
+            d += G1Affine::generator();
         }
         assert!(!bool::from(c.is_identity()));
         assert!(bool::from(c.is_on_curve()));
@@ -1148,6 +1569,7 @@ fn test_mixed_addition() {
 }
 
 #[test]
+#[allow(clippy::eq_op)]
 fn test_projective_negation_and_subtraction() {
     let a = G1Projective::generator().double();
     assert_eq!(a + (-a), G1Projective::identity());
@@ -1253,6 +1675,15 @@ fn test_clear_cofactor() {
     let id = G1Projective::identity();
     assert!(bool::from(id.clear_cofactor().is_on_curve()));
 
+    let z = Fp::from_raw_unchecked([
+        0x3d2d1c670671394e,
+        0x0ee3a800a2f7c1ca,
+        0x270f4f21da2e5050,
+        0xe02840a53f1be768,
+        0x55debeb597512690,
+        0x08bd25353dc8f791,
+    ]);
+
     let point = G1Projective {
         x: Fp::from_raw_unchecked([
             0x48af5ff540c817f0,
@@ -1261,7 +1692,7 @@ fn test_clear_cofactor() {
             0x1eda39c30f188b3e,
             0xf618c6d3ccc0f8d8,
             0x0073542cd671e16c,
-        ]),
+        ]) * z,
         y: Fp::from_raw_unchecked([
             0x57bf8be79461d0ba,
             0xfc61459cee3547c3,
@@ -1270,14 +1701,7 @@ fn test_clear_cofactor() {
             0xb0c8cfbe9dc8fdc1,
             0x1328661767ef368b,
         ]),
-        z: Fp::from_raw_unchecked([
-            0x3d2d1c670671394e,
-            0x0ee3a800a2f7c1ca,
-            0x270f4f21da2e5050,
-            0xe02840a53f1be768,
-            0x55debeb597512690,
-            0x08bd25353dc8f791,
-        ]),
+        z: z.square() * z,
     };
 
     assert!(bool::from(point.is_on_curve()));
@@ -1298,9 +1722,9 @@ fn test_batch_normalize() {
     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) {
+    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()
@@ -1331,10 +1755,59 @@ fn test_batch_normalize() {
     }
 }
 
+#[cfg(feature = "zeroize")]
+#[test]
+fn test_zeroize() {
+    use zeroize::Zeroize;
+
+    let mut a = G1Affine::generator();
+    a.zeroize();
+    assert!(bool::from(a.is_identity()));
+
+    let mut a = G1Projective::generator();
+    a.zeroize();
+    assert!(bool::from(a.is_identity()));
+
+    let mut a = GroupEncoding::to_bytes(&G1Affine::generator());
+    a.zeroize();
+    assert_eq!(&a, &G1Compressed::default());
+
+    let mut a = UncompressedEncoding::to_uncompressed(&G1Affine::generator());
+    a.zeroize();
+    assert_eq!(&a, &G1Uncompressed::default());
+}
+
+#[test]
+fn test_commutative_scalar_subgroup_multiplication() {
+    let a = BlsScalar::from_raw([
+        0x1fff_3231_233f_fffd,
+        0x4884_b7fa_0003_4802,
+        0x998c_4fef_ecbc_4ff3,
+        0x1824_b159_acc5_0562,
+    ]);
+
+    let g1_a = G1Affine::generator();
+    let g1_p = G1Projective::generator();
+
+    // By reference.
+    assert_eq!(&g1_a * &a, &a * &g1_a);
+    assert_eq!(&g1_p * &a, &a * &g1_p);
+
+    // Mixed
+    assert_eq!(&g1_a * a.clone(), a.clone() * &g1_a);
+    assert_eq!(&g1_p * a.clone(), a.clone() * &g1_p);
+    assert_eq!(g1_a.clone() * &a, &a * g1_a.clone());
+    assert_eq!(g1_p.clone() * &a, &a * g1_p.clone());
+
+    // By value.
+    assert_eq!(g1_p * a, a * g1_p);
+    assert_eq!(g1_a * a, a * g1_a);
+}
+
 mod dusk {
     use super::*;
 
-    #[cfg(feature = "serde_req")]
+    #[cfg(feature = "serde")]
     use serde::{de::Visitor, Deserialize, Deserializer, Serialize, Serializer};
 
     impl G1Affine {
@@ -1494,7 +1967,7 @@ mod dusk {
         }
     }
 
-    #[cfg(feature = "serde_req")]
+    #[cfg(feature = "serde")]
     impl Serialize for G1Affine {
         fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
         where
@@ -1502,14 +1975,14 @@ mod dusk {
         {
             use serde::ser::SerializeTuple;
             let mut tup = serializer.serialize_tuple(G1Affine::SIZE)?;
-            for byte in self.to_bytes().iter() {
+            for byte in <Self as Serializable<48>>::to_bytes(self).iter() {
                 tup.serialize_element(byte)?;
             }
             tup.end()
         }
     }
 
-    #[cfg(feature = "serde_req")]
+    #[cfg(feature = "serde")]
     impl<'de> Deserialize<'de> for G1Affine {
         fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
         where
@@ -1536,7 +2009,7 @@ mod dusk {
                             .ok_or(serde::de::Error::invalid_length(i, &"expected 48 bytes"))?;
                     }
 
-                    G1Affine::from_bytes(&bytes).map_err(|_| {
+                    <G1Affine as Serializable<48>>::from_bytes(&bytes).map_err(|_| {
                         serde::de::Error::custom(&"compressed G1Affine was not canonically encoded")
                     })
                 }
@@ -1547,7 +2020,7 @@ mod dusk {
     }
 
     #[test]
-    #[cfg(feature = "serde_req")]
+    #[cfg(feature = "serde")]
     fn g1_affine_serde_roundtrip() {
         use bincode;
 
@@ -1601,21 +2074,4 @@ mod dusk {
 
         assert_eq!(g, g_p);
     }
-
-    #[test]
-    fn g1_affine_hex() {
-        use dusk_bytes::ParseHexStr;
-
-        let gen = G1Affine::generator();
-        let ident = G1Affine::identity();
-
-        let gen_p = format!("{:x}", gen);
-        let gen_p = G1Affine::from_hex_str(gen_p.as_str()).unwrap();
-
-        let ident_p = format!("{:x}", ident);
-        let ident_p = G1Affine::from_hex_str(ident_p.as_str()).unwrap();
-
-        assert_eq!(gen, gen_p);
-        assert_eq!(ident, ident_p);
-    }
 }
diff --git a/src/g2.rs b/src/g2.rs
index e6cd549d..a51c9828 100644
--- a/src/g2.rs
+++ b/src/g2.rs
@@ -1,12 +1,21 @@
 //! This module provides an implementation of the $\mathbb{G}_2$ group of BLS12-381.
 
 use core::borrow::Borrow;
+use core::fmt;
 use core::iter::Sum;
 use core::ops::{Add, AddAssign, Mul, MulAssign, Neg, Sub, SubAssign};
 
-use dusk_bytes::{Error as BytesError, HexDebug, Serializable};
+use dusk_bytes::{Error as BytesError, Serializable};
+use group::{
+    prime::{PrimeCurve, PrimeCurveAffine, PrimeGroup},
+    Curve, Group, GroupEncoding, UncompressedEncoding,
+};
+use rand_core::RngCore;
 use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption};
 
+#[cfg(feature = "alloc")]
+use group::WnafGroup;
+
 use crate::choice;
 use crate::fp::Fp;
 use crate::fp2::Fp2;
@@ -23,9 +32,13 @@ use rkyv::{Archive, Deserialize as RkyvDeserialize, Serialize as RkyvSerialize};
 ///
 /// Values of `G2Affine` are guaranteed to be in the $q$-order subgroup unless an
 /// "unchecked" API was misused.
-#[derive(Copy, Clone, HexDebug)]
-#[cfg_attr(feature = "rkyv-impl", derive(Archive, RkyvSerialize, RkyvDeserialize))]
-#[cfg_attr(feature = "rkyv-impl", archive_attr(derive(CheckBytes)))]
+#[cfg_attr(docsrs, doc(cfg(feature = "groups")))]
+#[derive(Copy, Clone, Debug)]
+#[cfg_attr(
+    feature = "rkyv-impl",
+    derive(Archive, RkyvSerialize, RkyvDeserialize),
+    archive_attr(derive(CheckBytes))
+)]
 pub struct G2Affine {
     pub(crate) x: Fp2,
     pub(crate) y: Fp2,
@@ -38,13 +51,20 @@ impl Default for G2Affine {
     }
 }
 
+#[cfg(feature = "zeroize")]
+impl zeroize::DefaultIsZeroes for G2Affine {}
+
+impl fmt::Display for G2Affine {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        write!(f, "{:?}", self)
+    }
+}
+
 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 x = p.x * zinv;
+        let y = p.y * zinv;
 
         let tmp = G2Affine {
             x,
@@ -186,6 +206,8 @@ const B: Fp2 = Fp2 {
     ]),
 };
 
+const B3: Fp2 = Fp2::add(&Fp2::add(&B, &B), &B);
+
 impl G2Affine {
     /// Bytes size of the raw representation
     pub const RAW_SIZE: usize = 193;
@@ -243,6 +265,220 @@ impl G2Affine {
         }
     }
 
+    /// 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.into());
+
+        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.into());
+
+        // 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),
+            (!Choice::from(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.into());
+        let y = Fp2::conditional_select(&self.y, &Fp2::zero(), self.infinity.into());
+
+        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.into());
+
+        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.into(),
+                            },
+                            &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.into(),
+                            },
+                            (!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 {
@@ -255,7 +491,7 @@ impl G2Affine {
     pub fn is_torsion_free(&self) -> Choice {
         // Algorithm from Section 4 of https://eprint.iacr.org/2021/1130
         // Updated proof of correctness in https://eprint.iacr.org/2022/352
-        //
+        //.into()
         // Check that psi(P) == [x] P
         let p = G2Projective::from(self);
         p.psi().ct_eq(&p.mul_by_x())
@@ -271,15 +507,34 @@ impl G2Affine {
 }
 
 /// This is an element of $\mathbb{G}_2$ represented in the projective coordinate space.
+#[cfg_attr(docsrs, doc(cfg(feature = "groups")))]
 #[derive(Copy, Clone, Debug)]
-#[cfg_attr(feature = "rkyv-impl", derive(Archive, RkyvSerialize, RkyvDeserialize))]
-#[cfg_attr(feature = "rkyv-impl", archive_attr(derive(CheckBytes)))]
+#[cfg_attr(
+    feature = "rkyv-impl",
+    derive(Archive, RkyvSerialize, RkyvDeserialize),
+    archive_attr(derive(CheckBytes))
+)]
 pub struct G2Projective {
     pub(crate) x: Fp2,
     pub(crate) y: Fp2,
     pub(crate) z: Fp2,
 }
 
+impl Default for G2Projective {
+    fn default() -> G2Projective {
+        G2Projective::identity()
+    }
+}
+
+#[cfg(feature = "zeroize")]
+impl zeroize::DefaultIsZeroes for G2Projective {}
+
+impl fmt::Display for G2Projective {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        write!(f, "{:?}", self)
+    }
+}
+
 impl<'a> From<&'a G2Affine> for G2Projective {
     fn from(p: &'a G2Affine) -> G2Projective {
         G2Projective {
@@ -298,16 +553,13 @@ impl From<G2Affine> for G2Projective {
 
 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?
+        // Is (xz, yz, z) equal to (x'z', y'z', 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 x1 = self.x * other.z;
+        let x2 = other.x * self.z;
+
+        let y1 = self.y * other.z;
+        let y2 = other.y * self.z;
 
         let self_is_zero = self.z.is_zero();
         let other_is_zero = other.z.is_zero();
@@ -384,6 +636,15 @@ impl<'a, 'b> Mul<&'b BlsScalar> for &'a G2Projective {
     }
 }
 
+impl<'a, 'b> Mul<&'b G2Projective> for &'a BlsScalar {
+    type Output = G2Projective;
+
+    #[inline]
+    fn mul(self, rhs: &'b G2Projective) -> Self::Output {
+        rhs * self
+    }
+}
+
 impl<'a, 'b> Mul<&'b BlsScalar> for &'a G2Affine {
     type Output = G2Projective;
 
@@ -392,9 +653,25 @@ impl<'a, 'b> Mul<&'b BlsScalar> for &'a G2Affine {
     }
 }
 
+impl<'a, 'b> Mul<&'b G2Affine> for &'a BlsScalar {
+    type Output = G2Projective;
+
+    #[inline]
+    fn mul(self, rhs: &'b G2Affine) -> Self::Output {
+        rhs * self
+    }
+}
+
 impl_binops_additive!(G2Projective, G2Projective);
 impl_binops_multiplicative!(G2Projective, BlsScalar);
 impl_binops_multiplicative_mixed!(G2Affine, BlsScalar, G2Projective);
+impl_binops_multiplicative_mixed!(BlsScalar, G2Affine, G2Projective);
+impl_binops_multiplicative_mixed!(BlsScalar, G2Projective, G2Projective);
+
+#[inline(always)]
+fn mul_by_3b(x: Fp2) -> Fp2 {
+    x * B3
+}
 
 impl G2Projective {
     /// Returns the identity of the group: the point at infinity.
@@ -452,26 +729,26 @@ impl G2Projective {
 
     /// 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;
+        // Algorithm 9, https://eprint.iacr.org/2015/1060.pdf
+
+        let t0 = self.y.square();
+        let z3 = t0 + t0;
         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 z3 = z3 + z3;
+        let t1 = self.y * self.z;
+        let t2 = self.z.square();
+        let t2 = mul_by_3b(t2);
+        let x3 = t2 * z3;
+        let y3 = t0 + t2;
+        let z3 = t1 * z3;
+        let t1 = t2 + t2;
+        let t2 = t1 + t2;
+        let t0 = t0 - t2;
+        let y3 = t0 * y3;
+        let y3 = x3 + y3;
+        let t1 = self.x * self.y;
+        let x3 = t0 * t1;
+        let x3 = x3 + x3;
 
         let tmp = G2Projective {
             x: x3,
@@ -484,138 +761,79 @@ impl G2Projective {
 
     /// 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();
-        // We allow rhs.z != 1, so we must account for this.
-        let z3 = m_alt * self.z * rhs.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;
+        // Algorithm 7, https://eprint.iacr.org/2015/1060.pdf
+
+        let t0 = self.x * rhs.x;
+        let t1 = self.y * rhs.y;
+        let t2 = self.z * rhs.z;
+        let t3 = self.x + self.y;
+        let t4 = rhs.x + rhs.y;
+        let t3 = t3 * t4;
+        let t4 = t0 + t1;
+        let t3 = t3 - t4;
+        let t4 = self.y + self.z;
+        let x3 = rhs.y + rhs.z;
+        let t4 = t4 * x3;
+        let x3 = t1 + t2;
+        let t4 = t4 - x3;
+        let x3 = self.x + self.z;
+        let y3 = rhs.x + rhs.z;
+        let x3 = x3 * y3;
+        let y3 = t0 + t2;
+        let y3 = x3 - y3;
+        let x3 = t0 + t0;
+        let t0 = x3 + t0;
+        let t2 = mul_by_3b(t2);
+        let z3 = t1 + t2;
+        let t1 = t1 - t2;
+        let y3 = mul_by_3b(y3);
+        let x3 = t4 * y3;
+        let t2 = t3 * t1;
+        let x3 = t2 - x3;
+        let y3 = y3 * t0;
+        let t1 = t1 * z3;
+        let y3 = t1 + y3;
+        let t0 = t0 * t3;
+        let z3 = z3 * t4;
+        let z3 = z3 + t0;
 
-        let tmp = G2Projective {
+        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;
+        // Algorithm 8, https://eprint.iacr.org/2015/1060.pdf
+
+        let t0 = self.x * rhs.x;
+        let t1 = self.y * rhs.y;
+        let t3 = rhs.x + rhs.y;
+        let t4 = self.x + self.y;
+        let t3 = t3 * t4;
+        let t4 = t0 + t1;
+        let t3 = t3 - t4;
+        let t4 = rhs.y * self.z;
+        let t4 = t4 + self.y;
+        let y3 = rhs.x * self.z;
+        let y3 = y3 + self.x;
+        let x3 = t0 + t0;
+        let t0 = x3 + t0;
+        let t2 = mul_by_3b(self.z);
+        let z3 = t1 + t2;
+        let t1 = t1 - t2;
+        let y3 = mul_by_3b(y3);
+        let x3 = t4 * y3;
+        let t2 = t3 * t1;
+        let x3 = t2 - x3;
+        let y3 = y3 * t0;
+        let t1 = t1 * z3;
+        let y3 = t1 + y3;
+        let t0 = t0 * t3;
+        let z3 = z3 * t4;
+        let z3 = z3 + t0;
 
         let tmp = G2Projective {
             x: x3,
@@ -623,7 +841,7 @@ impl G2Projective {
             z: z3,
         };
 
-        G2Projective::conditional_select(&res, &tmp, (!f1) & (!f2) & (!f3))
+        G2Projective::conditional_select(&tmp, self, rhs.is_identity())
     }
 
     fn multiply(&self, by: &[u8]) -> G2Projective {
@@ -648,7 +866,6 @@ impl G2Projective {
         acc
     }
 
-    #[cfg(feature = "endo")]
     fn psi(&self) -> G2Projective {
         // 1 / ((u+1) ^ ((q-1)/3))
         let psi_coeff_x = Fp2 {
@@ -692,7 +909,6 @@ impl G2Projective {
         }
     }
 
-    #[cfg(feature = "endo")]
     fn psi2(&self) -> G2Projective {
         // 1 / 2 ^ ((q-1)/3)
         let psi2_coeff_x = Fp2 {
@@ -708,17 +924,16 @@ impl G2Projective {
         };
 
         G2Projective {
-            // x = frobenius^2(x)/2^((p-1)/3)
-            x: self.x.frobenius_map().frobenius_map() * psi2_coeff_x,
-            // y = -frobenius^2(y)
-            y: self.y.frobenius_map().frobenius_map().neg(),
+            // x = frobenius^2(x)/2^((p-1)/3); note that q^2 is the order of the field.
+            x: self.x * psi2_coeff_x,
+            // y = -frobenius^2(y); note that q^2 is the order of the field.
+            y: self.y.neg(),
             // z = z
             z: self.z,
         }
     }
 
     /// Multiply `self` by `crate::BLS_X`, using double and add.
-    #[cfg(feature = "endo")]
     fn mul_by_x(&self) -> G2Projective {
         let mut xself = G2Projective::identity();
         // NOTE: in BLS12-381 we can just skip the first bit.
@@ -742,36 +957,15 @@ impl G2Projective {
     /// This is equivalent to multiplying by $h\_\textrm{eff} = 3(z^2 - 1) \cdot
     /// h_2$, where $h_2$ is the cofactor of $\mathbb{G}\_2$ and $z$ is the
     /// parameter of BLS12-381.
-    ///
-    /// The endomorphism is only actually used if the crate feature `endo` is
-    /// enabled, and it is disabled by default to mitigate potential patent
-    /// issues.
     pub fn clear_cofactor(&self) -> G2Projective {
-        #[cfg(feature = "endo")]
-        fn clear_cofactor(this: &G2Projective) -> G2Projective {
-            let t1 = this.mul_by_x(); // [x] P
-            let t2 = this.psi(); // psi(P)
-
-            this.double().psi2() // psi^2(2P)
-                + (t1 + t2).mul_by_x() // psi^2(2P) + [x^2] P + [x] psi(P)
-                - t1 // psi^2(2P) + [x^2 - x] P + [x] psi(P)
-                - t2 // psi^2(2P) + [x^2 - x] P + [x - 1] psi(P)
-                - this // psi^2(2P) + [x^2 - x - 1] P + [x - 1] psi(P)
-        }
-
-        #[cfg(not(feature = "endo"))]
-        fn clear_cofactor(this: &G2Projective) -> G2Projective {
-            this.multiply(&[
-                0x51, 0x55, 0xa9, 0xaa, 0x5, 0x0, 0x2, 0xe8, 0xb4, 0xf6, 0xbb, 0xde, 0xa, 0x4c,
-                0x89, 0x59, 0xa3, 0xf6, 0x89, 0x66, 0xc0, 0xcb, 0x54, 0xe9, 0x1a, 0x7c, 0x47, 0xd7,
-                0x69, 0xec, 0xc0, 0x2e, 0xb0, 0x12, 0x12, 0x5d, 0x1, 0xbf, 0x82, 0x6d, 0x95, 0xdb,
-                0x31, 0x87, 0x17, 0x2f, 0x9c, 0x32, 0xe1, 0xff, 0x8, 0x15, 0x3, 0xff, 0x86, 0x99,
-                0x68, 0xd7, 0x5a, 0x14, 0xe9, 0xa8, 0xe2, 0x88, 0x28, 0x35, 0x1b, 0xa9, 0xe, 0x6a,
-                0x4c, 0x58, 0xb3, 0x75, 0xee, 0xf2, 0x8, 0x9f, 0xc6, 0xb,
-            ])
-        }
-
-        clear_cofactor(self)
+        let t1 = self.mul_by_x(); // [x] P
+        let t2 = self.psi(); // psi(P)
+
+        self.double().psi2() // psi^2(2P)
+            + (t1 + t2).mul_by_x() // psi^2(2P) + [x^2] P + [x] psi(P)
+            - t1 // psi^2(2P) + [x^2 - x] P + [x] psi(P)
+            - t2 // psi^2(2P) + [x^2 - x] P + [x - 1] psi(P)
+            - self // psi^2(2P) + [x^2 - x - 1] P + [x - 1] psi(P)
     }
 
     /// Converts a batch of `G2Projective` elements into `G2Affine` elements. This
@@ -803,14 +997,11 @@ impl G2Projective {
             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.x = p.x * tmp;
+            q.y = p.y * tmp;
             q.infinity = 0u8.into();
 
-            *q = G2Affine::conditional_select(&q, &G2Affine::identity(), skip);
+            *q = G2Affine::conditional_select(q, &G2Affine::identity(), skip);
         }
     }
 
@@ -823,14 +1014,259 @@ impl G2Projective {
     /// 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)
+        // Y^2 Z = X^3 + b Z^3
 
-        (self.y.square() - (self.x.square() * self.x))
-            .ct_eq(&((self.z.square() * self.z).square() * B))
+        (self.y.square() * self.z).ct_eq(&(self.x.square() * self.x + self.z.square() * self.z * B))
             | self.z.is_zero()
     }
 }
 
+#[derive(Clone, Copy)]
+#[cfg_attr(
+    feature = "rkyv-impl",
+    derive(Archive, RkyvSerialize, RkyvDeserialize),
+    archive_attr(derive(CheckBytes))
+)]
+pub struct G2Compressed([u8; 96]);
+
+impl fmt::Debug for G2Compressed {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        self.0[..].fmt(f)
+    }
+}
+
+impl Default for G2Compressed {
+    fn default() -> Self {
+        G2Compressed([0; 96])
+    }
+}
+
+#[cfg(feature = "zeroize")]
+impl zeroize::DefaultIsZeroes for G2Compressed {}
+
+impl AsRef<[u8]> for G2Compressed {
+    fn as_ref(&self) -> &[u8] {
+        &self.0
+    }
+}
+
+impl AsMut<[u8]> for G2Compressed {
+    fn as_mut(&mut self) -> &mut [u8] {
+        &mut self.0
+    }
+}
+
+impl ConstantTimeEq for G2Compressed {
+    fn ct_eq(&self, other: &Self) -> Choice {
+        self.0.ct_eq(&other.0)
+    }
+}
+
+impl Eq for G2Compressed {}
+impl PartialEq for G2Compressed {
+    #[inline]
+    fn eq(&self, other: &Self) -> bool {
+        bool::from(self.ct_eq(other))
+    }
+}
+
+#[derive(Clone, Copy)]
+#[cfg_attr(
+    feature = "rkyv-impl",
+    derive(Archive, RkyvSerialize, RkyvDeserialize),
+    archive_attr(derive(CheckBytes))
+)]
+pub struct G2Uncompressed([u8; 192]);
+
+impl fmt::Debug for G2Uncompressed {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        self.0[..].fmt(f)
+    }
+}
+
+impl Default for G2Uncompressed {
+    fn default() -> Self {
+        G2Uncompressed([0; 192])
+    }
+}
+
+#[cfg(feature = "zeroize")]
+impl zeroize::DefaultIsZeroes for G2Uncompressed {}
+
+impl AsRef<[u8]> for G2Uncompressed {
+    fn as_ref(&self) -> &[u8] {
+        &self.0
+    }
+}
+
+impl AsMut<[u8]> for G2Uncompressed {
+    fn as_mut(&mut self) -> &mut [u8] {
+        &mut self.0
+    }
+}
+
+impl ConstantTimeEq for G2Uncompressed {
+    fn ct_eq(&self, other: &Self) -> Choice {
+        self.0.ct_eq(&other.0)
+    }
+}
+
+impl Eq for G2Uncompressed {}
+impl PartialEq for G2Uncompressed {
+    #[inline]
+    fn eq(&self, other: &Self) -> bool {
+        bool::from(self.ct_eq(other))
+    }
+}
+
+impl Group for G2Projective {
+    type Scalar = BlsScalar;
+
+    fn random(mut rng: impl RngCore) -> Self {
+        loop {
+            let x = Fp2::random(&mut rng);
+            let flip_sign = rng.next_u32() % 2 != 0;
+
+            // Obtain the corresponding y-coordinate given x as y = sqrt(x^3 + 4)
+            let p = ((x.square() * x) + B).sqrt().map(|y| G2Affine {
+                x,
+                y: if flip_sign { -y } else { y },
+                infinity: 0.into(),
+            });
+
+            if p.is_some().into() {
+                let p = p.unwrap().to_curve().clear_cofactor();
+
+                if bool::from(!p.is_identity()) {
+                    return p;
+                }
+            }
+        }
+    }
+
+    fn identity() -> Self {
+        Self::identity()
+    }
+
+    fn generator() -> Self {
+        Self::generator()
+    }
+
+    fn is_identity(&self) -> Choice {
+        self.is_identity()
+    }
+
+    #[must_use]
+    fn double(&self) -> Self {
+        self.double()
+    }
+}
+
+#[cfg(feature = "alloc")]
+impl WnafGroup for G2Projective {
+    fn recommended_wnaf_for_num_scalars(num_scalars: usize) -> usize {
+        const RECOMMENDATIONS: [usize; 11] = [1, 3, 8, 20, 47, 126, 260, 826, 1501, 4555, 84071];
+
+        let mut ret = 4;
+        for r in &RECOMMENDATIONS {
+            if num_scalars > *r {
+                ret += 1;
+            } else {
+                break;
+            }
+        }
+
+        ret
+    }
+}
+
+impl PrimeGroup for G2Projective {}
+
+impl Curve for G2Projective {
+    type AffineRepr = G2Affine;
+
+    fn batch_normalize(p: &[Self], q: &mut [Self::AffineRepr]) {
+        Self::batch_normalize(p, q);
+    }
+
+    fn to_affine(&self) -> Self::AffineRepr {
+        self.into()
+    }
+}
+
+impl PrimeCurve for G2Projective {
+    type Affine = G2Affine;
+}
+
+impl PrimeCurveAffine for G2Affine {
+    type Scalar = BlsScalar;
+    type Curve = G2Projective;
+
+    fn identity() -> Self {
+        Self::identity()
+    }
+
+    fn generator() -> Self {
+        Self::generator()
+    }
+
+    fn is_identity(&self) -> Choice {
+        self.is_identity()
+    }
+
+    fn to_curve(&self) -> Self::Curve {
+        self.into()
+    }
+}
+
+impl GroupEncoding for G2Projective {
+    type Repr = G2Compressed;
+
+    fn from_bytes(bytes: &Self::Repr) -> CtOption<Self> {
+        <G2Affine as GroupEncoding>::from_bytes(bytes).map(Self::from)
+    }
+
+    fn from_bytes_unchecked(bytes: &Self::Repr) -> CtOption<Self> {
+        <G2Affine as GroupEncoding>::from_bytes_unchecked(bytes).map(Self::from)
+    }
+
+    fn to_bytes(&self) -> Self::Repr {
+        <G2Affine as GroupEncoding>::to_bytes(&G2Affine::from(self))
+    }
+}
+
+impl GroupEncoding for G2Affine {
+    type Repr = G2Compressed;
+
+    fn from_bytes(bytes: &Self::Repr) -> CtOption<Self> {
+        Self::from_compressed(&bytes.0)
+    }
+
+    fn from_bytes_unchecked(bytes: &Self::Repr) -> CtOption<Self> {
+        Self::from_compressed_unchecked(&bytes.0)
+    }
+
+    fn to_bytes(&self) -> Self::Repr {
+        G2Compressed(self.to_compressed())
+    }
+}
+
+impl UncompressedEncoding for G2Affine {
+    type Uncompressed = G2Uncompressed;
+
+    fn from_uncompressed(bytes: &Self::Uncompressed) -> CtOption<Self> {
+        Self::from_uncompressed(&bytes.0)
+    }
+
+    fn from_uncompressed_unchecked(bytes: &Self::Uncompressed) -> CtOption<Self> {
+        Self::from_uncompressed_unchecked(&bytes.0)
+    }
+
+    fn to_uncompressed(&self) -> Self::Uncompressed {
+        G2Uncompressed(self.to_uncompressed())
+    }
+}
+
 #[test]
 fn test_is_on_curve() {
     assert!(bool::from(G2Affine::identity().is_on_curve()));
@@ -859,8 +1295,8 @@ fn test_is_on_curve() {
 
     let gen = G2Affine::generator();
     let mut test = G2Projective {
-        x: gen.x * (z.square()),
-        y: gen.y * (z.square() * z),
+        x: gen.x * z,
+        y: gen.y * z,
         z,
     };
 
@@ -871,6 +1307,7 @@ fn test_is_on_curve() {
 }
 
 #[test]
+#[allow(clippy::eq_op)]
 fn test_affine_point_equality() {
     let a = G2Affine::generator();
     let b = G2Affine::identity();
@@ -882,6 +1319,7 @@ fn test_affine_point_equality() {
 }
 
 #[test]
+#[allow(clippy::eq_op)]
 fn test_projective_point_equality() {
     let a = G2Projective::generator();
     let b = G2Projective::identity();
@@ -911,8 +1349,8 @@ fn test_projective_point_equality() {
     };
 
     let mut c = G2Projective {
-        x: a.x * (z.square()),
-        y: a.y * (z.square() * z),
+        x: a.x * z,
+        y: a.y * z,
         z,
     };
     assert!(bool::from(c.is_on_curve()));
@@ -992,8 +1430,8 @@ fn test_projective_to_affine() {
     };
 
     let c = G2Projective {
-        x: a.x * (z.square()),
-        y: a.y * (z.square() * z),
+        x: a.x * z,
+        y: a.y * z,
         z,
     };
 
@@ -1101,8 +1539,8 @@ fn test_projective_addition() {
             };
 
             b = G2Projective {
-                x: b.x * (z.square()),
-                y: b.y * (z.square() * z),
+                x: b.x * z,
+                y: b.y * z,
                 z,
             };
         }
@@ -1135,8 +1573,8 @@ fn test_projective_addition() {
             };
 
             b = G2Projective {
-                x: b.x * (z.square()),
-                y: b.y * (z.square() * z),
+                x: b.x * z,
+                y: b.y * z,
                 z,
             };
         }
@@ -1152,7 +1590,7 @@ fn test_projective_addition() {
 
         let mut d = G2Projective::generator();
         for _ in 0..5 {
-            d = d + G2Projective::generator();
+            d += G2Projective::generator();
         }
         assert!(!bool::from(c.is_identity()));
         assert!(bool::from(c.is_on_curve()));
@@ -1265,8 +1703,8 @@ fn test_mixed_addition() {
             };
 
             b = G2Projective {
-                x: b.x * (z.square()),
-                y: b.y * (z.square() * z),
+                x: b.x * z,
+                y: b.y * z,
                 z,
             };
         }
@@ -1299,8 +1737,8 @@ fn test_mixed_addition() {
             };
 
             b = G2Projective {
-                x: b.x * (z.square()),
-                y: b.y * (z.square() * z),
+                x: b.x * z,
+                y: b.y * z,
                 z,
             };
         }
@@ -1316,7 +1754,7 @@ fn test_mixed_addition() {
 
         let mut d = G2Projective::generator();
         for _ in 0..5 {
-            d = d + G2Affine::generator();
+            d += G2Affine::generator();
         }
         assert!(!bool::from(c.is_identity()));
         assert!(bool::from(c.is_on_curve()));
@@ -1398,6 +1836,7 @@ fn test_mixed_addition() {
 }
 
 #[test]
+#[allow(clippy::eq_op)]
 fn test_projective_negation_and_subtraction() {
     let a = G2Projective::generator().double();
     assert_eq!(a + (-a), G2Projective::identity());
@@ -1498,7 +1937,6 @@ fn test_is_torsion_free() {
     assert!(bool::from(G2Affine::generator().is_torsion_free()));
 }
 
-#[cfg(feature = "endo")]
 #[test]
 fn test_mul_by_x() {
     // multiplying by `x` a point in G2 is the same as multiplying by
@@ -1515,11 +1953,29 @@ fn test_mul_by_x() {
     assert_eq!(point.mul_by_x(), point * x);
 }
 
-#[cfg(feature = "endo")]
 #[test]
 fn test_psi() {
     let generator = G2Projective::generator();
 
+    let z = Fp2 {
+        c0: Fp::from_raw_unchecked([
+            0x0ef2ddffab187c0a,
+            0x2424522b7d5ecbfc,
+            0xc6f341a3398054f4,
+            0x5523ddf409502df0,
+            0xd55c0b5a88e0dd97,
+            0x066428d704923e52,
+        ]),
+        c1: Fp::from_raw_unchecked([
+            0x538bbe0c95b4878d,
+            0xad04a50379522881,
+            0x6d5c05bf5c12fb64,
+            0x4ce4a069a2d34787,
+            0x59ea6c8d0dffaeaf,
+            0x0d42a083a75bd6f3,
+        ]),
+    };
+
     // `point` is a random point in the curve
     let point = G2Projective {
         x: Fp2 {
@@ -1539,7 +1995,7 @@ fn test_psi() {
                 0x8613eafd9d81ffb1,
                 0x10fe54daa2d3d495,
             ]),
-        },
+        } * z,
         y: Fp2 {
             c0: Fp::from_raw_unchecked([
                 0x7de7edc43953b75c,
@@ -1558,24 +2014,7 @@ fn test_psi() {
                 0x1555b67fc7bbe73d,
             ]),
         },
-        z: Fp2 {
-            c0: Fp::from_raw_unchecked([
-                0x0ef2ddffab187c0a,
-                0x2424522b7d5ecbfc,
-                0xc6f341a3398054f4,
-                0x5523ddf409502df0,
-                0xd55c0b5a88e0dd97,
-                0x066428d704923e52,
-            ]),
-            c1: Fp::from_raw_unchecked([
-                0x538bbe0c95b4878d,
-                0xad04a50379522881,
-                0x6d5c05bf5c12fb64,
-                0x4ce4a069a2d34787,
-                0x59ea6c8d0dffaeaf,
-                0x0d42a083a75bd6f3,
-            ]),
-        },
+        z: z.square() * z,
     };
     assert!(bool::from(point.is_on_curve()));
 
@@ -1595,6 +2034,25 @@ fn test_psi() {
 
 #[test]
 fn test_clear_cofactor() {
+    let z = Fp2 {
+        c0: Fp::from_raw_unchecked([
+            0x0ef2ddffab187c0a,
+            0x2424522b7d5ecbfc,
+            0xc6f341a3398054f4,
+            0x5523ddf409502df0,
+            0xd55c0b5a88e0dd97,
+            0x066428d704923e52,
+        ]),
+        c1: Fp::from_raw_unchecked([
+            0x538bbe0c95b4878d,
+            0xad04a50379522881,
+            0x6d5c05bf5c12fb64,
+            0x4ce4a069a2d34787,
+            0x59ea6c8d0dffaeaf,
+            0x0d42a083a75bd6f3,
+        ]),
+    };
+
     // `point` is a random point in the curve
     let point = G2Projective {
         x: Fp2 {
@@ -1614,7 +2072,7 @@ fn test_clear_cofactor() {
                 0x8613eafd9d81ffb1,
                 0x10fe54daa2d3d495,
             ]),
-        },
+        } * z,
         y: Fp2 {
             c0: Fp::from_raw_unchecked([
                 0x7de7edc43953b75c,
@@ -1633,24 +2091,7 @@ fn test_clear_cofactor() {
                 0x1555b67fc7bbe73d,
             ]),
         },
-        z: Fp2 {
-            c0: Fp::from_raw_unchecked([
-                0x0ef2ddffab187c0a,
-                0x2424522b7d5ecbfc,
-                0xc6f341a3398054f4,
-                0x5523ddf409502df0,
-                0xd55c0b5a88e0dd97,
-                0x066428d704923e52,
-            ]),
-            c1: Fp::from_raw_unchecked([
-                0x538bbe0c95b4878d,
-                0xad04a50379522881,
-                0x6d5c05bf5c12fb64,
-                0x4ce4a069a2d34787,
-                0x59ea6c8d0dffaeaf,
-                0x0d42a083a75bd6f3,
-            ]),
-        },
+        z: z.square() * z,
     };
 
     assert!(bool::from(point.is_on_curve()));
@@ -1687,9 +2128,9 @@ fn test_batch_normalize() {
     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) {
+    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()
@@ -1720,10 +2161,59 @@ fn test_batch_normalize() {
     }
 }
 
+#[cfg(feature = "zeroize")]
+#[test]
+fn test_zeroize() {
+    use zeroize::Zeroize;
+
+    let mut a = G2Affine::generator();
+    a.zeroize();
+    assert!(bool::from(a.is_identity()));
+
+    let mut a = G2Projective::generator();
+    a.zeroize();
+    assert!(bool::from(a.is_identity()));
+
+    let mut a = GroupEncoding::to_bytes(&G2Affine::generator());
+    a.zeroize();
+    assert_eq!(&a, &G2Compressed::default());
+
+    let mut a = UncompressedEncoding::to_uncompressed(&G2Affine::generator());
+    a.zeroize();
+    assert_eq!(&a, &G2Uncompressed::default());
+}
+
+#[test]
+fn test_commutative_scalar_subgroup_multiplication() {
+    let a = BlsScalar::from_raw([
+        0x1fff_3231_233f_fffd,
+        0x4884_b7fa_0003_4802,
+        0x998c_4fef_ecbc_4ff3,
+        0x1824_b159_acc5_0562,
+    ]);
+
+    let g2_a = G2Affine::generator();
+    let g2_p = G2Projective::generator();
+
+    // By reference.
+    assert_eq!(&g2_a * &a, &a * &g2_a);
+    assert_eq!(&g2_p * &a, &a * &g2_p);
+
+    // Mixed
+    assert_eq!(&g2_a * a.clone(), a.clone() * &g2_a);
+    assert_eq!(&g2_p * a.clone(), a.clone() * &g2_p);
+    assert_eq!(g2_a.clone() * &a, &a * g2_a.clone());
+    assert_eq!(g2_p.clone() * &a, &a * g2_p.clone());
+
+    // By value.
+    assert_eq!(g2_p * a, a * g2_p);
+    assert_eq!(g2_a * a, a * g2_a);
+}
+
 mod dusk {
     use super::*;
 
-    #[cfg(feature = "serde_req")]
+    #[cfg(feature = "serde")]
     use serde::{de::Visitor, Deserialize, Deserializer, Serialize, Serializer};
 
     impl G2Affine {
@@ -1909,7 +2399,7 @@ mod dusk {
         }
     }
 
-    #[cfg(feature = "serde_req")]
+    #[cfg(feature = "serde")]
     impl Serialize for G2Affine {
         fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
         where
@@ -1917,14 +2407,14 @@ mod dusk {
         {
             use serde::ser::SerializeTuple;
             let mut tup = serializer.serialize_tuple(Self::SIZE)?;
-            for byte in self.to_bytes().iter() {
+            for byte in <Self as Serializable<96>>::to_bytes(self).iter() {
                 tup.serialize_element(byte)?;
             }
             tup.end()
         }
     }
 
-    #[cfg(feature = "serde_req")]
+    #[cfg(feature = "serde")]
     impl<'de> Deserialize<'de> for G2Affine {
         fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
         where
@@ -1951,7 +2441,7 @@ mod dusk {
                             .ok_or(serde::de::Error::invalid_length(i, &"expected 48 bytes"))?;
                     }
 
-                    G2Affine::from_bytes(&bytes).map_err(|_| {
+                    <G2Affine as Serializable<96>>::from_bytes(&bytes).map_err(|_| {
                         serde::de::Error::custom(&"compressed G2Affine was not canonically encoded")
                     })
                 }
@@ -1962,7 +2452,7 @@ mod dusk {
     }
 
     #[test]
-    #[cfg(feature = "serde_req")]
+    #[cfg(feature = "serde")]
     fn g2_affine_serde_roundtrip() {
         use bincode;
 
@@ -1987,21 +2477,4 @@ mod dusk {
         assert_eq!(gen, gen_p);
         assert_eq!(ident, ident_p);
     }
-
-    #[test]
-    fn g2_affine_hex() {
-        use dusk_bytes::ParseHexStr;
-
-        let gen = G2Affine::generator();
-        let ident = G2Affine::identity();
-
-        let gen_p = format!("{:x}", gen);
-        let gen_p = G2Affine::from_hex_str(gen_p.as_str()).unwrap();
-
-        let ident_p = format!("{:x}", ident);
-        let ident_p = G2Affine::from_hex_str(ident_p.as_str()).unwrap();
-
-        assert_eq!(gen, gen_p);
-        assert_eq!(ident, ident_p);
-    }
 }
diff --git a/src/hash_to_curve/chain.rs b/src/hash_to_curve/chain.rs
new file mode 100644
index 00000000..c9d2352f
--- /dev/null
+++ b/src/hash_to_curve/chain.rs
@@ -0,0 +1,920 @@
+//! Addition chains for computing square roots.
+//! chain_pm3div4: input x, output x^((p-3)//4).
+//! chain_p2m9div16: input x, output x^((p**2 - 9) // 16).
+
+use core::ops::MulAssign;
+
+use crate::{fp::Fp, fp2::Fp2};
+
+macro_rules! square {
+    ($var:expr, $n:expr) => {
+        for _ in 0..$n {
+            $var = $var.square();
+        }
+    };
+}
+
+#[allow(clippy::cognitive_complexity)]
+/// addchain for 1000602388805416848354447456433976039139220704984751971333014534031007912622709466110671907282253916009473568139946
+/// Bos-Coster (win=4) : 458 links, 16 variables */
+/// Addition chain implementing exponentiation by (p - 3) // 4.
+pub fn chain_pm3div4(var0: &Fp) -> Fp {
+    let mut var1 = var0.square();
+    //Self::sqr(var1, var0);                              /*    0 : 2 */
+    let var9 = var1 * var0;
+    //Self::mul(&mut var9, var1, var0);                /*    1 : 3 */
+    let var5 = var1.square();
+    //Self::sqr(&mut var5, var1);                         /*    2 : 4 */
+    let var2 = var9 * var1;
+    //Self::mul(&mut var2, &var9, var1);               /*    3 : 5 */
+    let var7 = var5 * var9;
+    //Self::mul(&mut var7, &var5, &var9);              /*    4 : 7 */
+    let var10 = var2 * var5;
+    //Self::mul(&mut var10, &var2, &var5);             /*    5 : 9 */
+    let var13 = var7 * var5;
+    //Self::mul(&mut var13, &var7, &var5);             /*    6 : 11 */
+    let var4 = var10 * var5;
+    //Self::mul(&mut var4, &var10, &var5);             /*    7 : 13 */
+    let var8 = var13 * var5;
+    //Self::mul(&mut var8, &var13, &var5);             /*    8 : 15 */
+    let var15 = var4 * var5;
+    //Self::mul(&mut var15, &var4, &var5);             /*    9 : 17 */
+    let var11 = var8 * var5;
+    //Self::mul(&mut var11, &var8, &var5);             /*   10 : 19 */
+    let var3 = var15 * var5;
+    //Self::mul(&mut var3, &var15, &var5);             /*   11 : 21 */
+    let var12 = var11 * var5;
+    //Self::mul(&mut var12, &var11, &var5);            /*   12 : 23 */
+    var1 = var4.square();
+    //Self::sqr(var1, &var4);                             /*   13 : 26 */
+    let var14 = var12 * var5;
+    //Self::mul(&mut var14, &var12, &var5);            /*   14 : 27 */
+    let var6 = var1 * var9;
+    //Self::mul(&mut var6, var1, &var9);               /*   15 : 29 */
+    let var5 = var1 * var2;
+    //Self::mul(&mut var5, var1, &var2);               /*   16 : 31 */
+    //   17 : 106496
+    square!(var1, 12);
+    //   29 : 106513
+    var1.mul_assign(&var15);
+    //   30 : 13633664
+    square!(var1, 7);
+    //   37 : 13633679
+    var1.mul_assign(&var8);
+    //   38 : 218138864
+    square!(var1, 4);
+    //   42 : 218138869
+    var1.mul_assign(&var2);
+    //   43 : 13960887616
+    square!(var1, 6);
+    //   49 : 13960887623
+    var1.mul_assign(&var7);
+    //   50 : 1786993615744
+    square!(var1, 7);
+    //   57 : 1786993615767
+    var1.mul_assign(&var12);
+    //   58 : 57183795704544
+    square!(var1, 5);
+    //   63 : 57183795704575
+    var1.mul_assign(&var5);
+    //   64 : 228735182818300
+    square!(var1, 2);
+    //   66 : 228735182818303
+    var1.mul_assign(&var9);
+    //   67 : 14639051700371392
+    square!(var1, 6);
+    //   73 : 14639051700371405
+    var1.mul_assign(&var4);
+    //   74 : 936899308823769920
+    square!(var1, 6);
+    //   80 : 936899308823769933
+    var1.mul_assign(&var4);
+    //   81 : 59961555764721275712
+    square!(var1, 6);
+    //   87 : 59961555764721275721
+    var1.mul_assign(&var10);
+    //   88 : 479692446117770205768
+    square!(var1, 3);
+    //   91 : 479692446117770205771
+    var1.mul_assign(&var9);
+    //   92 : 61400633103074586338688
+    square!(var1, 7);
+    //   99 : 61400633103074586338701
+    var1.mul_assign(&var4);
+    //  100 : 982410129649193381419216
+    square!(var1, 4);
+    //  104 : 982410129649193381419229
+    var1.mul_assign(&var4);
+    //  105 : 62874248297548376410830656
+    square!(var1, 6);
+    //  111 : 62874248297548376410830671
+    var1.mul_assign(&var8);
+    //  112 : 4023951891043096090293162944
+    square!(var1, 6);
+    //  118 : 4023951891043096090293162971
+    var1.mul_assign(&var14);
+    //  119 : 32191615128344768722345303768
+    square!(var1, 3);
+    //  122 : 32191615128344768722345303769
+    var1.mul_assign(var0);
+    //  123 : 8241053472856260792920397764864
+    square!(var1, 8);
+    //  131 : 8241053472856260792920397764877
+    var1.mul_assign(&var4);
+    //  132 : 1054854844525601381493810913904256
+    square!(var1, 7);
+    //  139 : 1054854844525601381493810913904279
+    var1.mul_assign(&var12);
+    //  140 : 33755355024819244207801949244936928
+    square!(var1, 5);
+    //  145 : 33755355024819244207801949244936939
+    var1.mul_assign(&var13);
+    //  146 : 2160342721588431629299324751675964096
+    square!(var1, 6);
+    //  152 : 2160342721588431629299324751675964109
+    var1.mul_assign(&var4);
+    //  153 : 138261934181659624275156784107261702976
+    square!(var1, 6);
+    //  159 : 138261934181659624275156784107261703005
+    var1.mul_assign(&var6);
+    //  160 : 2212190946906553988402508545716187248080
+    square!(var1, 4);
+    //  164 : 2212190946906553988402508545716187248089
+    var1.mul_assign(&var10);
+    //  165 : 566320882408077821031042187703343935510784
+    square!(var1, 8);
+    //  173 : 566320882408077821031042187703343935510813
+    var1.mul_assign(&var6);
+    //  174 : 9061134118529245136496675003253502968173008
+    square!(var1, 4);
+    //  178 : 9061134118529245136496675003253502968173021
+    var1.mul_assign(&var4);
+    //  179 : 1159825167171743377471574400416448379926146688
+    square!(var1, 7);
+    //  186 : 1159825167171743377471574400416448379926146711
+    var1.mul_assign(&var12);
+    //  187 : 593830485591932609265446093013221570522187116032
+    square!(var1, 9);
+    //  196 : 593830485591932609265446093013221570522187116051
+    var1.mul_assign(&var11);
+    //  197 : 2375321942367730437061784372052886282088748464204
+    square!(var1, 2);
+    //  199 : 2375321942367730437061784372052886282088748464207
+    var1.mul_assign(&var9);
+    //  200 : 76010302155767373985977099905692361026839950854624
+    square!(var1, 5);
+    //  205 : 76010302155767373985977099905692361026839950854631
+    var1.mul_assign(&var7);
+    //  206 : 9729318675938223870205068787928622211435513709392768
+    square!(var1, 7);
+    //  213 : 9729318675938223870205068787928622211435513709392773
+    var1.mul_assign(&var2);
+    //  214 : 1245352790520092655386248804854863643063745754802274944
+    square!(var1, 7);
+    //  221 : 1245352790520092655386248804854863643063745754802274953
+    var1.mul_assign(&var10);
+    //  222 : 79702578593285929944719923510711273156079728307345596992
+    square!(var1, 6);
+    //  228 : 79702578593285929944719923510711273156079728307345597015
+    var1.mul_assign(&var12);
+    //  229 : 2550482514985149758231037552342760740994551305835059104480
+    square!(var1, 5);
+    //  234 : 2550482514985149758231037552342760740994551305835059104509
+    var1.mul_assign(&var6);
+    //  235 : 81615440479524792263393201674968343711825641786721891344288
+    square!(var1, 5);
+    //  240 : 81615440479524792263393201674968343711825641786721891344307
+    var1.mul_assign(&var11);
+    //  241 : 2611694095344793352428582453598986998778420537175100523017824
+    square!(var1, 5);
+    //  246 : 2611694095344793352428582453598986998778420537175100523017843
+    var1.mul_assign(&var11);
+    //  247 : 668593688408267098221717108121340671687275657516825733892567808
+    square!(var1, 8);
+    //  255 : 668593688408267098221717108121340671687275657516825733892567821
+    var1.mul_assign(&var4);
+    //  256 : 85579992116258188572379789839531605975971284162153693938248681088
+    square!(var1, 7);
+    //  263 : 85579992116258188572379789839531605975971284162153693938248681109
+    var1.mul_assign(&var3);
+    //  264 : 43816955963524192549058452397840182259697297491022691296383324727808
+    square!(var1, 9);
+    //  273 : 43816955963524192549058452397840182259697297491022691296383324727823
+    var1.mul_assign(&var8);
+    //  274 : 1402142590832774161569870476730885832310313519712726121484266391290336
+    square!(var1, 5);
+    //  279 : 1402142590832774161569870476730885832310313519712726121484266391290349
+    var1.mul_assign(&var4);
+    //  280 : 11217140726662193292558963813847086658482508157701808971874131130322792
+    square!(var1, 3);
+    //  283 : 11217140726662193292558963813847086658482508157701808971874131130322795
+    var1.mul_assign(&var9);
+    //  284 : 2871588026025521482895094736344854184571522088371663096799777569362635520
+    square!(var1, 8);
+    //  292 : 2871588026025521482895094736344854184571522088371663096799777569362635535
+    var1.mul_assign(&var8);
+    //  293 : 22972704208204171863160757890758833476572176706973304774398220554901084280
+    square!(var1, 3);
+    //  296 : 22972704208204171863160757890758833476572176706973304774398220554901084283
+    var1.mul_assign(&var9);
+    //  297 : 2940506138650133998484577010017130685001238618492583011122972231027338788224
+    square!(var1, 7);
+    //  304 : 2940506138650133998484577010017130685001238618492583011122972231027338788233
+    var1.mul_assign(&var10);
+    //  305 : 1505539142988868607224103429128770910720634172668202501694961782285997459575296
+    square!(var1, 9);
+    //  314 : 1505539142988868607224103429128770910720634172668202501694961782285997459575311
+    var1.mul_assign(&var8);
+    //  315 : 96354505151287590862342619464241338286120587050764960108477554066303837412819904
+    square!(var1, 6);
+    //  321 : 96354505151287590862342619464241338286120587050764960108477554066303837412819925
+    var1.mul_assign(&var3);
+    //  322 : 6166688329682405815189927645711445650311717571248957446942563460243445594420475200
+    square!(var1, 6);
+    //  328 : 6166688329682405815189927645711445650311717571248957446942563460243445594420475231
+    var1.mul_assign(&var5);
+    //  329 : 197334026549836986086077684662766260809974962279966638302162030727790259021455207392
+    square!(var1, 5);
+    //  334 : 197334026549836986086077684662766260809974962279966638302162030727790259021455207423
+    var1.mul_assign(&var5);
+    //  335 : 6314688849594783554754485909208520345919198792958932425669184983289288288686566637536
+    square!(var1, 5);
+    //  340 : 6314688849594783554754485909208520345919198792958932425669184983289288288686566637567
+    var1.mul_assign(&var5);
+    //  341 : 101035021593516536876071774547336325534707180687342918810706959732628612618985066201072
+    square!(var1, 4);
+    //  345 : 101035021593516536876071774547336325534707180687342918810706959732628612618985066201085
+    var1.mul_assign(&var4);
+    //  346 : 808280172748132295008574196378690604277657445498743350485655677861028900951880529608680
+    square!(var1, 3);
+    //  349 : 808280172748132295008574196378690604277657445498743350485655677861028900951880529608683
+    var1.mul_assign(&var9);
+    //  350 : 206919724223521867522194994272944794695080306047678297724327853532423398643681415579822848
+    square!(var1, 8);
+    //  358 : 206919724223521867522194994272944794695080306047678297724327853532423398643681415579822869
+    var1.mul_assign(&var3);
+    //  359 : 26485724700610799042840959266936933720970279174102822108713965252150195026391221194217327232
+    square!(var1, 7);
+    //  366 : 26485724700610799042840959266936933720970279174102822108713965252150195026391221194217327263
+    var1.mul_assign(&var5);
+    //  367 : 847543190419545569370910696541981879071048933571290307478846888068806240844519078214954472416
+    square!(var1, 5);
+    //  372 : 847543190419545569370910696541981879071048933571290307478846888068806240844519078214954472447
+    var1.mul_assign(&var5);
+    //  373 : 27121382093425458219869142289343420130273565874281289839323100418201799707024610502878543118304
+    square!(var1, 5);
+    //  378 : 27121382093425458219869142289343420130273565874281289839323100418201799707024610502878543118335
+    var1.mul_assign(&var5);
+    //  379 : 433942113494807331517906276629494722084377053988500637429169606691228795312393768046056689893360
+    square!(var1, 4);
+    //  383 : 433942113494807331517906276629494722084377053988500637429169606691228795312393768046056689893375
+    var1.mul_assign(&var8);
+    //  384 : 6943073815916917304286500426071915553350032863816010198866713707059660724998300288736907038294000
+    square!(var1, 4);
+    //  388 : 6943073815916917304286500426071915553350032863816010198866713707059660724998300288736907038294007
+    var1.mul_assign(&var7);
+    //  389 : 888713448437365414948672054537205190828804206568449305454939354503636572799782436958324100901632896
+    square!(var1, 7);
+    //  396 : 888713448437365414948672054537205190828804206568449305454939354503636572799782436958324100901632927
+    var1.mul_assign(&var5);
+    //  397 : 28438830349995693278357505745190566106521734610190377774558059344116370329593037982666371228852253664
+    square!(var1, 5);
+    //  402 : 28438830349995693278357505745190566106521734610190377774558059344116370329593037982666371228852253693
+    var1.mul_assign(&var6);
+    //  403 : 910042571199862184907440183846098115408695507526092088785857899011723850546977215445323879323272118176
+    square!(var1, 5);
+    //  408 : 910042571199862184907440183846098115408695507526092088785857899011723850546977215445323879323272118207
+    var1.mul_assign(&var5);
+    //  409 : 29121362278395589917038085883075139693078256240834946841147452768375163217503270894250364138344707782624
+    square!(var1, 5);
+    //  414 : 29121362278395589917038085883075139693078256240834946841147452768375163217503270894250364138344707782655
+    var1.mul_assign(&var5);
+    //  415 : 931883592908658877345218748258404470178504199706718298916718488588005222960104668616011652427030649044960
+    square!(var1, 5);
+    //  420 : 931883592908658877345218748258404470178504199706718298916718488588005222960104668616011652427030649044991
+    var1.mul_assign(&var5);
+    //  421 : 29820274973077084075046999944268943045712134390614985565334991634816167134723349395712372877664980769439712
+    square!(var1, 5);
+    //  426 : 29820274973077084075046999944268943045712134390614985565334991634816167134723349395712372877664980769439743
+    var1.mul_assign(&var5);
+    //  427 : 954248799138466690401503998216606177462788300499679538090719732314117348311147180662795932085279384622071776
+    square!(var1, 5);
+    //  432 : 954248799138466690401503998216606177462788300499679538090719732314117348311147180662795932085279384622071807
+    var1.mul_assign(&var5);
+    //  433 : 30535961572430934092848127942931397678809225615989745218903031434051755145956709781209469826728940307906297824
+    square!(var1, 5);
+    //  438 : 30535961572430934092848127942931397678809225615989745218903031434051755145956709781209469826728940307906297855
+    var1.mul_assign(&var5);
+    //  439 : 488575385158894945485570047086902362860947609855835923502448502944828082335307356499351517227663044926500765680
+    square!(var1, 4);
+    //  443 : 488575385158894945485570047086902362860947609855835923502448502944828082335307356499351517227663044926500765693
+    var1.mul_assign(&var4);
+    //  444 : 31268824650169276511076483013561751223100647030773499104156704188468997269459670815958497102570434875296049004352
+    square!(var1, 6);
+    //  450 : 31268824650169276511076483013561751223100647030773499104156704188468997269459670815958497102570434875296049004373
+    var1.mul_assign(&var3);
+    //  451 : 500301194402708424177223728216988019569610352492375985666507267015503956311354733055335953641126958004736784069968
+    square!(var1, 4);
+    //  455 : 500301194402708424177223728216988019569610352492375985666507267015503956311354733055335953641126958004736784069973
+    var1.mul_assign(&var2);
+    //  456 : 1000602388805416848354447456433976039139220704984751971333014534031007912622709466110671907282253916009473568139946
+    var1.square()
+}
+
+#[allow(clippy::cognitive_complexity)]
+/// addchain for 1001205140483106588246484290269935788605945006208159541241399033561623546780709821462541004956387089373434649096260670658193992783731681621012512651314777238193313314641988297376025498093520728838658813979860931248214124593092835
+/// Bos-Coster (win=4) : 895 links, 17 variables
+/// Addition chain implementing exponentiation by (p**2 - 9) // 16.
+pub fn chain_p2m9div16(var0: &Fp2) -> Fp2 {
+    let mut var1 = var0.square();
+    //Self::sqr(var1, var0);                              /*    0 : 2 */
+    let var2 = var1 * var0;
+    //Self::mul(&mut var2, var1, var0);                /*    1 : 3 */
+    let var15 = var2 * var1;
+    //Self::mul(&mut var15, &var2, var1);              /*    2 : 5 */
+    let var3 = var15 * var1;
+    //Self::mul(&mut var3, &var15, var1);              /*    3 : 7 */
+    let var14 = var3 * var1;
+    //Self::mul(&mut var14, &var3, var1);              /*    4 : 9 */
+    let var13 = var14 * var1;
+    //Self::mul(&mut var13, &var14, var1);             /*    5 : 11 */
+    let var5 = var13 * var1;
+    //Self::mul(&mut var5, &var13, var1);              /*    6 : 13 */
+    let var10 = var5 * var1;
+    //Self::mul(&mut var10, &var5, var1);              /*    7 : 15 */
+    let var9 = var10 * var1;
+    //Self::mul(&mut var9, &var10, var1);              /*    8 : 17 */
+    let var16 = var9 * var1;
+    //Self::mul(&mut var16, &var9, var1);              /*    9 : 19 */
+    let var4 = var16 * var1;
+    //Self::mul(&mut var4, &var16, var1);              /*   10 : 21 */
+    let var7 = var4 * var1;
+    //Self::mul(&mut var7, &var4, var1);               /*   11 : 23 */
+    let var6 = var7 * var1;
+    //Self::mul(&mut var6, &var7, var1);               /*   12 : 25 */
+    let var12 = var6 * var1;
+    //Self::mul(&mut var12, &var6, var1);              /*   13 : 27 */
+    let var8 = var12 * var1;
+    //Self::mul(&mut var8, &var12, var1);              /*   14 : 29 */
+    let var11 = var8 * var1;
+    //Self::mul(&mut var11, &var8, var1);              /*   15 : 31 */
+    var1 = var4.square();
+    //Self::sqr(var1, &var4);                             /*   16 : 42 */
+    //   17 : 168
+    square!(var1, 2);
+    //   19 : 169
+    var1.mul_assign(var0);
+    //   20 : 86528
+    square!(var1, 9);
+    //   29 : 86555
+    var1.mul_assign(&var12);
+    //   30 : 1384880
+    square!(var1, 4);
+    //   34 : 1384893
+    var1.mul_assign(&var5);
+    //   35 : 88633152
+    square!(var1, 6);
+    //   41 : 88633161
+    var1.mul_assign(&var14);
+    //   42 : 1418130576
+    square!(var1, 4);
+    //   46 : 1418130583
+    var1.mul_assign(&var3);
+    //   47 : 45380178656
+    square!(var1, 5);
+    //   52 : 45380178659
+    var1.mul_assign(&var2);
+    //   53 : 11617325736704
+    square!(var1, 8);
+    //   61 : 11617325736717
+    var1.mul_assign(&var5);
+    //   62 : 185877211787472
+    square!(var1, 4);
+    //   66 : 185877211787479
+    var1.mul_assign(&var3);
+    //   67 : 2974035388599664
+    square!(var1, 4);
+    //   71 : 2974035388599679
+    var1.mul_assign(&var10);
+    //   72 : 761353059481517824
+    square!(var1, 8);
+    //   80 : 761353059481517853
+    var1.mul_assign(&var8);
+    //   81 : 48726595806817142592
+    square!(var1, 6);
+    //   87 : 48726595806817142603
+    var1.mul_assign(&var13);
+    //   88 : 779625532909074281648
+    square!(var1, 4);
+    //   92 : 779625532909074281661
+    var1.mul_assign(&var5);
+    //   93 : 6237004263272594253288
+    square!(var1, 3);
+    //   96 : 6237004263272594253289
+    var1.mul_assign(var0);
+    //   97 : 399168272849446032210496
+    square!(var1, 6);
+    //  103 : 399168272849446032210511
+    var1.mul_assign(&var10);
+    //  104 : 102187077849458184245890816
+    square!(var1, 8);
+    //  112 : 102187077849458184245890845
+    var1.mul_assign(&var8);
+    //  113 : 6539972982365323791737014080
+    square!(var1, 6);
+    //  119 : 6539972982365323791737014101
+    var1.mul_assign(&var4);
+    //  120 : 1674233083485522890684675609856
+    square!(var1, 8);
+    //  128 : 1674233083485522890684675609873
+    var1.mul_assign(&var9);
+    //  129 : 53575458671536732501909619515936
+    square!(var1, 5);
+    //  134 : 53575458671536732501909619515951
+    var1.mul_assign(&var10);
+    //  135 : 3428829354978350880122215649020864
+    square!(var1, 6);
+    //  141 : 3428829354978350880122215649020873
+    var1.mul_assign(&var14);
+    //  142 : 109722539359307228163910900768667936
+    square!(var1, 5);
+    //  147 : 109722539359307228163910900768667951
+    var1.mul_assign(&var10);
+    //  148 : 438890157437228912655643603074671804
+    square!(var1, 2);
+    //  150 : 438890157437228912655643603074671805
+    var1.mul_assign(var0);
+    //  151 : 28088970075982650409961190596778995520
+    square!(var1, 6);
+    //  157 : 28088970075982650409961190596778995535
+    var1.mul_assign(&var10);
+    //  158 : 3595388169725779252475032396387711428480
+    square!(var1, 7);
+    //  165 : 3595388169725779252475032396387711428491
+    var1.mul_assign(&var13);
+    //  166 : 57526210715612468039600518342203382855856
+    square!(var1, 4);
+    //  170 : 57526210715612468039600518342203382855863
+    var1.mul_assign(&var3);
+    //  171 : 3681677485799197954534433173901016502775232
+    square!(var1, 6);
+    //  177 : 3681677485799197954534433173901016502775241
+    var1.mul_assign(&var14);
+    //  178 : 471254718182297338180407446259330112355230848
+    square!(var1, 7);
+    //  185 : 471254718182297338180407446259330112355230855
+    var1.mul_assign(&var3);
+    //  186 : 15080150981833514821773038280298563595367387360
+    square!(var1, 5);
+    //  191 : 15080150981833514821773038280298563595367387365
+    var1.mul_assign(&var15);
+    //  192 : 1930259325674689897186948899878216140207025582720
+    square!(var1, 7);
+    //  199 : 1930259325674689897186948899878216140207025582727
+    var1.mul_assign(&var3);
+    //  200 : 61768298421590076709982364796102916486624818647264
+    square!(var1, 5);
+    //  205 : 61768298421590076709982364796102916486624818647271
+    var1.mul_assign(&var3);
+    //  206 : 63250737583708238551021941551209386482303814294805504
+    square!(var1, 10);
+    //  216 : 63250737583708238551021941551209386482303814294805521
+    var1.mul_assign(&var9);
+    //  217 : 506005900669665908408175532409675091858430514358444168
+    square!(var1, 3);
+    //  220 : 506005900669665908408175532409675091858430514358444173
+    var1.mul_assign(&var15);
+    //  221 : 16192188821429309069061617037109602939469776459470213536
+    square!(var1, 5);
+    //  226 : 16192188821429309069061617037109602939469776459470213549
+    var1.mul_assign(&var5);
+    //  227 : 4145200338285903121679773961500058352504262773624374668544
+    square!(var1, 8);
+    //  235 : 4145200338285903121679773961500058352504262773624374668569
+    var1.mul_assign(&var6);
+    //  236 : 132646410825148899893752766768001867280136408755979989394208
+    square!(var1, 5);
+    //  241 : 132646410825148899893752766768001867280136408755979989394231
+    var1.mul_assign(&var7);
+    //  242 : 8489370292809529593200177073152119505928730160382719321230784
+    square!(var1, 6);
+    //  248 : 8489370292809529593200177073152119505928730160382719321230795
+    var1.mul_assign(&var13);
+    //  249 : 543319698739809893964811332681735648379438730264494036558770880
+    square!(var1, 6);
+    //  255 : 543319698739809893964811332681735648379438730264494036558770895
+    var1.mul_assign(&var10);
+    //  256 : 34772460719347833213747925291631081496284078736927618339761337280
+    square!(var1, 6);
+    //  262 : 34772460719347833213747925291631081496284078736927618339761337289
+    var1.mul_assign(&var14);
+    //  263 : 4450874972076522651359734437328778431524362078326735147489451172992
+    square!(var1, 7);
+    //  270 : 4450874972076522651359734437328778431524362078326735147489451173011
+    var1.mul_assign(&var16);
+    //  271 : 142427999106448724843511501994520909808779586506455524719662437536352
+    square!(var1, 5);
+    //  276 : 142427999106448724843511501994520909808779586506455524719662437536361
+    var1.mul_assign(&var14);
+    //  277 : 9115391942812718389984736127649338227761893536413153582058396002327104
+    square!(var1, 6);
+    //  283 : 9115391942812718389984736127649338227761893536413153582058396002327119
+    var1.mul_assign(&var10);
+    //  284 : 583385084340013976959023112169557646576761186330441829251737344148935616
+    square!(var1, 6);
+    //  290 : 583385084340013976959023112169557646576761186330441829251737344148935633
+    var1.mul_assign(&var9);
+    //  291 : 18668322698880447262688739589425844690456357962574138536055595012765940256
+    square!(var1, 5);
+    //  296 : 18668322698880447262688739589425844690456357962574138536055595012765940271
+    var1.mul_assign(&var10);
+    //  297 : 74673290795521789050754958357703378761825431850296554144222380051063761084
+    square!(var1, 2);
+    //  299 : 74673290795521789050754958357703378761825431850296554144222380051063761085
+    var1.mul_assign(var0);
+    //  300 : 19116362443653577996993269339572064963027310553675917860920929293072322837760
+    square!(var1, 8);
+    //  308 : 19116362443653577996993269339572064963027310553675917860920929293072322837765
+    var1.mul_assign(&var15);
+    //  309 : 2446894392787657983615138475465224315267495750870517486197878949513257323233920
+    square!(var1, 7);
+    //  316 : 2446894392787657983615138475465224315267495750870517486197878949513257323233925
+    var1.mul_assign(&var15);
+    //  317 : 39150310284602527737842215607443589044279932013928279779166063192212117171742800
+    square!(var1, 4);
+    //  321 : 39150310284602527737842215607443589044279932013928279779166063192212117171742803
+    var1.mul_assign(&var2);
+    //  322 : 5011239716429123550443803597752779397667831297782819811733256088603150997983078784
+    square!(var1, 7);
+    //  329 : 5011239716429123550443803597752779397667831297782819811733256088603150997983078795
+    var1.mul_assign(&var13);
+    //  330 : 320719341851463907228403430256177881450741203058100467950928389670601663870917042880
+    square!(var1, 6);
+    //  336 : 320719341851463907228403430256177881450741203058100467950928389670601663870917042895
+    var1.mul_assign(&var10);
+    //  337 : 5131509469623422515654454884098846103211859248929607487214854234729626621934672686320
+    square!(var1, 4);
+    //  341 : 5131509469623422515654454884098846103211859248929607487214854234729626621934672686333
+    var1.mul_assign(&var5);
+    //  342 : 656833212111798082003770225164652301211117983862989758363501342045392207607638103850624
+    square!(var1, 7);
+    //  349 : 656833212111798082003770225164652301211117983862989758363501342045392207607638103850635
+    var1.mul_assign(&var13);
+    //  350 : 42037325575155077248241294410537747277511550967231344535264085890905101286888838646440640
+    square!(var1, 6);
+    //  356 : 42037325575155077248241294410537747277511550967231344535264085890905101286888838646440667
+    var1.mul_assign(&var12);
+    //  357 : 1345194418404962471943721421137207912880369630951403025128450748508963241180442836686101344
+    square!(var1, 5);
+    //  362 : 1345194418404962471943721421137207912880369630951403025128450748508963241180442836686101367
+    var1.mul_assign(&var7);
+    //  363 : 43046221388958799102199085476390653212171828190444896804110423952286823717774170773955243744
+    square!(var1, 5);
+    //  368 : 43046221388958799102199085476390653212171828190444896804110423952286823717774170773955243749
+    var1.mul_assign(&var15);
+    //  369 : 5509916337786726285081482940978003611157994008376946790926134265892713435875093859066271199872
+    square!(var1, 7);
+    //  376 : 5509916337786726285081482940978003611157994008376946790926134265892713435875093859066271199899
+    var1.mul_assign(&var12);
+    //  377 : 176317322809175241122607454111296115557055808268062297309636296508566829948003003490120678396768
+    square!(var1, 5);
+    //  382 : 176317322809175241122607454111296115557055808268062297309636296508566829948003003490120678396791
+    var1.mul_assign(&var7);
+    //  383 : 5642154329893607715923438531561475697825785864577993513908361488274138558336096111683861708697312
+    square!(var1, 5);
+    //  388 : 5642154329893607715923438531561475697825785864577993513908361488274138558336096111683861708697333
+    var1.mul_assign(&var4);
+    //  389 : 90274469278297723454775016504983611165212573833247896222533783812386216933377537786941787339157328
+    square!(var1, 4);
+    //  393 : 90274469278297723454775016504983611165212573833247896222533783812386216933377537786941787339157331
+    var1.mul_assign(&var2);
+    //  394 : 5777566033811054301105601056318951114573604725327865358242162163992717883736162418364274389706069184
+    square!(var1, 6);
+    //  400 : 5777566033811054301105601056318951114573604725327865358242162163992717883736162418364274389706069189
+    var1.mul_assign(&var15);
+    //  401 : 369764226163907475270758467604412871332710702420983382927498378495533944559114394775313560941188428096
+    square!(var1, 6);
+    //  407 : 369764226163907475270758467604412871332710702420983382927498378495533944559114394775313560941188428105
+    var1.mul_assign(&var14);
+    //  408 : 5916227618622519604332135481670605941323371238735734126839974055928543112945830316405016975059014849680
+    square!(var1, 4);
+    //  412 : 5916227618622519604332135481670605941323371238735734126839974055928543112945830316405016975059014849683
+    var1.mul_assign(&var2);
+    //  413 : 94659641897960313669314167706729695061173939819771746029439584894856689807133285062480271600944237594928
+    square!(var1, 4);
+    //  417 : 94659641897960313669314167706729695061173939819771746029439584894856689807133285062480271600944237594931
+    var1.mul_assign(&var2);
+    //  418 : 24232868325877840299344426932922801935660528593861566983536533733083312590626120975994949529841724824302336
+    square!(var1, 8);
+    //  426 : 24232868325877840299344426932922801935660528593861566983536533733083312590626120975994949529841724824302345
+    var1.mul_assign(&var14);
+    //  427 : 775451786428090889579021661853529661941136915003570143473169079458666002900035871231838384954935194377675040
+    square!(var1, 5);
+    //  432 : 775451786428090889579021661853529661941136915003570143473169079458666002900035871231838384954935194377675055
+    var1.mul_assign(&var10);
+    //  433 : 49628914331397816933057386358625898364232762560228489182282821085354624185602295758837656637115852440171203520
+    square!(var1, 6);
+    //  439 : 49628914331397816933057386358625898364232762560228489182282821085354624185602295758837656637115852440171203527
+    var1.mul_assign(&var3);
+    //  440 : 1588125258604730141857836363476028747655448401927311653833050274731347973939273464282805012387707278085478512864
+    square!(var1, 5);
+    //  445 : 1588125258604730141857836363476028747655448401927311653833050274731347973939273464282805012387707278085478512879
+    var1.mul_assign(&var10);
+    //  446 : 6504961059244974661049697744797813750396716654294268534100173925299601301255264109702369330740049011038119988752384
+    square!(var1, 12);
+    //  458 : 6504961059244974661049697744797813750396716654294268534100173925299601301255264109702369330740049011038119988752401
+    var1.mul_assign(&var9);
+    //  459 : 104079376947919594576795163916765020006347466468708296545602782804793620820084225755237909291840784176609919820038416
+    square!(var1, 4);
+    //  463 : 104079376947919594576795163916765020006347466468708296545602782804793620820084225755237909291840784176609919820038429
+    var1.mul_assign(&var5);
+    //  464 : 3330540062333427026457445245336480640203118926998665489459289049753395866242695224167613097338905093651517434241229728
+    square!(var1, 5);
+    //  469 : 3330540062333427026457445245336480640203118926998665489459289049753395866242695224167613097338905093651517434241229741
+    var1.mul_assign(&var5);
+    //  470 : 213154563989339329693276495701534760972999611327914591325394499184217335439532494346727238229689925993697115791438703424
+    square!(var1, 6);
+    //  476 : 213154563989339329693276495701534760972999611327914591325394499184217335439532494346727238229689925993697115791438703427
+    var1.mul_assign(&var2);
+    //  477 : 109135136762541736802957565799185797618175800999892270758601983582319275745040637105524345973601242108772923285216616154624
+    square!(var1, 9);
+    //  486 : 109135136762541736802957565799185797618175800999892270758601983582319275745040637105524345973601242108772923285216616154649
+    var1.mul_assign(&var6);
+    //  487 : 3492324376401335577694642105573945523781625631996552664275263474634216823841300387376779071155239747480733545126931716948768
+    square!(var1, 5);
+    //  492 : 3492324376401335577694642105573945523781625631996552664275263474634216823841300387376779071155239747480733545126931716948793
+    var1.mul_assign(&var6);
+    //  493 : 223508760089685476972457094756732513522024040447779370513616862376589876725843224792113860553935343838766946888123629884722752
+    square!(var1, 6);
+    //  499 : 223508760089685476972457094756732513522024040447779370513616862376589876725843224792113860553935343838766946888123629884722755
+    var1.mul_assign(&var2);
+    //  500 : 14304560645739870526237254064430880865409538588657879712871479192101752110453966386695287075451862005681084600839912312622256320
+    square!(var1, 6);
+    //  506 : 14304560645739870526237254064430880865409538588657879712871479192101752110453966386695287075451862005681084600839912312622256323
+    var1.mul_assign(&var2);
+    //  507 : 7323935050618813709433474080988611003089683757392834412990197346356097080552430789987986982631353346908715315630035104062595237376
+    square!(var1, 9);
+    //  516 : 7323935050618813709433474080988611003089683757392834412990197346356097080552430789987986982631353346908715315630035104062595237399
+    var1.mul_assign(&var7);
+    //  517 : 937463686479208154807484682366542208395479520946282804862745260333580426310711141118462333776813228404315560400644493320012190387072
+    square!(var1, 7);
+    //  524 : 937463686479208154807484682366542208395479520946282804862745260333580426310711141118462333776813228404315560400644493320012190387087
+    var1.mul_assign(&var10);
+    //  525 : 59997675934669321907679019671458701337310689340562099511215696661349147283885513031581589361716046617876195865641247572480780184773568
+    square!(var1, 6);
+    //  531 : 59997675934669321907679019671458701337310689340562099511215696661349147283885513031581589361716046617876195865641247572480780184773593
+    var1.mul_assign(&var6);
+    //  532 : 1919925629909418301045728629486678442793942058897987184358902293163172713084336417010610859574913491772038267700519922319384965912754976
+    square!(var1, 5);
+    //  537 : 1919925629909418301045728629486678442793942058897987184358902293163172713084336417010610859574913491772038267700519922319384965912754985
+    var1.mul_assign(&var14);
+    //  538 : 245750480628405542533853264574294840677624583538942359597939493524886107274795061377358190025588926946820898265666550056881275636832638080
+    square!(var1, 7);
+    //  545 : 245750480628405542533853264574294840677624583538942359597939493524886107274795061377358190025588926946820898265666550056881275636832638103
+    var1.mul_assign(&var7);
+    //  546 : 983001922513622170135413058297179362710498334155769438391757974099544429099180245509432760102355707787283593062666200227525102547330552412
+    square!(var1, 2);
+    //  548 : 983001922513622170135413058297179362710498334155769438391757974099544429099180245509432760102355707787283593062666200227525102547330552413
+    var1.mul_assign(var0);
+    //  549 : 251648492163487275554665742924077916853887573543876976228290041369483373849390142850414786586203061193544599824042547258246426252116621417728
+    square!(var1, 8);
+    //  557 : 251648492163487275554665742924077916853887573543876976228290041369483373849390142850414786586203061193544599824042547258246426252116621417739
+    var1.mul_assign(&var13);
+    //  558 : 4026375874615796408874651886785246669662201176702031619652640661911733981590242285606636585379248979096713597184680756131942820033865942683824
+    square!(var1, 4);
+    //  562 : 4026375874615796408874651886785246669662201176702031619652640661911733981590242285606636585379248979096713597184680756131942820033865942683829
+    var1.mul_assign(&var15);
+    //  563 : 515376111950821940335955441508511573716761750617860047315538004724701949643551012557649482928543869324379340439639136784888680964334840663530112
+    square!(var1, 7);
+    //  570 : 515376111950821940335955441508511573716761750617860047315538004724701949643551012557649482928543869324379340439639136784888680964334840663530119
+    var1.mul_assign(&var3);
+    //  571 : 131936284659410416726004593026178962871491008158172172112777729209523699108749059214758267629707230547041111152547619016931502326869719209863710464
+    square!(var1, 8);
+    //  579 : 131936284659410416726004593026178962871491008158172172112777729209523699108749059214758267629707230547041111152547619016931502326869719209863710473
+    var1.mul_assign(&var14);
+    //  580 : 16887844436404533340928587907350907247550849044246038030435549338819033485919879579489058256602525510021262227526095234167232297839324058862554940544
+    square!(var1, 7);
+    //  587 : 16887844436404533340928587907350907247550849044246038030435549338819033485919879579489058256602525510021262227526095234167232297839324058862554940557
+    var1.mul_assign(&var5);
+    //  588 : 17293152702878242141110874017127329021492069421307942943166002522950690289581956689396795654760986122261772520986721519787245872987467836275256259130368
+    square!(var1, 10);
+    //  598 : 17293152702878242141110874017127329021492069421307942943166002522950690289581956689396795654760986122261772520986721519787245872987467836275256259130377
+    var1.mul_assign(&var14);
+    //  599 : 1106761772984207497031095937096149057375492442963708348362624161468844178533245228121394921904703111824753441343150177266383735871197941521616400584344128
+    square!(var1, 6);
+    //  605 : 1106761772984207497031095937096149057375492442963708348362624161468844178533245228121394921904703111824753441343150177266383735871197941521616400584344139
+    var1.mul_assign(&var13);
+    //  606 : 70832753470989279809990139974153539672031516349677334295207946334006027426127694599769275001900999156784220245961611345048559095756668257383449637398024896
+    square!(var1, 6);
+    //  612 : 70832753470989279809990139974153539672031516349677334295207946334006027426127694599769275001900999156784220245961611345048559095756668257383449637398024909
+    var1.mul_assign(&var5);
+    //  613 : 4533296222143313907839368958345826539010017046379349394893308565376385755272172454385233600121663946034190095741543126083107782128426768472540776793473594176
+    square!(var1, 6);
+    //  619 : 4533296222143313907839368958345826539010017046379349394893308565376385755272172454385233600121663946034190095741543126083107782128426768472540776793473594207
+    var1.mul_assign(&var11);
+    //  620 : 145065479108586045050859806667066449248320545484139180636585874092044344168709518540327475203893246273094083063729380034659449028109656591121304857391155014624
+    square!(var1, 5);
+    //  625 : 145065479108586045050859806667066449248320545484139180636585874092044344168709518540327475203893246273094083063729380034659449028109656591121304857391155014649
+    var1.mul_assign(&var6);
+    //  626 : 18568381325899013766510055253384505503785029821969815121482991883781676053594818373161916826098335522956042632157360644436409475598036043663527021746067841875072
+    square!(var1, 7);
+    //  633 : 18568381325899013766510055253384505503785029821969815121482991883781676053594818373161916826098335522956042632157360644436409475598036043663527021746067841875087
+    var1.mul_assign(&var10);
+    //  634 : 594188202428768440528321768108304176121120954303034083887455740281013633715034187941181338435146736734593364229035540621965103219137153397232864695874170940002784
+    square!(var1, 5);
+    //  639 : 594188202428768440528321768108304176121120954303034083887455740281013633715034187941181338435146736734593364229035540621965103219137153397232864695874170940002797
+    var1.mul_assign(&var5);
+    //  640 : 76056089910882360387625186317862934543503482150788362737594334755969745115524376056471211319698782302027950621316549199611533212049555634845806681071893880320358016
+    square!(var1, 7);
+    //  647 : 76056089910882360387625186317862934543503482150788362737594334755969745115524376056471211319698782302027950621316549199611533212049555634845806681071893880320358047
+    var1.mul_assign(&var11);
+    //  648 : 2433794877148235532404005962171613905392111428825227607603018712191031843696780033807078762230361033664894419882129574387569062785585780315065813794300604170251457504
+    square!(var1, 5);
+    //  653 : 2433794877148235532404005962171613905392111428825227607603018712191031843696780033807078762230361033664894419882129574387569062785585780315065813794300604170251457511
+    var1.mul_assign(&var3);
+    //  654 : 623051488549948296295425526315933159780380525779258267546372790320904151986375688654612163130972424618212971489825171043217680073109959760656848331340954667584373122816
+    square!(var1, 8);
+    //  662 : 623051488549948296295425526315933159780380525779258267546372790320904151986375688654612163130972424618212971489825171043217680073109959760656848331340954667584373122843
+    var1.mul_assign(&var12);
+    //  663 : 39875295267196690962907233684219722225944353649872529122967858580537865727128044073895178440382235175565630175348810946765931524679037424682038293205821098725399879861952
+    square!(var1, 6);
+    //  669 : 39875295267196690962907233684219722225944353649872529122967858580537865727128044073895178440382235175565630175348810946765931524679037424682038293205821098725399879861981
+    var1.mul_assign(&var8);
+    //  670 : 2552018897100588221626062955790062222460438633591841863869942949154423406536194820729291420184463051236200331222323900593019617579458395179650450765172550318425592311166784
+    square!(var1, 6);
+    //  676 : 2552018897100588221626062955790062222460438633591841863869942949154423406536194820729291420184463051236200331222323900593019617579458395179650450765172550318425592311166787
+    var1.mul_assign(&var2);
+    //  677 : 326658418828875292368136058341127964474936145099755758575352697491766196036632937053349301783611270558233642396457459275906511050170674582995257697942086440758475815829348736
+    square!(var1, 7);
+    //  684 : 326658418828875292368136058341127964474936145099755758575352697491766196036632937053349301783611270558233642396457459275906511050170674582995257697942086440758475815829348747
+    var1.mul_assign(&var13);
+    //  685 : 41812277610096037423121415467664379452791826572768737097645145278946073092689015942828710628302242631453906226746554787316033414421846346623392985336587064417084904426156639616
+    square!(var1, 7);
+    //  692 : 41812277610096037423121415467664379452791826572768737097645145278946073092689015942828710628302242631453906226746554787316033414421846346623392985336587064417084904426156639627
+    var1.mul_assign(&var13);
+    //  693 : 2675985767046146395079770589930520284978676900657199174249289297852548677932097020341037480211343528413049998511779506388226138522998166183897151061541572122693433883274024936128
+    square!(var1, 6);
+    //  699 : 2675985767046146395079770589930520284978676900657199174249289297852548677932097020341037480211343528413049998511779506388226138522998166183897151061541572122693433883274024936131
+    var1.mul_assign(&var2);
+    //  700 : 85631544545476684642552658877776649119317660821030373575977257531281557693827104650913199366762992909217599952376944204423236432735941317884708833969330307926189884264768797956192
+    square!(var1, 5);
+    //  705 : 85631544545476684642552658877776649119317660821030373575977257531281557693827104650913199366762992909217599952376944204423236432735941317884708833969330307926189884264768797956199
+    var1.mul_assign(&var3);
+    //  706 : 87686701614568125073973922690843288698181284680735102541800711712032315078478955162535116151565304739038822351233990865329394107121603909513941845984594235316418441487123249107147776
+    square!(var1, 10);
+    //  716 : 87686701614568125073973922690843288698181284680735102541800711712032315078478955162535116151565304739038822351233990865329394107121603909513941845984594235316418441487123249107147803
+    var1.mul_assign(&var12);
+    //  717 : 1402987225833090001183582763053492619170900554891761640668811387392517041255663282600561858425044875824621157619743853845270305713945662552223069535753507765062695063793971985714364848
+    square!(var1, 4);
+    //  721 : 1402987225833090001183582763053492619170900554891761640668811387392517041255663282600561858425044875824621157619743853845270305713945662552223069535753507765062695063793971985714364849
+    var1.mul_assign(var0);
+    //  722 : 718329459626542080605994374683388221015501084104581960022431430344968725122899600691487671513622976422206032701308853168778396525540179226738211602305795975712099872662513656685754802688
+    square!(var1, 9);
+    //  731 : 718329459626542080605994374683388221015501084104581960022431430344968725122899600691487671513622976422206032701308853168778396525540179226738211602305795975712099872662513656685754802705
+    var1.mul_assign(&var9);
+    //  732 : 45973085416098693158783639979736846144992069382693245441435611542077998407865574444255210976871870491021186092883766602801817377634571470511245542547570942445574391850400874027888307373120
+    square!(var1, 6);
+    //  738 : 45973085416098693158783639979736846144992069382693245441435611542077998407865574444255210976871870491021186092883766602801817377634571470511245542547570942445574391850400874027888307373135
+    var1.mul_assign(&var10);
+    //  739 : 5884554933260632724324305917406316306558984880984735416503758277385983796206793528864667005039599422850711819889122125158632624337225148225439429446089080633033522156851311875569703343761280
+    square!(var1, 7);
+    //  746 : 5884554933260632724324305917406316306558984880984735416503758277385983796206793528864667005039599422850711819889122125158632624337225148225439429446089080633033522156851311875569703343761311
+    var1.mul_assign(&var11);
+    //  747 : 188305757864340247178377789357002121809887516191511533328120264876351481478617392923669344161267181531222778236451908005076243978791204743214061742274850580257072709019241980018230507000361952
+    square!(var1, 5);
+    //  752 : 188305757864340247178377789357002121809887516191511533328120264876351481478617392923669344161267181531222778236451908005076243978791204743214061742274850580257072709019241980018230507000361973
+    var1.mul_assign(&var4);
+    //  753 : 3012892125829443954854044629712033948958200259064184533249924238021623703657878286778709506580274904499564451783230528081219903660659275891424987876397609284113163344307871680291688112005791568
+    square!(var1, 4);
+    //  757 : 3012892125829443954854044629712033948958200259064184533249924238021623703657878286778709506580274904499564451783230528081219903660659275891424987876397609284113163344307871680291688112005791583
+    var1.mul_assign(&var10);
+    //  758 : 385650192106168826221317712603140345466649633160215620255990302466767834068208420707674816842275187775944249828253507594396147668564387314102398448178893988366484908071407575077336078336741322624
+    square!(var1, 7);
+    //  765 : 385650192106168826221317712603140345466649633160215620255990302466767834068208420707674816842275187775944249828253507594396147668564387314102398448178893988366484908071407575077336078336741322653
+    var1.mul_assign(&var8);
+    //  766 : 12340806147397402439082166803300491054932788261126899848191689678936570690182669462645594138952806008830215994504112243020676725394060394051276750341724607627727517058285042402474754506775722324896
+    square!(var1, 5);
+    //  771 : 12340806147397402439082166803300491054932788261126899848191689678936570690182669462645594138952806008830215994504112243020676725394060394051276750341724607627727517058285042402474754506775722324917
+    var1.mul_assign(&var4);
+    //  772 : 394905796716716878050629337705615713757849224356060795142134069725970262085845422804659012446489792282566911824131591776661655212609932609640856010935187444087280545865121356879192144216823114397344
+    square!(var1, 5);
+    //  777 : 394905796716716878050629337705615713757849224356060795142134069725970262085845422804659012446489792282566911824131591776661655212609932609640856010935187444087280545865121356879192144216823114397365
+    var1.mul_assign(&var4);
+    //  778 : 12636985494934940097620138806579702840251175179393945444548290231231048386747053529749088398287673353042141178372210936853172966803517843508507392349925998210792977467683883420134148614938339660715680
+    square!(var1, 5);
+    //  783 : 12636985494934940097620138806579702840251175179393945444548290231231048386747053529749088398287673353042141178372210936853172966803517843508507392349925998210792977467683883420134148614938339660715697
+    var1.mul_assign(&var9);
+    //  784 : 202191767918959041561922220905275245444018802870303127112772643699696774187952856475985414372602773648674258853955374989650767468856285496136118277598815971372687639482942134722146377839013434571451152
+    square!(var1, 4);
+    //  788 : 202191767918959041561922220905275245444018802870303127112772643699696774187952856475985414372602773648674258853955374989650767468856285496136118277598815971372687639482942134722146377839013434571451165
+    var1.mul_assign(&var5);
+    //  789 : 12940273146813378659963022137937615708417203383699400135217449196780593548028982814463066519846577513515152566653143999337649118006802271752711569766324222167852008926908296622217368181696859812572874560
+    square!(var1, 6);
+    //  795 : 12940273146813378659963022137937615708417203383699400135217449196780593548028982814463066519846577513515152566653143999337649118006802271752711569766324222167852008926908296622217368181696859812572874589
+    var1.mul_assign(&var8);
+    //  796 : 25880546293626757319926044275875231416834406767398800270434898393561187096057965628926133039693155027030305133306287998675298236013604543505423139532648444335704017853816593244434736363393719625145749178
+    var1 = var1.square();
+    //  797 : 25880546293626757319926044275875231416834406767398800270434898393561187096057965628926133039693155027030305133306287998675298236013604543505423139532648444335704017853816593244434736363393719625145749179
+    var1.mul_assign(var0);
+    //  798 : 1656354962792112468475266833656014810677402033113523217307833497187915974147709800251272514540361921729939528531602431915219087104870690784347080930089500437485057142644261967643823127257198056009327947456
+    square!(var1, 6);
+    //  804 : 1656354962792112468475266833656014810677402033113523217307833497187915974147709800251272514540361921729939528531602431915219087104870690784347080930089500437485057142644261967643823127257198056009327947463
+    var1.mul_assign(&var3);
+    //  805 : 1696107481899123167718673237663759166133659681908247774523221501120425957527254835457303054889330607851458077216360890281184345195387587363171410872411648447984698514067724254867274882311370809353551818202112
+    square!(var1, 10);
+    //  815 : 1696107481899123167718673237663759166133659681908247774523221501120425957527254835457303054889330607851458077216360890281184345195387587363171410872411648447984698514067724254867274882311370809353551818202135
+    var1.mul_assign(&var7);
+    //  816 : 108550878841543882733995087210480586632554219642127857569486176071707261281744309469267395512917158902493316941847096977995798092504805591242970295834345500671020704900334352311505592467927731798627316364936640
+    square!(var1, 6);
+    //  822 : 108550878841543882733995087210480586632554219642127857569486176071707261281744309469267395512917158902493316941847096977995798092504805591242970295834345500671020704900334352311505592467927731798627316364936661
+    var1.mul_assign(&var4);
+    //  823 : 6947256245858808494975685581470757544483470057096182884447115268589264722031635806033113312826698169759572284278214206591731077920307557839550098933398112042945325113621398547936357917947374835112148247355946304
+    square!(var1, 6);
+    //  829 : 6947256245858808494975685581470757544483470057096182884447115268589264722031635806033113312826698169759572284278214206591731077920307557839550098933398112042945325113621398547936357917947374835112148247355946329
+    var1.mul_assign(&var6);
+    //  830 : 444624399734963743678443877214128482846942083654155704604615377189712942210024691586119252020908682864612626193805709221870788986899683701731206331737479170748500807271769507067926906748631989447177487830780565056
+    square!(var1, 6);
+    //  836 : 444624399734963743678443877214128482846942083654155704604615377189712942210024691586119252020908682864612626193805709221870788986899683701731206331737479170748500807271769507067926906748631989447177487830780565069
+    var1.mul_assign(&var5);
+    //  837 : 28455961583037679595420408141704222902204293353865965094695384140141628301441580261511632129338155703335208076403565390199730495161579756910797205231198666927904051665393248452347322031912447324619359221169956164416
+    square!(var1, 6);
+    //  843 : 28455961583037679595420408141704222902204293353865965094695384140141628301441580261511632129338155703335208076403565390199730495161579756910797205231198666927904051665393248452347322031912447324619359221169956164437
+    var1.mul_assign(&var4);
+    //  844 : 238705906983162543355580399100765177871214152862586865721082456961065184302499251714358569373223087638243353151383559860752580829556389241459968722180074986980751351032731127113348364375477010926880553717580063640645533696
+    square!(var1, 23);
+    //  867 : 238705906983162543355580399100765177871214152862586865721082456961065184302499251714358569373223087638243353151383559860752580829556389241459968722180074986980751351032731127113348364375477010926880553717580063640645533703
+    var1.mul_assign(&var3);
+    //  868 : 15277178046922402774757145542448971383757705783205559406149277245508171795359952109718948439886277608847574601688547831088165173091608911453437998219524799166768086466094792135254295320030528699320355437925124073001314156992
+    square!(var1, 6);
+    //  874 : 15277178046922402774757145542448971383757705783205559406149277245508171795359952109718948439886277608847574601688547831088165173091608911453437998219524799166768086466094792135254295320030528699320355437925124073001314156999
+    var1.mul_assign(&var3);
+    //  875 : 488869697501516888792228657358367084280246585062577900996776871856261497451518467511006350076360883483122387254033530594821285538931485166510015943024793573336578766915033348328137450240976918378251374013603970336042053023968
+    square!(var1, 5);
+    //  880 : 488869697501516888792228657358367084280246585062577900996776871856261497451518467511006350076360883483122387254033530594821285538931485166510015943024793573336578766915033348328137450240976918378251374013603970336042053023971
+    var1.mul_assign(&var2);
+    //  881 : 31287660640097080882702634070935493393935781444004985663793719798800735836897181920704406404887096542919832784258145958068562274491615050656641020353586788693541041082562134293000796815422522776208087936870654101506691393534144
+    square!(var1, 6);
+    //  887 : 31287660640097080882702634070935493393935781444004985663793719798800735836897181920704406404887096542919832784258145958068562274491615050656641020353586788693541041082562134293000796815422522776208087936870654101506691393534151
+    var1.mul_assign(&var3);
+    //  888 : 1001205140483106588246484290269935788605945006208159541241399033561623546780709821462541004956387089373434649096260670658193992783731681621012512651314777238193313314641988297376025498093520728838658813979860931248214124593092832
+    square!(var1, 5);
+    //  893 : 1001205140483106588246484290269935788605945006208159541241399033561623546780709821462541004956387089373434649096260670658193992783731681621012512651314777238193313314641988297376025498093520728838658813979860931248214124593092835
+    var1 * var2
+}
+
+/// Tests for addition chains
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use rand_core::SeedableRng;
+
+    const SEED: [u8; 16] = [
+        0x59, 0x62, 0xbe, 0x5d, 0x76, 0x3d, 0x31, 0x8d, 0x17, 0xdb, 0x37, 0x32, 0x54, 0x06, 0xbc,
+        0xe5,
+    ];
+
+    #[test]
+    fn test_fp_chain() {
+        let mut rng = rand_xorshift::XorShiftRng::from_seed(SEED);
+        let p_m3_over4 = [
+            0xee7f_bfff_ffff_eaaa,
+            0x07aa_ffff_ac54_ffff,
+            0xd9cc_34a8_3dac_3d89,
+            0xd91d_d2e1_3ce1_44af,
+            0x92c6_e9ed_90d2_eb35,
+            0x0680_447a_8e5f_f9a6,
+        ];
+
+        for _ in 0..32 {
+            let input = Fp::random(&mut rng);
+            assert_eq!(chain_pm3div4(&input), input.pow_vartime(&p_m3_over4));
+        }
+    }
+
+    #[test]
+    fn test_fp2_chain() {
+        let mut rng = rand_xorshift::XorShiftRng::from_seed(SEED);
+        let p_sq_m9_over16 = [
+            0xb26a_a000_01c7_18e3,
+            0xd7ce_d6b1_d763_82ea,
+            0x3162_c338_3621_13cf,
+            0x966b_f91e_d3e7_1b74,
+            0xb292_e85a_8709_1a04,
+            0x11d6_8619_c861_85c7,
+            0xef53_1493_3097_8ef0,
+            0x050a_62cf_d16d_dca6,
+            0x466e_59e4_9349_e8bd,
+            0x9e2d_c90e_50e7_046b,
+            0x74bd_278e_aa22_f25e,
+            0x002a_437a_4b8c_35fc,
+        ];
+
+        for _ in 0..32 {
+            let input = Fp2::random(&mut rng);
+            assert_eq!(
+                chain_p2m9div16(&input),
+                input.pow_vartime_extended(&p_sq_m9_over16[..]),
+            );
+        }
+    }
+}
diff --git a/src/hash_to_curve/expand_msg.rs b/src/hash_to_curve/expand_msg.rs
new file mode 100644
index 00000000..7187d46b
--- /dev/null
+++ b/src/hash_to_curve/expand_msg.rs
@@ -0,0 +1,1282 @@
+//! This module implements message expansion consistent with the
+//! hash-to-curve RFC drafts 7 through 10
+
+use core::{
+    fmt::{self, Debug, Formatter},
+    marker::PhantomData,
+};
+
+use digest::{BlockInput, Digest, ExtendableOutputDirty, Update, XofReader};
+
+use crate::generic_array::{
+    typenum::{Unsigned, U32},
+    ArrayLength, GenericArray,
+};
+
+#[cfg(feature = "alloc")]
+use alloc::vec::Vec;
+
+const OVERSIZE_DST_SALT: &[u8] = b"H2C-OVERSIZE-DST-";
+
+/// The domain separation tag for a message expansion.
+///
+/// Implements [section 5.4.3 of `draft-irtf-cfrg-hash-to-curve-12`][dst].
+///
+/// [dst]: https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-12#section-5.4.3
+#[derive(Debug)]
+enum ExpandMsgDst<'x, L: ArrayLength<u8>> {
+    /// DST produced by hashing a very long (> 255 chars) input DST.
+    Hashed(GenericArray<u8, L>),
+    /// A raw input DST (<= 255 chars).
+    Raw(&'x [u8]),
+}
+
+impl<'x, L: ArrayLength<u8>> ExpandMsgDst<'x, L> {
+    /// Produces a DST for use with `expand_message_xof`.
+    pub fn process_xof<H>(dst: &'x [u8]) -> Self
+    where
+        H: Default + Update + ExtendableOutputDirty,
+    {
+        if dst.len() > 255 {
+            let mut data = GenericArray::<u8, L>::default();
+            H::default()
+                .chain(OVERSIZE_DST_SALT)
+                .chain(&dst)
+                .finalize_xof_dirty()
+                .read(&mut data);
+            Self::Hashed(data)
+        } else {
+            Self::Raw(dst)
+        }
+    }
+
+    /// Produces a DST for use with `expand_message_xmd`.
+    pub fn process_xmd<H>(dst: &'x [u8]) -> Self
+    where
+        H: Digest<OutputSize = L>,
+    {
+        if dst.len() > 255 {
+            Self::Hashed(H::new().chain(OVERSIZE_DST_SALT).chain(&dst).finalize())
+        } else {
+            Self::Raw(dst)
+        }
+    }
+
+    /// Returns the raw bytes of the DST.
+    pub fn data(&'x self) -> &'x [u8] {
+        match self {
+            Self::Hashed(arr) => &arr[..],
+            Self::Raw(buf) => buf,
+        }
+    }
+
+    /// Returns the length of the DST.
+    pub fn len(&'x self) -> usize {
+        match self {
+            Self::Hashed(_) => L::to_usize(),
+            Self::Raw(buf) => buf.len(),
+        }
+    }
+}
+
+/// A trait for message expansion methods supported by hash-to-curve.
+pub trait ExpandMessage: for<'x> InitExpandMessage<'x> {
+    // This intermediate is likely only necessary until GATs allow
+    // associated types with lifetimes.
+}
+
+/// Trait for constructing a new message expander.
+pub trait InitExpandMessage<'x> {
+    /// The state object used during message expansion.
+    type Expander: ExpandMessageState<'x>;
+
+    /// Initializes a message expander.
+    fn init_expand(message: &[u8], dst: &'x [u8], len_in_bytes: usize) -> Self::Expander;
+}
+
+// Automatically derive trait
+impl<X: for<'x> InitExpandMessage<'x>> ExpandMessage for X {}
+
+/// Trait for types implementing the `expand_message` interface for `hash_to_field`.
+pub trait ExpandMessageState<'x> {
+    /// Reads bytes from the generated output.
+    fn read_into(&mut self, output: &mut [u8]) -> usize;
+
+    /// Retrieves the number of bytes remaining in the generator.
+    fn remain(&self) -> usize;
+
+    #[cfg(feature = "alloc")]
+    /// Constructs a `Vec` containing the remaining bytes of the output.
+    fn into_vec(mut self) -> Vec<u8>
+    where
+        Self: Sized,
+    {
+        let mut result = alloc::vec![0u8; self.remain()];
+        self.read_into(&mut result[..]);
+        result
+    }
+}
+
+/// A generator for the output of `expand_message_xof` for a given
+/// extendable hash function, message, DST, and output length.
+///
+/// Implements [section 5.4.2 of `draft-irtf-cfrg-hash-to-curve-12`][expand_message_xof]
+/// with `k = 128`.
+///
+/// [expand_message_xof]: https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-12#section-5.4.2
+pub struct ExpandMsgXof<H: ExtendableOutputDirty> {
+    hash: <H as ExtendableOutputDirty>::Reader,
+    remain: usize,
+}
+
+impl<H: ExtendableOutputDirty> Debug for ExpandMsgXof<H> {
+    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
+        f.debug_struct("ExpandMsgXof")
+            .field("remain", &self.remain)
+            .finish()
+    }
+}
+
+impl<'x, H> ExpandMessageState<'x> for ExpandMsgXof<H>
+where
+    H: ExtendableOutputDirty,
+{
+    fn read_into(&mut self, output: &mut [u8]) -> usize {
+        let len = self.remain.min(output.len());
+        self.hash.read(&mut output[..len]);
+        self.remain -= len;
+        len
+    }
+
+    fn remain(&self) -> usize {
+        self.remain
+    }
+}
+
+impl<'x, H> InitExpandMessage<'x> for ExpandMsgXof<H>
+where
+    H: Default + Update + ExtendableOutputDirty,
+{
+    type Expander = Self;
+
+    fn init_expand(message: &[u8], dst: &[u8], len_in_bytes: usize) -> Self {
+        // Use U32 here for k = 128.
+        let dst = ExpandMsgDst::<U32>::process_xof::<H>(dst);
+        let hash = H::default()
+            .chain(message)
+            .chain((len_in_bytes as u16).to_be_bytes())
+            .chain(dst.data())
+            .chain([dst.len() as u8])
+            .finalize_xof_dirty();
+        Self {
+            hash,
+            remain: len_in_bytes,
+        }
+    }
+}
+
+/// Constructor for `expand_message_xmd` for a given digest hash function, message, DST,
+/// and output length.
+///
+/// Implements [section 5.4.1 of `draft-irtf-cfrg-hash-to-curve-12`][expand_message_xmd].
+///
+/// [expand_message_xmd]: https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-12#section-5.4.1
+#[derive(Debug)]
+pub struct ExpandMsgXmd<H: Digest>(PhantomData<H>);
+
+/// A generator for the output of `expand_message_xmd` for a given
+/// digest hash function, message, DST, and output length.
+///
+/// Implements [section 5.4.1 of `draft-irtf-cfrg-hash-to-curve-12`][expand_message_xmd].
+///
+/// [expand_message_xmd]: https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-12#section-5.4.1
+pub struct ExpandMsgXmdState<'x, H: Digest> {
+    dst: ExpandMsgDst<'x, H::OutputSize>,
+    b_0: GenericArray<u8, H::OutputSize>,
+    b_i: GenericArray<u8, H::OutputSize>,
+    i: usize,
+    b_offs: usize,
+    remain: usize,
+}
+
+impl<H: Digest> Debug for ExpandMsgXmdState<'_, H> {
+    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
+        f.debug_struct("ExpandMsgXmdState")
+            .field("remain", &self.remain)
+            .finish()
+    }
+}
+
+impl<'x, H> InitExpandMessage<'x> for ExpandMsgXmd<H>
+where
+    H: Digest + BlockInput,
+{
+    type Expander = ExpandMsgXmdState<'x, H>;
+
+    fn init_expand(message: &[u8], dst: &'x [u8], len_in_bytes: usize) -> Self::Expander {
+        let hash_size = <H as Digest>::OutputSize::to_usize();
+        let ell = (len_in_bytes + hash_size - 1) / hash_size;
+        if ell > 255 {
+            panic!("Invalid ExpandMsgXmd usage: ell > 255");
+        }
+        let dst = ExpandMsgDst::process_xmd::<H>(dst);
+        let b_0 = H::new()
+            .chain(GenericArray::<u8, <H as BlockInput>::BlockSize>::default())
+            .chain(message)
+            .chain((len_in_bytes as u16).to_be_bytes())
+            .chain([0u8])
+            .chain(dst.data())
+            .chain([dst.len() as u8])
+            .finalize();
+        // init with b_1
+        let b_i = H::new()
+            .chain(&b_0)
+            .chain([1u8])
+            .chain(dst.data())
+            .chain([dst.len() as u8])
+            .finalize();
+        ExpandMsgXmdState {
+            dst,
+            b_0,
+            b_i,
+            i: 2,
+            b_offs: 0,
+            remain: len_in_bytes,
+        }
+    }
+}
+
+impl<'x, H> ExpandMessageState<'x> for ExpandMsgXmdState<'x, H>
+where
+    H: Digest + BlockInput,
+{
+    fn read_into(&mut self, output: &mut [u8]) -> usize {
+        let read_len = self.remain.min(output.len());
+        let mut offs = 0;
+        let hash_size = H::OutputSize::to_usize();
+        while offs < read_len {
+            let b_offs = self.b_offs;
+            let mut copy_len = hash_size - b_offs;
+            if copy_len > 0 {
+                copy_len = copy_len.min(read_len - offs);
+                output[offs..(offs + copy_len)]
+                    .copy_from_slice(&self.b_i[b_offs..(b_offs + copy_len)]);
+                offs += copy_len;
+                self.b_offs = b_offs + copy_len;
+            } else {
+                let mut b_prev_xor = self.b_0.clone();
+                for j in 0..hash_size {
+                    b_prev_xor[j] ^= self.b_i[j];
+                }
+                self.b_i = H::new()
+                    .chain(b_prev_xor)
+                    .chain([self.i as u8])
+                    .chain(self.dst.data())
+                    .chain([self.dst.len() as u8])
+                    .finalize();
+                self.b_offs = 0;
+                self.i += 1;
+            }
+        }
+        self.remain -= read_len;
+        read_len
+    }
+
+    fn remain(&self) -> usize {
+        self.remain
+    }
+}
+
+#[cfg(feature = "alloc")]
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use sha2::{Sha256, Sha512};
+    use sha3::{Shake128, Shake256};
+
+    /// From <https://tools.ietf.org/html/draft-irtf-cfrg-hash-to-curve-12#appendix-K.1>
+    #[test]
+    fn expand_message_xmd_works_for_draft12_testvectors_sha256() {
+        let dst = b"QUUX-V01-CS02-with-expander-SHA256-128";
+
+        let msg = b"";
+        let len_in_bytes = 0x20;
+        let uniform_bytes = hex::decode(
+            "68a985b87eb6b46952128911f2a4412bbc302a9d759667f8\
+            7f7a21d803f07235",
+        )
+        .unwrap();
+        assert_eq!(
+            ExpandMsgXmd::<Sha256>::init_expand(msg, dst, len_in_bytes).into_vec(),
+            uniform_bytes
+        );
+
+        let msg = b"abc";
+        let len_in_bytes = 0x20;
+        let uniform_bytes = hex::decode(
+            "d8ccab23b5985ccea865c6c97b6e5b8350e794e603b4b979\
+            02f53a8a0d605615",
+        )
+        .unwrap();
+        assert_eq!(
+            ExpandMsgXmd::<Sha256>::init_expand(msg, dst, len_in_bytes).into_vec(),
+            uniform_bytes
+        );
+
+        let msg = b"abcdef0123456789";
+        let len_in_bytes = 0x20;
+        let uniform_bytes = hex::decode(
+            "eff31487c770a893cfb36f912fbfcbff40d5661771ca4b2c\
+            b4eafe524333f5c1",
+        )
+        .unwrap();
+        assert_eq!(
+            ExpandMsgXmd::<Sha256>::init_expand(msg, dst, len_in_bytes).into_vec(),
+            uniform_bytes
+        );
+
+        let msg = b"q128_qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq\
+            qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq\
+            qqqqqqqqqqqqqqqqqqqqqqqqq";
+        let len_in_bytes = 0x20;
+        let uniform_bytes = hex::decode(
+            "b23a1d2b4d97b2ef7785562a7e8bac7eed54ed6e97e29aa5\
+            1bfe3f12ddad1ff9",
+        )
+        .unwrap();
+        assert_eq!(
+            ExpandMsgXmd::<Sha256>::init_expand(msg, dst, len_in_bytes).into_vec(),
+            uniform_bytes
+        );
+
+        let msg = b"a512_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+            aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+            aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+            aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+            aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+            aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+            aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+            aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+            aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+            aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
+        let len_in_bytes = 0x20;
+        let uniform_bytes = hex::decode(
+            "4623227bcc01293b8c130bf771da8c298dede7383243dc09\
+            93d2d94823958c4c",
+        )
+        .unwrap();
+        assert_eq!(
+            ExpandMsgXmd::<Sha256>::init_expand(msg, dst, len_in_bytes).into_vec(),
+            uniform_bytes
+        );
+
+        let msg = b"";
+        let len_in_bytes = 0x80;
+        let uniform_bytes = hex::decode(
+            "af84c27ccfd45d41914fdff5df25293e221afc53d8ad2ac0\
+            6d5e3e29485dadbee0d121587713a3e0dd4d5e69e93eb7cd4f5df4\
+            cd103e188cf60cb02edc3edf18eda8576c412b18ffb658e3dd6ec8\
+            49469b979d444cf7b26911a08e63cf31f9dcc541708d3491184472\
+            c2c29bb749d4286b004ceb5ee6b9a7fa5b646c993f0ced",
+        )
+        .unwrap();
+        assert_eq!(
+            ExpandMsgXmd::<Sha256>::init_expand(msg, dst, len_in_bytes).into_vec(),
+            uniform_bytes
+        );
+
+        let msg = b"abc";
+        let len_in_bytes = 0x80;
+        let uniform_bytes = hex::decode(
+            "abba86a6129e366fc877aab32fc4ffc70120d8996c88aee2\
+            fe4b32d6c7b6437a647e6c3163d40b76a73cf6a5674ef1d890f95b\
+            664ee0afa5359a5c4e07985635bbecbac65d747d3d2da7ec2b8221\
+            b17b0ca9dc8a1ac1c07ea6a1e60583e2cb00058e77b7b72a298425\
+            cd1b941ad4ec65e8afc50303a22c0f99b0509b4c895f40",
+        )
+        .unwrap();
+        assert_eq!(
+            ExpandMsgXmd::<Sha256>::init_expand(msg, dst, len_in_bytes).into_vec(),
+            uniform_bytes
+        );
+
+        let msg = b"abcdef0123456789";
+        let len_in_bytes = 0x80;
+        let uniform_bytes = hex::decode(
+            "ef904a29bffc4cf9ee82832451c946ac3c8f8058ae97d8d6\
+            29831a74c6572bd9ebd0df635cd1f208e2038e760c4994984ce73f\
+            0d55ea9f22af83ba4734569d4bc95e18350f740c07eef653cbb9f8\
+            7910d833751825f0ebefa1abe5420bb52be14cf489b37fe1a72f7d\
+            e2d10be453b2c9d9eb20c7e3f6edc5a60629178d9478df",
+        )
+        .unwrap();
+        assert_eq!(
+            ExpandMsgXmd::<Sha256>::init_expand(msg, dst, len_in_bytes).into_vec(),
+            uniform_bytes
+        );
+
+        let msg = b"q128_qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq\
+            qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq\
+            qqqqqqqqqqqqqqqqqqqqqqqqq";
+        let len_in_bytes = 0x80;
+        let uniform_bytes = hex::decode(
+            "80be107d0884f0d881bb460322f0443d38bd222db8bd0b0a\
+            5312a6fedb49c1bbd88fd75d8b9a09486c60123dfa1d73c1cc3169\
+            761b17476d3c6b7cbbd727acd0e2c942f4dd96ae3da5de368d26b3\
+            2286e32de7e5a8cb2949f866a0b80c58116b29fa7fabb3ea7d520e\
+            e603e0c25bcaf0b9a5e92ec6a1fe4e0391d1cdbce8c68a",
+        )
+        .unwrap();
+        assert_eq!(
+            ExpandMsgXmd::<Sha256>::init_expand(msg, dst, len_in_bytes).into_vec(),
+            uniform_bytes
+        );
+
+        let msg = b"a512_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+            aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+            aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+            aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+            aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+            aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+            aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+            aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+            aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+            aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
+        let len_in_bytes = 0x80;
+        let uniform_bytes = hex::decode(
+            "546aff5444b5b79aa6148bd81728704c32decb73a3ba76e9\
+            e75885cad9def1d06d6792f8a7d12794e90efed817d96920d72889\
+            6a4510864370c207f99bd4a608ea121700ef01ed879745ee3e4cee\
+            f777eda6d9e5e38b90c86ea6fb0b36504ba4a45d22e86f6db5dd43\
+            d98a294bebb9125d5b794e9d2a81181066eb954966a487",
+        )
+        .unwrap();
+        assert_eq!(
+            ExpandMsgXmd::<Sha256>::init_expand(msg, dst, len_in_bytes).into_vec(),
+            uniform_bytes
+        );
+    }
+
+    /// From <https://tools.ietf.org/html/draft-irtf-cfrg-hash-to-curve-12#appendix-K.2>
+    #[test]
+    fn expand_message_xmd_works_for_draft12_testvectors_sha256_long_dst() {
+        let dst = b"QUUX-V01-CS02-with-expander-SHA256-128-long-DST-111111\
+            111111111111111111111111111111111111111111111111111111\
+            111111111111111111111111111111111111111111111111111111\
+            111111111111111111111111111111111111111111111111111111\
+            1111111111111111111111111111111111111111";
+
+        let msg = b"";
+        let len_in_bytes = 0x20;
+        let uniform_bytes = hex::decode(
+            "e8dc0c8b686b7ef2074086fbdd2f30e3f8bfbd3bdf177f73\
+            f04b97ce618a3ed3",
+        )
+        .unwrap();
+        assert_eq!(
+            ExpandMsgXmd::<Sha256>::init_expand(msg, dst, len_in_bytes).into_vec(),
+            uniform_bytes
+        );
+
+        let msg = b"abc";
+        let len_in_bytes = 0x20;
+        let uniform_bytes = hex::decode(
+            "52dbf4f36cf560fca57dedec2ad924ee9c266341d8f3d6af\
+            e5171733b16bbb12",
+        )
+        .unwrap();
+        assert_eq!(
+            ExpandMsgXmd::<Sha256>::init_expand(msg, dst, len_in_bytes).into_vec(),
+            uniform_bytes
+        );
+
+        let msg = b"abcdef0123456789";
+        let len_in_bytes = 0x20;
+        let uniform_bytes = hex::decode(
+            "35387dcf22618f3728e6c686490f8b431f76550b0b2c61cb\
+            c1ce7001536f4521",
+        )
+        .unwrap();
+        assert_eq!(
+            ExpandMsgXmd::<Sha256>::init_expand(msg, dst, len_in_bytes).into_vec(),
+            uniform_bytes
+        );
+
+        let msg = b"q128_qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq\
+            qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq\
+            qqqqqqqqqqqqqqqqqqqqqqqqq";
+        let len_in_bytes = 0x20;
+        let uniform_bytes = hex::decode(
+            "01b637612bb18e840028be900a833a74414140dde0c4754c\
+            198532c3a0ba42bc",
+        )
+        .unwrap();
+        assert_eq!(
+            ExpandMsgXmd::<Sha256>::init_expand(msg, dst, len_in_bytes).into_vec(),
+            uniform_bytes
+        );
+
+        let msg = b"a512_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+            aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+            aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+            aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+            aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+            aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+            aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+            aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+            aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+            aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
+        let len_in_bytes = 0x20;
+        let uniform_bytes = hex::decode(
+            "20cce7033cabc5460743180be6fa8aac5a103f56d481cf36\
+            9a8accc0c374431b",
+        )
+        .unwrap();
+        assert_eq!(
+            ExpandMsgXmd::<Sha256>::init_expand(msg, dst, len_in_bytes).into_vec(),
+            uniform_bytes
+        );
+
+        let msg = b"";
+        let len_in_bytes = 0x80;
+        let uniform_bytes = hex::decode(
+            "14604d85432c68b757e485c8894db3117992fc57e0e136f7\
+            1ad987f789a0abc287c47876978e2388a02af86b1e8d1342e5ce4f\
+            7aaa07a87321e691f6fba7e0072eecc1218aebb89fb14a0662322d\
+            5edbd873f0eb35260145cd4e64f748c5dfe60567e126604bcab1a3\
+            ee2dc0778102ae8a5cfd1429ebc0fa6bf1a53c36f55dfc",
+        )
+        .unwrap();
+        assert_eq!(
+            ExpandMsgXmd::<Sha256>::init_expand(msg, dst, len_in_bytes).into_vec(),
+            uniform_bytes
+        );
+
+        let msg = b"abc";
+        let len_in_bytes = 0x80;
+        let uniform_bytes = hex::decode(
+            "1a30a5e36fbdb87077552b9d18b9f0aee16e80181d5b951d\
+            0471d55b66684914aef87dbb3626eaabf5ded8cd0686567e503853\
+            e5c84c259ba0efc37f71c839da2129fe81afdaec7fbdc0ccd4c794\
+            727a17c0d20ff0ea55e1389d6982d1241cb8d165762dbc39fb0cee\
+            4474d2cbbd468a835ae5b2f20e4f959f56ab24cd6fe267",
+        )
+        .unwrap();
+        assert_eq!(
+            ExpandMsgXmd::<Sha256>::init_expand(msg, dst, len_in_bytes).into_vec(),
+            uniform_bytes
+        );
+
+        let msg = b"abcdef0123456789";
+        let len_in_bytes = 0x80;
+        let uniform_bytes = hex::decode(
+            "d2ecef3635d2397f34a9f86438d772db19ffe9924e28a1ca\
+            f6f1c8f15603d4028f40891044e5c7e39ebb9b31339979ff33a424\
+            9206f67d4a1e7c765410bcd249ad78d407e303675918f20f26ce6d\
+            7027ed3774512ef5b00d816e51bfcc96c3539601fa48ef1c07e494\
+            bdc37054ba96ecb9dbd666417e3de289d4f424f502a982",
+        )
+        .unwrap();
+        assert_eq!(
+            ExpandMsgXmd::<Sha256>::init_expand(msg, dst, len_in_bytes).into_vec(),
+            uniform_bytes
+        );
+
+        let msg = b"q128_qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq\
+            qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq\
+            qqqqqqqqqqqqqqqqqqqqqqqqq";
+        let len_in_bytes = 0x80;
+        let uniform_bytes = hex::decode(
+            "ed6e8c036df90111410431431a232d41a32c86e296c05d42\
+            6e5f44e75b9a50d335b2412bc6c91e0a6dc131de09c43110d9180d\
+            0a70f0d6289cb4e43b05f7ee5e9b3f42a1fad0f31bac6a625b3b5c\
+            50e3a83316783b649e5ecc9d3b1d9471cb5024b7ccf40d41d1751a\
+            04ca0356548bc6e703fca02ab521b505e8e45600508d32",
+        )
+        .unwrap();
+        assert_eq!(
+            ExpandMsgXmd::<Sha256>::init_expand(msg, dst, len_in_bytes).into_vec(),
+            uniform_bytes
+        );
+
+        let msg = b"a512_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+            aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+            aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+            aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+            aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+            aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+            aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+            aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+            aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+            aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
+        let len_in_bytes = 0x80;
+        let uniform_bytes = hex::decode(
+            "78b53f2413f3c688f07732c10e5ced29a17c6a16f717179f\
+            fbe38d92d6c9ec296502eb9889af83a1928cd162e845b0d3c5424e\
+            83280fed3d10cffb2f8431f14e7a23f4c68819d40617589e4c4116\
+            9d0b56e0e3535be1fd71fbb08bb70c5b5ffed953d6c14bf7618b35\
+            fc1f4c4b30538236b4b08c9fbf90462447a8ada60be495",
+        )
+        .unwrap();
+        assert_eq!(
+            ExpandMsgXmd::<Sha256>::init_expand(msg, dst, len_in_bytes).into_vec(),
+            uniform_bytes
+        );
+    }
+
+    /// From <https://tools.ietf.org/html/draft-irtf-cfrg-hash-to-curve-12#appendix-K.3>
+    #[test]
+    fn expand_message_xmd_works_for_draft12_testvectors_sha512() {
+        let dst = b"QUUX-V01-CS02-with-expander-SHA512-256";
+
+        let msg = b"";
+        let len_in_bytes = 0x20;
+        let uniform_bytes = hex::decode(
+            "6b9a7312411d92f921c6f68ca0b6380730a1a4d982c50721\
+            1a90964c394179ba",
+        )
+        .unwrap();
+        assert_eq!(
+            ExpandMsgXmd::<Sha512>::init_expand(msg, dst, len_in_bytes).into_vec(),
+            uniform_bytes
+        );
+
+        let msg = b"abc";
+        let len_in_bytes = 0x20;
+        let uniform_bytes = hex::decode(
+            "0da749f12fbe5483eb066a5f595055679b976e93abe9be6f\
+            0f6318bce7aca8dc",
+        )
+        .unwrap();
+        assert_eq!(
+            ExpandMsgXmd::<Sha512>::init_expand(msg, dst, len_in_bytes).into_vec(),
+            uniform_bytes
+        );
+
+        let msg = b"abcdef0123456789";
+        let len_in_bytes = 0x20;
+        let uniform_bytes = hex::decode(
+            "087e45a86e2939ee8b91100af1583c4938e0f5fc6c9db4b1\
+            07b83346bc967f58",
+        )
+        .unwrap();
+        assert_eq!(
+            ExpandMsgXmd::<Sha512>::init_expand(msg, dst, len_in_bytes).into_vec(),
+            uniform_bytes
+        );
+
+        let msg = b"q128_qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq\
+            qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq\
+            qqqqqqqqqqqqqqqqqqqqqqqqq";
+        let len_in_bytes = 0x20;
+        let uniform_bytes = hex::decode(
+            "7336234ee9983902440f6bc35b348352013becd88938d2af\
+            ec44311caf8356b3",
+        )
+        .unwrap();
+        assert_eq!(
+            ExpandMsgXmd::<Sha512>::init_expand(msg, dst, len_in_bytes).into_vec(),
+            uniform_bytes
+        );
+
+        let msg = b"a512_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+            aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+            aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+            aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+            aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+            aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+            aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+            aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+            aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+            aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
+        let len_in_bytes = 0x20;
+        let uniform_bytes = hex::decode(
+            "57b5f7e766d5be68a6bfe1768e3c2b7f1228b3e4b3134956\
+            dd73a59b954c66f4",
+        )
+        .unwrap();
+        assert_eq!(
+            ExpandMsgXmd::<Sha512>::init_expand(msg, dst, len_in_bytes).into_vec(),
+            uniform_bytes
+        );
+
+        let msg = b"";
+        let len_in_bytes = 0x80;
+        let uniform_bytes = hex::decode(
+            "41b037d1734a5f8df225dd8c7de38f851efdb45c372887be\
+            655212d07251b921b052b62eaed99b46f72f2ef4cc96bfaf254ebb\
+            bec091e1a3b9e4fb5e5b619d2e0c5414800a1d882b62bb5cd1778f\
+            098b8eb6cb399d5d9d18f5d5842cf5d13d7eb00a7cff859b605da6\
+            78b318bd0e65ebff70bec88c753b159a805d2c89c55961",
+        )
+        .unwrap();
+        assert_eq!(
+            ExpandMsgXmd::<Sha512>::init_expand(msg, dst, len_in_bytes).into_vec(),
+            uniform_bytes
+        );
+
+        let msg = b"abc";
+        let len_in_bytes = 0x80;
+        let uniform_bytes = hex::decode(
+            "7f1dddd13c08b543f2e2037b14cefb255b44c83cc397c178\
+            6d975653e36a6b11bdd7732d8b38adb4a0edc26a0cef4bb4521713\
+            5456e58fbca1703cd6032cb1347ee720b87972d63fbf232587043e\
+            d2901bce7f22610c0419751c065922b488431851041310ad659e4b\
+            23520e1772ab29dcdeb2002222a363f0c2b1c972b3efe1",
+        )
+        .unwrap();
+        assert_eq!(
+            ExpandMsgXmd::<Sha512>::init_expand(msg, dst, len_in_bytes).into_vec(),
+            uniform_bytes
+        );
+
+        let msg = b"abcdef0123456789";
+        let len_in_bytes = 0x80;
+        let uniform_bytes = hex::decode(
+            "3f721f208e6199fe903545abc26c837ce59ac6fa45733f1b\
+            aaf0222f8b7acb0424814fcb5eecf6c1d38f06e9d0a6ccfbf85ae6\
+            12ab8735dfdf9ce84c372a77c8f9e1c1e952c3a61b7567dd069301\
+            6af51d2745822663d0c2367e3f4f0bed827feecc2aaf98c949b5ed\
+            0d35c3f1023d64ad1407924288d366ea159f46287e61ac",
+        )
+        .unwrap();
+        assert_eq!(
+            ExpandMsgXmd::<Sha512>::init_expand(msg, dst, len_in_bytes).into_vec(),
+            uniform_bytes
+        );
+
+        let msg = b"q128_qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq\
+            qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq\
+            qqqqqqqqqqqqqqqqqqqqqqqqq";
+        let len_in_bytes = 0x80;
+        let uniform_bytes = hex::decode(
+            "b799b045a58c8d2b4334cf54b78260b45eec544f9f2fb5bd\
+            12fb603eaee70db7317bf807c406e26373922b7b8920fa29142703\
+            dd52bdf280084fb7ef69da78afdf80b3586395b433dc66cde048a2\
+            58e476a561e9deba7060af40adf30c64249ca7ddea79806ee5beb9\
+            a1422949471d267b21bc88e688e4014087a0b592b695ed",
+        )
+        .unwrap();
+        assert_eq!(
+            ExpandMsgXmd::<Sha512>::init_expand(msg, dst, len_in_bytes).into_vec(),
+            uniform_bytes
+        );
+
+        let msg = b"a512_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+            aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+            aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+            aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+            aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+            aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+            aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+            aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+            aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+            aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
+        let len_in_bytes = 0x80;
+        let uniform_bytes = hex::decode(
+            "05b0bfef265dcee87654372777b7c44177e2ae4c13a27f10\
+            3340d9cd11c86cb2426ffcad5bd964080c2aee97f03be1ca18e30a\
+            1f14e27bc11ebbd650f305269cc9fb1db08bf90bfc79b42a952b46\
+            daf810359e7bc36452684784a64952c343c52e5124cd1f71d474d5\
+            197fefc571a92929c9084ffe1112cf5eea5192ebff330b",
+        )
+        .unwrap();
+        assert_eq!(
+            ExpandMsgXmd::<Sha512>::init_expand(msg, dst, len_in_bytes).into_vec(),
+            uniform_bytes
+        );
+    }
+
+    /// From <https://tools.ietf.org/html/draft-irtf-cfrg-hash-to-curve-12#appendix-K.4>
+    #[test]
+    fn expand_message_xof_works_for_draft12_testvectors_shake128() {
+        let dst = b"QUUX-V01-CS02-with-expander-SHAKE128";
+
+        let msg = b"";
+        let len_in_bytes = 0x20;
+        let uniform_bytes = hex::decode(
+            "86518c9cd86581486e9485aa74ab35ba150d1c75c88e26b7\
+            043e44e2acd735a2",
+        )
+        .unwrap();
+        assert_eq!(
+            ExpandMsgXof::<Shake128>::init_expand(msg, dst, len_in_bytes).into_vec(),
+            uniform_bytes
+        );
+
+        let msg = b"abc";
+        let len_in_bytes = 0x20;
+        let uniform_bytes = hex::decode(
+            "8696af52a4d862417c0763556073f47bc9b9ba43c99b5053\
+            05cb1ec04a9ab468",
+        )
+        .unwrap();
+        assert_eq!(
+            ExpandMsgXof::<Shake128>::init_expand(msg, dst, len_in_bytes).into_vec(),
+            uniform_bytes
+        );
+
+        let msg = b"abcdef0123456789";
+        let len_in_bytes = 0x20;
+        let uniform_bytes = hex::decode(
+            "912c58deac4821c3509dbefa094df54b34b8f5d01a191d1d\
+            3108a2c89077acca",
+        )
+        .unwrap();
+        assert_eq!(
+            ExpandMsgXof::<Shake128>::init_expand(msg, dst, len_in_bytes).into_vec(),
+            uniform_bytes
+        );
+
+        let msg = b"q128_qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq\
+            qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq\
+            qqqqqqqqqqqqqqqqqqqqqqqqq";
+        let len_in_bytes = 0x20;
+        let uniform_bytes = hex::decode(
+            "1adbcc448aef2a0cebc71dac9f756b22e51839d348e031e6\
+            3b33ebb50faeaf3f",
+        )
+        .unwrap();
+        assert_eq!(
+            ExpandMsgXof::<Shake128>::init_expand(msg, dst, len_in_bytes).into_vec(),
+            uniform_bytes
+        );
+
+        let msg = b"a512_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+            aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+            aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+            aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+            aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+            aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+            aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+            aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+            aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+            aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
+        let len_in_bytes = 0x20;
+        let uniform_bytes = hex::decode(
+            "df3447cc5f3e9a77da10f819218ddf31342c310778e0e4ef\
+            72bbaecee786a4fe",
+        )
+        .unwrap();
+        assert_eq!(
+            ExpandMsgXof::<Shake128>::init_expand(msg, dst, len_in_bytes).into_vec(),
+            uniform_bytes
+        );
+
+        let msg = b"";
+        let len_in_bytes = 0x80;
+        let uniform_bytes = hex::decode(
+            "7314ff1a155a2fb99a0171dc71b89ab6e3b2b7d59e38e644\
+            19b8b6294d03ffee42491f11370261f436220ef787f8f76f5b26bd\
+            cd850071920ce023f3ac46847744f4612b8714db8f5db83205b2e6\
+            25d95afd7d7b4d3094d3bdde815f52850bb41ead9822e08f22cf41\
+            d615a303b0d9dde73263c049a7b9898208003a739a2e57",
+        )
+        .unwrap();
+        assert_eq!(
+            ExpandMsgXof::<Shake128>::init_expand(msg, dst, len_in_bytes).into_vec(),
+            uniform_bytes
+        );
+
+        let msg = b"abc";
+        let len_in_bytes = 0x80;
+        let uniform_bytes = hex::decode(
+            "c952f0c8e529ca8824acc6a4cab0e782fc3648c563ddb00d\
+            a7399f2ae35654f4860ec671db2356ba7baa55a34a9d7f79197b60\
+            ddae6e64768a37d699a78323496db3878c8d64d909d0f8a7de4927\
+            dcab0d3dbbc26cb20a49eceb0530b431cdf47bc8c0fa3e0d88f53b\
+            318b6739fbed7d7634974f1b5c386d6230c76260d5337a",
+        )
+        .unwrap();
+        assert_eq!(
+            ExpandMsgXof::<Shake128>::init_expand(msg, dst, len_in_bytes).into_vec(),
+            uniform_bytes
+        );
+
+        let msg = b"abcdef0123456789";
+        let len_in_bytes = 0x80;
+        let uniform_bytes = hex::decode(
+            "19b65ee7afec6ac06a144f2d6134f08eeec185f1a890fe34\
+            e68f0e377b7d0312883c048d9b8a1d6ecc3b541cb4987c26f45e0c\
+            82691ea299b5e6889bbfe589153016d8131717ba26f07c3c14ffbe\
+            f1f3eff9752e5b6183f43871a78219a75e7000fbac6a7072e2b83c\
+            790a3a5aecd9d14be79f9fd4fb180960a3772e08680495",
+        )
+        .unwrap();
+        assert_eq!(
+            ExpandMsgXof::<Shake128>::init_expand(msg, dst, len_in_bytes).into_vec(),
+            uniform_bytes
+        );
+
+        let msg = b"q128_qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq\
+            qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq\
+            qqqqqqqqqqqqqqqqqqqqqqqqq";
+        let len_in_bytes = 0x80;
+        let uniform_bytes = hex::decode(
+            "ca1b56861482b16eae0f4a26212112362fcc2d76dcc80c93\
+            c4182ed66c5113fe41733ed68be2942a3487394317f3379856f482\
+            2a611735e50528a60e7ade8ec8c71670fec6661e2c59a09ed36386\
+            513221688b35dc47e3c3111ee8c67ff49579089d661caa29db1ef1\
+            0eb6eace575bf3dc9806e7c4016bd50f3c0e2a6481ee6d",
+        )
+        .unwrap();
+        assert_eq!(
+            ExpandMsgXof::<Shake128>::init_expand(msg, dst, len_in_bytes).into_vec(),
+            uniform_bytes
+        );
+
+        let msg = b"a512_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+            aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+            aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+            aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+            aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+            aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+            aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+            aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+            aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+            aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
+        let len_in_bytes = 0x80;
+        let uniform_bytes = hex::decode(
+            "9d763a5ce58f65c91531b4100c7266d479a5d9777ba76169\
+            3d052acd37d149e7ac91c796a10b919cd74a591a1e38719fb91b72\
+            03e2af31eac3bff7ead2c195af7d88b8bc0a8adf3d1e90ab9bed6d\
+            dc2b7f655dd86c730bdeaea884e73741097142c92f0e3fc1811b69\
+            9ba593c7fbd81da288a29d423df831652e3a01a9374999",
+        )
+        .unwrap();
+        assert_eq!(
+            ExpandMsgXof::<Shake128>::init_expand(msg, dst, len_in_bytes).into_vec(),
+            uniform_bytes
+        );
+    }
+
+    /// From <https://tools.ietf.org/html/draft-irtf-cfrg-hash-to-curve-12#appendix-K.5>
+    #[test]
+    fn expand_message_xof_works_for_draft12_testvectors_shake128_long_dst() {
+        let dst = b"QUUX-V01-CS02-with-expander-SHAKE128-long-DST-11111111\
+            111111111111111111111111111111111111111111111111111111\
+            111111111111111111111111111111111111111111111111111111\
+            111111111111111111111111111111111111111111111111111111\
+            1111111111111111111111111111111111111111";
+
+        let msg = b"";
+        let len_in_bytes = 0x20;
+        let uniform_bytes = hex::decode(
+            "827c6216330a122352312bccc0c8d6e7a146c5257a776dbd\
+            9ad9d75cd880fc53",
+        )
+        .unwrap();
+        assert_eq!(
+            ExpandMsgXof::<Shake128>::init_expand(msg, dst, len_in_bytes).into_vec(),
+            uniform_bytes
+        );
+
+        let msg = b"abc";
+        let len_in_bytes = 0x20;
+        let uniform_bytes = hex::decode(
+            "690c8d82c7213b4282c6cb41c00e31ea1d3e2005f93ad19b\
+            bf6da40f15790c5c",
+        )
+        .unwrap();
+        assert_eq!(
+            ExpandMsgXof::<Shake128>::init_expand(msg, dst, len_in_bytes).into_vec(),
+            uniform_bytes
+        );
+
+        let msg = b"abcdef0123456789";
+        let len_in_bytes = 0x20;
+        let uniform_bytes = hex::decode(
+            "979e3a15064afbbcf99f62cc09fa9c85028afcf3f825eb07\
+            11894dcfc2f57057",
+        )
+        .unwrap();
+        assert_eq!(
+            ExpandMsgXof::<Shake128>::init_expand(msg, dst, len_in_bytes).into_vec(),
+            uniform_bytes
+        );
+
+        let msg = b"q128_qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq\
+            qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq\
+            qqqqqqqqqqqqqqqqqqqqqqqqq";
+        let len_in_bytes = 0x20;
+        let uniform_bytes = hex::decode(
+            "c5a9220962d9edc212c063f4f65b609755a1ed96e62f9db5\
+            d1fd6adb5a8dc52b",
+        )
+        .unwrap();
+        assert_eq!(
+            ExpandMsgXof::<Shake128>::init_expand(msg, dst, len_in_bytes).into_vec(),
+            uniform_bytes
+        );
+
+        let msg = b"a512_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+            aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+            aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+            aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+            aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+            aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+            aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+            aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+            aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+            aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
+        let len_in_bytes = 0x20;
+        let uniform_bytes = hex::decode(
+            "f7b96a5901af5d78ce1d071d9c383cac66a1dfadb508300e\
+            c6aeaea0d62d5d62",
+        )
+        .unwrap();
+        assert_eq!(
+            ExpandMsgXof::<Shake128>::init_expand(msg, dst, len_in_bytes).into_vec(),
+            uniform_bytes
+        );
+
+        let msg = b"";
+        let len_in_bytes = 0x80;
+        let uniform_bytes = hex::decode(
+            "3890dbab00a2830be398524b71c2713bbef5f4884ac2e6f0\
+            70b092effdb19208c7df943dc5dcbaee3094a78c267ef276632ee2\
+            c8ea0c05363c94b6348500fae4208345dd3475fe0c834c2beac7fa\
+            7bc181692fb728c0a53d809fc8111495222ce0f38468b11becb15b\
+            32060218e285c57a60162c2c8bb5b6bded13973cd41819",
+        )
+        .unwrap();
+        assert_eq!(
+            ExpandMsgXof::<Shake128>::init_expand(msg, dst, len_in_bytes).into_vec(),
+            uniform_bytes
+        );
+
+        let msg = b"abc";
+        let len_in_bytes = 0x80;
+        let uniform_bytes = hex::decode(
+            "41b7ffa7a301b5c1441495ebb9774e2a53dbbf4e54b9a1af\
+            6a20fd41eafd69ef7b9418599c5545b1ee422f363642b01d4a5344\
+            9313f68da3e49dddb9cd25b97465170537d45dcbdf92391b5bdff3\
+            44db4bd06311a05bca7dcd360b6caec849c299133e5c9194f4e15e\
+            3e23cfaab4003fab776f6ac0bfae9144c6e2e1c62e7d57",
+        )
+        .unwrap();
+        assert_eq!(
+            ExpandMsgXof::<Shake128>::init_expand(msg, dst, len_in_bytes).into_vec(),
+            uniform_bytes
+        );
+
+        let msg = b"abcdef0123456789";
+        let len_in_bytes = 0x80;
+        let uniform_bytes = hex::decode(
+            "55317e4a21318472cd2290c3082957e1242241d9e0d04f47\
+            026f03401643131401071f01aa03038b2783e795bdfa8a3541c194\
+            ad5de7cb9c225133e24af6c86e748deb52e560569bd54ef4dac034\
+            65111a3a44b0ea490fb36777ff8ea9f1a8a3e8e0de3cf0880b4b2f\
+            8dd37d3a85a8b82375aee4fa0e909f9763319b55778e71",
+        )
+        .unwrap();
+        assert_eq!(
+            ExpandMsgXof::<Shake128>::init_expand(msg, dst, len_in_bytes).into_vec(),
+            uniform_bytes
+        );
+
+        let msg = b"q128_qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq\
+            qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq\
+            qqqqqqqqqqqqqqqqqqqqqqqqq";
+        let len_in_bytes = 0x80;
+        let uniform_bytes = hex::decode(
+            "19fdd2639f082e31c77717ac9bb032a22ff0958382b2dbb3\
+            9020cdc78f0da43305414806abf9a561cb2d0067eb2f7bc544482f\
+            75623438ed4b4e39dd9e6e2909dd858bd8f1d57cd0fce2d3150d90\
+            aa67b4498bdf2df98c0100dd1a173436ba5d0df6be1defb0b2ce55\
+            ccd2f4fc05eb7cb2c019c35d5398b85adc676da4238bc7",
+        )
+        .unwrap();
+        assert_eq!(
+            ExpandMsgXof::<Shake128>::init_expand(msg, dst, len_in_bytes).into_vec(),
+            uniform_bytes
+        );
+
+        let msg = b"a512_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+            aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+            aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+            aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+            aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+            aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+            aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+            aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+            aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+            aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
+        let len_in_bytes = 0x80;
+        let uniform_bytes = hex::decode(
+            "945373f0b3431a103333ba6a0a34f1efab2702efde41754c\
+            4cb1d5216d5b0a92a67458d968562bde7fa6310a83f53dda138368\
+            0a276a283438d58ceebfa7ab7ba72499d4a3eddc860595f63c93b1\
+            c5e823ea41fc490d938398a26db28f61857698553e93f0574eb8c5\
+            017bfed6249491f9976aaa8d23d9485339cc85ca329308",
+        )
+        .unwrap();
+        assert_eq!(
+            ExpandMsgXof::<Shake128>::init_expand(msg, dst, len_in_bytes).into_vec(),
+            uniform_bytes
+        );
+    }
+
+    /// From <https://tools.ietf.org/html/draft-irtf-cfrg-hash-to-curve-12#appendix-K.6>
+    #[test]
+    fn expand_message_xof_works_for_draft12_testvectors_shake256() {
+        let dst = b"QUUX-V01-CS02-with-expander-SHAKE256";
+
+        let msg = b"";
+        let len_in_bytes = 0x20;
+        let uniform_bytes = hex::decode(
+            "2ffc05c48ed32b95d72e807f6eab9f7530dd1c2f013914c8\
+            fed38c5ccc15ad76",
+        )
+        .unwrap();
+        assert_eq!(
+            ExpandMsgXof::<Shake256>::init_expand(msg, dst, len_in_bytes).into_vec(),
+            uniform_bytes
+        );
+
+        let msg = b"abc";
+        let len_in_bytes = 0x20;
+        let uniform_bytes = hex::decode(
+            "b39e493867e2767216792abce1f2676c197c0692aed06156\
+            0ead251821808e07",
+        )
+        .unwrap();
+        assert_eq!(
+            ExpandMsgXof::<Shake256>::init_expand(msg, dst, len_in_bytes).into_vec(),
+            uniform_bytes
+        );
+
+        let msg = b"abcdef0123456789";
+        let len_in_bytes = 0x20;
+        let uniform_bytes = hex::decode(
+            "245389cf44a13f0e70af8665fe5337ec2dcd138890bb7901\
+            c4ad9cfceb054b65",
+        )
+        .unwrap();
+        assert_eq!(
+            ExpandMsgXof::<Shake256>::init_expand(msg, dst, len_in_bytes).into_vec(),
+            uniform_bytes
+        );
+
+        let msg = b"q128_qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq\
+            qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq\
+            qqqqqqqqqqqqqqqqqqqqqqqqq";
+        let len_in_bytes = 0x20;
+        let uniform_bytes = hex::decode(
+            "719b3911821e6428a5ed9b8e600f2866bcf23c8f0515e52d\
+            6c6c019a03f16f0e",
+        )
+        .unwrap();
+        assert_eq!(
+            ExpandMsgXof::<Shake256>::init_expand(msg, dst, len_in_bytes).into_vec(),
+            uniform_bytes
+        );
+
+        let msg = b"a512_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+            aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+            aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+            aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+            aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+            aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+            aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+            aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+            aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+            aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
+        let len_in_bytes = 0x20;
+        let uniform_bytes = hex::decode(
+            "9181ead5220b1963f1b5951f35547a5ea86a820562287d6c\
+            a4723633d17ccbbc",
+        )
+        .unwrap();
+        assert_eq!(
+            ExpandMsgXof::<Shake256>::init_expand(msg, dst, len_in_bytes).into_vec(),
+            uniform_bytes
+        );
+
+        let msg = b"";
+        let len_in_bytes = 0x80;
+        let uniform_bytes = hex::decode(
+            "7a1361d2d7d82d79e035b8880c5a3c86c5afa719478c007d\
+            96e6c88737a3f631dd74a2c88df79a4cb5e5d9f7504957c70d669e\
+            c6bfedc31e01e2bacc4ff3fdf9b6a00b17cc18d9d72ace7d6b81c2\
+            e481b4f73f34f9a7505dccbe8f5485f3d20c5409b0310093d5d649\
+            2dea4e18aa6979c23c8ea5de01582e9689612afbb353df",
+        )
+        .unwrap();
+        assert_eq!(
+            ExpandMsgXof::<Shake256>::init_expand(msg, dst, len_in_bytes).into_vec(),
+            uniform_bytes
+        );
+
+        let msg = b"abc";
+        let len_in_bytes = 0x80;
+        let uniform_bytes = hex::decode(
+            "a54303e6b172909783353ab05ef08dd435a558c3197db0c1\
+            32134649708e0b9b4e34fb99b92a9e9e28fc1f1d8860d85897a8e0\
+            21e6382f3eea10577f968ff6df6c45fe624ce65ca25932f679a42a\
+            404bc3681efe03fcd45ef73bb3a8f79ba784f80f55ea8a3c367408\
+            f30381299617f50c8cf8fbb21d0f1e1d70b0131a7b6fbe",
+        )
+        .unwrap();
+        assert_eq!(
+            ExpandMsgXof::<Shake256>::init_expand(msg, dst, len_in_bytes).into_vec(),
+            uniform_bytes
+        );
+
+        let msg = b"abcdef0123456789";
+        let len_in_bytes = 0x80;
+        let uniform_bytes = hex::decode(
+            "e42e4d9538a189316e3154b821c1bafb390f78b2f010ea40\
+            4e6ac063deb8c0852fcd412e098e231e43427bd2be1330bb47b403\
+            9ad57b30ae1fc94e34993b162ff4d695e42d59d9777ea18d3848d9\
+            d336c25d2acb93adcad009bcfb9cde12286df267ada283063de0bb\
+            1505565b2eb6c90e31c48798ecdc71a71756a9110ff373",
+        )
+        .unwrap();
+        assert_eq!(
+            ExpandMsgXof::<Shake256>::init_expand(msg, dst, len_in_bytes).into_vec(),
+            uniform_bytes
+        );
+
+        let msg = b"q128_qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq\
+            qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq\
+            qqqqqqqqqqqqqqqqqqqqqqqqq";
+        let len_in_bytes = 0x80;
+        let uniform_bytes = hex::decode(
+            "4ac054dda0a38a65d0ecf7afd3c2812300027c8789655e47\
+            aecf1ecc1a2426b17444c7482c99e5907afd9c25b991990490bb9c\
+            686f43e79b4471a23a703d4b02f23c669737a886a7ec28bddb92c3\
+            a98de63ebf878aa363a501a60055c048bea11840c4717beae7eee2\
+            8c3cfa42857b3d130188571943a7bd747de831bd6444e0",
+        )
+        .unwrap();
+        assert_eq!(
+            ExpandMsgXof::<Shake256>::init_expand(msg, dst, len_in_bytes).into_vec(),
+            uniform_bytes
+        );
+
+        let msg = b"a512_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+            aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+            aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+            aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+            aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+            aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+            aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+            aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+            aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+            aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
+        let len_in_bytes = 0x80;
+        let uniform_bytes = hex::decode(
+            "09afc76d51c2cccbc129c2315df66c2be7295a231203b8ab\
+            2dd7f95c2772c68e500bc72e20c602abc9964663b7a03a389be128\
+            c56971ce81001a0b875e7fd17822db9d69792ddf6a23a151bf4700\
+            79c518279aef3e75611f8f828994a9988f4a8a256ddb8bae161e65\
+            8d5a2a09bcfe839c6396dc06ee5c8ff3c22d3b1f9deb7e",
+        )
+        .unwrap();
+        assert_eq!(
+            ExpandMsgXof::<Shake256>::init_expand(msg, dst, len_in_bytes).into_vec(),
+            uniform_bytes
+        );
+    }
+}
diff --git a/src/hash_to_curve/map_g1.rs b/src/hash_to_curve/map_g1.rs
new file mode 100644
index 00000000..0f2c94e6
--- /dev/null
+++ b/src/hash_to_curve/map_g1.rs
@@ -0,0 +1,961 @@
+//! Implementation of hash-to-curve for the G1 group.
+
+use subtle::{Choice, ConditionallyNegatable, ConditionallySelectable, ConstantTimeEq};
+
+use super::chain::chain_pm3div4;
+use super::{HashToField, MapToCurve, Sgn0};
+use crate::fp::Fp;
+use crate::g1::G1Projective;
+use crate::generic_array::{typenum::U64, GenericArray};
+
+/// Coefficients of the 11-isogeny x map's numerator
+const ISO11_XNUM: [Fp; 12] = [
+    Fp::from_raw_unchecked([
+        0x4d18_b6f3_af00_131c,
+        0x19fa_2197_93fe_e28c,
+        0x3f28_85f1_467f_19ae,
+        0x23dc_ea34_f2ff_b304,
+        0xd15b_58d2_ffc0_0054,
+        0x0913_be20_0a20_bef4,
+    ]),
+    Fp::from_raw_unchecked([
+        0x8989_8538_5cdb_bd8b,
+        0x3c79_e43c_c7d9_66aa,
+        0x1597_e193_f4cd_233a,
+        0x8637_ef1e_4d66_23ad,
+        0x11b2_2dee_d20d_827b,
+        0x0709_7bc5_9987_84ad,
+    ]),
+    Fp::from_raw_unchecked([
+        0xa542_583a_480b_664b,
+        0xfc71_69c0_26e5_68c6,
+        0x5ba2_ef31_4ed8_b5a6,
+        0x5b54_91c0_5102_f0e7,
+        0xdf6e_9970_7d2a_0079,
+        0x0784_151e_d760_5524,
+    ]),
+    Fp::from_raw_unchecked([
+        0x494e_2128_70f7_2741,
+        0xab9b_e52f_bda4_3021,
+        0x26f5_5779_94e3_4c3d,
+        0x049d_fee8_2aef_bd60,
+        0x65da_dd78_2850_5289,
+        0x0e93_d431_ea01_1aeb,
+    ]),
+    Fp::from_raw_unchecked([
+        0x90ee_774b_d6a7_4d45,
+        0x7ada_1c8a_41bf_b185,
+        0x0f1a_8953_b325_f464,
+        0x104c_2421_1be4_805c,
+        0x1691_39d3_19ea_7a8f,
+        0x09f2_0ead_8e53_2bf6,
+    ]),
+    Fp::from_raw_unchecked([
+        0x6ddd_93e2_f436_26b7,
+        0xa548_2c9a_a1cc_d7bd,
+        0x1432_4563_1883_f4bd,
+        0x2e0a_94cc_f77e_c0db,
+        0xb028_2d48_0e56_489f,
+        0x18f4_bfcb_b436_8929,
+    ]),
+    Fp::from_raw_unchecked([
+        0x23c5_f0c9_5340_2dfd,
+        0x7a43_ff69_58ce_4fe9,
+        0x2c39_0d3d_2da5_df63,
+        0xd0df_5c98_e1f9_d70f,
+        0xffd8_9869_a572_b297,
+        0x1277_ffc7_2f25_e8fe,
+    ]),
+    Fp::from_raw_unchecked([
+        0x79f4_f049_0f06_a8a6,
+        0x85f8_94a8_8030_fd81,
+        0x12da_3054_b18b_6410,
+        0xe2a5_7f65_0588_0d65,
+        0xbba0_74f2_60e4_00f1,
+        0x08b7_6279_f621_d028,
+    ]),
+    Fp::from_raw_unchecked([
+        0xe672_45ba_78d5_b00b,
+        0x8456_ba9a_1f18_6475,
+        0x7888_bff6_e6b3_3bb4,
+        0xe215_85b9_a30f_86cb,
+        0x05a6_9cdc_ef55_feee,
+        0x09e6_99dd_9adf_a5ac,
+    ]),
+    Fp::from_raw_unchecked([
+        0x0de5_c357_bff5_7107,
+        0x0a0d_b4ae_6b1a_10b2,
+        0xe256_bb67_b3b3_cd8d,
+        0x8ad4_5657_4e9d_b24f,
+        0x0443_915f_50fd_4179,
+        0x098c_4bf7_de8b_6375,
+    ]),
+    Fp::from_raw_unchecked([
+        0xe6b0_617e_7dd9_29c7,
+        0xfe6e_37d4_4253_7375,
+        0x1daf_deda_137a_489e,
+        0xe4ef_d1ad_3f76_7ceb,
+        0x4a51_d866_7f0f_e1cf,
+        0x054f_df4b_bf1d_821c,
+    ]),
+    Fp::from_raw_unchecked([
+        0x72db_2a50_658d_767b,
+        0x8abf_91fa_a257_b3d5,
+        0xe969_d683_3764_ab47,
+        0x4641_7014_2a10_09eb,
+        0xb14f_01aa_db30_be2f,
+        0x18ae_6a85_6f40_715d,
+    ]),
+];
+
+/// Coefficients of the 11-isogeny x map's denominator
+const ISO11_XDEN: [Fp; 11] = [
+    Fp::from_raw_unchecked([
+        0xb962_a077_fdb0_f945,
+        0xa6a9_740f_efda_13a0,
+        0xc14d_568c_3ed6_c544,
+        0xb43f_c37b_908b_133e,
+        0x9c0b_3ac9_2959_9016,
+        0x0165_aa6c_93ad_115f,
+    ]),
+    Fp::from_raw_unchecked([
+        0x2327_9a3b_a506_c1d9,
+        0x92cf_ca0a_9465_176a,
+        0x3b29_4ab1_3755_f0ff,
+        0x116d_da1c_5070_ae93,
+        0xed45_3092_4cec_2045,
+        0x0833_83d6_ed81_f1ce,
+    ]),
+    Fp::from_raw_unchecked([
+        0x9885_c2a6_449f_ecfc,
+        0x4a2b_54cc_d377_33f0,
+        0x17da_9ffd_8738_c142,
+        0xa0fb_a727_32b3_fafd,
+        0xff36_4f36_e54b_6812,
+        0x0f29_c13c_6605_23e2,
+    ]),
+    Fp::from_raw_unchecked([
+        0xe349_cc11_8278_f041,
+        0xd487_228f_2f32_04fb,
+        0xc9d3_2584_9ade_5150,
+        0x43a9_2bd6_9c15_c2df,
+        0x1c2c_7844_bc41_7be4,
+        0x1202_5184_f407_440c,
+    ]),
+    Fp::from_raw_unchecked([
+        0x587f_65ae_6acb_057b,
+        0x1444_ef32_5140_201f,
+        0xfbf9_95e7_1270_da49,
+        0xccda_0660_7243_6a42,
+        0x7408_904f_0f18_6bb2,
+        0x13b9_3c63_edf6_c015,
+    ]),
+    Fp::from_raw_unchecked([
+        0xfb91_8622_cd14_1920,
+        0x4a4c_6442_3eca_ddb4,
+        0x0beb_2329_27f7_fb26,
+        0x30f9_4df6_f83a_3dc2,
+        0xaeed_d424_d780_f388,
+        0x06cc_402d_d594_bbeb,
+    ]),
+    Fp::from_raw_unchecked([
+        0xd41f_7611_51b2_3f8f,
+        0x32a9_2465_4357_19b3,
+        0x64f4_36e8_88c6_2cb9,
+        0xdf70_a9a1_f757_c6e4,
+        0x6933_a38d_5b59_4c81,
+        0x0c6f_7f72_37b4_6606,
+    ]),
+    Fp::from_raw_unchecked([
+        0x693c_0874_7876_c8f7,
+        0x22c9_850b_f9cf_80f0,
+        0x8e90_71da_b950_c124,
+        0x89bc_62d6_1c7b_af23,
+        0xbc6b_e2d8_dad5_7c23,
+        0x1791_6987_aa14_a122,
+    ]),
+    Fp::from_raw_unchecked([
+        0x1be3_ff43_9c13_16fd,
+        0x9965_243a_7571_dfa7,
+        0xc7f7_f629_62f5_cd81,
+        0x32c6_aa9a_f394_361c,
+        0xbbc2_ee18_e1c2_27f4,
+        0x0c10_2cba_c531_bb34,
+    ]),
+    Fp::from_raw_unchecked([
+        0x9976_14c9_7bac_bf07,
+        0x61f8_6372_b991_92c0,
+        0x5b8c_95fc_1435_3fc3,
+        0xca2b_066c_2a87_492f,
+        0x1617_8f5b_bf69_8711,
+        0x12a6_dcd7_f0f4_e0e8,
+    ]),
+    Fp::from_raw_unchecked([
+        0x7609_0000_0002_fffd,
+        0xebf4_000b_c40c_0002,
+        0x5f48_9857_53c7_58ba,
+        0x77ce_5853_7052_5745,
+        0x5c07_1a97_a256_ec6d,
+        0x15f6_5ec3_fa80_e493,
+    ]),
+];
+
+/// Coefficients of the 11-isogeny y map's numerator
+const ISO11_YNUM: [Fp; 16] = [
+    Fp::from_raw_unchecked([
+        0x2b56_7ff3_e283_7267,
+        0x1d4d_9e57_b958_a767,
+        0xce02_8fea_04bd_7373,
+        0xcc31_a30a_0b6c_d3df,
+        0x7d7b_18a6_8269_2693,
+        0x0d30_0744_d42a_0310,
+    ]),
+    Fp::from_raw_unchecked([
+        0x99c2_555f_a542_493f,
+        0xfe7f_53cc_4874_f878,
+        0x5df0_608b_8f97_608a,
+        0x14e0_3832_052b_49c8,
+        0x7063_26a6_957d_d5a4,
+        0x0a8d_add9_c241_4555,
+    ]),
+    Fp::from_raw_unchecked([
+        0x13d9_4292_2a5c_f63a,
+        0x357e_33e3_6e26_1e7d,
+        0xcf05_a27c_8456_088d,
+        0x0000_bd1d_e7ba_50f0,
+        0x83d0_c753_2f8c_1fde,
+        0x13f7_0bf3_8bbf_2905,
+    ]),
+    Fp::from_raw_unchecked([
+        0x5c57_fd95_bfaf_bdbb,
+        0x28a3_59a6_5e54_1707,
+        0x3983_ceb4_f636_0b6d,
+        0xafe1_9ff6_f97e_6d53,
+        0xb346_8f45_5019_2bf7,
+        0x0bb6_cde4_9d8b_a257,
+    ]),
+    Fp::from_raw_unchecked([
+        0x590b_62c7_ff8a_513f,
+        0x314b_4ce3_72ca_cefd,
+        0x6bef_32ce_94b8_a800,
+        0x6ddf_84a0_9571_3d5f,
+        0x64ea_ce4c_b098_2191,
+        0x0386_213c_651b_888d,
+    ]),
+    Fp::from_raw_unchecked([
+        0xa531_0a31_111b_bcdd,
+        0xa14a_c0f5_da14_8982,
+        0xf9ad_9cc9_5423_d2e9,
+        0xaa6e_c095_283e_e4a7,
+        0xcf5b_1f02_2e1c_9107,
+        0x01fd_df5a_ed88_1793,
+    ]),
+    Fp::from_raw_unchecked([
+        0x65a5_72b0_d7a7_d950,
+        0xe25c_2d81_8347_3a19,
+        0xc2fc_ebe7_cb87_7dbd,
+        0x05b2_d36c_769a_89b0,
+        0xba12_961b_e86e_9efb,
+        0x07eb_1b29_c1df_de1f,
+    ]),
+    Fp::from_raw_unchecked([
+        0x93e0_9572_f7c4_cd24,
+        0x364e_9290_7679_5091,
+        0x8569_467e_68af_51b5,
+        0xa47d_a894_39f5_340f,
+        0xf4fa_9180_82e4_4d64,
+        0x0ad5_2ba3_e669_5a79,
+    ]),
+    Fp::from_raw_unchecked([
+        0x9114_2984_4e0d_5f54,
+        0xd03f_51a3_516b_b233,
+        0x3d58_7e56_4053_6e66,
+        0xfa86_d2a3_a9a7_3482,
+        0xa90e_d5ad_f1ed_5537,
+        0x149c_9c32_6a5e_7393,
+    ]),
+    Fp::from_raw_unchecked([
+        0x462b_beb0_3c12_921a,
+        0xdc9a_f5fa_0a27_4a17,
+        0x9a55_8ebd_e836_ebed,
+        0x649e_f8f1_1a4f_ae46,
+        0x8100_e165_2b3c_dc62,
+        0x1862_bd62_c291_dacb,
+    ]),
+    Fp::from_raw_unchecked([
+        0x05c9_b8ca_89f1_2c26,
+        0x0194_160f_a9b9_ac4f,
+        0x6a64_3d5a_6879_fa2c,
+        0x1466_5bdd_8846_e19d,
+        0xbb1d_0d53_af3f_f6bf,
+        0x12c7_e1c3_b289_62e5,
+    ]),
+    Fp::from_raw_unchecked([
+        0xb55e_bf90_0b8a_3e17,
+        0xfedc_77ec_1a92_01c4,
+        0x1f07_db10_ea1a_4df4,
+        0x0dfb_d15d_c41a_594d,
+        0x3895_47f2_334a_5391,
+        0x0241_9f98_1658_71a4,
+    ]),
+    Fp::from_raw_unchecked([
+        0xb416_af00_0745_fc20,
+        0x8e56_3e9d_1ea6_d0f5,
+        0x7c76_3e17_763a_0652,
+        0x0145_8ef0_159e_bbef,
+        0x8346_fe42_1f96_bb13,
+        0x0d2d_7b82_9ce3_24d2,
+    ]),
+    Fp::from_raw_unchecked([
+        0x9309_6bb5_38d6_4615,
+        0x6f2a_2619_951d_823a,
+        0x8f66_b3ea_5951_4fa4,
+        0xf563_e637_04f7_092f,
+        0x724b_136c_4cf2_d9fa,
+        0x0469_59cf_cfd0_bf49,
+    ]),
+    Fp::from_raw_unchecked([
+        0xea74_8d4b_6e40_5346,
+        0x91e9_079c_2c02_d58f,
+        0x4106_4965_946d_9b59,
+        0xa067_31f1_d2bb_e1ee,
+        0x07f8_97e2_67a3_3f1b,
+        0x1017_2909_1921_0e5f,
+    ]),
+    Fp::from_raw_unchecked([
+        0x872a_a6c1_7d98_5097,
+        0xeecc_5316_1264_562a,
+        0x07af_e37a_fff5_5002,
+        0x5475_9078_e5be_6838,
+        0xc4b9_2d15_db8a_cca8,
+        0x106d_87d1_b51d_13b9,
+    ]),
+];
+
+/// Coefficients of the 11-isogeny y map's denominator
+const ISO11_YDEN: [Fp; 16] = [
+    Fp::from_raw_unchecked([
+        0xeb6c_359d_47e5_2b1c,
+        0x18ef_5f8a_1063_4d60,
+        0xddfa_71a0_889d_5b7e,
+        0x723e_71dc_c5fc_1323,
+        0x52f4_5700_b70d_5c69,
+        0x0a8b_981e_e476_91f1,
+    ]),
+    Fp::from_raw_unchecked([
+        0x616a_3c4f_5535_b9fb,
+        0x6f5f_0373_95db_d911,
+        0xf25f_4cc5_e35c_65da,
+        0x3e50_dffe_a3c6_2658,
+        0x6a33_dca5_2356_0776,
+        0x0fad_eff7_7b6b_fe3e,
+    ]),
+    Fp::from_raw_unchecked([
+        0x2be9_b66d_f470_059c,
+        0x24a2_c159_a3d3_6742,
+        0x115d_be7a_d10c_2a37,
+        0xb663_4a65_2ee5_884d,
+        0x04fe_8bb2_b8d8_1af4,
+        0x01c2_a7a2_56fe_9c41,
+    ]),
+    Fp::from_raw_unchecked([
+        0xf27b_f8ef_3b75_a386,
+        0x898b_3674_76c9_073f,
+        0x2448_2e6b_8c2f_4e5f,
+        0xc8e0_bbd6_fe11_0806,
+        0x59b0_c17f_7631_448a,
+        0x1103_7cd5_8b3d_bfbd,
+    ]),
+    Fp::from_raw_unchecked([
+        0x31c7_912e_a267_eec6,
+        0x1dbf_6f1c_5fcd_b700,
+        0xd30d_4fe3_ba86_fdb1,
+        0x3cae_528f_bee9_a2a4,
+        0xb1cc_e69b_6aa9_ad9a,
+        0x0443_93bb_632d_94fb,
+    ]),
+    Fp::from_raw_unchecked([
+        0xc66e_f6ef_eeb5_c7e8,
+        0x9824_c289_dd72_bb55,
+        0x71b1_a4d2_f119_981d,
+        0x104f_c1aa_fb09_19cc,
+        0x0e49_df01_d942_a628,
+        0x096c_3a09_7732_72d4,
+    ]),
+    Fp::from_raw_unchecked([
+        0x9abc_11eb_5fad_eff4,
+        0x32dc_a50a_8857_28f0,
+        0xfb1f_a372_1569_734c,
+        0xc4b7_6271_ea65_06b3,
+        0xd466_a755_99ce_728e,
+        0x0c81_d464_5f4c_b6ed,
+    ]),
+    Fp::from_raw_unchecked([
+        0x4199_f10e_5b8b_e45b,
+        0xda64_e495_b1e8_7930,
+        0xcb35_3efe_9b33_e4ff,
+        0x9e9e_fb24_aa64_24c6,
+        0xf08d_3368_0a23_7465,
+        0x0d33_7802_3e4c_7406,
+    ]),
+    Fp::from_raw_unchecked([
+        0x7eb4_ae92_ec74_d3a5,
+        0xc341_b4aa_9fac_3497,
+        0x5be6_0389_9e90_7687,
+        0x03bf_d9cc_a75c_bdeb,
+        0x564c_2935_a96b_fa93,
+        0x0ef3_c333_71e2_fdb5,
+    ]),
+    Fp::from_raw_unchecked([
+        0x7ee9_1fd4_49f6_ac2e,
+        0xe5d5_bd5c_b935_7a30,
+        0x773a_8ca5_196b_1380,
+        0xd0fd_a172_174e_d023,
+        0x6cb9_5e0f_a776_aead,
+        0x0d22_d5a4_0cec_7cff,
+    ]),
+    Fp::from_raw_unchecked([
+        0xf727_e092_85fd_8519,
+        0xdc9d_55a8_3017_897b,
+        0x7549_d8bd_0578_94ae,
+        0x1784_1961_3d90_d8f8,
+        0xfce9_5ebd_eb5b_490a,
+        0x0467_ffae_f23f_c49e,
+    ]),
+    Fp::from_raw_unchecked([
+        0xc176_9e6a_7c38_5f1b,
+        0x79bc_930d_eac0_1c03,
+        0x5461_c75a_23ed_e3b5,
+        0x6e20_829e_5c23_0c45,
+        0x828e_0f1e_772a_53cd,
+        0x116a_efa7_4912_7bff,
+    ]),
+    Fp::from_raw_unchecked([
+        0x101c_10bf_2744_c10a,
+        0xbbf1_8d05_3a6a_3154,
+        0xa0ec_f39e_f026_f602,
+        0xfc00_9d49_96dc_5153,
+        0xb900_0209_d5bd_08d3,
+        0x189e_5fe4_470c_d73c,
+    ]),
+    Fp::from_raw_unchecked([
+        0x7ebd_546c_a157_5ed2,
+        0xe47d_5a98_1d08_1b55,
+        0x57b2_b625_b6d4_ca21,
+        0xb0a1_ba04_2285_20cc,
+        0x9873_8983_c210_7ff3,
+        0x13dd_dbc4_799d_81d6,
+    ]),
+    Fp::from_raw_unchecked([
+        0x0931_9f2e_3983_4935,
+        0x039e_952c_bdb0_5c21,
+        0x55ba_77a9_a2f7_6493,
+        0xfd04_e3df_c608_6467,
+        0xfb95_832e_7d78_742e,
+        0x0ef9_c24e_ccaf_5e0e,
+    ]),
+    Fp::from_raw_unchecked([
+        0x7609_0000_0002_fffd,
+        0xebf4_000b_c40c_0002,
+        0x5f48_9857_53c7_58ba,
+        0x77ce_5853_7052_5745,
+        0x5c07_1a97_a256_ec6d,
+        0x15f6_5ec3_fa80_e493,
+    ]),
+];
+
+const SSWU_ELLP_A: Fp = Fp::from_raw_unchecked([
+    0x2f65_aa0e_9af5_aa51,
+    0x8646_4c2d_1e84_16c3,
+    0xb85c_e591_b7bd_31e2,
+    0x27e1_1c91_b5f2_4e7c,
+    0x2837_6eda_6bfc_1835,
+    0x1554_55c3_e507_1d85,
+]);
+
+const SSWU_ELLP_B: Fp = Fp::from_raw_unchecked([
+    0xfb99_6971_fe22_a1e0,
+    0x9aa9_3eb3_5b74_2d6f,
+    0x8c47_6013_de99_c5c4,
+    0x873e_27c3_a221_e571,
+    0xca72_b5e4_5a52_d888,
+    0x0682_4061_418a_386b,
+]);
+
+const SSWU_XI: Fp = Fp::from_raw_unchecked([
+    0x886c_0000_0023_ffdc,
+    0x0f70_008d_3090_001d,
+    0x7767_2417_ed58_28c3,
+    0x9dac_23e9_43dc_1740,
+    0x5055_3f1b_9c13_1521,
+    0x078c_712f_be0a_b6e8,
+]);
+
+const SQRT_M_XI_CUBED: Fp = Fp::from_raw_unchecked([
+    0x43b5_71ca_d321_5f1f,
+    0xccb4_60ef_1c70_2dc2,
+    0x742d_884f_4f97_100b,
+    0xdb2c_3e32_38a3_382b,
+    0xe40f_3fa1_3fce_8f88,
+    0x0073_a2af_9892_a2ff,
+]);
+
+impl HashToField for Fp {
+    // ceil(log2(p)) = 381, m = 1, k = 128.
+    type InputLength = U64;
+
+    fn from_okm(okm: &GenericArray<u8, U64>) -> Fp {
+        const F_2_256: Fp = Fp::from_raw_unchecked([
+            0x075b_3cd7_c5ce_820f,
+            0x3ec6_ba62_1c3e_db0b,
+            0x168a_13d8_2bff_6bce,
+            0x8766_3c4b_f8c4_49d2,
+            0x15f3_4c83_ddc8_d830,
+            0x0f96_28b4_9caa_2e85,
+        ]);
+
+        let mut bs = [0u8; 48];
+        bs[16..].copy_from_slice(&okm[..32]);
+        let db = Fp::from_bytes(&bs).unwrap();
+
+        bs[16..].copy_from_slice(&okm[32..]);
+        let da = Fp::from_bytes(&bs).unwrap();
+
+        db * F_2_256 + da
+    }
+}
+
+impl Sgn0 for Fp {
+    fn sgn0(&self) -> Choice {
+        // 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,
+        );
+        Choice::from((tmp.0[0] & 1) as u8)
+    }
+}
+
+/// Maps an element of [`Fp`] to a point on iso-G1.
+///
+/// Implements [section 6.6.2 of `draft-irtf-cfrg-hash-to-curve-12`][sswu].
+///
+/// [sswu]: https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-12#section-6.6.2
+fn map_to_curve_simple_swu(u: &Fp) -> G1Projective {
+    let usq = u.square();
+    let xi_usq = SSWU_XI * usq;
+    let xisq_u4 = xi_usq.square();
+    let nd_common = xisq_u4 + xi_usq; // XI^2 * u^4 + XI * u^2
+    let x_den = SSWU_ELLP_A * Fp::conditional_select(&(-nd_common), &SSWU_XI, nd_common.is_zero());
+    let x0_num = SSWU_ELLP_B * (Fp::one() + nd_common); // B * (1 + (XI^2 * u^4 + XI * u^2))
+
+    // compute g(x0(u))
+    let x_densq = x_den.square();
+    let gx_den = x_densq * x_den;
+    // x0_num^3 + A * x0_num * x_den^2 + B * x_den^3
+    let gx0_num = (x0_num.square() + SSWU_ELLP_A * x_densq) * x0_num + SSWU_ELLP_B * gx_den;
+
+    // compute g(X0(u)) ^ ((p - 3) // 4)
+    let sqrt_candidate = {
+        let u_v = gx0_num * gx_den; // u*v
+        let vsq = gx_den.square(); // v^2
+        u_v * chain_pm3div4(&(u_v * vsq)) // u v (u v^3) ^ ((p - 3) // 4)
+    };
+
+    let gx0_square = (sqrt_candidate.square() * gx_den).ct_eq(&gx0_num); // g(x0) is square
+    let x1_num = x0_num * xi_usq;
+    // sqrt(-XI**3) * u^3 g(x0) ^ ((p - 3) // 4)
+    let y1 = SQRT_M_XI_CUBED * usq * u * sqrt_candidate;
+
+    let x_num = Fp::conditional_select(&x1_num, &x0_num, gx0_square);
+    let mut y = Fp::conditional_select(&y1, &sqrt_candidate, gx0_square);
+    // ensure sign of y and sign of u agree
+    y.conditional_negate(y.sgn0() ^ u.sgn0());
+
+    G1Projective {
+        x: x_num,
+        y: y * x_den,
+        z: x_den,
+    }
+}
+
+/// Maps an iso-G1 point to a G1 point.
+fn iso_map(u: &G1Projective) -> G1Projective {
+    const COEFFS: [&[Fp]; 4] = [&ISO11_XNUM, &ISO11_XDEN, &ISO11_YNUM, &ISO11_YDEN];
+
+    // unpack input point
+    let G1Projective { x, y, z } = *u;
+
+    // xnum, xden, ynum, yden
+    let mut mapvals = [Fp::zero(); 4];
+
+    // pre-compute powers of z
+    let zpows = {
+        let mut zpows = [Fp::zero(); 15];
+        zpows[0] = z;
+        for idx in 1..zpows.len() {
+            zpows[idx] = zpows[idx - 1] * z;
+        }
+        zpows
+    };
+
+    // compute map value by Horner's rule
+    for idx in 0..4 {
+        let coeff = COEFFS[idx];
+        let clast = coeff.len() - 1;
+        mapvals[idx] = coeff[clast];
+        for jdx in 0..clast {
+            mapvals[idx] = mapvals[idx] * x + zpows[jdx] * coeff[clast - 1 - jdx];
+        }
+    }
+
+    // x denominator is order 1 less than x numerator, so we need an extra factor of z
+    mapvals[1] *= z;
+
+    // multiply result of Y map by the y-coord, y / z
+    mapvals[2] *= y;
+    mapvals[3] *= z;
+
+    G1Projective {
+        x: mapvals[0] * mapvals[3], // xnum * yden,
+        y: mapvals[2] * mapvals[1], // ynum * xden,
+        z: mapvals[1] * mapvals[3], // xden * yden
+    }
+}
+
+impl MapToCurve for G1Projective {
+    type Field = Fp;
+
+    fn map_to_curve(u: &Fp) -> G1Projective {
+        let pt = map_to_curve_simple_swu(u);
+        iso_map(&pt)
+    }
+
+    fn clear_h(&self) -> Self {
+        self.clear_cofactor()
+    }
+}
+
+#[cfg(test)]
+fn check_g1_prime(pt: &G1Projective) -> bool {
+    // (X : Y : Z)==(X/Z, Y/Z) is on E': y^2 = x^3 + A * x + B.
+    // y^2 z = (x^3) + A (x z^2) + B z^3
+    let zsq = pt.z.square();
+    (pt.y.square() * pt.z)
+        == (pt.x.square() * pt.x + SSWU_ELLP_A * pt.x * zsq + SSWU_ELLP_B * zsq * pt.z)
+}
+
+#[test]
+fn test_simple_swu_expected() {
+    // exceptional case: zero
+    let p = map_to_curve_simple_swu(&Fp::zero());
+    let G1Projective { x, y, z } = &p;
+    let xo = Fp::from_raw_unchecked([
+        0xfb99_6971_fe22_a1e0,
+        0x9aa9_3eb3_5b74_2d6f,
+        0x8c47_6013_de99_c5c4,
+        0x873e_27c3_a221_e571,
+        0xca72_b5e4_5a52_d888,
+        0x0682_4061_418a_386b,
+    ]);
+    let yo = Fp::from_raw_unchecked([
+        0xfd6f_ced8_7a7f_11a3,
+        0x9a6b_314b_03c8_db31,
+        0x41f8_5416_e0ea_b593,
+        0xfeeb_089f_7e6e_c4d7,
+        0x85a1_34c3_7ed1_278f,
+        0x0575_c525_bb9f_74bb,
+    ]);
+    let zo = Fp::from_raw_unchecked([
+        0x7f67_4ea0_a891_5178,
+        0xb0f9_45fc_13b8_fa65,
+        0x4b46_759a_38e8_7d76,
+        0x2e7a_9296_41bb_b6a1,
+        0x1668_ddfa_462b_f6b6,
+        0x0096_0e2e_d1cf_294c,
+    ]);
+    assert_eq!(x, &xo);
+    assert_eq!(y, &yo);
+    assert_eq!(z, &zo);
+    assert!(check_g1_prime(&p));
+
+    // exceptional case: sqrt(-1/XI) (positive)
+    let excp = Fp::from_raw_unchecked([
+        0x00f3_d047_7e91_edbf,
+        0x08d6_621e_4ca8_dc69,
+        0xb9cf_7927_b19b_9726,
+        0xba13_3c99_6caf_a2ec,
+        0xed2a_5ccd_5ca7_bb68,
+        0x19cb_022f_8ee9_d73b,
+    ]);
+    let p = map_to_curve_simple_swu(&excp);
+    let G1Projective { x, y, z } = &p;
+    assert_eq!(x, &xo);
+    assert_eq!(y, &yo);
+    assert_eq!(z, &zo);
+    assert!(check_g1_prime(&p));
+
+    // exceptional case: sqrt(-1/XI) (negative)
+    let excp = Fp::from_raw_unchecked([
+        0xb90b_2fb8_816d_bcec,
+        0x15d5_9de0_64ab_2396,
+        0xad61_5979_4515_5efe,
+        0xaa64_0eeb_86d5_6fd2,
+        0x5df1_4ae8_e6a3_f16e,
+        0x0036_0fba_aa96_0f5e,
+    ]);
+    let p = map_to_curve_simple_swu(&excp);
+    let G1Projective { x, y, z } = &p;
+    let myo = -yo;
+    assert_eq!(x, &xo);
+    assert_eq!(y, &myo);
+    assert_eq!(z, &zo);
+    assert!(check_g1_prime(&p));
+
+    let u = Fp::from_raw_unchecked([
+        0xa618_fa19_f7e2_eadc,
+        0x93c7_f1fc_876b_a245,
+        0xe2ed_4cc4_7b5c_0ae0,
+        0xd49e_fa74_e4a8_d000,
+        0xa0b2_3ba6_92b5_431c,
+        0x0d15_51f2_d7d8_d193,
+    ]);
+    let xo = Fp::from_raw_unchecked([
+        0x2197_ca55_fab3_ba48,
+        0x591d_eb39_f434_949a,
+        0xf9df_7fb4_f1fa_6a08,
+        0x59e3_c16a_9dfa_8fa5,
+        0xe592_9b19_4aad_5f7a,
+        0x130a_46a4_c61b_44ed,
+    ]);
+    let yo = Fp::from_raw_unchecked([
+        0xf721_5b58_c720_0ad0,
+        0x8905_1631_3a4e_66bf,
+        0xc903_1acc_8a36_19a8,
+        0xea1f_9978_fde3_ffec,
+        0x0548_f02d_6cfb_f472,
+        0x1693_7557_3529_163f,
+    ]);
+    let zo = Fp::from_raw_unchecked([
+        0xf36f_eb2e_1128_ade0,
+        0x42e2_2214_250b_cd94,
+        0xb94f_6ba2_dddf_62d6,
+        0xf56d_4392_782b_f0a2,
+        0xb2d7_ce1e_c263_09e7,
+        0x182b_57ed_6b99_f0a1,
+    ]);
+    let p = map_to_curve_simple_swu(&u);
+    let G1Projective { x, y, z } = &p;
+    assert_eq!(x, &xo);
+    assert_eq!(y, &yo);
+    assert_eq!(z, &zo);
+    assert!(check_g1_prime(&p));
+}
+
+#[test]
+fn test_osswu_semirandom() {
+    use rand_core::SeedableRng;
+    let mut rng = rand_xorshift::XorShiftRng::from_seed([
+        0x59, 0x62, 0xbe, 0x5d, 0x76, 0x3d, 0x31, 0x8d, 0x17, 0xdb, 0x37, 0x32, 0x54, 0x06, 0xbc,
+        0xe5,
+    ]);
+    for _ in 0..32 {
+        let input = Fp::random(&mut rng);
+        let p = map_to_curve_simple_swu(&input);
+        assert!(check_g1_prime(&p));
+
+        let p_iso = iso_map(&p);
+        assert!(bool::from(p_iso.is_on_curve()));
+    }
+}
+
+// test vectors from the draft 10 RFC
+#[test]
+fn test_encode_to_curve_10() {
+    use crate::{
+        g1::G1Affine,
+        hash_to_curve::{ExpandMsgXmd, HashToCurve},
+    };
+    use std::string::{String, ToString};
+
+    struct TestCase {
+        msg: &'static [u8],
+        expected: [&'static str; 2],
+    }
+    impl TestCase {
+        fn expected(&self) -> String {
+            self.expected[0].to_string() + self.expected[1]
+        }
+    }
+
+    const DOMAIN: &[u8] = b"QUUX-V01-CS02-with-BLS12381G1_XMD:SHA-256_SSWU_NU_";
+
+    let cases = vec![
+        TestCase {
+            msg: b"",
+            expected: [
+                "184bb665c37ff561a89ec2122dd343f20e0f4cbcaec84e3c3052ea81d1834e192c426074b02ed3dca4e7676ce4ce48ba",
+                "04407b8d35af4dacc809927071fc0405218f1401a6d15af775810e4e460064bcc9468beeba82fdc751be70476c888bf3",
+            ],
+        },
+        TestCase {
+            msg: b"abc",
+            expected: [
+                "009769f3ab59bfd551d53a5f846b9984c59b97d6842b20a2c565baa167945e3d026a3755b6345df8ec7e6acb6868ae6d",
+                "1532c00cf61aa3d0ce3e5aa20c3b531a2abd2c770a790a2613818303c6b830ffc0ecf6c357af3317b9575c567f11cd2c",
+            ],
+        },
+        TestCase {
+            msg: b"abcdef0123456789",
+            expected: [
+                "1974dbb8e6b5d20b84df7e625e2fbfecb2cdb5f77d5eae5fb2955e5ce7313cae8364bc2fff520a6c25619739c6bdcb6a",
+                "15f9897e11c6441eaa676de141c8d83c37aab8667173cbe1dfd6de74d11861b961dccebcd9d289ac633455dfcc7013a3",
+            ]
+        },
+        TestCase {
+            msg: b"q128_qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq\
+                   qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq\
+                   qqqqqqqqqqqqqqqqqqqqqqqqq",
+            expected: [
+        "0a7a047c4a8397b3446450642c2ac64d7239b61872c9ae7a59707a8f4f950f101e766afe58223b3bff3a19a7f754027c",
+        "1383aebba1e4327ccff7cf9912bda0dbc77de048b71ef8c8a81111d71dc33c5e3aa6edee9cf6f5fe525d50cc50b77cc9",
+            ]
+        },
+        TestCase {
+            msg: b"a512_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+                   aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+                   aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+                   aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+                   aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+                   aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+                   aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+                   aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+                   aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+                   aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
+            expected: [
+                "0e7a16a975904f131682edbb03d9560d3e48214c9986bd50417a77108d13dc957500edf96462a3d01e62dc6cd468ef11",
+                "0ae89e677711d05c30a48d6d75e76ca9fb70fe06c6dd6ff988683d89ccde29ac7d46c53bb97a59b1901abf1db66052db",
+            ]
+        }
+    ];
+
+    for case in cases {
+        let g = <G1Projective as HashToCurve<ExpandMsgXmd<sha2::Sha256>>>::encode_to_curve(
+            &case.msg, DOMAIN,
+        );
+        let aff = G1Affine::from(g);
+        let g_uncompressed = aff.to_uncompressed();
+
+        assert_eq!(case.expected(), hex::encode(&g_uncompressed[..]));
+    }
+}
+
+// test vectors from the draft 10 RFC
+#[test]
+fn test_hash_to_curve_10() {
+    use crate::{
+        g1::G1Affine,
+        hash_to_curve::{ExpandMsgXmd, HashToCurve},
+    };
+    use std::string::{String, ToString};
+
+    struct TestCase {
+        msg: &'static [u8],
+        expected: [&'static str; 2],
+    }
+    impl TestCase {
+        fn expected(&self) -> String {
+            self.expected[0].to_string() + self.expected[1]
+        }
+    }
+
+    const DOMAIN: &[u8] = b"QUUX-V01-CS02-with-BLS12381G1_XMD:SHA-256_SSWU_RO_";
+
+    let cases = vec![
+        TestCase {
+            msg: b"",
+            expected: [
+                "052926add2207b76ca4fa57a8734416c8dc95e24501772c814278700eed6d1e4e8cf62d9c09db0fac349612b759e79a1",
+                "08ba738453bfed09cb546dbb0783dbb3a5f1f566ed67bb6be0e8c67e2e81a4cc68ee29813bb7994998f3eae0c9c6a265",
+            ],
+        },
+        TestCase {
+            msg: b"abc",
+            expected: [
+                "03567bc5ef9c690c2ab2ecdf6a96ef1c139cc0b2f284dca0a9a7943388a49a3aee664ba5379a7655d3c68900be2f6903",
+                "0b9c15f3fe6e5cf4211f346271d7b01c8f3b28be689c8429c85b67af215533311f0b8dfaaa154fa6b88176c229f2885d"
+            ],
+        },
+        TestCase {
+            msg: b"abcdef0123456789",
+            expected: [
+                "11e0b079dea29a68f0383ee94fed1b940995272407e3bb916bbf268c263ddd57a6a27200a784cbc248e84f357ce82d98",
+                "03a87ae2caf14e8ee52e51fa2ed8eefe80f02457004ba4d486d6aa1f517c0889501dc7413753f9599b099ebcbbd2d709"
+            ]
+        },
+        TestCase {
+            msg: b"q128_qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq\
+                   qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq\
+                   qqqqqqqqqqqqqqqqqqqqqqqqq",
+            expected: [
+                "15f68eaa693b95ccb85215dc65fa81038d69629f70aeee0d0f677cf22285e7bf58d7cb86eefe8f2e9bc3f8cb84fac488",
+                "1807a1d50c29f430b8cafc4f8638dfeeadf51211e1602a5f184443076715f91bb90a48ba1e370edce6ae1062f5e6dd38"
+            ]
+        },
+        TestCase {
+            msg: b"a512_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+                   aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+                   aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+                   aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+                   aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+                   aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+                   aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+                   aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+                   aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+                   aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
+            expected: [
+                "082aabae8b7dedb0e78aeb619ad3bfd9277a2f77ba7fad20ef6aabdc6c31d19ba5a6d12283553294c1825c4b3ca2dcfe",
+                "05b84ae5a942248eea39e1d91030458c40153f3b654ab7872d779ad1e942856a20c438e8d99bc8abfbf74729ce1f7ac8"
+            ]
+        }
+    ];
+
+    for case in cases {
+        let g = <G1Projective as HashToCurve<ExpandMsgXmd<sha2::Sha256>>>::hash_to_curve(
+            &case.msg, DOMAIN,
+        );
+        let g_uncompressed = G1Affine::from(g).to_uncompressed();
+
+        assert_eq!(case.expected(), hex::encode(&g_uncompressed[..]));
+    }
+}
+
+#[cfg(test)]
+// p-1 / 2
+pub const P_M1_OVER2: Fp = Fp::from_raw_unchecked([
+    0xa1fa_ffff_fffe_5557,
+    0x995b_fff9_76a3_fffe,
+    0x03f4_1d24_d174_ceb4,
+    0xf654_7998_c199_5dbd,
+    0x778a_468f_507a_6034,
+    0x0205_5993_1f7f_8103,
+]);
+
+#[test]
+fn test_sgn0() {
+    assert_eq!(bool::from(Fp::zero().sgn0()), false);
+    assert_eq!(bool::from(Fp::one().sgn0()), true);
+    assert_eq!(bool::from((-Fp::one()).sgn0()), false);
+    assert_eq!(bool::from((-Fp::zero()).sgn0()), false);
+    assert_eq!(bool::from(P_M1_OVER2.sgn0()), true);
+
+    let p_p1_over2 = P_M1_OVER2 + Fp::one();
+    assert_eq!(bool::from(p_p1_over2.sgn0()), false);
+
+    let neg_p_p1_over2 = {
+        let mut tmp = p_p1_over2;
+        tmp.conditional_negate(Choice::from(1u8));
+        tmp
+    };
+    assert_eq!(neg_p_p1_over2, P_M1_OVER2);
+}
diff --git a/src/hash_to_curve/map_g2.rs b/src/hash_to_curve/map_g2.rs
new file mode 100644
index 00000000..b2cdb956
--- /dev/null
+++ b/src/hash_to_curve/map_g2.rs
@@ -0,0 +1,900 @@
+//! Implementation of hash-to-curve for the G2 group
+
+use subtle::{Choice, ConditionallyNegatable, ConditionallySelectable, ConstantTimeEq};
+
+use super::chain::chain_p2m9div16;
+use super::{HashToField, MapToCurve, Sgn0};
+use crate::generic_array::{
+    typenum::{U128, U64},
+    GenericArray,
+};
+use crate::{fp::Fp, fp2::Fp2, g2::G2Projective};
+
+/// Coefficients of the 3-isogeny x map's numerator
+const ISO3_XNUM: [Fp2; 4] = [
+    Fp2 {
+        c0: Fp::from_raw_unchecked([
+            0x47f6_71c7_1ce0_5e62,
+            0x06dd_5707_1206_393e,
+            0x7c80_cd2a_f3fd_71a2,
+            0x0481_03ea_9e6c_d062,
+            0xc545_16ac_c8d0_37f6,
+            0x1380_8f55_0920_ea41,
+        ]),
+        c1: Fp::from_raw_unchecked([
+            0x47f6_71c7_1ce0_5e62,
+            0x06dd_5707_1206_393e,
+            0x7c80_cd2a_f3fd_71a2,
+            0x0481_03ea_9e6c_d062,
+            0xc545_16ac_c8d0_37f6,
+            0x1380_8f55_0920_ea41,
+        ]),
+    },
+    Fp2 {
+        c0: Fp::zero(),
+        c1: Fp::from_raw_unchecked([
+            0x5fe5_5555_554c_71d0,
+            0x873f_ffdd_236a_aaa3,
+            0x6a6b_4619_b26e_f918,
+            0x21c2_8884_0887_4945,
+            0x2836_cda7_028c_abc5,
+            0x0ac7_3310_a7fd_5abd,
+        ]),
+    },
+    Fp2 {
+        c0: Fp::from_raw_unchecked([
+            0x0a0c_5555_5559_71c3,
+            0xdb0c_0010_1f9e_aaae,
+            0xb1fb_2f94_1d79_7997,
+            0xd396_0742_ef41_6e1c,
+            0xb700_40e2_c205_56f4,
+            0x149d_7861_e581_393b,
+        ]),
+        c1: Fp::from_raw_unchecked([
+            0xaff2_aaaa_aaa6_38e8,
+            0x439f_ffee_91b5_5551,
+            0xb535_a30c_d937_7c8c,
+            0x90e1_4442_0443_a4a2,
+            0x941b_66d3_8146_55e2,
+            0x0563_9988_53fe_ad5e,
+        ]),
+    },
+    Fp2 {
+        c0: Fp::from_raw_unchecked([
+            0x40aa_c71c_71c7_25ed,
+            0x1909_5555_7a84_e38e,
+            0xd817_050a_8f41_abc3,
+            0xd864_85d4_c87f_6fb1,
+            0x696e_b479_f885_d059,
+            0x198e_1a74_3280_02d2,
+        ]),
+        c1: Fp::zero(),
+    },
+];
+
+/// Coefficients of the 3-isogeny x map's denominator
+const ISO3_XDEN: [Fp2; 3] = [
+    Fp2 {
+        c0: Fp::zero(),
+        c1: Fp::from_raw_unchecked([
+            0x1f3a_ffff_ff13_ab97,
+            0xf25b_fc61_1da3_ff3e,
+            0xca37_57cb_3819_b208,
+            0x3e64_2736_6f8c_ec18,
+            0x0397_7bc8_6095_b089,
+            0x04f6_9db1_3f39_a952,
+        ]),
+    },
+    Fp2 {
+        c0: Fp::from_raw_unchecked([
+            0x4476_0000_0027_552e,
+            0xdcb8_009a_4348_0020,
+            0x6f7e_e9ce_4a6e_8b59,
+            0xb103_30b7_c0a9_5bc6,
+            0x6140_b1fc_fb1e_54b7,
+            0x0381_be09_7f0b_b4e1,
+        ]),
+        c1: Fp::from_raw_unchecked([
+            0x7588_ffff_ffd8_557d,
+            0x41f3_ff64_6e0b_ffdf,
+            0xf7b1_e8d2_ac42_6aca,
+            0xb374_1acd_32db_b6f8,
+            0xe9da_f5b9_482d_581f,
+            0x167f_53e0_ba74_31b8,
+        ]),
+    },
+    Fp2::one(),
+];
+
+/// Coefficients of the 3-isogeny y map's numerator
+const ISO3_YNUM: [Fp2; 4] = [
+    Fp2 {
+        c0: Fp::from_raw_unchecked([
+            0x96d8_f684_bdfc_77be,
+            0xb530_e4f4_3b66_d0e2,
+            0x184a_88ff_3796_52fd,
+            0x57cb_23ec_fae8_04e1,
+            0x0fd2_e39e_ada3_eba9,
+            0x08c8_055e_31c5_d5c3,
+        ]),
+        c1: Fp::from_raw_unchecked([
+            0x96d8_f684_bdfc_77be,
+            0xb530_e4f4_3b66_d0e2,
+            0x184a_88ff_3796_52fd,
+            0x57cb_23ec_fae8_04e1,
+            0x0fd2_e39e_ada3_eba9,
+            0x08c8_055e_31c5_d5c3,
+        ]),
+    },
+    Fp2 {
+        c0: Fp::zero(),
+        c1: Fp::from_raw_unchecked([
+            0xbf0a_71c7_1c91_b406,
+            0x4d6d_55d2_8b76_38fd,
+            0x9d82_f98e_5f20_5aee,
+            0xa27a_a27b_1d1a_18d5,
+            0x02c3_b2b2_d293_8e86,
+            0x0c7d_1342_0b09_807f,
+        ]),
+    },
+    Fp2 {
+        c0: Fp::from_raw_unchecked([
+            0xd7f9_5555_5553_1c74,
+            0x21cf_fff7_48da_aaa8,
+            0x5a9a_d186_6c9b_be46,
+            0x4870_a221_0221_d251,
+            0x4a0d_b369_c0a3_2af1,
+            0x02b1_ccc4_29ff_56af,
+        ]),
+        c1: Fp::from_raw_unchecked([
+            0xe205_aaaa_aaac_8e37,
+            0xfcdc_0007_6879_5556,
+            0x0c96_011a_8a15_37dd,
+            0x1c06_a963_f163_406e,
+            0x010d_f44c_82a8_81e6,
+            0x174f_4526_0f80_8feb,
+        ]),
+    },
+    Fp2 {
+        c0: Fp::from_raw_unchecked([
+            0xa470_bda1_2f67_f35c,
+            0xc0fe_38e2_3327_b425,
+            0xc9d3_d0f2_c6f0_678d,
+            0x1c55_c993_5b5a_982e,
+            0x27f6_c0e2_f074_6764,
+            0x117c_5e6e_28aa_9054,
+        ]),
+        c1: Fp::zero(),
+    },
+];
+
+/// Coefficients of the 3-isogeny y map's denominator
+const ISO3_YDEN: [Fp2; 4] = [
+    Fp2 {
+        c0: Fp::from_raw_unchecked([
+            0x0162_ffff_fa76_5adf,
+            0x8f7b_ea48_0083_fb75,
+            0x561b_3c22_59e9_3611,
+            0x11e1_9fc1_a9c8_75d5,
+            0xca71_3efc_0036_7660,
+            0x03c6_a03d_41da_1151,
+        ]),
+        c1: Fp::from_raw_unchecked([
+            0x0162_ffff_fa76_5adf,
+            0x8f7b_ea48_0083_fb75,
+            0x561b_3c22_59e9_3611,
+            0x11e1_9fc1_a9c8_75d5,
+            0xca71_3efc_0036_7660,
+            0x03c6_a03d_41da_1151,
+        ]),
+    },
+    Fp2 {
+        c0: Fp::zero(),
+        c1: Fp::from_raw_unchecked([
+            0x5db0_ffff_fd3b_02c5,
+            0xd713_f523_58eb_fdba,
+            0x5ea6_0761_a84d_161a,
+            0xbb2c_75a3_4ea6_c44a,
+            0x0ac6_7359_21c1_119b,
+            0x0ee3_d913_bdac_fbf6,
+        ]),
+    },
+    Fp2 {
+        c0: Fp::from_raw_unchecked([
+            0x66b1_0000_003a_ffc5,
+            0xcb14_00e7_64ec_0030,
+            0xa73e_5eb5_6fa5_d106,
+            0x8984_c913_a0fe_09a9,
+            0x11e1_0afb_78ad_7f13,
+            0x0542_9d0e_3e91_8f52,
+        ]),
+        c1: Fp::from_raw_unchecked([
+            0x534d_ffff_ffc4_aae6,
+            0x5397_ff17_4c67_ffcf,
+            0xbff2_73eb_870b_251d,
+            0xdaf2_8271_5287_0915,
+            0x393a_9cba_ca9e_2dc3,
+            0x14be_74db_faee_5748,
+        ]),
+    },
+    Fp2::one(),
+];
+
+const SSWU_ELLP_A: Fp2 = Fp2 {
+    c0: Fp::zero(),
+    c1: Fp::from_raw_unchecked([
+        0xe53a_0000_0313_5242,
+        0x0108_0c0f_def8_0285,
+        0xe788_9edb_e340_f6bd,
+        0x0b51_3751_2631_0601,
+        0x02d6_9857_17c7_44ab,
+        0x1220_b4e9_79ea_5467,
+    ]),
+};
+
+const SSWU_ELLP_B: Fp2 = Fp2 {
+    c0: Fp::from_raw_unchecked([
+        0x22ea_0000_0cf8_9db2,
+        0x6ec8_32df_7138_0aa4,
+        0x6e1b_9440_3db5_a66e,
+        0x75bf_3c53_a794_73ba,
+        0x3dd3_a569_412c_0a34,
+        0x125c_db5e_74dc_4fd1,
+    ]),
+    c1: Fp::from_raw_unchecked([
+        0x22ea_0000_0cf8_9db2,
+        0x6ec8_32df_7138_0aa4,
+        0x6e1b_9440_3db5_a66e,
+        0x75bf_3c53_a794_73ba,
+        0x3dd3_a569_412c_0a34,
+        0x125c_db5e_74dc_4fd1,
+    ]),
+};
+
+const SSWU_XI: Fp2 = Fp2 {
+    c0: Fp::from_raw_unchecked([
+        0x87eb_ffff_fff9_555c,
+        0x656f_ffe5_da8f_fffa,
+        0x0fd0_7493_45d3_3ad2,
+        0xd951_e663_0665_76f4,
+        0xde29_1a3d_41e9_80d3,
+        0x0815_664c_7dfe_040d,
+    ]),
+    c1: Fp::from_raw_unchecked([
+        0x43f5_ffff_fffc_aaae,
+        0x32b7_fff2_ed47_fffd,
+        0x07e8_3a49_a2e9_9d69,
+        0xeca8_f331_8332_bb7a,
+        0xef14_8d1e_a0f4_c069,
+        0x040a_b326_3eff_0206,
+    ]),
+};
+
+const SSWU_ETAS: [Fp2; 4] = [
+    Fp2 {
+        c0: Fp::from_raw_unchecked([
+            0x05e5_1466_8ac7_36d2,
+            0x9089_b4d6_b84f_3ea5,
+            0x603c_384c_224a_8b32,
+            0xf325_7909_536a_fea6,
+            0x5c5c_dbab_ae65_6d81,
+            0x075b_fa08_63c9_87e9,
+        ]),
+        c1: Fp::from_raw_unchecked([
+            0x338d_9bfe_0808_7330,
+            0x7b8e_48b2_bd83_cefe,
+            0x530d_ad5d_306b_5be7,
+            0x5a4d_7e8e_6c40_8b6d,
+            0x6258_f7a6_232c_ab9b,
+            0x0b98_5811_cce1_4db5,
+        ]),
+    },
+    Fp2 {
+        c0: Fp::from_raw_unchecked([
+            0x8671_6401_f7f7_377b,
+            0xa31d_b74b_f3d0_3101,
+            0x1423_2543_c645_9a3c,
+            0x0a29_ccf6_8744_8752,
+            0xe8c2_b010_201f_013c,
+            0x0e68_b9d8_6c9e_98e4,
+        ]),
+        c1: Fp::from_raw_unchecked([
+            0x05e5_1466_8ac7_36d2,
+            0x9089_b4d6_b84f_3ea5,
+            0x603c_384c_224a_8b32,
+            0xf325_7909_536a_fea6,
+            0x5c5c_dbab_ae65_6d81,
+            0x075b_fa08_63c9_87e9,
+        ]),
+    },
+    Fp2 {
+        c0: Fp::from_raw_unchecked([
+            0x718f_dad2_4ee1_d90f,
+            0xa58c_025b_ed82_76af,
+            0x0c3a_1023_0ab7_976f,
+            0xf0c5_4df5_c8f2_75e1,
+            0x4ec2_478c_28ba_f465,
+            0x1129_373a_90c5_08e6,
+        ]),
+        c1: Fp::from_raw_unchecked([
+            0x019a_f5f9_80a3_680c,
+            0x4ed7_da0e_6606_3afa,
+            0x6003_5472_3b5d_9972,
+            0x8b2f_958b_20d0_9d72,
+            0x0474_938f_02d4_61db,
+            0x0dcf_8b9e_0684_ab1c,
+        ]),
+    },
+    Fp2 {
+        c0: Fp::from_raw_unchecked([
+            0xb864_0a06_7f5c_429f,
+            0xcfd4_25f0_4b4d_c505,
+            0x072d_7e2e_bb53_5cb1,
+            0xd947_b5f9_d2b4_754d,
+            0x46a7_1427_4077_4afb,
+            0x0c31_864c_32fb_3b7e,
+        ]),
+        c1: Fp::from_raw_unchecked([
+            0x718f_dad2_4ee1_d90f,
+            0xa58c_025b_ed82_76af,
+            0x0c3a_1023_0ab7_976f,
+            0xf0c5_4df5_c8f2_75e1,
+            0x4ec2_478c_28ba_f465,
+            0x1129_373a_90c5_08e6,
+        ]),
+    },
+];
+
+const SSWU_RV1: Fp2 = Fp2 {
+    c0: Fp::from_raw_unchecked([
+        0x7bcf_a7a2_5aa3_0fda,
+        0xdc17_dec1_2a92_7e7c,
+        0x2f08_8dd8_6b4e_bef1,
+        0xd1ca_2087_da74_d4a7,
+        0x2da2_5966_96ce_bc1d,
+        0x0e2b_7eed_bbfd_87d2,
+    ]),
+    c1: Fp::from_raw_unchecked([
+        0x7bcf_a7a2_5aa3_0fda,
+        0xdc17_dec1_2a92_7e7c,
+        0x2f08_8dd8_6b4e_bef1,
+        0xd1ca_2087_da74_d4a7,
+        0x2da2_5966_96ce_bc1d,
+        0x0e2b_7eed_bbfd_87d2,
+    ]),
+};
+
+impl HashToField for Fp2 {
+    // ceil(log2(p)) = 381, m = 2, k = 128.
+    type InputLength = U128;
+
+    fn from_okm(okm: &GenericArray<u8, U128>) -> Fp2 {
+        let c0 = <Fp as HashToField>::from_okm(GenericArray::<u8, U64>::from_slice(&okm[..64]));
+        let c1 = <Fp as HashToField>::from_okm(GenericArray::<u8, U64>::from_slice(&okm[64..]));
+        Fp2 { c0, c1 }
+    }
+}
+
+impl Sgn0 for Fp2 {
+    fn sgn0(&self) -> Choice {
+        let sign_0 = self.c0.sgn0();
+        let zero_0 = self.c0.is_zero();
+        let sign_1 = self.c1.sgn0();
+        sign_0 | (zero_0 & sign_1)
+    }
+}
+
+/// Maps from an [`Fp2]` element to a point on iso-G2.
+fn map_to_curve_simple_swu(u: &Fp2) -> G2Projective {
+    let usq = u.square();
+    let xi_usq = SSWU_XI * usq;
+    let xisq_u4 = xi_usq.square();
+    let nd_common = xisq_u4 + xi_usq; // XI^2 * u^4 + XI * u^2
+    let x_den = SSWU_ELLP_A * Fp2::conditional_select(&(-nd_common), &SSWU_XI, nd_common.is_zero());
+    let x0_num = SSWU_ELLP_B * (Fp2::one() + nd_common); // B * (1 + (XI^2 * u^4 + XI * u^2))
+
+    // compute g(x0(u))
+    let x_densq = x_den.square();
+    let gx_den = x_densq * x_den;
+    // x0_num^3 + A * x0_num * x_den^2 + B * x_den^3
+    let gx0_num = (x0_num.square() + SSWU_ELLP_A * x_densq) * x0_num + SSWU_ELLP_B * gx_den;
+
+    // compute g(x0(u)) ^ ((p^2 - 9) // 16)
+    let sqrt_candidate = {
+        let vsq = gx_den.square(); // v^2
+        let v_3 = vsq * gx_den; // v^3
+        let v_4 = vsq.square(); // v^4
+        let uv_7 = gx0_num * v_3 * v_4; // u v^7
+        let uv_15 = uv_7 * v_4.square(); // u v^15
+        uv_7 * chain_p2m9div16(&uv_15) // u v^7 (u v^15) ^ ((p^2 - 9) // 16)
+    };
+
+    // set y = sqrt_candidate * Fp2::one(), check candidate against other roots of unity
+    let mut y = sqrt_candidate;
+    // check Fp2(0, 1)
+    let tmp = Fp2 {
+        c0: -sqrt_candidate.c1,
+        c1: sqrt_candidate.c0,
+    };
+    y.conditional_assign(&tmp, (tmp.square() * gx_den).ct_eq(&gx0_num));
+    // check Fp2(RV1, RV1)
+    let tmp = sqrt_candidate * SSWU_RV1;
+    y.conditional_assign(&tmp, (tmp.square() * gx_den).ct_eq(&gx0_num));
+    // check Fp2(RV1, -RV1)
+    let tmp = Fp2 {
+        c0: tmp.c1,
+        c1: -tmp.c0,
+    };
+    y.conditional_assign(&tmp, (tmp.square() * gx_den).ct_eq(&gx0_num));
+
+    // compute g(x1(u)) = g(x0(u)) * XI^3 * u^6
+    let gx1_num = gx0_num * xi_usq * xisq_u4;
+    // compute g(x1(u)) * u^3
+    let sqrt_candidate = sqrt_candidate * usq * u;
+    let mut eta_found = Choice::from(0u8);
+    for eta in &SSWU_ETAS[..] {
+        let tmp = sqrt_candidate * eta;
+        let found = (tmp.square() * gx_den).ct_eq(&gx1_num);
+        y.conditional_assign(&tmp, found);
+        eta_found |= found;
+    }
+
+    let x_num = Fp2::conditional_select(&x0_num, &(x0_num * xi_usq), eta_found);
+    // ensure sign of y and sign of u agree
+    y.conditional_negate(u.sgn0() ^ y.sgn0());
+
+    G2Projective {
+        x: x_num,
+        y: y * x_den,
+        z: x_den,
+    }
+}
+
+/// Maps from an iso-G2 point to a G2 point.
+fn iso_map(u: &G2Projective) -> G2Projective {
+    const COEFFS: [&[Fp2]; 4] = [&ISO3_XNUM, &ISO3_XDEN, &ISO3_YNUM, &ISO3_YDEN];
+
+    // unpack input point
+    let G2Projective { x, y, z } = *u;
+
+    // xnum, xden, ynum, yden
+    let mut mapvals = [Fp2::zero(); 4];
+
+    // compute powers of z
+    let zsq = z.square();
+    let zpows = [z, zsq, zsq * z];
+
+    // compute map value by Horner's rule
+    for idx in 0..4 {
+        let coeff = COEFFS[idx];
+        let clast = coeff.len() - 1;
+        mapvals[idx] = coeff[clast];
+        for jdx in 0..clast {
+            mapvals[idx] = mapvals[idx] * x + zpows[jdx] * coeff[clast - 1 - jdx];
+        }
+    }
+
+    // x denominator is order 1 less than x numerator, so we need an extra factor of z
+    mapvals[1] *= z;
+
+    // multiply result of Y map by the y-coord, y / z
+    mapvals[2] *= y;
+    mapvals[3] *= z;
+
+    G2Projective {
+        x: mapvals[0] * mapvals[3], // xnum * yden,
+        y: mapvals[2] * mapvals[1], // ynum * xden,
+        z: mapvals[1] * mapvals[3], // xden * yden
+    }
+}
+
+impl MapToCurve for G2Projective {
+    type Field = Fp2;
+
+    fn map_to_curve(u: &Fp2) -> G2Projective {
+        let pt = map_to_curve_simple_swu(u);
+        iso_map(&pt)
+    }
+
+    fn clear_h(&self) -> Self {
+        self.clear_cofactor()
+    }
+}
+
+#[cfg(test)]
+fn check_g2_prime(pt: &G2Projective) -> bool {
+    // (X : Y : Z)==(X/Z, Y/Z) is on E': y^2 = x^3 + A * x + B.
+    // y^2 z = (x^3) + A (x z^2) + B z^3
+    let zsq = pt.z.square();
+    (pt.y.square() * pt.z)
+        == (pt.x.square() * pt.x + SSWU_ELLP_A * pt.x * zsq + SSWU_ELLP_B * zsq * pt.z)
+}
+
+#[test]
+fn test_osswu_semirandom() {
+    use rand_core::SeedableRng;
+    let mut rng = rand_xorshift::XorShiftRng::from_seed([
+        0x59, 0x62, 0xbe, 0x5d, 0x76, 0x3d, 0x31, 0x8d, 0x17, 0xdb, 0x37, 0x32, 0x54, 0x06, 0xbc,
+        0xe5,
+    ]);
+    for _ in 0..32 {
+        let input = Fp2::random(&mut rng);
+        let p = map_to_curve_simple_swu(&input);
+        assert!(check_g2_prime(&p));
+
+        let p_iso = iso_map(&p);
+        assert!(bool::from(p_iso.is_on_curve()));
+    }
+}
+
+// test vectors from the draft 10 RFC
+#[test]
+fn test_encode_to_curve_10() {
+    use crate::{
+        g2::G2Affine,
+        hash_to_curve::{ExpandMsgXmd, HashToCurve},
+    };
+    use std::string::{String, ToString};
+
+    struct TestCase {
+        msg: &'static [u8],
+        expected: [&'static str; 4],
+    }
+    impl TestCase {
+        fn expected(&self) -> String {
+            self.expected[0].to_string() + self.expected[1] + self.expected[2] + self.expected[3]
+        }
+    }
+
+    const DOMAIN: &[u8] = b"QUUX-V01-CS02-with-BLS12381G2_XMD:SHA-256_SSWU_NU_";
+
+    let cases = vec![
+        TestCase {
+            msg: b"",
+            expected: [
+                "126b855e9e69b1f691f816e48ac6977664d24d99f8724868a184186469ddfd4617367e94527d4b74fc86413483afb35b",
+                "00e7f4568a82b4b7dc1f14c6aaa055edf51502319c723c4dc2688c7fe5944c213f510328082396515734b6612c4e7bb7",
+                "1498aadcf7ae2b345243e281ae076df6de84455d766ab6fcdaad71fab60abb2e8b980a440043cd305db09d283c895e3d",
+                "0caead0fd7b6176c01436833c79d305c78be307da5f6af6c133c47311def6ff1e0babf57a0fb5539fce7ee12407b0a42",
+            ],
+        },
+        TestCase {
+            msg: b"abc",
+            expected: [
+                "0296238ea82c6d4adb3c838ee3cb2346049c90b96d602d7bb1b469b905c9228be25c627bffee872def773d5b2a2eb57d",
+                "108ed59fd9fae381abfd1d6bce2fd2fa220990f0f837fa30e0f27914ed6e1454db0d1ee957b219f61da6ff8be0d6441f",
+                "153606c417e59fb331b7ae6bce4fbf7c5190c33ce9402b5ebe2b70e44fca614f3f1382a3625ed5493843d0b0a652fc3f",
+                "033f90f6057aadacae7963b0a0b379dd46750c1c94a6357c99b65f63b79e321ff50fe3053330911c56b6ceea08fee656",
+            ],
+        },
+        TestCase {
+            msg: b"abcdef0123456789",
+            expected: [
+                "0da75be60fb6aa0e9e3143e40c42796edf15685cafe0279afd2a67c3dff1c82341f17effd402e4f1af240ea90f4b659b",
+                "038af300ef34c7759a6caaa4e69363cafeed218a1f207e93b2c70d91a1263d375d6730bd6b6509dcac3ba5b567e85bf3",
+                "0492f4fed741b073e5a82580f7c663f9b79e036b70ab3e51162359cec4e77c78086fe879b65ca7a47d34374c8315ac5e",
+                "19b148cbdf163cf0894f29660d2e7bfb2b68e37d54cc83fd4e6e62c020eaa48709302ef8e746736c0e19342cc1ce3df4",
+            ]
+        },
+        TestCase {
+            msg: b"q128_qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq\
+                   qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq\
+                   qqqqqqqqqqqqqqqqqqqqqqqqq",
+            expected: [
+                "12c8c05c1d5fc7bfa847f4d7d81e294e66b9a78bc9953990c358945e1f042eedafce608b67fdd3ab0cb2e6e263b9b1ad",
+                "0c5ae723be00e6c3f0efe184fdc0702b64588fe77dda152ab13099a3bacd3876767fa7bbad6d6fd90b3642e902b208f9",
+                "11c624c56dbe154d759d021eec60fab3d8b852395a89de497e48504366feedd4662d023af447d66926a28076813dd646",
+                "04e77ddb3ede41b5ec4396b7421dd916efc68a358a0d7425bddd253547f2fb4830522358491827265dfc5bcc1928a569",
+            ]
+        },
+        TestCase {
+            msg: b"a512_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+                   aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+                   aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+                   aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+                   aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+                   aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+                   aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+                   aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+                   aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+                   aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
+            expected: [
+                "1565c2f625032d232f13121d3cfb476f45275c303a037faa255f9da62000c2c864ea881e2bcddd111edc4a3c0da3e88d",
+                "0ea4e7c33d43e17cc516a72f76437c4bf81d8f4eac69ac355d3bf9b71b8138d55dc10fd458be115afa798b55dac34be1",
+                "0f8991d2a1ad662e7b6f58ab787947f1fa607fce12dde171bc17903b012091b657e15333e11701edcf5b63ba2a561247",
+                "043b6f5fe4e52c839148dc66f2b3751e69a0f6ebb3d056d6465d50d4108543ecd956e10fa1640dfd9bc0030cc2558d28",
+            ]
+        }
+    ];
+
+    for case in cases {
+        let g = <G2Projective as HashToCurve<ExpandMsgXmd<sha2::Sha256>>>::encode_to_curve(
+            &case.msg, DOMAIN,
+        );
+        let g_uncompressed = G2Affine::from(g).to_uncompressed();
+
+        assert_eq!(case.expected(), hex::encode(&g_uncompressed[..]));
+    }
+}
+
+// test vectors from the draft 10 RFC
+#[test]
+fn test_hash_to_curve_10() {
+    use crate::{
+        g2::G2Affine,
+        hash_to_curve::{ExpandMsgXmd, HashToCurve},
+    };
+    use std::string::{String, ToString};
+
+    struct TestCase {
+        msg: &'static [u8],
+        expected: [&'static str; 4],
+    }
+    impl TestCase {
+        fn expected(&self) -> String {
+            self.expected[0].to_string() + self.expected[1] + self.expected[2] + self.expected[3]
+        }
+    }
+
+    const DOMAIN: &[u8] = b"QUUX-V01-CS02-with-BLS12381G2_XMD:SHA-256_SSWU_RO_";
+
+    let cases = vec![
+        TestCase {
+            msg: b"",
+            expected: [
+                "05cb8437535e20ecffaef7752baddf98034139c38452458baeefab379ba13dff5bf5dd71b72418717047f5b0f37da03d",
+                "0141ebfbdca40eb85b87142e130ab689c673cf60f1a3e98d69335266f30d9b8d4ac44c1038e9dcdd5393faf5c41fb78a",
+                "12424ac32561493f3fe3c260708a12b7c620e7be00099a974e259ddc7d1f6395c3c811cdd19f1e8dbf3e9ecfdcbab8d6",
+                "0503921d7f6a12805e72940b963c0cf3471c7b2a524950ca195d11062ee75ec076daf2d4bc358c4b190c0c98064fdd92",
+            ],
+        },
+        TestCase {
+            msg: b"abc",
+            expected: [
+                "139cddbccdc5e91b9623efd38c49f81a6f83f175e80b06fc374de9eb4b41dfe4ca3a230ed250fbe3a2acf73a41177fd8",
+                "02c2d18e033b960562aae3cab37a27ce00d80ccd5ba4b7fe0e7a210245129dbec7780ccc7954725f4168aff2787776e6",
+                "00aa65dae3c8d732d10ecd2c50f8a1baf3001578f71c694e03866e9f3d49ac1e1ce70dd94a733534f106d4cec0eddd16",
+                "1787327b68159716a37440985269cf584bcb1e621d3a7202be6ea05c4cfe244aeb197642555a0645fb87bf7466b2ba48",
+            ],
+        },
+        TestCase {
+            msg: b"abcdef0123456789",
+            expected: [
+                "190d119345b94fbd15497bcba94ecf7db2cbfd1e1fe7da034d26cbba169fb3968288b3fafb265f9ebd380512a71c3f2c",
+                "121982811d2491fde9ba7ed31ef9ca474f0e1501297f68c298e9f4c0028add35aea8bb83d53c08cfc007c1e005723cd0",
+                "0bb5e7572275c567462d91807de765611490205a941a5a6af3b1691bfe596c31225d3aabdf15faff860cb4ef17c7c3be",
+                "05571a0f8d3c08d094576981f4a3b8eda0a8e771fcdcc8ecceaf1356a6acf17574518acb506e435b639353c2e14827c8",
+            ]
+        },
+        TestCase {
+            msg: b"q128_qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq\
+                   qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq\
+                   qqqqqqqqqqqqqqqqqqqqqqqqq",
+            expected: [
+                "0934aba516a52d8ae479939a91998299c76d39cc0c035cd18813bec433f587e2d7a4fef038260eef0cef4d02aae3eb91",
+                "19a84dd7248a1066f737cc34502ee5555bd3c19f2ecdb3c7d9e24dc65d4e25e50d83f0f77105e955d78f4762d33c17da",
+                "09bcccfa036b4847c9950780733633f13619994394c23ff0b32fa6b795844f4a0673e20282d07bc69641cee04f5e5662",
+                "14f81cd421617428bc3b9fe25afbb751d934a00493524bc4e065635b0555084dd54679df1536101b2c979c0152d09192",
+            ]
+        },
+        TestCase {
+            msg: b"a512_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+                   aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+                   aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+                   aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+                   aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+                   aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+                   aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+                   aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+                   aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
+                   aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
+            expected: [
+                "11fca2ff525572795a801eed17eb12785887c7b63fb77a42be46ce4a34131d71f7a73e95fee3f812aea3de78b4d01569",
+                "01a6ba2f9a11fa5598b2d8ace0fbe0a0eacb65deceb476fbbcb64fd24557c2f4b18ecfc5663e54ae16a84f5ab7f62534",
+                "03a47f8e6d1763ba0cad63d6114c0accbef65707825a511b251a660a9b3994249ae4e63fac38b23da0c398689ee2ab52",
+                "0b6798718c8aed24bc19cb27f866f1c9effcdbf92397ad6448b5c9db90d2b9da6cbabf48adc1adf59a1a28344e79d57e",
+            ]
+        }
+    ];
+
+    for case in cases {
+        let g = <G2Projective as HashToCurve<ExpandMsgXmd<sha2::Sha256>>>::hash_to_curve(
+            &case.msg, DOMAIN,
+        );
+        let g_uncompressed = G2Affine::from(g).to_uncompressed();
+
+        assert_eq!(case.expected(), hex::encode(&g_uncompressed[..]));
+    }
+}
+
+#[test]
+fn test_sgn0() {
+    use super::map_g1::P_M1_OVER2;
+
+    assert_eq!(bool::from(Fp2::zero().sgn0()), false);
+    assert_eq!(bool::from(Fp2::one().sgn0()), true);
+    assert_eq!(
+        bool::from(
+            Fp2 {
+                c0: P_M1_OVER2,
+                c1: Fp::zero()
+            }
+            .sgn0()
+        ),
+        true
+    );
+    assert_eq!(
+        bool::from(
+            Fp2 {
+                c0: P_M1_OVER2,
+                c1: Fp::one()
+            }
+            .sgn0()
+        ),
+        true
+    );
+    assert_eq!(
+        bool::from(
+            Fp2 {
+                c0: Fp::zero(),
+                c1: P_M1_OVER2,
+            }
+            .sgn0()
+        ),
+        true
+    );
+    assert_eq!(
+        bool::from(
+            Fp2 {
+                c0: Fp::one(),
+                c1: P_M1_OVER2,
+            }
+            .sgn0()
+        ),
+        true
+    );
+
+    let p_p1_over2 = P_M1_OVER2 + Fp::one();
+    assert_eq!(
+        bool::from(
+            Fp2 {
+                c0: p_p1_over2,
+                c1: Fp::zero()
+            }
+            .sgn0()
+        ),
+        false
+    );
+    assert_eq!(
+        bool::from(
+            Fp2 {
+                c0: p_p1_over2,
+                c1: Fp::one()
+            }
+            .sgn0()
+        ),
+        false
+    );
+    assert_eq!(
+        bool::from(
+            Fp2 {
+                c0: Fp::zero(),
+                c1: p_p1_over2,
+            }
+            .sgn0()
+        ),
+        false
+    );
+    assert_eq!(
+        bool::from(
+            Fp2 {
+                c0: Fp::one(),
+                c1: p_p1_over2,
+            }
+            .sgn0()
+        ),
+        true
+    );
+
+    assert_eq!(
+        bool::from(
+            Fp2 {
+                c0: P_M1_OVER2,
+                c1: -Fp::one()
+            }
+            .sgn0()
+        ),
+        true
+    );
+    assert_eq!(
+        bool::from(
+            Fp2 {
+                c0: p_p1_over2,
+                c1: -Fp::one()
+            }
+            .sgn0()
+        ),
+        false
+    );
+    assert_eq!(
+        bool::from(
+            Fp2 {
+                c0: Fp::zero(),
+                c1: -Fp::one()
+            }
+            .sgn0()
+        ),
+        false
+    );
+    assert_eq!(
+        bool::from(
+            Fp2 {
+                c0: P_M1_OVER2,
+                c1: p_p1_over2
+            }
+            .sgn0()
+        ),
+        true
+    );
+    assert_eq!(
+        bool::from(
+            Fp2 {
+                c0: p_p1_over2,
+                c1: P_M1_OVER2
+            }
+            .sgn0()
+        ),
+        false
+    );
+
+    assert_eq!(
+        bool::from(
+            Fp2 {
+                c0: -Fp::one(),
+                c1: P_M1_OVER2,
+            }
+            .sgn0()
+        ),
+        false
+    );
+    assert_eq!(
+        bool::from(
+            Fp2 {
+                c0: -Fp::one(),
+                c1: p_p1_over2,
+            }
+            .sgn0()
+        ),
+        false
+    );
+    assert_eq!(
+        bool::from(
+            Fp2 {
+                c0: -Fp::one(),
+                c1: Fp::zero(),
+            }
+            .sgn0()
+        ),
+        false
+    );
+    assert_eq!(
+        bool::from(
+            Fp2 {
+                c0: p_p1_over2,
+                c1: P_M1_OVER2,
+            }
+            .sgn0()
+        ),
+        false
+    );
+    assert_eq!(
+        bool::from(
+            Fp2 {
+                c0: P_M1_OVER2,
+                c1: p_p1_over2,
+            }
+            .sgn0()
+        ),
+        true
+    );
+}
diff --git a/src/hash_to_curve/map_scalar.rs b/src/hash_to_curve/map_scalar.rs
new file mode 100644
index 00000000..ec4bb9e0
--- /dev/null
+++ b/src/hash_to_curve/map_scalar.rs
@@ -0,0 +1,39 @@
+//! Implementation of hash-to-field for Scalar values
+
+use super::HashToField;
+use crate::generic_array::{typenum::U48, GenericArray};
+use crate::scalar::Scalar;
+
+impl HashToField for Scalar {
+    // ceil(log2(p)) = 255, m = 1, k = 128.
+    type InputLength = U48;
+
+    fn from_okm(okm: &GenericArray<u8, U48>) -> Scalar {
+        let mut bs = [0u8; 64];
+        bs[16..].copy_from_slice(okm);
+        bs.reverse(); // into little endian
+        Scalar::from_bytes_wide(&bs)
+    }
+}
+
+#[test]
+fn test_hash_to_scalar() {
+    let tests: &[(&[u8], &str)] = &[
+        (
+            &[0u8; 48],
+            "0x0000000000000000000000000000000000000000000000000000000000000000",
+        ),
+        (
+            b"aaaaaabbbbbbccccccddddddeeeeeeffffffgggggghhhhhh",
+            "0x2228450bf55d8fe62395161bd3677ff6fc28e45b89bc87e02a818eda11a8c5da",
+        ),
+        (
+            b"111111222222333333444444555555666666777777888888",
+            "0x4aa543cbd2f0c8f37f8a375ce2e383eb343e7e3405f61e438b0a15fb8899d1ae",
+        ),
+    ];
+    for (input, expected) in tests {
+        let output = format!("{:?}", Scalar::from_okm(GenericArray::from_slice(input)));
+        assert_eq!(&output, expected);
+    }
+}
diff --git a/src/hash_to_curve/mod.rs b/src/hash_to_curve/mod.rs
new file mode 100644
index 00000000..4c15a498
--- /dev/null
+++ b/src/hash_to_curve/mod.rs
@@ -0,0 +1,113 @@
+//! This module implements hash_to_curve, hash_to_field and related
+//! hashing primitives for use with BLS signatures.
+
+use core::ops::Add;
+
+use subtle::Choice;
+
+pub(crate) mod chain;
+
+mod expand_msg;
+pub use self::expand_msg::{
+    ExpandMessage, ExpandMessageState, ExpandMsgXmd, ExpandMsgXof, InitExpandMessage,
+};
+
+mod map_g1;
+mod map_g2;
+mod map_scalar;
+
+use crate::generic_array::{typenum::Unsigned, ArrayLength, GenericArray};
+
+/// Enables a byte string to be hashed into one or more field elements for a given curve.
+///
+/// Implements [section 5 of `draft-irtf-cfrg-hash-to-curve-12`][hash_to_field].
+///
+/// [hash_to_field]: https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-12#section-5
+pub trait HashToField: Sized {
+    /// The length of the data used to produce an individual field element.
+    ///
+    /// This must be set to `m * L = m * ceil((ceil(log2(p)) + k) / 8)`, where `p` is the
+    /// characteristic of `Self`, `m` is the extension degree of `Self`, and `k` is the
+    /// security parameter.
+    type InputLength: ArrayLength<u8>;
+
+    /// Interprets the given output keying material as a big endian integer, and reduces
+    /// it into a field element.
+    fn from_okm(okm: &GenericArray<u8, Self::InputLength>) -> Self;
+
+    /// Hashes a byte string of arbitrary length into one or more elements of `Self`,
+    /// using [`ExpandMessage`] variant `X`.
+    ///
+    /// Implements [section 5.3 of `draft-irtf-cfrg-hash-to-curve-12`][hash_to_field].
+    ///
+    /// [hash_to_field]: https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-12#section-5.3
+    fn hash_to_field<X: ExpandMessage>(message: &[u8], dst: &[u8], output: &mut [Self]) {
+        let len_per_elm = Self::InputLength::to_usize();
+        let len_in_bytes = output.len() * len_per_elm;
+        let mut expander = X::init_expand(message, dst, len_in_bytes);
+
+        let mut buf = GenericArray::<u8, Self::InputLength>::default();
+        output.iter_mut().for_each(|item| {
+            expander.read_into(&mut buf[..]);
+            *item = Self::from_okm(&buf);
+        });
+    }
+}
+
+/// Allow conversion from the output of hashed or encoded input into points on the curve
+pub trait MapToCurve: Sized {
+    /// The field element type.
+    type Field: Copy + Default + HashToField;
+
+    /// Maps an element of the finite field `Self::Field` to a point on the curve `Self`.
+    fn map_to_curve(elt: &Self::Field) -> Self;
+
+    /// Clears the cofactor, sending a point on curve E to the target group (G1/G2).
+    fn clear_h(&self) -> Self;
+}
+
+/// Implementation of random oracle maps to the curve.
+pub trait HashToCurve<X: ExpandMessage>: MapToCurve + for<'a> Add<&'a Self, Output = Self> {
+    /// Implements a uniform encoding from byte strings to elements of `Self`.
+    ///
+    /// This function is suitable for most applications requiring a random
+    /// oracle returning points in `Self`.
+    fn hash_to_curve(message: impl AsRef<[u8]>, dst: &[u8]) -> Self {
+        let mut u = [Self::Field::default(); 2];
+        Self::Field::hash_to_field::<X>(message.as_ref(), dst, &mut u);
+        let p1 = Self::map_to_curve(&u[0]);
+        let p2 = Self::map_to_curve(&u[1]);
+        (p1 + &p2).clear_h()
+    }
+
+    /// Implements a **non-uniform** encoding from byte strings to elements of `Self`.
+    ///
+    /// The distribution of its output is not uniformly random in `Self`: the set of
+    /// possible outputs of this function is only a fraction of the points in `Self`, and
+    /// some elements of this set are more likely to be output than others. See
+    /// [section 10.1 of `draft-irtf-cfrg-hash-to-curve-12`][encode_to_curve-distribution]
+    /// for a more precise definition of `encode_to_curve`'s output distribution.
+    ///
+    /// [encode_to_curve-distribution]: https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-12#section-10.1
+    fn encode_to_curve(message: impl AsRef<[u8]>, dst: &[u8]) -> Self {
+        let mut u = [Self::Field::default(); 1];
+        Self::Field::hash_to_field::<X>(message.as_ref(), dst, &mut u);
+        let p = Self::map_to_curve(&u[0]);
+        p.clear_h()
+    }
+}
+
+impl<G, X> HashToCurve<X> for G
+where
+    G: MapToCurve + for<'a> Add<&'a Self, Output = Self>,
+    X: ExpandMessage,
+{
+}
+
+pub(crate) trait Sgn0 {
+    /// Returns either 0 or 1 indicating the "sign" of x, where sgn0(x) == 1
+    /// just when x is "negative". (In other words, this function always considers 0 to be positive.)
+    /// <https://tools.ietf.org/html/draft-irtf-cfrg-hash-to-curve-10#section-4.1>
+    /// The equivalent for draft 6 would be `lexicographically_largest`.
+    fn sgn0(&self) -> Choice;
+}
diff --git a/src/lib.rs b/src/lib.rs
index c30e8c34..13efc394 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -8,13 +8,13 @@
 //! * This implementation does not require the Rust standard library.
 //! * All operations are constant time unless explicitly noted.
 
-#![cfg_attr(not(feature = "std"), no_std)]
+#![no_std]
+#![cfg_attr(docsrs, feature(doc_cfg))]
 // Catch documentation errors caused by code changes.
 #![deny(rustdoc::broken_intra_doc_links)]
 #![deny(missing_debug_implementations)]
 #![deny(missing_docs)]
 #![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
@@ -88,7 +88,7 @@ const BLS_X_IS_NEGATIVE: bool = true;
 mod pairings;
 
 #[cfg(feature = "pairings")]
-pub use pairings::{pairing, Gt, MillerLoopResult};
+pub use pairings::{pairing, Bls12, Gt, MillerLoopResult};
 
 #[cfg(all(feature = "pairings", feature = "alloc"))]
 pub use pairings::{multi_miller_loop, G2Prepared};
@@ -98,3 +98,10 @@ pub use pairings::{
     ArchivedG2Prepared, ArchivedGt, ArchivedMillerLoopResult, G2PreparedResolver, GtResolver,
     MillerLoopResultResolver,
 };
+
+/// Use the generic_array re-exported by digest to avoid a version mismatch
+#[cfg(feature = "experimental")]
+pub(crate) use digest::generic_array;
+
+#[cfg(feature = "experimental")]
+pub mod hash_to_curve;
diff --git a/src/notes/design.rs b/src/notes/design.rs
index 11d52261..685a7e75 100644
--- a/src/notes/design.rs
+++ b/src/notes/design.rs
@@ -60,3 +60,49 @@
 //!             print("g2 generator: {}".format(p))
 //!             break
 //! ```
+//!
+//! ## Nontrivial third root of unity
+//!
+//! To use the fast subgroup check algorithm for $\mathbb{G_1}$ from https://eprint.iacr.org/2019/814.pdf and
+//! https://eprint.iacr.org/2021/1130, it is necessary to find a nontrivial cube root of
+//! unity β in Fp to define the endomorphism:
+//!        $$(x, y) \rightarrow (\beta x, y)$$
+//! which is equivalent to
+//!        $$P \rightarrow \lambda P$$
+//! where $\lambda$, a nontrivial cube root of unity in Fr, satisfies $\lambda^2 + \lambda +1 = 0 \pmod{r}.
+//!
+//! $$\beta = 793479390729215512621379701633421447060886740281060493010456487427281649075476305620758731620350$$
+//! can be derived using the following sage commands after running the above sage script:
+//!
+//! ```text
+//! # Prints the given field element in Montgomery form.
+//! def print_fq(a):
+//!     R = 1 << 384
+//!     tmp = ZZ(Fq(a*R))
+//!     while tmp > 0:
+//!         print("0x{:_x}, ".format(tmp % (1 << 64)))
+//!         tmp >>= 64
+//! β = (Fq.multiplicative_generator() ** ((q-1)/3))
+//! print_fq(β)
+//! ```
+//!
+//! ## Psi
+//!
+//! To use the fast subgroup check algorithm for $\mathbb{G_2}$ from https://eprint.iacr.org/2019/814.pdf and
+//! https://eprint.iacr.org/2021/1130, it is necessary to find the endomorphism:
+//!
+//! $$(x, y, z) \rightarrow (x^q \psi_x, y^q \psi_y, z^q)$$
+//!
+//! where:
+//!
+//! 1. $\psi_x = 1 / ((i+1) ^ ((q-1)/3)) \in \mathbb{F}_{q^2}$, and
+//! 2. $\psi_y = 1 / ((i+1) ^ ((q-1)/2)) \in \mathbb{F}_{q^2}$
+//!
+//! can be derived using the following sage commands after running the above script and commands:
+//! ```text
+//! psi_x = (1/((i+1)**((q-1)/3)))
+//! psi_y = (1/((i+1)**((q-1)/2)))
+//! print_fq(psi_x.polynomial().coefficients()[0])
+//! print_fq(psi_y.polynomial().coefficients()[0])
+//! print_fq(psi_y.polynomial().coefficients()[1])
+//! ```
diff --git a/src/pairings.rs b/src/pairings.rs
index 7e1fcb1e..c67bbbd6 100644
--- a/src/pairings.rs
+++ b/src/pairings.rs
@@ -3,16 +3,21 @@ use crate::fp::Fp;
 use crate::fp12::Fp12;
 use crate::fp2::Fp2;
 use crate::fp6::Fp6;
-use crate::{BlsScalar, G1Affine, G2Affine, G2Projective, BLS_X, BLS_X_IS_NEGATIVE};
+use crate::{BlsScalar, G1Affine, G1Projective, G2Affine, G2Projective, BLS_X, BLS_X_IS_NEGATIVE};
 
+use core::borrow::Borrow;
+use core::fmt;
+use core::iter::Sum;
 use core::ops::{Add, AddAssign, Mul, MulAssign, Neg, Sub, SubAssign};
-
+use group::Group;
+use pairing::{Engine, PairingCurveAffine};
+use rand_core::RngCore;
 use subtle::{Choice, ConditionallySelectable, ConstantTimeEq};
 
 #[cfg(feature = "alloc")]
 use alloc::vec::Vec;
-
-use dusk_bytes::Serializable;
+#[cfg(feature = "alloc")]
+use pairing::MultiMillerLoop;
 
 #[cfg(feature = "rkyv-impl")]
 use bytecheck::CheckBytes;
@@ -22,11 +27,24 @@ use rkyv::{Archive, Deserialize as RkyvDeserialize, Serialize as RkyvSerialize};
 /// 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.
+#[cfg_attr(docsrs, doc(cfg(feature = "pairings")))]
 #[derive(Copy, Clone, Debug)]
-#[cfg_attr(feature = "rkyv-impl", derive(Archive, RkyvDeserialize, RkyvSerialize))]
-#[cfg_attr(feature = "rkyv-impl", archive_attr(derive(CheckBytes)))]
+#[cfg_attr(
+    feature = "rkyv-impl",
+    derive(Archive, RkyvSerialize, RkyvDeserialize),
+    archive_attr(derive(CheckBytes))
+)]
 pub struct MillerLoopResult(pub(crate) Fp12);
 
+impl Default for MillerLoopResult {
+    fn default() -> Self {
+        MillerLoopResult(Fp12::one())
+    }
+}
+
+#[cfg(feature = "zeroize")]
+impl zeroize::DefaultIsZeroes for MillerLoopResult {}
+
 impl ConditionallySelectable for MillerLoopResult {
     fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self {
         MillerLoopResult(Fp12::conditional_select(&a.0, &b.0, choice))
@@ -68,28 +86,28 @@ impl MillerLoopResult {
 
             // For A
             z0 = t0 - z0;
-            z0 += z0 + t0;
+            z0 = z0 + z0 + t0;
 
             z1 = t1 + z1;
-            z1 += 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;
+            z4 = z4 + z4 + t0;
 
             z5 = t1 + z5;
-            z5 += z5 + t1;
+            z5 = z5 + z5 + t1;
 
             // For B
             t0 = t3.mul_by_nonresidue();
             z2 = t0 + z2;
-            z2 += z2 + t0;
+            z2 = z2 + z2 + t0;
 
             z3 = t2 - z3;
-            z3 += z3 + t2;
+            z3 = z3 + z3 + t2;
 
             Fp12 {
                 c0: Fp6 {
@@ -124,7 +142,7 @@ impl MillerLoopResult {
             tmp.conjugate()
         }
 
-        let mut f = self.0.clone();
+        let mut f = self.0;
         let mut t0 = f
             .frobenius_map()
             .frobenius_map()
@@ -135,7 +153,7 @@ impl MillerLoopResult {
         Gt(f.invert()
             .map(|mut t1| {
                 let mut t2 = t0 * t1;
-                t1 = t2.clone();
+                t1 = t2;
                 t2 = t2.frobenius_map().frobenius_map();
                 t2 *= t1;
                 t1 = cyclotomic_square(t2).conjugate();
@@ -180,16 +198,46 @@ impl<'a, 'b> Add<&'b MillerLoopResult> for &'a MillerLoopResult {
 
 impl_add_binop_specify_output!(MillerLoopResult, MillerLoopResult, MillerLoopResult);
 
+impl AddAssign<MillerLoopResult> for MillerLoopResult {
+    #[inline]
+    fn add_assign(&mut self, rhs: MillerLoopResult) {
+        *self = *self + rhs;
+    }
+}
+
+impl<'b> AddAssign<&'b MillerLoopResult> for MillerLoopResult {
+    #[inline]
+    fn add_assign(&mut self, rhs: &'b MillerLoopResult) {
+        *self = *self + rhs;
+    }
+}
+
 /// 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.
+#[cfg_attr(docsrs, doc(cfg(feature = "pairings")))]
 #[derive(Copy, Clone, Debug)]
 #[cfg_attr(feature = "rkyv-impl", derive(Archive, RkyvDeserialize, RkyvSerialize))]
 #[cfg_attr(feature = "rkyv-impl", archive_attr(derive(CheckBytes)))]
 pub struct Gt(pub(crate) Fp12);
 
+impl Default for Gt {
+    fn default() -> Self {
+        Self::identity()
+    }
+}
+
+#[cfg(feature = "zeroize")]
+impl zeroize::DefaultIsZeroes for Gt {}
+
+impl fmt::Display for Gt {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        write!(f, "{:?}", self)
+    }
+}
+
 impl ConstantTimeEq for Gt {
     fn ct_eq(&self, other: &Self) -> Choice {
         self.0.ct_eq(&other.0)
@@ -289,6 +337,166 @@ impl<'a, 'b> Mul<&'b BlsScalar> for &'a Gt {
 impl_binops_additive!(Gt, Gt);
 impl_binops_multiplicative!(Gt, BlsScalar);
 
+impl<T> Sum<T> for Gt
+where
+    T: Borrow<Gt>,
+{
+    fn sum<I>(iter: I) -> Self
+    where
+        I: Iterator<Item = T>,
+    {
+        iter.fold(Self::identity(), |acc, item| acc + item.borrow())
+    }
+}
+
+impl Group for Gt {
+    type Scalar = BlsScalar;
+
+    fn random(mut rng: impl RngCore) -> Self {
+        loop {
+            let inner = Fp12::random(&mut rng);
+
+            // Not all elements of Fp12 are elements of the prime-order multiplicative
+            // subgroup. We run the random element through final_exponentiation to obtain
+            // a valid element, which requires that it is non-zero.
+            if !bool::from(inner.is_zero()) {
+                return MillerLoopResult(inner).final_exponentiation();
+            }
+        }
+    }
+
+    fn identity() -> Self {
+        Self::identity()
+    }
+
+    fn generator() -> Self {
+        // pairing(&G1Affine::generator(), &G2Affine::generator())
+        Gt(Fp12 {
+            c0: Fp6 {
+                c0: Fp2 {
+                    c0: Fp::from_raw_unchecked([
+                        0x1972_e433_a01f_85c5,
+                        0x97d3_2b76_fd77_2538,
+                        0xc8ce_546f_c96b_cdf9,
+                        0xcef6_3e73_66d4_0614,
+                        0xa611_3427_8184_3780,
+                        0x13f3_448a_3fc6_d825,
+                    ]),
+                    c1: Fp::from_raw_unchecked([
+                        0xd263_31b0_2e9d_6995,
+                        0x9d68_a482_f779_7e7d,
+                        0x9c9b_2924_8d39_ea92,
+                        0xf480_1ca2_e131_07aa,
+                        0xa16c_0732_bdbc_b066,
+                        0x083c_a4af_ba36_0478,
+                    ]),
+                },
+                c1: Fp2 {
+                    c0: Fp::from_raw_unchecked([
+                        0x59e2_61db_0916_b641,
+                        0x2716_b6f4_b23e_960d,
+                        0xc8e5_5b10_a0bd_9c45,
+                        0x0bdb_0bd9_9c4d_eda8,
+                        0x8cf8_9ebf_57fd_aac5,
+                        0x12d6_b792_9e77_7a5e,
+                    ]),
+                    c1: Fp::from_raw_unchecked([
+                        0x5fc8_5188_b0e1_5f35,
+                        0x34a0_6e3a_8f09_6365,
+                        0xdb31_26a6_e02a_d62c,
+                        0xfc6f_5aa9_7d9a_990b,
+                        0xa12f_55f5_eb89_c210,
+                        0x1723_703a_926f_8889,
+                    ]),
+                },
+                c2: Fp2 {
+                    c0: Fp::from_raw_unchecked([
+                        0x9358_8f29_7182_8778,
+                        0x43f6_5b86_11ab_7585,
+                        0x3183_aaf5_ec27_9fdf,
+                        0xfa73_d7e1_8ac9_9df6,
+                        0x64e1_76a6_a64c_99b0,
+                        0x179f_a78c_5838_8f1f,
+                    ]),
+                    c1: Fp::from_raw_unchecked([
+                        0x672a_0a11_ca2a_ef12,
+                        0x0d11_b9b5_2aa3_f16b,
+                        0xa444_12d0_699d_056e,
+                        0xc01d_0177_221a_5ba5,
+                        0x66e0_cede_6c73_5529,
+                        0x05f5_a71e_9fdd_c339,
+                    ]),
+                },
+            },
+            c1: Fp6 {
+                c0: Fp2 {
+                    c0: Fp::from_raw_unchecked([
+                        0xd30a_88a1_b062_c679,
+                        0x5ac5_6a5d_35fc_8304,
+                        0xd0c8_34a6_a81f_290d,
+                        0xcd54_30c2_da37_07c7,
+                        0xf0c2_7ff7_8050_0af0,
+                        0x0924_5da6_e2d7_2eae,
+                    ]),
+                    c1: Fp::from_raw_unchecked([
+                        0x9f2e_0676_791b_5156,
+                        0xe2d1_c823_4918_fe13,
+                        0x4c9e_459f_3c56_1bf4,
+                        0xa3e8_5e53_b9d3_e3c1,
+                        0x820a_121e_21a7_0020,
+                        0x15af_6183_41c5_9acc,
+                    ]),
+                },
+                c1: Fp2 {
+                    c0: Fp::from_raw_unchecked([
+                        0x7c95_658c_2499_3ab1,
+                        0x73eb_3872_1ca8_86b9,
+                        0x5256_d749_4774_34bc,
+                        0x8ba4_1902_ea50_4a8b,
+                        0x04a3_d3f8_0c86_ce6d,
+                        0x18a6_4a87_fb68_6eaa,
+                    ]),
+                    c1: Fp::from_raw_unchecked([
+                        0xbb83_e71b_b920_cf26,
+                        0x2a52_77ac_92a7_3945,
+                        0xfc0e_e59f_94f0_46a0,
+                        0x7158_cdf3_7860_58f7,
+                        0x7cc1_061b_82f9_45f6,
+                        0x03f8_47aa_9fdb_e567,
+                    ]),
+                },
+                c2: Fp2 {
+                    c0: Fp::from_raw_unchecked([
+                        0x8078_dba5_6134_e657,
+                        0x1cd7_ec9a_4399_8a6e,
+                        0xb1aa_599a_1a99_3766,
+                        0xc9a0_f62f_0842_ee44,
+                        0x8e15_9be3_b605_dffa,
+                        0x0c86_ba0d_4af1_3fc2,
+                    ]),
+                    c1: Fp::from_raw_unchecked([
+                        0xe80f_f2a0_6a52_ffb1,
+                        0x7694_ca48_721a_906c,
+                        0x7583_183e_03b0_8514,
+                        0xf567_afdd_40ce_e4e2,
+                        0x9a6d_96d2_e526_a5fc,
+                        0x197e_9f49_861f_2242,
+                    ]),
+                },
+            },
+        })
+    }
+
+    fn is_identity(&self) -> Choice {
+        self.ct_eq(&Self::identity())
+    }
+
+    #[must_use]
+    fn double(&self) -> Self {
+        self.double()
+    }
+}
+
 /// 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
@@ -298,9 +506,13 @@ impl_binops_multiplicative!(Gt, BlsScalar);
 ///
 /// Requires the `alloc` and `pairing` crate features to be enabled.
 #[cfg(feature = "alloc")]
+#[cfg_attr(docsrs, doc(cfg(all(feature = "pairings", feature = "alloc"))))]
 #[derive(Clone, Debug)]
-#[cfg_attr(feature = "rkyv-impl", derive(Archive, RkyvSerialize, RkyvDeserialize))]
-#[cfg_attr(feature = "rkyv-impl", archive_attr(derive(CheckBytes)))]
+#[cfg_attr(
+    feature = "rkyv-impl",
+    derive(Archive, RkyvSerialize, RkyvDeserialize),
+    archive_attr(derive(CheckBytes))
+)]
 pub struct G2Prepared {
     infinity: choice::Choice,
     coeffs: Vec<(Fp2, Fp2, Fp2)>,
@@ -326,15 +538,9 @@ impl From<G2Affine> for G2Prepared {
                 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 {
-                ()
-            }
+            fn square_output(_: Self::Output) -> Self::Output {}
+            fn conjugate(_: Self::Output) -> Self::Output {}
+            fn one() -> Self::Output {}
         }
 
         let is_identity = q.is_identity();
@@ -358,6 +564,7 @@ impl From<G2Affine> for G2Prepared {
 }
 
 #[cfg(feature = "alloc")]
+#[cfg_attr(docsrs, doc(cfg(all(feature = "pairings", 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).$$
 ///
@@ -414,6 +621,7 @@ pub fn multi_miller_loop(terms: &[(&G1Affine, &G2Prepared)]) -> MillerLoopResult
 }
 
 /// Invoke the pairing function without the use of precomputation and other optimizations.
+#[cfg_attr(docsrs, doc(cfg(feature = "pairings")))]
 pub fn pairing(p: &G1Affine, q: &G2Affine) -> Gt {
     struct Adder {
         cur: G2Projective,
@@ -444,8 +652,8 @@ pub fn pairing(p: &G1Affine, q: &G2Affine) -> Gt {
     }
 
     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 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),
@@ -579,6 +787,68 @@ fn addition_step(r: &mut G2Projective, q: &G2Affine) -> (Fp2, Fp2, Fp2) {
     (t10, t1, t9)
 }
 
+impl PairingCurveAffine for G1Affine {
+    type Pair = G2Affine;
+    type PairingResult = Gt;
+
+    fn pairing_with(&self, other: &Self::Pair) -> Self::PairingResult {
+        pairing(self, other)
+    }
+}
+
+impl PairingCurveAffine for G2Affine {
+    type Pair = G1Affine;
+    type PairingResult = Gt;
+
+    fn pairing_with(&self, other: &Self::Pair) -> Self::PairingResult {
+        pairing(other, self)
+    }
+}
+
+/// A [`pairing::Engine`] for BLS12-381 pairing operations.
+#[cfg_attr(docsrs, doc(cfg(feature = "pairings")))]
+#[derive(Clone, Debug)]
+pub struct Bls12;
+
+impl Engine for Bls12 {
+    type Fr = BlsScalar;
+    type G1 = G1Projective;
+    type G1Affine = G1Affine;
+    type G2 = G2Projective;
+    type G2Affine = G2Affine;
+    type Gt = Gt;
+
+    fn pairing(p: &Self::G1Affine, q: &Self::G2Affine) -> Self::Gt {
+        pairing(p, q)
+    }
+}
+
+impl pairing::MillerLoopResult for MillerLoopResult {
+    type Gt = Gt;
+
+    fn final_exponentiation(&self) -> Self::Gt {
+        self.final_exponentiation()
+    }
+}
+
+#[cfg(feature = "alloc")]
+impl MultiMillerLoop for Bls12 {
+    type G2Prepared = G2Prepared;
+    type Result = MillerLoopResult;
+
+    fn multi_miller_loop(terms: &[(&Self::G1Affine, &Self::G2Prepared)]) -> Self::Result {
+        multi_miller_loop(terms)
+    }
+}
+
+#[test]
+fn test_gt_generator() {
+    assert_eq!(
+        Gt::generator(),
+        pairing(&G1Affine::generator(), &G2Affine::generator())
+    );
+}
+
 #[test]
 fn test_bilinearity() {
     use crate::BlsScalar;
@@ -676,10 +946,59 @@ fn test_multi_miller_loop() {
     assert_eq!(expected, test);
 }
 
+#[test]
+fn test_miller_loop_result_default() {
+    assert_eq!(
+        MillerLoopResult::default().final_exponentiation(),
+        Gt::identity(),
+    );
+}
+
+#[cfg(feature = "zeroize")]
+#[test]
+fn test_miller_loop_result_zeroize() {
+    use zeroize::Zeroize;
+
+    let mut m = multi_miller_loop(&[
+        (&G1Affine::generator(), &G2Affine::generator().into()),
+        (&-G1Affine::generator(), &G2Affine::generator().into()),
+    ]);
+    m.zeroize();
+    assert_eq!(m.0, MillerLoopResult::default().0);
+}
+
+#[test]
+fn tricking_miller_loop_result() {
+    assert_eq!(
+        multi_miller_loop(&[(&G1Affine::identity(), &G2Affine::generator().into())]).0,
+        Fp12::one()
+    );
+    assert_eq!(
+        multi_miller_loop(&[(&G1Affine::generator(), &G2Affine::identity().into())]).0,
+        Fp12::one()
+    );
+    assert_ne!(
+        multi_miller_loop(&[
+            (&G1Affine::generator(), &G2Affine::generator().into()),
+            (&-G1Affine::generator(), &G2Affine::generator().into())
+        ])
+        .0,
+        Fp12::one()
+    );
+    assert_eq!(
+        multi_miller_loop(&[
+            (&G1Affine::generator(), &G2Affine::generator().into()),
+            (&-G1Affine::generator(), &G2Affine::generator().into())
+        ])
+        .final_exponentiation(),
+        Gt::identity()
+    );
+}
+
 mod dusk {
     use super::*;
 
-    #[cfg(feature = "serde_req")]
+    #[cfg(feature = "serde")]
     use serde::{
         self, de::Visitor, ser::SerializeStruct, Deserialize, Deserializer, Serialize, Serializer,
     };
@@ -765,7 +1084,7 @@ mod dusk {
         }
     }
 
-    #[cfg(feature = "serde_req")]
+    #[cfg(feature = "serde")]
     impl Serialize for G2Prepared {
         fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
         where
@@ -781,7 +1100,7 @@ mod dusk {
         }
     }
 
-    #[cfg(feature = "serde_req")]
+    #[cfg(feature = "serde")]
     impl<'de> Deserialize<'de> for G2Prepared {
         fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
         where
@@ -858,7 +1177,7 @@ mod dusk {
     }
 
     #[test]
-    #[cfg(feature = "serde_req")]
+    #[cfg(feature = "serde")]
     fn g2_prepared_serde_roundtrip() {
         use bincode;
 
diff --git a/src/scalar.rs b/src/scalar.rs
index c145b793..3bc9967e 100644
--- a/src/scalar.rs
+++ b/src/scalar.rs
@@ -1,16 +1,18 @@
 //! This module provides an implementation of the BLS12-381 scalar field $\mathbb{F}_q$
 //! where `q = 0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001`
 
-use core::borrow::Borrow;
-use core::cmp::{Ord, Ordering, PartialOrd};
-use core::convert::TryFrom;
-use core::iter::{Product, Sum};
-use core::ops::{Add, AddAssign, BitAnd, BitXor, Mul, MulAssign, Neg, Sub, SubAssign};
-use rand_core::{CryptoRng, RngCore};
-
-use dusk_bytes::{Error as BytesError, HexDebug, Serializable};
+use core::fmt;
+use core::ops::{Add, AddAssign, Mul, MulAssign, Neg, Sub, SubAssign};
+use rand_core::RngCore;
+
+use ff::{Field, PrimeField};
 use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption};
 
+use dusk_bytes::{Error as BytesError, Serializable};
+
+#[cfg(feature = "bits")]
+use ff::{FieldBits, PrimeFieldBits};
+
 use crate::util::{adc, mac, sbb};
 
 #[cfg(feature = "rkyv-impl")]
@@ -23,11 +25,31 @@ use rkyv::{Archive, Deserialize as RkyvDeserialize, Serialize as RkyvSerialize};
 // 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, Hash, HexDebug)]
-#[cfg_attr(feature = "rkyv-impl", derive(Archive, RkyvSerialize, RkyvDeserialize))]
-#[cfg_attr(feature = "rkyv-impl", archive_attr(derive(CheckBytes)))]
+#[derive(Clone, Copy, Eq, Hash)]
+#[cfg_attr(
+    feature = "rkyv-impl",
+    derive(Archive, RkyvSerialize, RkyvDeserialize),
+    archive_attr(derive(CheckBytes))
+)]
 pub struct Scalar(pub [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 fmt::Display for Scalar {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        write!(f, "{:?}", self)
+    }
+}
+
 impl From<u64> for Scalar {
     fn from(val: u64) -> Scalar {
         Scalar([val, 0, 0, 0]) * R2
@@ -46,7 +68,7 @@ impl ConstantTimeEq for Scalar {
 impl PartialEq for Scalar {
     #[inline]
     fn eq(&self, other: &Self) -> bool {
-        self.ct_eq(other).unwrap_u8() == 1
+        bool::from(self.ct_eq(other))
     }
 }
 
@@ -63,11 +85,35 @@ impl ConditionallySelectable for Scalar {
 
 /// Constant representing the modulus
 /// q = 0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001
-pub const MODULUS: Scalar = Scalar([
-    0xffffffff00000001,
-    0x53bda402fffe5bfe,
-    0x3339d80809a1d805,
-    0x73eda753299d7d48,
+const MODULUS: Scalar = Scalar([
+    0xffff_ffff_0000_0001,
+    0x53bd_a402_fffe_5bfe,
+    0x3339_d808_09a1_d805,
+    0x73ed_a753_299d_7d48,
+]);
+
+/// The modulus as u32 limbs.
+#[cfg(all(feature = "bits", not(target_pointer_width = "64")))]
+const MODULUS_LIMBS_32: [u32; 8] = [
+    0x0000_0001,
+    0xffff_ffff,
+    0xfffe_5bfe,
+    0x53bd_a402,
+    0x09a1_d805,
+    0x3339_d808,
+    0x299d_7d48,
+    0x73ed_a753,
+];
+
+// The number of bits needed to represent the modulus.
+const MODULUS_BITS: u32 = 255;
+
+// GENERATOR = 7 (multiplicative generator of r-1 order, that is also quadratic nonresidue)
+const GENERATOR: Scalar = Scalar([
+    0x0000_000e_ffff_fff1,
+    0x17e3_63d3_0018_9c0f,
+    0xff9c_5787_6f84_57b0,
+    0x3513_3220_8fc5_a8c4,
 ]);
 
 impl<'a> Neg for &'a Scalar {
@@ -145,7 +191,14 @@ const R3: Scalar = Scalar([
     0x6e2a_5bb9_c8db_33e9,
 ]);
 
-/// Two adacity
+/// 2^-1
+const TWO_INV: Scalar = Scalar([
+    0x0000_0000_ffff_ffff,
+    0xac42_5bfd_0001_a401,
+    0xccc6_27f7_f65e_27fa,
+    0x0c12_58ac_d662_82b7,
+]);
+
 /// 2^TWO_ADACITY * t = MODULUS - 1 with t odd
 pub const TWO_ADACITY: u32 = 32;
 
@@ -163,6 +216,23 @@ pub const ROOT_OF_UNITY: Scalar = Scalar([
     0x5bf3_adda_19e9_b27b,
 ]);
 
+/// ROOT_OF_UNITY^-1
+const ROOT_OF_UNITY_INV: Scalar = Scalar([
+    0x4256_481a_dcf3_219a,
+    0x45f3_7b7f_96b6_cad3,
+    0xf9c3_f1d7_5f7a_3b27,
+    0x2d2f_c049_658a_fd43,
+]);
+
+/// GENERATOR^{2^s} where t * 2^s + 1 = q with t odd.
+/// In other words, this is a t root of unity.
+const DELTA: Scalar = Scalar([
+    0x70e3_10d3_d146_f96a,
+    0x4b64_c089_19e2_99e6,
+    0x51e1_1418_6a8b_970d,
+    0x6185_d066_27c0_67cb,
+]);
+
 impl Default for Scalar {
     #[inline]
     fn default() -> Self {
@@ -170,6 +240,9 @@ impl Default for Scalar {
     }
 }
 
+#[cfg(feature = "zeroize")]
+impl zeroize::DefaultIsZeroes for Scalar {}
+
 impl Scalar {
     /// Returns zero, the additive identity.
     #[inline]
@@ -190,6 +263,50 @@ impl Scalar {
         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 {
@@ -534,36 +651,194 @@ impl Scalar {
     }
 }
 
-impl<'a> From<&'a Scalar> for [u8; Scalar::SIZE] {
-    fn from(value: &'a Scalar) -> [u8; Scalar::SIZE] {
+impl From<Scalar> for [u8; 32] {
+    fn from(value: Scalar) -> [u8; 32] {
+        value.to_bytes()
+    }
+}
+
+impl<'a> From<&'a Scalar> for [u8; 32] {
+    fn from(value: &'a Scalar) -> [u8; 32] {
         value.to_bytes()
     }
 }
 
-impl<T> Sum<T> for Scalar
+impl Field for Scalar {
+    const ZERO: Self = Self::zero();
+    const ONE: Self = Self::one();
+
+    fn random(mut rng: impl RngCore) -> Self {
+        let mut buf = [0; 64];
+        rng.fill_bytes(&mut buf);
+        Self::from_bytes_wide(&buf)
+    }
+
+    #[must_use]
+    fn square(&self) -> Self {
+        self.square()
+    }
+
+    #[must_use]
+    fn double(&self) -> Self {
+        self.double()
+    }
+
+    fn invert(&self) -> CtOption<Self> {
+        self.invert_ct()
+    }
+
+    fn sqrt_ratio(num: &Self, div: &Self) -> (Choice, Self) {
+        ff::helpers::sqrt_ratio_generic(num, div)
+    }
+
+    fn sqrt(&self) -> CtOption<Self> {
+        // (t - 1) // 2 = 6104339283789297388802252303364915521546564123189034618274734669823
+        ff::helpers::sqrt_tonelli_shanks(
+            self,
+            [
+                0x7fff_2dff_7fff_ffff,
+                0x04d0_ec02_a9de_d201,
+                0x94ce_bea4_199c_ec04,
+                0x0000_0000_39f6_d3a9,
+            ],
+        )
+    }
+
+    fn is_zero_vartime(&self) -> bool {
+        self.0 == Self::zero().0
+    }
+}
+
+impl PrimeField for Scalar {
+    type Repr = [u8; 32];
+
+    fn from_repr(r: Self::Repr) -> CtOption<Self> {
+        Self::from_bytes(&r)
+    }
+
+    fn to_repr(&self) -> Self::Repr {
+        self.to_bytes()
+    }
+
+    fn is_odd(&self) -> Choice {
+        Choice::from(self.to_bytes()[0] & 1)
+    }
+
+    const MODULUS: &'static str =
+        "0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001";
+    const NUM_BITS: u32 = MODULUS_BITS;
+    const CAPACITY: u32 = Self::NUM_BITS - 1;
+    const TWO_INV: Self = TWO_INV;
+    const MULTIPLICATIVE_GENERATOR: Self = GENERATOR;
+    const S: u32 = TWO_ADACITY;
+    const ROOT_OF_UNITY: Self = ROOT_OF_UNITY;
+    const ROOT_OF_UNITY_INV: Self = ROOT_OF_UNITY_INV;
+    const DELTA: Self = DELTA;
+}
+
+#[cfg(all(feature = "bits", not(target_pointer_width = "64")))]
+type ReprBits = [u32; 8];
+
+#[cfg(all(feature = "bits", target_pointer_width = "64"))]
+type ReprBits = [u64; 4];
+
+#[cfg(feature = "bits")]
+impl PrimeFieldBits for Scalar {
+    type ReprBits = ReprBits;
+
+    fn to_le_bits(&self) -> FieldBits<Self::ReprBits> {
+        let bytes = self.to_bytes();
+
+        #[cfg(not(target_pointer_width = "64"))]
+        let limbs = [
+            u32::from_le_bytes(bytes[0..4].try_into().unwrap()),
+            u32::from_le_bytes(bytes[4..8].try_into().unwrap()),
+            u32::from_le_bytes(bytes[8..12].try_into().unwrap()),
+            u32::from_le_bytes(bytes[12..16].try_into().unwrap()),
+            u32::from_le_bytes(bytes[16..20].try_into().unwrap()),
+            u32::from_le_bytes(bytes[20..24].try_into().unwrap()),
+            u32::from_le_bytes(bytes[24..28].try_into().unwrap()),
+            u32::from_le_bytes(bytes[28..32].try_into().unwrap()),
+        ];
+
+        #[cfg(target_pointer_width = "64")]
+        let limbs = [
+            u64::from_le_bytes(bytes[0..8].try_into().unwrap()),
+            u64::from_le_bytes(bytes[8..16].try_into().unwrap()),
+            u64::from_le_bytes(bytes[16..24].try_into().unwrap()),
+            u64::from_le_bytes(bytes[24..32].try_into().unwrap()),
+        ];
+
+        FieldBits::new(limbs)
+    }
+
+    fn char_le_bits() -> FieldBits<Self::ReprBits> {
+        #[cfg(not(target_pointer_width = "64"))]
+        {
+            FieldBits::new(MODULUS_LIMBS_32)
+        }
+
+        #[cfg(target_pointer_width = "64")]
+        FieldBits::new(MODULUS.0)
+    }
+}
+
+impl<T> core::iter::Sum<T> for Scalar
 where
-    T: Borrow<Scalar>,
+    T: core::borrow::Borrow<Scalar>,
 {
     fn sum<I>(iter: I) -> Self
     where
         I: Iterator<Item = T>,
     {
-        iter.fold(Scalar::zero(), |acc, item| acc + item.borrow())
+        iter.fold(Self::zero(), |acc, item| acc + item.borrow())
     }
 }
 
-impl<T> Product<T> for Scalar
+impl<T> core::iter::Product<T> for Scalar
 where
-    T: Borrow<Scalar>,
+    T: core::borrow::Borrow<Scalar>,
 {
     fn product<I>(iter: I) -> Self
     where
         I: Iterator<Item = T>,
     {
-        iter.fold(Scalar::one(), |acc, item| acc * item.borrow())
+        iter.fold(Self::one(), |acc, item| acc * item.borrow())
     }
 }
 
+#[test]
+fn test_constants() {
+    assert_eq!(
+        Scalar::MODULUS,
+        "0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001",
+    );
+
+    assert_eq!(Scalar::from(2) * Scalar::TWO_INV, Scalar::ONE);
+
+    assert_eq!(
+        Scalar::ROOT_OF_UNITY * Scalar::ROOT_OF_UNITY_INV,
+        Scalar::ONE,
+    );
+
+    // ROOT_OF_UNITY^{2^s} mod m == 1
+    assert_eq!(
+        Scalar::ROOT_OF_UNITY.pow(&[1u64 << Scalar::S, 0, 0, 0]),
+        Scalar::ONE,
+    );
+
+    // DELTA^{t} mod m == 1
+    assert_eq!(
+        Scalar::DELTA.pow(&[
+            0xfffe_5bfe_ffff_ffff,
+            0x09a1_d805_53bd_a402,
+            0x299d_7d48_3339_d808,
+            0x0000_0000_73ed_a753,
+        ]),
+        Scalar::ONE,
+    );
+}
+
 #[test]
 fn test_inv() {
     // Compute -(q^{-1} mod 2^64) mod 2^64 by exponentiating
@@ -579,24 +854,19 @@ fn test_inv() {
     assert_eq!(inv, INV);
 }
 
-#[cfg(feature = "std")]
 #[test]
 fn test_debug() {
     assert_eq!(
         format!("{:?}", Scalar::zero()),
-        "0000000000000000000000000000000000000000000000000000000000000000"
+        "0x0000000000000000000000000000000000000000000000000000000000000000"
     );
     assert_eq!(
         format!("{:?}", Scalar::one()),
-        "0100000000000000000000000000000000000000000000000000000000000000"
+        "0x0000000000000000000000000000000000000000000000000000000000000001"
     );
     assert_eq!(
         format!("{:?}", R2),
-        "feffffff0100000002480300fab78458f54fbcecef4f8c996f05c5ac59b12418"
-    );
-    assert_eq!(
-        format!("{:#x}", R2),
-        "0xfeffffff0100000002480300fab78458f54fbcecef4f8c996f05c5ac59b12418"
+        "0x1824b159acc5056f998c4fefecbc4ff55884b7fa0003480200000001fffffffe"
     );
 }
 
@@ -675,35 +945,45 @@ fn test_from_bytes() {
     );
 
     // -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_ok());
+    assert!(bool::from(
+        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()
+    ));
 
     // 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_err());
+    assert!(bool::from(
+        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()
+    ));
 
     // 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_err());
-    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_err());
-    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_err());
+    assert!(bool::from(
+        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()
+    ));
+    assert!(bool::from(
+        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()
+    ));
+    assert!(bool::from(
+        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()
+    ));
 }
 
 #[test]
@@ -735,7 +1015,7 @@ fn test_from_u512_r2() {
 
 #[test]
 fn test_from_u512_max() {
-    let max_u64 = 0xffffffffffffffff;
+    let max_u64 = 0xffff_ffff_ffff_ffff;
     assert_eq!(
         R3 - R,
         Scalar::from_u512([max_u64, max_u64, max_u64, max_u64, max_u64, max_u64, max_u64, max_u64])
@@ -770,10 +1050,10 @@ fn test_from_bytes_wide_negative_one() {
 fn test_from_bytes_wide_maximum() {
     assert_eq!(
         Scalar([
-            0xc62c1805439b73b1,
-            0xc2b9551e8ced218e,
-            0xda44ec81daf9a422,
-            0x5605aa601c162e79
+            0xc62c_1805_439b_73b1,
+            0xc2b9_551e_8ced_218e,
+            0xda44_ec81_daf9_a422,
+            0x5605_aa60_1c16_2e79,
         ]),
         Scalar::from_bytes_wide(&[0xff; 64])
     );
@@ -904,7 +1184,7 @@ fn test_squaring() {
 
 #[test]
 fn test_inversion() {
-    assert_eq!(Scalar::zero().invert(), None);
+    assert!(bool::from(Scalar::zero().invert().is_none()));
     assert_eq!(Scalar::one().invert().unwrap(), Scalar::one());
     assert_eq!((-&Scalar::one()).invert().unwrap(), -&Scalar::one());
 
@@ -954,17 +1234,17 @@ fn test_sqrt() {
     }
 
     let mut square = Scalar([
-        0x46cd85a5f273077e,
-        0x1d30c47dd68fc735,
-        0x77f656f60beca0eb,
-        0x494aa01bdf32468d,
+        0x46cd_85a5_f273_077e,
+        0x1d30_c47d_d68f_c735,
+        0x77f6_56f6_0bec_a0eb,
+        0x494a_a01b_df32_468d,
     ]);
 
     let mut none_count = 0;
 
     for _ in 0..100 {
         let square_root = square.sqrt();
-        if square_root.is_none().unwrap_u8() == 1 {
+        if bool::from(square_root.is_none()) {
             none_count += 1;
         } else {
             assert_eq!(square_root.unwrap() * square_root.unwrap(), square);
@@ -979,12 +1259,12 @@ fn test_sqrt() {
 fn test_from_raw() {
     assert_eq!(
         Scalar::from_raw([
-            0x1fffffffd,
-            0x5884b7fa00034802,
-            0x998c4fefecbc4ff5,
-            0x1824b159acc5056f
+            0x0001_ffff_fffd,
+            0x5884_b7fa_0003_4802,
+            0x998c_4fef_ecbc_4ff5,
+            0x1824_b159_acc5_056f,
         ]),
-        Scalar::from_raw([0xffffffffffffffff; 4])
+        Scalar::from_raw([0xffff_ffff_ffff_ffff; 4])
     );
 
     assert_eq!(Scalar::from_raw(MODULUS.0), Scalar::zero());
@@ -995,18 +1275,37 @@ fn test_from_raw() {
 #[test]
 fn test_double() {
     let a = Scalar::from_raw([
-        0x1fff3231233ffffd,
-        0x4884b7fa00034802,
-        0x998c4fefecbc4ff3,
-        0x1824b159acc50562,
+        0x1fff_3231_233f_fffd,
+        0x4884_b7fa_0003_4802,
+        0x998c_4fef_ecbc_4ff3,
+        0x1824_b159_acc5_0562,
     ]);
     assert_eq!(a.double(), a + a);
 }
 
+#[cfg(feature = "zeroize")]
+#[test]
+fn test_zeroize() {
+    use zeroize::Zeroize;
+
+    let mut a = Scalar::from_raw([
+        0x1fff_3231_233f_fffd,
+        0x4884_b7fa_0003_4802,
+        0x998c_4fef_ecbc_4ff3,
+        0x1824_b159_acc5_0562,
+    ]);
+    a.zeroize();
+    assert!(bool::from(a.is_zero()));
+}
+
 mod dusk {
     use super::*;
 
-    #[cfg(feature = "serde_req")]
+    use core::cmp::{Ord, Ordering, PartialOrd};
+    use core::convert::TryFrom;
+    use core::ops::{BitAnd, BitXor};
+
+    #[cfg(feature = "serde")]
     use serde::{de::Visitor, Deserialize, Deserializer, Serialize, Serializer};
 
     impl PartialOrd for Scalar {
@@ -1084,7 +1383,7 @@ mod dusk {
         }
     }
 
-    #[cfg(feature = "serde_req")]
+    #[cfg(feature = "serde")]
     impl Serialize for Scalar {
         fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
         where
@@ -1099,7 +1398,7 @@ mod dusk {
         }
     }
 
-    #[cfg(feature = "serde_req")]
+    #[cfg(feature = "serde")]
     impl<'de> Deserialize<'de> for Scalar {
         fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
         where
@@ -1111,7 +1410,7 @@ mod dusk {
                 type Value = Scalar;
 
                 fn expecting(&self, formatter: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
-                    formatter.write_str("a 32-byte cannonical Scalar from Bls12_381")
+                    formatter.write_str("a 32-byte canonical Scalar from Bls12_381")
                 }
 
                 fn visit_seq<A>(self, mut seq: A) -> Result<Scalar, A::Error>
@@ -1126,7 +1425,7 @@ mod dusk {
                             .ok_or(serde::de::Error::invalid_length(i, &"expected 32 bytes"))?;
                     }
 
-                    Scalar::from_bytes(&bytes).map_err(|_| {
+                    <Scalar as Serializable<32>>::from_bytes(&bytes).map_err(|_| {
                         serde::de::Error::custom(&"scalar was not canonically encoded")
                     })
                 }
@@ -1227,74 +1526,6 @@ mod dusk {
             res
         }
 
-        /// Generate a valid Scalar choosen uniformly using user-
-        /// provided rng.
-        ///
-        /// By `rng` we mean any Rng that implements: `Rng` + `CryptoRng`.
-        pub fn random<T>(rand: &mut T) -> Scalar
-        where
-            T: RngCore + CryptoRng,
-        {
-            let mut bytes = [0u8; Self::SIZE];
-            rand.fill_bytes(&mut bytes);
-
-            // Ensure that the value is lower than `MODULUS`.
-            // Since modulus has 254-bits or less, we cut our bytes
-            // to get cannonical Scalars.
-            bytes[31] &= 0b0011_1111;
-
-            Scalar::from_bytes(&bytes).unwrap_or_default()
-        }
-
-        /// 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 = TWO_ADACITY;
-            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..=TWO_ADACITY).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.
-            )
-        }
-
         /// Reduces the scalar and returns it multiplied by the montgomery
         /// radix.
         pub fn reduce(&self) -> Scalar {
@@ -1381,7 +1612,7 @@ mod dusk {
     }
 
     #[test]
-    #[cfg(feature = "serde_req")]
+    #[cfg(feature = "serde")]
     fn serde_bincode_scalar_roundtrip() {
         use bincode;
         let scalar = -Scalar::from(3u64);
@@ -1434,19 +1665,4 @@ mod dusk {
             assert_eq!(Scalar::pow_of_2(i as u64), two.pow(&[i as u64, 0, 0, 0]));
         }
     }
-
-    #[test]
-    fn scalar_hex_serialization() {
-        use dusk_bytes::ParseHexStr;
-
-        let scalar = -Scalar::one();
-        let scalar_p = format!("{:x}", scalar);
-        let scalar_p = Scalar::from_hex_str(scalar_p.as_str()).unwrap();
-
-        assert_eq!(scalar, scalar_p);
-        assert!(Scalar::from_hex_str(
-            "00000000fffffffffe5bfeff02a4bd5305d8a10908d83933487d9d2953a7ed74"
-        )
-        .is_err());
-    }
 }


Test patch:
diff --git a/src/tests/mod.rs b/src/tests/mod.rs
index 39d108b1..daf4c6fa 100644
--- a/src/tests/mod.rs
+++ b/src/tests/mod.rs
@@ -41,6 +41,7 @@ fn g2_compressed_valid_test_vectors() {
 }
 
 #[test]
+#[cfg(all(feature = "alloc", feature = "pairing"))]
 fn test_pairing_result_against_relic() {
     /*
     Sent to me from Diego Aranha (author of RELIC library):