Repository: facebook/opaque-ke (language: rust)

Issue:
Version 0.6.1 (draft-irtf-cfrg-opaque-05) support for p256/SHA256
Hi All

Is it possible to make somehow version 0.6.1 implementing draft-irtf-cfrg-opaque-05 running for p256/SHA256 something like 
main [draft-irtf-cfrg-opaque-07]
  type OprfGroup = p256_::ProjectivePoint;
    type KeGroup = p256_::PublicKey;
    type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDH;
    type Hash = sha2::Sha256;
    type SlowHash = opaque_ke::slow_hash::NoOpHash;
or version 0.6.1 is only for Group = curve25519_dalek::ristretto::RistrettoPoint/Hash = sha2::Sha512


Discussion / hints:

Fix patch:
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index d4e28ce3..70f0a78d 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -13,13 +13,20 @@ jobs:
       fail-fast: false
       matrix:
         backend_feature:
-          - ristretto255_u64
-          - ristretto255_u32
-          - p256
-          - x25519_u64,ristretto255_u64
+          - --features ristretto255_u64,ristretto255_voprf
+          - --features ristretto255_u32,ristretto255_voprf
+          - 
+          - --features x25519_u64,ristretto255_u64,ristretto255_voprf
+          - --features x25519_u32,ristretto255_u32,ristretto255_voprf
+          - --features x25519_u64
+          - --features x25519_u32
+        frontend_feature:
+          - 
+          - --features slow-hash
+          - --features serde
         toolchain:
           - stable
-          - 1.56.1
+          - 1.57.0
     name: test
     steps:
       - name: Checkout sources
@@ -36,13 +43,13 @@ jobs:
         uses: actions-rs/cargo@v1
         with:
           command: test
-          args: --no-default-features --features ${{ matrix.backend_feature }}
+          args: --no-default-features ${{ matrix.backend_feature }} ${{ matrix.frontend_feature }}
 
       - name: Run cargo test with std
         uses: actions-rs/cargo@v1
         with:
           command: test
-          args: --no-default-features --features std --features ${{ matrix.backend_feature }}
+          args: --no-default-features --features std ${{ matrix.backend_feature }} ${{ matrix.frontend_feature }}
 
   cross-test:
     name: Test on ${{ matrix.target }} (using cross)
@@ -54,43 +61,21 @@ jobs:
           # 32-bit x86
           - i686-unknown-linux-gnu
         backend_feature:
-          - ristretto255_u64
-          - ristretto255_u32
-          - p256
-          - x25519_u64,ristretto255_u64
+          - --features ristretto255_u64,ristretto255_voprf
+          - --features ristretto255_u32,ristretto255_voprf
+          - 
+          - x25519_u64,ristretto255_u64,ristretto255_voprf
+          - x25519_u32,ristretto255_u64,ristretto255_voprf
+          - x25519_u64
+          - x25519_u32
     steps:
       - uses: actions/checkout@v2
       - uses: hecrj/setup-rust-action@v1
       - run: cargo install cross
       # Note: just use `cross` as you would `cargo`, but always
       # pass the `--target=${{ matrix.target }}` arg. (Yes, really).
-      - run: cross test --verbose --target=${{ matrix.target }} --no-default-features --features ${{ matrix.backend_feature }}
-      - run: cross test --verbose --target=${{ matrix.target }} --no-default-features --features std --features ${{ matrix.backend_feature }}
-
-  feature-test:
-    name: Test on ${{ matrix.target }} with ${{ matrix.frontend_feature }}
-    runs-on: ubuntu-latest
-    strategy:
-      fail-fast: false
-      matrix:
-        backend_feature:
-          - ristretto255_u64
-          - ristretto255_u32
-          - p256
-          - ristretto255_u64,p256
-          - x25519_u64,ristretto255_u64
-          - x25519_u32,ristretto255_u32
-          - x25519_u64,p256
-          - x25519_u32,p256
-          - x25519_u64,ristretto255_u64,p256
-        frontend_feature:
-          - slow-hash
-          - serde
-    steps:
-      - uses: actions/checkout@v2
-      - uses: hecrj/setup-rust-action@v1
-      - run: cargo test --verbose --features ${{ matrix.frontend_feature }} --no-default-features --features ${{ matrix.backend_feature }}
-      - run: cargo test --verbose --features ${{ matrix.frontend_feature }},std --no-default-features --features ${{ matrix.backend_feature }}
+      - run: cross test --verbose --target=${{ matrix.target }} --no-default-features ${{ matrix.backend_feature }}
+      - run: cross test --verbose --target=${{ matrix.target }} --no-default-features --features std ${{ matrix.backend_feature }}
 
   simple-login-test:
     runs-on: ubuntu-latest
@@ -99,7 +84,7 @@ jobs:
       matrix:
         toolchain:
           - stable
-          - 1.56.1
+          - 1.57.0
     name: test simple_login command-line example
     steps:
       - name: install expect
@@ -122,7 +107,7 @@ jobs:
       matrix:
         toolchain:
           - stable
-          - 1.56.1
+          - 1.57.0
     name: test digital_locker command-line example
     steps:
       - name: install expect
@@ -150,10 +135,13 @@ jobs:
           # for any no_std target
           - thumbv6m-none-eabi
         backend_feature:
-          - ristretto255_u64
-          - ristretto255_u32
-          - p256
-          - x25519_u64,ristretto255_u64
+          - ristretto255_u64,ristretto255_voprf
+          - ristretto255_u32,ristretto255_voprf
+          - 
+          - x25519_u64,ristretto255_u64,ristretto255_voprf
+          - x25519_u32,ristretto255_u32,ristretto255_voprf
+          - x25519_u64
+          - x25519_u32
         frontend_feature:
           - slow-hash
           - serde
@@ -170,10 +158,13 @@ jobs:
       fail-fast: false
       matrix:
         backend_feature:
-          - ristretto255_u64
-          - ristretto255_u32
-          - p256
-          - x25519_u64,ristretto255_u64
+          - --features ristretto255_u64,ristretto255_voprf
+          - --features ristretto255_u32,ristretto255_voprf
+          - 
+          - --features x25519_u64,ristretto255_u64,ristretto255_voprf
+          - --features x25519_u32,ristretto255_u32,ristretto255_voprf
+          - --features x25519_u32
+          - --features x25519_u32
     steps:
       - name: Checkout sources
         uses: actions/checkout@v2
@@ -189,7 +180,7 @@ jobs:
         uses: actions-rs/cargo@v1
         with:
           command: bench
-          args: --no-default-features --features ${{ matrix.backend_feature }} --no-run
+          args: --no-default-features ${{ matrix.backend_feature }} --no-run
 
   clippy:
     name: cargo clippy
@@ -210,7 +201,7 @@ jobs:
         uses: actions-rs/cargo@v1
         with:
           command: clippy
-          args: --all-targets -- -D warnings
+          args: --all-targets --features slow-hash,std,x25519_u64 -- -D warnings
 
       - name: Run cargo doc
         uses: actions-rs/cargo@v1
@@ -218,7 +209,7 @@ jobs:
           RUSTDOCFLAGS: -D warnings
         with:
           command: doc
-          args: --no-deps --document-private-items --features p256,slow-hash,std
+          args: --no-deps --document-private-items --features slow-hash,std,x25519_u64
 
   format:
     name: cargo fmt
diff --git a/Cargo.toml b/Cargo.toml
old mode 100755
new mode 100644
index 672707dc..6899371d
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -8,47 +8,27 @@ license = "Apache-2.0 OR MIT"
 name = "opaque-ke"
 readme = "README.md"
 repository = "https://github.com/novifinancial/opaque-ke"
-rust-version = "1.56"
+rust-version = "1.57"
 version = "2.0.0-pre.1"
 
 [features]
-default = ["ristretto255_u64", "serde"]
-p256 = ["p256_", "voprf/p256"]
-ristretto255 = []
-ristretto255_fiat_u32 = [
-  "curve25519-dalek/fiat_u32_backend",
-  "ristretto255",
-  "voprf/ristretto255_fiat_u32",
-]
-ristretto255_fiat_u64 = [
-  "curve25519-dalek/fiat_u64_backend",
-  "ristretto255",
-  "voprf/ristretto255_fiat_u64",
-]
-ristretto255_simd = [
-  "curve25519-dalek/simd_backend",
-  "ristretto255",
-  "voprf/ristretto255_simd",
-]
-ristretto255_u32 = [
-  "curve25519-dalek/u32_backend",
-  "ristretto255",
-  "voprf/ristretto255_u32",
-]
-ristretto255_u64 = [
-  "curve25519-dalek/u64_backend",
-  "ristretto255",
-  "voprf/ristretto255_u64",
-]
+default = ["ristretto255_u64", "ristretto255_voprf", "serde"]
+ristretto255 = ["curve25519-dalek", "voprf/ristretto255"]
+ristretto255_fiat_u32 = ["curve25519-dalek/fiat_u32_backend", "ristretto255"]
+ristretto255_fiat_u64 = ["curve25519-dalek/fiat_u64_backend", "ristretto255"]
+ristretto255_simd = ["curve25519-dalek/simd_backend", "ristretto255"]
+ristretto255_u32 = ["curve25519-dalek/u32_backend", "ristretto255"]
+ristretto255_u64 = ["curve25519-dalek/u64_backend", "ristretto255"]
+ristretto255_voprf = ["ristretto255", "voprf/ristretto255-ciphersuite"]
 serde = ["serde_", "generic-array/serde", "voprf/serde"]
 slow-hash = ["argon2"]
 std = ["getrandom", "rand/std", "rand/std_rng", "voprf/std"]
-x25519 = []
+x25519 = ["curve25519-dalek-3"]
 x25519_fiat_u32 = ["x25519", "x25519-dalek/fiat_u32_backend"]
 x25519_fiat_u64 = ["x25519", "x25519-dalek/fiat_u64_backend"]
 # x25519-dalek isn't properly re-exposing `simd_backend`.
 x25519_simd = [
-  "curve25519-dalek/simd_backend",
+  "curve25519-dalek-3/simd_backend",
   "x25519",
   "x25519-dalek/nightly",
 ]
@@ -60,26 +40,23 @@ argon2 = { version = "0.3", default-features = false, features = [
   "alloc",
 ], optional = true }
 constant_time_eq = "0.1"
-curve25519-dalek = { version = "3", default-features = false, optional = true }
-derive-where = { version = "1.0.0-rc.1", features = ["zeroize"] }
+curve25519-dalek = { version = "=4.0.0-pre.1", default-features = false, optional = true }
+curve25519-dalek-3 = { version = "3", package = "curve25519-dalek", default-features = false, optional = true }
+derive-where = { version = "=1.0.0-rc.3", features = ["zeroize-on-drop"] }
 digest = "0.10"
 displaydoc = { version = "0.2", default-features = false }
+elliptic-curve = { version = "0.12.0-pre.1", features = ["hash2curve", "sec1"] }
 generic-array = "0.14"
 getrandom = { version = "0.2", optional = true }
 hkdf = "0.12"
 hmac = "0.12"
-p256_ = { package = "p256", version = "0.10", default-features = false, features = [
-  "arithmetic",
-], optional = true }
 rand = { version = "0.8", default-features = false }
 serde_ = { version = "1", package = "serde", default-features = false, features = [
   "derive",
 ], optional = true }
 subtle = { version = "2.3", default-features = false }
-voprf = { git = "https://github.com/novifinancial/voprf", rev = "55ef981a3f9a12eddd8c372ffdf51818011343ee", default-features = false, features = [
-  "danger",
-] }
-x25519-dalek = { version = "1", default-features = false, optional = true }
+voprf = { version = "0.3", default-features = false, features = ["danger"] }
+x25519-dalek = { version = "=2.0.0-pre.1", default-features = false, optional = true }
 zeroize = { version = "1", features = ["zeroize_derive"] }
 
 [target.'cfg(target_arch = "wasm32")'.dependencies]
@@ -93,7 +70,12 @@ criterion = "0.3"
 hex = "0.4"
 json = "0.12"
 lazy_static = "1"
+p256 = { version = "=0.11.0-pre.0", default-features = false, features = [
+  "hash2curve",
+  "voprf",
+] }
 proptest = "1"
+rand = "0.8"
 regex = "1"
 rustyline = "9"
 serde_json = "1"
@@ -102,3 +84,14 @@ sha2 = "0.10"
 [[bench]]
 harness = false
 name = "opaque"
+
+[package.metadata.docs.rs]
+features = ["std", "slow-hash", "x25519_u64"]
+targets = []
+
+[patch.crates-io]
+chacha20 = { git = "https://github.com/RustCrypto/stream-ciphers" }
+chacha20poly1305 = { git = "https://github.com/khonsulabs/aeads", branch = "update-dependencies" }
+derive-where = { git = "https://github.com/ModProg/derive-where" }
+poly1305 = { git = "https://github.com/RustCrypto/universal-hashes" }
+voprf = { git = "https://github.com/khonsulabs/voprf", branch = "v08" }
diff --git a/README.md b/README.md
index 6793ee61..9465a5d3 100644
--- a/README.md
+++ b/README.md
@@ -27,7 +27,7 @@ opaque-ke = "0.6.0"
 
 ### Minimum Supported Rust Version
 
-Rust **1.56** or higher.
+Rust **1.57** or higher.
 
 Audit
 -----
diff --git a/benches/opaque.rs b/benches/opaque.rs
index 200a5717..d5ed20eb 100644
--- a/benches/opaque.rs
+++ b/benches/opaque.rs
@@ -20,26 +20,24 @@ static SUFFIX: &str = "ristretto255_u32";
 static SUFFIX: &str = "ristretto255_fiat_u64";
 #[cfg(feature = "ristretto255_fiat_u32")]
 static SUFFIX: &str = "ristretto255_fiat_u32";
-#[cfg(all(not(feature = "ristretto255"), feature = "p256"))]
+#[cfg(all(not(feature = "ristretto255")))]
 static SUFFIX: &str = "p256";
 
 struct Default;
 
 #[cfg(feature = "ristretto255")]
 impl CipherSuite for Default {
-    type OprfGroup = curve25519_dalek::ristretto::RistrettoPoint;
-    type KeGroup = curve25519_dalek::ristretto::RistrettoPoint;
+    type OprfGroup = opaque_ke::Ristretto255;
+    type KeGroup = opaque_ke::Ristretto255;
     type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDH;
-    type Hash = sha2::Sha512;
     type SlowHash = opaque_ke::slow_hash::NoOpHash;
 }
 
 #[cfg(not(feature = "ristretto255"))]
 impl CipherSuite for Default {
-    type OprfGroup = p256_::ProjectivePoint;
-    type KeGroup = p256_::PublicKey;
+    type OprfGroup = p256::NistP256;
+    type KeGroup = p256::NistP256;
     type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDH;
-    type Hash = sha2::Sha256;
     type SlowHash = opaque_ke::slow_hash::NoOpHash;
 }
 
diff --git a/examples/digital_locker.rs b/examples/digital_locker.rs
index c04effc0..c7b8ba1d 100644
--- a/examples/digital_locker.rs
+++ b/examples/digital_locker.rs
@@ -49,19 +49,17 @@ struct Default;
 
 #[cfg(feature = "ristretto255")]
 impl CipherSuite for Default {
-    type OprfGroup = curve25519_dalek::ristretto::RistrettoPoint;
-    type KeGroup = curve25519_dalek::ristretto::RistrettoPoint;
+    type OprfGroup = opaque_ke::Ristretto255;
+    type KeGroup = opaque_ke::Ristretto255;
     type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDH;
-    type Hash = sha2::Sha512;
     type SlowHash = opaque_ke::slow_hash::NoOpHash;
 }
 
 #[cfg(not(feature = "ristretto255"))]
 impl CipherSuite for Default {
-    type OprfGroup = p256_::ProjectivePoint;
-    type KeGroup = p256_::PublicKey;
+    type OprfGroup = p256::NistP256;
+    type KeGroup = p256::NistP256;
     type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDH;
-    type Hash = sha2::Sha256;
     type SlowHash = opaque_ke::slow_hash::NoOpHash;
 }
 
diff --git a/examples/simple_login.rs b/examples/simple_login.rs
index 4cb50220..77c060ba 100644
--- a/examples/simple_login.rs
+++ b/examples/simple_login.rs
@@ -43,19 +43,17 @@ struct Default;
 
 #[cfg(feature = "ristretto255")]
 impl CipherSuite for Default {
-    type OprfGroup = curve25519_dalek::ristretto::RistrettoPoint;
-    type KeGroup = curve25519_dalek::ristretto::RistrettoPoint;
+    type OprfGroup = opaque_ke::Ristretto255;
+    type KeGroup = opaque_ke::Ristretto255;
     type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDH;
-    type Hash = sha2::Sha512;
     type SlowHash = opaque_ke::slow_hash::NoOpHash;
 }
 
 #[cfg(not(feature = "ristretto255"))]
 impl CipherSuite for Default {
-    type OprfGroup = p256_::ProjectivePoint;
-    type KeGroup = p256_::PublicKey;
+    type OprfGroup = p256::NistP256;
+    type KeGroup = p256::NistP256;
     type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDH;
-    type Hash = sha2::Sha256;
     type SlowHash = opaque_ke::slow_hash::NoOpHash;
 }
 
diff --git a/scripts/simple_login.exp b/scripts/simple_login.exp
old mode 100755
new mode 100644
diff --git a/src/ciphersuite.rs b/src/ciphersuite.rs
index 2bbe8687..16e0a4ac 100644
--- a/src/ciphersuite.rs
+++ b/src/ciphersuite.rs
@@ -9,8 +9,8 @@
 //! OPAQUE
 
 use digest::core_api::{BlockSizeUser, CoreProxy};
-use generic_array::typenum::{IsLess, Le, NonZero, U256};
-use voprf::Group as OprfGroup;
+use digest::OutputSizeUser;
+use generic_array::typenum::{IsLess, IsLessOrEqual, Le, NonZero, U256};
 
 use crate::hash::{Hash, ProxyHash};
 use crate::key_exchange::group::KeGroup;
@@ -28,21 +28,24 @@ use crate::slow_hash::SlowHash;
 /// * `SlowHash`: A slow hashing function, typically used for password hashing
 pub trait CipherSuite
 where
-    <Self::Hash as CoreProxy>::Core: ProxyHash,
-    <<Self::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
-    Le<<<Self::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
+    <OprfHash<Self> as OutputSizeUser>::OutputSize:
+        IsLess<U256> + IsLessOrEqual<<OprfHash<Self> as BlockSizeUser>::BlockSize>,
+    OprfHash<Self>: Hash,
+    <OprfHash<Self> as CoreProxy>::Core: ProxyHash,
+    <<OprfHash<Self> as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
+    Le<<<OprfHash<Self> as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
 {
     /// A finite cyclic group along with a point representation along with an
     /// extension trait PasswordToCurve that allows some customization on how to
     /// hash a password to a curve point. See `group::Group`.
-    type OprfGroup: OprfGroup;
+    type OprfGroup: voprf::CipherSuite;
     /// A `Group` used for the `KeyExchange`.
     type KeGroup: KeGroup;
     /// A key exchange protocol
-    type KeyExchange: KeyExchange<Self::Hash, Self::KeGroup>;
-    /// The main hash function use (for HKDF computations and hashing
-    /// transcripts)
-    type Hash: Hash;
+    type KeyExchange: KeyExchange<OprfHash<Self>, Self::KeGroup>;
     /// A slow hashing function, typically used for password hashing
-    type SlowHash: SlowHash<Self::Hash>;
+    type SlowHash: SlowHash;
 }
+
+pub(crate) type OprfGroup<CS> = <<CS as CipherSuite>::OprfGroup as voprf::CipherSuite>::Group;
+pub(crate) type OprfHash<CS> = <<CS as CipherSuite>::OprfGroup as voprf::CipherSuite>::Hash;
diff --git a/src/envelope.rs b/src/envelope.rs
old mode 100755
new mode 100644
index 31e7c7e9..468e924a
--- a/src/envelope.rs
+++ b/src/envelope.rs
@@ -8,19 +8,18 @@
 use core::convert::TryFrom;
 use core::ops::Add;
 
-use derive_where::DeriveWhere;
+use derive_where::derive_where;
 use digest::core_api::{BlockSizeUser, CoreProxy};
-use digest::Output;
+use digest::{Output, OutputSizeUser};
 use generic_array::sequence::Concat;
-use generic_array::typenum::{IsLess, Le, NonZero, Sum, Unsigned, U2, U256, U32};
+use generic_array::typenum::{IsLess, IsLessOrEqual, Le, NonZero, Sum, Unsigned, U2, U256, U32};
 use generic_array::{ArrayLength, GenericArray};
 use hkdf::Hkdf;
 use hmac::{Hmac, Mac};
 use rand::{CryptoRng, RngCore};
-use voprf::Group;
-use zeroize::Zeroize;
+use zeroize::{Zeroize, ZeroizeOnDrop};
 
-use crate::ciphersuite::CipherSuite;
+use crate::ciphersuite::{CipherSuite, OprfHash};
 use crate::errors::utils::check_slice_size;
 use crate::errors::{InternalError, ProtocolError};
 use crate::hash::{Hash, OutputSize, ProxyHash};
@@ -36,13 +35,18 @@ const STR_PRIVATE_KEY: [u8; 10] = *b"PrivateKey";
 const STR_OPAQUE_DERIVE_AUTH_KEY_PAIR: [u8; 24] = *b"OPAQUE-DeriveAuthKeyPair";
 type NonceLen = U32;
 
-#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Zeroize)]
-#[zeroize(drop)]
+#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, ZeroizeOnDrop)]
 pub(crate) enum InnerEnvelopeMode {
     Zero = 0,
     Internal = 1,
 }
 
+impl Zeroize for InnerEnvelopeMode {
+    fn zeroize(&mut self) {
+        *self = Self::Zero
+    }
+}
+
 impl TryFrom<u8> for InnerEnvelopeMode {
     type Error = ProtocolError;
     fn try_from(x: u8) -> Result<Self, Self::Error> {
@@ -61,17 +65,46 @@ impl TryFrom<u8> for InnerEnvelopeMode {
 /// The specification update has simplified this assumption by taking an
 /// XOR-based approach without compromising on security, and to avoid the
 /// confusion around the implementation of an RKR-secure encryption.
-#[derive(DeriveWhere)]
-#[derive_where(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Zeroize(drop))]
+#[derive_where(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
 pub(crate) struct Envelope<CS: CipherSuite>
 where
-    <CS::Hash as CoreProxy>::Core: ProxyHash,
-    <<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
-    Le<<<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
+    <OprfHash<CS> as OutputSizeUser>::OutputSize:
+        IsLess<U256> + IsLessOrEqual<<OprfHash<CS> as BlockSizeUser>::BlockSize>,
+    OprfHash<CS>: Hash,
+    <OprfHash<CS> as CoreProxy>::Core: ProxyHash,
+    <<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
+    Le<<<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
 {
-    mode: InnerEnvelopeMode,
+    pub(crate) mode: InnerEnvelopeMode,
     nonce: GenericArray<u8, NonceLen>,
-    hmac: Output<CS::Hash>,
+    hmac: Output<OprfHash<CS>>,
+}
+
+impl<CS: CipherSuite> Drop for Envelope<CS>
+where
+    <OprfHash<CS> as OutputSizeUser>::OutputSize:
+        IsLess<U256> + IsLessOrEqual<<OprfHash<CS> as BlockSizeUser>::BlockSize>,
+    OprfHash<CS>: Hash,
+    <OprfHash<CS> as CoreProxy>::Core: ProxyHash,
+    <<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
+    Le<<<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
+{
+    fn drop(&mut self) {
+        self.mode.zeroize();
+        self.nonce.zeroize();
+        self.hmac.zeroize();
+    }
+}
+
+impl<CS: CipherSuite> ZeroizeOnDrop for Envelope<CS>
+where
+    <OprfHash<CS> as OutputSizeUser>::OutputSize:
+        IsLess<U256> + IsLessOrEqual<<OprfHash<CS> as BlockSizeUser>::BlockSize>,
+    OprfHash<CS>: Hash,
+    <OprfHash<CS> as CoreProxy>::Core: ProxyHash,
+    <<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
+    Le<<<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
+{
 }
 
 // Note that this struct represents an envelope that has been "opened" with the
@@ -80,12 +113,15 @@ where
 // contents.
 pub(crate) struct OpenedEnvelope<'a, CS: CipherSuite>
 where
-    <CS::Hash as CoreProxy>::Core: ProxyHash,
-    <<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
-    Le<<<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
+    <OprfHash<CS> as OutputSizeUser>::OutputSize:
+        IsLess<U256> + IsLessOrEqual<<OprfHash<CS> as BlockSizeUser>::BlockSize>,
+    OprfHash<CS>: Hash,
+    <OprfHash<CS> as CoreProxy>::Core: ProxyHash,
+    <<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
+    Le<<<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
 {
     pub(crate) client_static_keypair: KeyPair<CS::KeGroup>,
-    pub(crate) export_key: Output<CS::Hash>,
+    pub(crate) export_key: Output<OprfHash<CS>>,
     pub(crate) id_u: Serialize<'a, U2, <CS::KeGroup as KeGroup>::PkLen>,
     pub(crate) id_s: Serialize<'a, U2, <CS::KeGroup as KeGroup>::PkLen>,
 }
@@ -100,31 +136,34 @@ where
 }
 
 #[cfg(not(test))]
-type SealRawResult<CS: CipherSuite> = (Envelope<CS>, Output<CS::Hash>);
+type SealRawResult<CS: CipherSuite> = (Envelope<CS>, Output<OprfHash<CS>>);
 #[cfg(test)]
-type SealRawResult<CS: CipherSuite> = (Envelope<CS>, Output<CS::Hash>, Output<CS::Hash>);
+type SealRawResult<CS: CipherSuite> = (Envelope<CS>, Output<OprfHash<CS>>, Output<OprfHash<CS>>);
 #[cfg(not(test))]
-type SealResult<CS: CipherSuite> = (Envelope<CS>, PublicKey<CS::KeGroup>, Output<CS::Hash>);
+type SealResult<CS: CipherSuite> = (Envelope<CS>, PublicKey<CS::KeGroup>, Output<OprfHash<CS>>);
 #[cfg(test)]
 type SealResult<CS: CipherSuite> = (
     Envelope<CS>,
     PublicKey<CS::KeGroup>,
-    Output<CS::Hash>,
-    Output<CS::Hash>,
+    Output<OprfHash<CS>>,
+    Output<OprfHash<CS>>,
 );
 
-pub(crate) type EnvelopeLen<CS: CipherSuite> = Sum<NonceLen, OutputSize<CS::Hash>>;
+pub(crate) type EnvelopeLen<CS: CipherSuite> = Sum<NonceLen, OutputSize<OprfHash<CS>>>;
 
 impl<CS: CipherSuite> Envelope<CS>
 where
-    <CS::Hash as CoreProxy>::Core: ProxyHash,
-    <<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
-    Le<<<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
+    <OprfHash<CS> as OutputSizeUser>::OutputSize:
+        IsLess<U256> + IsLessOrEqual<<OprfHash<CS> as BlockSizeUser>::BlockSize>,
+    OprfHash<CS>: Hash,
+    <OprfHash<CS> as CoreProxy>::Core: ProxyHash,
+    <<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
+    Le<<<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
 {
     #[allow(clippy::type_complexity)]
     pub(crate) fn seal<R: RngCore + CryptoRng>(
         rng: &mut R,
-        randomized_pwd_hasher: Hkdf<CS::Hash>,
+        randomized_pwd_hasher: Hkdf<OprfHash<CS>>,
         server_s_pk: &PublicKey<CS::KeGroup>,
         ids: Identifiers,
     ) -> Result<SealResult<CS>, ProtocolError> {
@@ -136,12 +175,13 @@ where
             build_inner_envelope_internal::<CS>(randomized_pwd_hasher.clone(), nonce)?,
         );
 
+        let server_s_pk_bytes = server_s_pk.to_bytes();
         let (id_u, id_s) = bytestrings_from_identifiers::<CS::KeGroup>(
             ids,
-            client_s_pk.to_arr(),
-            server_s_pk.to_arr(),
+            client_s_pk.to_bytes(),
+            server_s_pk_bytes.clone(),
         )?;
-        let aad = construct_aad(id_u.iter(), id_s.iter(), server_s_pk);
+        let aad = construct_aad(id_u.iter(), id_s.iter(), &server_s_pk_bytes);
 
         let result = Self::seal_raw(randomized_pwd_hasher, nonce, aad, mode)?;
         Ok((
@@ -157,13 +197,13 @@ where
     /// the aad field. Note that a new nonce is sampled for each call to seal.
     #[allow(clippy::type_complexity)]
     pub(crate) fn seal_raw<'a>(
-        randomized_pwd_hasher: Hkdf<CS::Hash>,
+        randomized_pwd_hasher: Hkdf<OprfHash<CS>>,
         nonce: GenericArray<u8, NonceLen>,
         aad: impl Iterator<Item = &'a [u8]>,
         mode: InnerEnvelopeMode,
     ) -> Result<SealRawResult<CS>, InternalError> {
-        let mut hmac_key = Output::<CS::Hash>::default();
-        let mut export_key = Output::<CS::Hash>::default();
+        let mut hmac_key = Output::<OprfHash<CS>>::default();
+        let mut export_key = Output::<OprfHash<CS>>::default();
 
         randomized_pwd_hasher
             .expand_multi_info(&[&nonce, &STR_AUTH_KEY], &mut hmac_key)
@@ -172,8 +212,8 @@ where
             .expand_multi_info(&[&nonce, &STR_EXPORT_KEY], &mut export_key)
             .map_err(|_| InternalError::HkdfError)?;
 
-        let mut hmac =
-            Hmac::<CS::Hash>::new_from_slice(&hmac_key).map_err(|_| InternalError::HmacError)?;
+        let mut hmac = Hmac::<OprfHash<CS>>::new_from_slice(&hmac_key)
+            .map_err(|_| InternalError::HmacError)?;
         hmac.update(&nonce);
         hmac.update_iter(aad);
 
@@ -193,7 +233,7 @@ where
 
     pub(crate) fn open<'a>(
         &self,
-        randomized_pwd_hasher: Hkdf<CS::Hash>,
+        randomized_pwd_hasher: Hkdf<OprfHash<CS>>,
         server_s_pk: PublicKey<CS::KeGroup>,
         optional_ids: Identifiers<'a>,
     ) -> Result<OpenedEnvelope<'a, CS>, ProtocolError> {
@@ -206,12 +246,13 @@ where
             }
         };
 
+        let server_s_pk_bytes = server_s_pk.to_bytes();
         let (id_u, id_s) = bytestrings_from_identifiers::<CS::KeGroup>(
             optional_ids,
-            client_static_keypair.public().to_arr(),
-            server_s_pk.to_arr(),
+            client_static_keypair.public().to_bytes(),
+            server_s_pk_bytes.clone(),
         )?;
-        let aad = construct_aad(id_u.iter(), id_s.iter(), &server_s_pk);
+        let aad = construct_aad(id_u.iter(), id_s.iter(), &server_s_pk_bytes);
 
         let opened = self.open_raw(randomized_pwd_hasher, aad)?;
 
@@ -227,11 +268,11 @@ where
     /// if the key and aad used to construct the envelope are the same.
     pub(crate) fn open_raw<'a>(
         &self,
-        randomized_pwd_hasher: Hkdf<CS::Hash>,
+        randomized_pwd_hasher: Hkdf<OprfHash<CS>>,
         aad: impl Iterator<Item = &'a [u8]>,
-    ) -> Result<OpenedInnerEnvelope<CS::Hash>, InternalError> {
-        let mut hmac_key = Output::<CS::Hash>::default();
-        let mut export_key = Output::<CS::Hash>::default();
+    ) -> Result<OpenedInnerEnvelope<OprfHash<CS>>, InternalError> {
+        let mut hmac_key = Output::<OprfHash<CS>>::default();
+        let mut export_key = Output::<OprfHash<CS>>::default();
 
         randomized_pwd_hasher
             .expand(&self.nonce.concat(STR_AUTH_KEY.into()), &mut hmac_key)
@@ -240,8 +281,8 @@ where
             .expand(&self.nonce.concat(STR_EXPORT_KEY.into()), &mut export_key)
             .map_err(|_| InternalError::HkdfError)?;
 
-        let mut hmac =
-            Hmac::<CS::Hash>::new_from_slice(&hmac_key).map_err(|_| InternalError::HmacError)?;
+        let mut hmac = Hmac::<OprfHash<CS>>::new_from_slice(&hmac_key)
+            .map_err(|_| InternalError::HmacError)?;
         hmac.update(&self.nonce);
         hmac.update_iter(aad);
         hmac.verify(&self.hmac)
@@ -260,17 +301,17 @@ where
     }
 
     fn hmac_key_size() -> usize {
-        OutputSize::<CS::Hash>::USIZE
+        OutputSize::<OprfHash<CS>>::USIZE
     }
 
     pub(crate) fn len() -> usize {
-        OutputSize::<CS::Hash>::USIZE + NonceLen::USIZE
+        OutputSize::<OprfHash<CS>>::USIZE + NonceLen::USIZE
     }
 
     pub(crate) fn serialize(&self) -> GenericArray<u8, EnvelopeLen<CS>>
     where
         // Envelope: Nonce + Hash
-        NonceLen: Add<OutputSize<CS::Hash>>,
+        NonceLen: Add<OutputSize<OprfHash<CS>>>,
         EnvelopeLen<CS>: ArrayLength<u8>,
     {
         self.nonce.concat(self.hmac.clone())
@@ -305,22 +346,25 @@ where
 // Helper functions
 
 fn build_inner_envelope_internal<CS: CipherSuite>(
-    randomized_pwd_hasher: Hkdf<CS::Hash>,
+    randomized_pwd_hasher: Hkdf<OprfHash<CS>>,
     nonce: GenericArray<u8, NonceLen>,
 ) -> Result<PublicKey<CS::KeGroup>, ProtocolError>
 where
-    <CS::Hash as CoreProxy>::Core: ProxyHash,
-    <<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
-    Le<<<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
+    <OprfHash<CS> as OutputSizeUser>::OutputSize:
+        IsLess<U256> + IsLessOrEqual<<OprfHash<CS> as BlockSizeUser>::BlockSize>,
+    OprfHash<CS>: Hash,
+    <OprfHash<CS> as CoreProxy>::Core: ProxyHash,
+    <<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
+    Le<<<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
 {
     let mut keypair_seed = GenericArray::<_, <CS::KeGroup as KeGroup>::SkLen>::default();
     randomized_pwd_hasher
         .expand(&nonce.concat(STR_PRIVATE_KEY.into()), &mut keypair_seed)
         .map_err(|_| InternalError::HkdfError)?;
     let client_static_keypair = KeyPair::<CS::KeGroup>::from_private_key_slice(
-        &CS::OprfGroup::scalar_as_bytes(CS::OprfGroup::hash_to_scalar::<CS::Hash, _, _>(
-            [keypair_seed.as_slice()],
-            GenericArray::from(STR_OPAQUE_DERIVE_AUTH_KEY_PAIR),
+        &CS::KeGroup::serialize_sk(&CS::KeGroup::hash_to_scalar::<OprfHash<CS>>(
+            &[keypair_seed.as_slice()],
+            &GenericArray::from(STR_OPAQUE_DERIVE_AUTH_KEY_PAIR),
         )?),
     )?;
 
@@ -328,22 +372,25 @@ where
 }
 
 fn recover_keys_internal<CS: CipherSuite>(
-    randomized_pwd_hasher: Hkdf<CS::Hash>,
+    randomized_pwd_hasher: Hkdf<OprfHash<CS>>,
     nonce: GenericArray<u8, NonceLen>,
 ) -> Result<KeyPair<CS::KeGroup>, ProtocolError>
 where
-    <CS::Hash as CoreProxy>::Core: ProxyHash,
-    <<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
-    Le<<<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
+    <OprfHash<CS> as OutputSizeUser>::OutputSize:
+        IsLess<U256> + IsLessOrEqual<<OprfHash<CS> as BlockSizeUser>::BlockSize>,
+    OprfHash<CS>: Hash,
+    <OprfHash<CS> as CoreProxy>::Core: ProxyHash,
+    <<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
+    Le<<<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
 {
     let mut keypair_seed = GenericArray::<_, <CS::KeGroup as KeGroup>::SkLen>::default();
     randomized_pwd_hasher
         .expand(&nonce.concat(STR_PRIVATE_KEY.into()), &mut keypair_seed)
         .map_err(|_| InternalError::HkdfError)?;
     let client_static_keypair = KeyPair::<CS::KeGroup>::from_private_key_slice(
-        &CS::OprfGroup::scalar_as_bytes(CS::OprfGroup::hash_to_scalar::<CS::Hash, _, _>(
-            [keypair_seed.as_slice()],
-            GenericArray::from(STR_OPAQUE_DERIVE_AUTH_KEY_PAIR),
+        &CS::KeGroup::serialize_sk(&CS::KeGroup::hash_to_scalar::<OprfHash<CS>>(
+            &[keypair_seed.as_slice()],
+            &GenericArray::from(STR_OPAQUE_DERIVE_AUTH_KEY_PAIR),
         )?),
     )?;
 
diff --git a/src/errors.rs b/src/errors.rs
index e35cd38a..4080a4da 100644
--- a/src/errors.rs
+++ b/src/errors.rs
@@ -31,8 +31,8 @@ pub enum InternalError<T = Infallible> {
     },
     /// Could not decompress point.
     PointError,
-    /// Computing the hash-to-curve function failed
-    HashToCurveError,
+    /// Size of input is empty or longer then [`u16::MAX`].
+    HashToScalar,
     /// Computing HKDF failed while deriving subkeys
     HkdfError,
     /// Computing HMAC failed while supplying a secret key
@@ -45,12 +45,10 @@ pub enum InternalError<T = Infallible> {
     /** This error occurs when attempting to open an envelope of the wrong
     type (base mode, custom identifier) */
     IncompatibleEnvelopeModeError,
-    /// This error occurs when the inner envelope is malformed
-    InvalidInnerEnvelopeError,
     /// Error from the OPRF evaluation
     OprfError(voprf::Error),
-    /// Error encountered when attempting to produce a keypair
-    InvalidKeypairError,
+    /// Error from the OPRF evaluation
+    OprfInternalError(voprf::InternalError),
 }
 
 impl<T: Debug> Debug for InternalError<T> {
@@ -69,7 +67,7 @@ impl<T: Debug> Debug for InternalError<T> {
                 .field("actual_len", actual_len)
                 .finish(),
             Self::PointError => f.debug_tuple("PointError").finish(),
-            Self::HashToCurveError => f.debug_tuple("HashToCurveError").finish(),
+            Self::HashToScalar => f.debug_tuple("HashToScalar").finish(),
             Self::HkdfError => f.debug_tuple("HkdfError").finish(),
             Self::HmacError => f.debug_tuple("HmacError").finish(),
             Self::SlowHashError => f.debug_tuple("SlowHashError").finish(),
@@ -77,9 +75,10 @@ impl<T: Debug> Debug for InternalError<T> {
             Self::IncompatibleEnvelopeModeError => {
                 f.debug_tuple("IncompatibleEnvelopeModeError").finish()
             }
-            Self::InvalidInnerEnvelopeError => f.debug_tuple("InvalidInnerEnvelopeError").finish(),
             Self::OprfError(error) => f.debug_tuple("OprfError").field(error).finish(),
-            Self::InvalidKeypairError => f.debug_tuple("InvalidKeypairError").finish(),
+            Self::OprfInternalError(error) => {
+                f.debug_tuple("OprfInternalError").field(error).finish()
+            }
         }
     }
 }
@@ -103,15 +102,14 @@ impl InternalError {
                 actual_len,
             },
             Self::PointError => InternalError::PointError,
-            Self::HashToCurveError => InternalError::HashToCurveError,
+            Self::HashToScalar => InternalError::HashToScalar,
             Self::HkdfError => InternalError::HkdfError,
             Self::HmacError => InternalError::HmacError,
             Self::SlowHashError => InternalError::SlowHashError,
             Self::SealOpenHmacError => InternalError::SealOpenHmacError,
             Self::IncompatibleEnvelopeModeError => InternalError::IncompatibleEnvelopeModeError,
-            Self::InvalidInnerEnvelopeError => InternalError::InvalidInnerEnvelopeError,
             Self::OprfError(error) => InternalError::OprfError(error),
-            Self::InvalidKeypairError => InternalError::InvalidKeypairError,
+            Self::OprfInternalError(error) => InternalError::OprfInternalError(error),
         }
     }
 }
@@ -128,6 +126,12 @@ impl From<voprf::Error> for ProtocolError {
     }
 }
 
+impl From<voprf::InternalError> for ProtocolError {
+    fn from(voprf_error: voprf::InternalError) -> Self {
+        Self::LibraryError(InternalError::OprfInternalError(voprf_error))
+    }
+}
+
 /// Represents an error in protocol handling
 #[derive(Clone, Copy, Display, Eq, Hash, Ord, PartialEq, PartialOrd)]
 pub enum ProtocolError<T = Infallible> {
diff --git a/src/impls.rs b/src/impls.rs
old mode 100755
new mode 100644
index a579a767..ccdb6376
--- a/src/impls.rs
+++ b/src/impls.rs
@@ -11,9 +11,12 @@ macro_rules! impl_serialize_and_deserialize_for {
         #[cfg(feature = "serde")]
         impl<CS: CipherSuite> serde_::Serialize for $item<CS>
         where
-            <CS::Hash as CoreProxy>::Core: ProxyHash,
-            <<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
-            Le<<<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
+            <OprfHash<CS> as OutputSizeUser>::OutputSize:
+                IsLess<U256> + IsLessOrEqual<<OprfHash<CS> as BlockSizeUser>::BlockSize>,
+            OprfHash<CS>: Hash,
+            <OprfHash<CS> as CoreProxy>::Core: ProxyHash,
+            <<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
+            Le<<<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
             $($($path: $bound1 $(+ $bound2)*),+)?
         {
             fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
@@ -27,9 +30,12 @@ macro_rules! impl_serialize_and_deserialize_for {
         #[cfg(feature = "serde")]
         impl<'de, CS: CipherSuite> serde_::Deserialize<'de> for $item<CS>
         where
-            <CS::Hash as CoreProxy>::Core: ProxyHash,
-            <<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
-            Le<<<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
+            <OprfHash<CS> as OutputSizeUser>::OutputSize:
+                IsLess<U256> + IsLessOrEqual<<OprfHash<CS> as BlockSizeUser>::BlockSize>,
+            OprfHash<CS>: Hash,
+                <OprfHash<CS> as CoreProxy>::Core: ProxyHash,
+                <<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
+                Le<<<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
         {
             fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
             where
@@ -39,15 +45,21 @@ macro_rules! impl_serialize_and_deserialize_for {
 
                 struct ByteVisitor<CS: CipherSuite>(core::marker::PhantomData<CS>)
                 where
-                    <CS::Hash as CoreProxy>::Core: ProxyHash,
-                    <<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
-                    Le<<<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero;
+                    <OprfHash<CS> as OutputSizeUser>::OutputSize:
+                        IsLess<U256> + IsLessOrEqual<<OprfHash<CS> as BlockSizeUser>::BlockSize>,
+                    OprfHash<CS>: Hash,
+                        <OprfHash<CS> as CoreProxy>::Core: ProxyHash,
+                        <<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
+                        Le<<<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero;
 
                 impl<'de, CS: CipherSuite> serde_::de::Visitor<'de> for ByteVisitor<CS>
                 where
-                    <CS::Hash as CoreProxy>::Core: ProxyHash,
-                    <<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
-                    Le<<<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
+                    <OprfHash<CS> as OutputSizeUser>::OutputSize:
+                        IsLess<U256> + IsLessOrEqual<<OprfHash<CS> as BlockSizeUser>::BlockSize>,
+                    OprfHash<CS>: Hash,
+                        <OprfHash<CS> as CoreProxy>::Core: ProxyHash,
+                        <<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
+                        Le<<<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
                 {
                     type Value = $item<CS>;
 
diff --git a/src/key_exchange/group/elliptic_curve.rs b/src/key_exchange/group/elliptic_curve.rs
new file mode 100644
index 00000000..fef2f5ff
--- /dev/null
+++ b/src/key_exchange/group/elliptic_curve.rs
@@ -0,0 +1,85 @@
+// Copyright (c) Facebook, Inc. and its affiliates.
+//
+// This source code is licensed under both the MIT license found in the
+// LICENSE-MIT file in the root directory of this source tree and the Apache
+// License, Version 2.0 found in the LICENSE-APACHE file in the root directory
+// of this source tree.
+
+use digest::core_api::BlockSizeUser;
+use digest::Digest;
+use elliptic_curve::group::cofactor::CofactorGroup;
+use elliptic_curve::hash2curve::{ExpandMsgXmd, FromOkm, GroupDigest};
+use elliptic_curve::sec1::{FromEncodedPoint, ModulusSize, ToEncodedPoint};
+use elliptic_curve::{
+    AffinePoint, Curve, FieldSize, NonZeroScalar, ProjectiveArithmetic, ProjectivePoint, PublicKey,
+    Scalar, SecretKey,
+};
+use generic_array::typenum::{IsLess, IsLessOrEqual, U256};
+use generic_array::GenericArray;
+use rand::{CryptoRng, RngCore};
+
+use super::KeGroup;
+use crate::errors::InternalError;
+
+impl<G: Curve + GroupDigest + ProjectiveArithmetic> KeGroup for G
+where
+    FieldSize<Self>: ModulusSize,
+    AffinePoint<Self>: FromEncodedPoint<Self> + ToEncodedPoint<Self>,
+    ProjectivePoint<Self>: CofactorGroup + ToEncodedPoint<Self>,
+    Scalar<Self>: FromOkm,
+{
+    type Pk = PublicKey<Self>;
+
+    type PkLen = <FieldSize<Self> as ModulusSize>::CompressedPointSize;
+
+    type Sk = SecretKey<Self>;
+
+    type SkLen = FieldSize<Self>;
+
+    fn serialize_pk(pk: &Self::Pk) -> GenericArray<u8, Self::PkLen> {
+        GenericArray::clone_from_slice(pk.to_encoded_point(true).as_bytes())
+    }
+
+    fn deserialize_pk(bytes: &GenericArray<u8, Self::PkLen>) -> Result<Self::Pk, InternalError> {
+        PublicKey::from_sec1_bytes(bytes).map_err(|_| InternalError::PointError)
+    }
+
+    fn random_sk<R: RngCore + CryptoRng>(rng: &mut R) -> Self::Sk {
+        SecretKey::random(rng)
+    }
+
+    // Implements the `HashToScalar()` function
+    fn hash_to_scalar<H>(input: &[&[u8]], dst: &[u8]) -> Result<Self::Sk, InternalError>
+    where
+        H: Digest + BlockSizeUser,
+        H::OutputSize: IsLess<U256> + IsLessOrEqual<H::BlockSize>,
+    {
+        Self::hash_to_scalar::<ExpandMsgXmd<H>>(input, dst)
+            .ok()
+            .and_then(|scalar| Option::<NonZeroScalar<Self>>::from(NonZeroScalar::new(scalar)))
+            .map(SecretKey::from)
+            .ok_or(InternalError::HashToScalar)
+    }
+
+    fn public_key(sk: &Self::Sk) -> Self::Pk {
+        sk.public_key()
+    }
+
+    fn diffie_hellman(pk: &Self::Pk, sk: &Self::Sk) -> GenericArray<u8, Self::PkLen> {
+        GenericArray::clone_from_slice(
+            (pk.to_projective() * sk.to_nonzero_scalar().as_ref())
+                .to_encoded_point(true)
+                .as_bytes(),
+        )
+    }
+
+    fn zeroize_sk_on_drop(_sk: &mut Self::Sk) {}
+
+    fn serialize_sk(sk: &Self::Sk) -> GenericArray<u8, Self::SkLen> {
+        sk.to_be_bytes()
+    }
+
+    fn deserialize_sk(bytes: &GenericArray<u8, Self::SkLen>) -> Result<Self::Sk, InternalError> {
+        SecretKey::from_be_bytes(bytes).map_err(|_| InternalError::PointError)
+    }
+}
diff --git a/src/key_exchange/group/mod.rs b/src/key_exchange/group/mod.rs
index dad51636..95f4526b 100644
--- a/src/key_exchange/group/mod.rs
+++ b/src/key_exchange/group/mod.rs
@@ -7,37 +7,62 @@
 
 //! Includes the KeGroup trait and definitions for the key exchange groups
 
+mod elliptic_curve;
+#[cfg(feature = "ristretto255")]
+pub mod ristretto255;
+#[cfg(feature = "x25519")]
+pub mod x25519;
+
+use digest::core_api::BlockSizeUser;
+use digest::Digest;
+use generic_array::typenum::{IsLess, IsLessOrEqual, U256};
 use generic_array::{ArrayLength, GenericArray};
 use rand::{CryptoRng, RngCore};
 
 use crate::errors::InternalError;
 
 /// A group representation for use in the key exchange
-pub trait KeGroup: Sized + Clone {
+pub trait KeGroup {
+    /// Public key
+    type Pk: Clone;
     /// Length of the public key
-    type PkLen: ArrayLength<u8> + 'static;
+    type PkLen: ArrayLength<u8>;
+    /// Secret key
+    type Sk: Clone;
     /// Length of the secret key
-    type SkLen: ArrayLength<u8> + 'static;
+    type SkLen: ArrayLength<u8>;
+
+    /// Serializes `self`
+    fn serialize_pk(pk: &Self::Pk) -> GenericArray<u8, Self::PkLen>;
 
     /// Return a public key from its fixed-length bytes representation
-    fn from_pk_slice(element_bits: &GenericArray<u8, Self::PkLen>) -> Result<Self, InternalError>;
+    fn deserialize_pk(bytes: &GenericArray<u8, Self::PkLen>) -> Result<Self::Pk, InternalError>;
 
     /// Generate a random secret key
-    fn random_sk<R: RngCore + CryptoRng>(rng: &mut R) -> GenericArray<u8, Self::SkLen>;
+    fn random_sk<R: RngCore + CryptoRng>(rng: &mut R) -> Self::Sk;
+
+    /// Hashes a slice of pseudo-random bytes to a scalar
+    ///
+    /// # Errors
+    /// [`InternalError::HashToScalar`] if the `input` is empty or longer then
+    /// [`u16::MAX`].
+    fn hash_to_scalar<H>(input: &[&[u8]], dst: &[u8]) -> Result<Self::Sk, InternalError>
+    where
+        H: Digest + BlockSizeUser,
+        H::OutputSize: IsLess<U256> + IsLessOrEqual<H::BlockSize>;
 
     /// Return a public key from its secret key
-    fn public_key(sk: &GenericArray<u8, Self::SkLen>) -> Self;
+    fn public_key(sk: &Self::Sk) -> Self::Pk;
+
+    /// Diffie-Hellman key exchange
+    fn diffie_hellman(pk: &Self::Pk, sk: &Self::Sk) -> GenericArray<u8, Self::PkLen>;
+
+    /// Zeroize secret key on drop.
+    fn zeroize_sk_on_drop(sk: &mut Self::Sk);
 
     /// Serializes `self`
-    fn to_arr(&self) -> GenericArray<u8, Self::PkLen>;
+    fn serialize_sk(sk: &Self::Sk) -> GenericArray<u8, Self::SkLen>;
 
-    /// Diffie-Hellman key exchange
-    fn diffie_hellman(&self, sk: &GenericArray<u8, Self::SkLen>) -> GenericArray<u8, Self::PkLen>;
+    /// Return a public key from its fixed-length bytes representation
+    fn deserialize_sk(bytes: &GenericArray<u8, Self::SkLen>) -> Result<Self::Sk, InternalError>;
 }
-
-#[cfg(feature = "p256")]
-pub mod p256;
-#[cfg(feature = "ristretto255")]
-pub mod ristretto255;
-#[cfg(feature = "x25519")]
-pub mod x25519;
diff --git a/src/key_exchange/group/p256.rs b/src/key_exchange/group/p256.rs
deleted file mode 100644
index 50eb9cb7..00000000
--- a/src/key_exchange/group/p256.rs
+++ /dev/null
@@ -1,52 +0,0 @@
-// Copyright (c) Facebook, Inc. and its affiliates.
-//
-// This source code is licensed under both the MIT license found in the
-// LICENSE-MIT file in the root directory of this source tree and the Apache
-// License, Version 2.0 found in the LICENSE-APACHE file in the root directory
-// of this source tree.
-
-//! Key Exchange group implementation for p256
-
-use generic_array::typenum::{U32, U33};
-use generic_array::GenericArray;
-use p256_::elliptic_curve::group::GroupEncoding;
-use p256_::elliptic_curve::sec1::ToEncodedPoint;
-use p256_::elliptic_curve::{PublicKey, SecretKey};
-use p256_::NistP256;
-use rand::{CryptoRng, RngCore};
-
-use super::KeGroup;
-use crate::errors::InternalError;
-
-impl KeGroup for PublicKey<NistP256> {
-    type PkLen = U33;
-    type SkLen = U32;
-
-    fn from_pk_slice(element_bits: &GenericArray<u8, Self::PkLen>) -> Result<Self, InternalError> {
-        Self::from_sec1_bytes(element_bits).map_err(|_| InternalError::PointError)
-    }
-
-    fn random_sk<R: RngCore + CryptoRng>(rng: &mut R) -> GenericArray<u8, Self::SkLen> {
-        SecretKey::<NistP256>::random(rng).to_be_bytes()
-    }
-
-    fn public_key(sk: &GenericArray<u8, Self::SkLen>) -> Self {
-        SecretKey::<NistP256>::from_be_bytes(sk)
-            .unwrap()
-            .public_key()
-    }
-
-    fn to_arr(&self) -> GenericArray<u8, Self::PkLen> {
-        GenericArray::clone_from_slice(self.to_encoded_point(true).as_bytes())
-    }
-
-    fn diffie_hellman(&self, sk: &GenericArray<u8, Self::SkLen>) -> GenericArray<u8, Self::PkLen> {
-        (self.to_projective()
-            * SecretKey::<NistP256>::from_be_bytes(sk)
-                .unwrap()
-                .to_nonzero_scalar()
-                .as_ref())
-        .to_affine()
-        .to_bytes()
-    }
-}
diff --git a/src/key_exchange/group/ristretto255.rs b/src/key_exchange/group/ristretto255.rs
index c4c6ebd7..cf7fe3b8 100644
--- a/src/key_exchange/group/ristretto255.rs
+++ b/src/key_exchange/group/ristretto255.rs
@@ -10,24 +10,40 @@
 use curve25519_dalek::constants::RISTRETTO_BASEPOINT_POINT;
 use curve25519_dalek::ristretto::{CompressedRistretto, RistrettoPoint};
 use curve25519_dalek::scalar::Scalar;
-use generic_array::typenum::U32;
+use digest::core_api::BlockSizeUser;
+use digest::{Digest, OutputSizeUser};
+use elliptic_curve::hash2curve::{ExpandMsg, ExpandMsgXmd, Expander};
+use generic_array::typenum::{IsLess, IsLessOrEqual, U256, U32, U64};
 use generic_array::GenericArray;
 use rand::{CryptoRng, RngCore};
+use voprf::Group;
+use zeroize::Zeroize;
 
 use super::KeGroup;
 use crate::errors::InternalError;
 
-impl KeGroup for RistrettoPoint {
+/// Implementation for Ristretto255.
+// This is necessary because Rust lacks specialization, otherwise we could
+// implement `KeGroup` for `voprf::Ristretto255`.
+pub struct Ristretto255;
+
+impl KeGroup for Ristretto255 {
+    type Pk = RistrettoPoint;
     type PkLen = U32;
+    type Sk = Scalar;
     type SkLen = U32;
 
-    fn from_pk_slice(element_bits: &GenericArray<u8, Self::PkLen>) -> Result<Self, InternalError> {
-        CompressedRistretto::from_slice(element_bits)
+    fn serialize_pk(pk: &Self::Pk) -> GenericArray<u8, Self::PkLen> {
+        pk.compress().to_bytes().into()
+    }
+
+    fn deserialize_pk(bytes: &GenericArray<u8, Self::PkLen>) -> Result<Self::Pk, InternalError> {
+        CompressedRistretto::from_slice(bytes)
             .decompress()
             .ok_or(InternalError::PointError)
     }
 
-    fn random_sk<R: RngCore + CryptoRng>(rng: &mut R) -> GenericArray<u8, Self::SkLen> {
+    fn random_sk<R: RngCore + CryptoRng>(rng: &mut R) -> Self::Sk {
         loop {
             let scalar = {
                 #[cfg(not(test))]
@@ -47,21 +63,121 @@ impl KeGroup for RistrettoPoint {
                 }
             };
 
-            if scalar != Scalar::zero() {
-                break scalar.to_bytes().into();
+            if scalar != Scalar::zero() && scalar.is_canonical() {
+                break scalar;
             }
         }
     }
 
-    fn public_key(sk: &GenericArray<u8, Self::SkLen>) -> Self {
-        RISTRETTO_BASEPOINT_POINT * Scalar::from_bits(*sk.as_ref())
+    // Implements the `HashToScalar()` function from
+    // https://www.ietf.org/archive/id/draft-irtf-cfrg-voprf-08.html#section-4.1
+    fn hash_to_scalar<'a, H>(input: &[&[u8]], dst: &[u8]) -> Result<Self::Sk, InternalError>
+    where
+        H: Digest + BlockSizeUser,
+        H::OutputSize: IsLess<U256> + IsLessOrEqual<H::BlockSize>,
+    {
+        let mut uniform_bytes = GenericArray::<_, U64>::default();
+        ExpandMsgXmd::<H>::expand_message(input, dst, 64)
+            .map_err(|_| InternalError::HashToScalar)?
+            .fill_bytes(&mut uniform_bytes);
+
+        Ok(Scalar::from_bytes_mod_order_wide(&uniform_bytes.into()))
+    }
+
+    fn public_key(sk: &Self::Sk) -> Self::Pk {
+        RISTRETTO_BASEPOINT_POINT * sk
+    }
+
+    fn diffie_hellman(pk: &Self::Pk, sk: &Self::Sk) -> GenericArray<u8, Self::PkLen> {
+        Self::serialize_pk(&(pk * sk))
+    }
+
+    fn zeroize_sk_on_drop(sk: &mut Self::Sk) {
+        sk.zeroize()
+    }
+
+    fn serialize_sk(sk: &Self::Sk) -> GenericArray<u8, Self::SkLen> {
+        sk.to_bytes().into()
+    }
+
+    fn deserialize_sk(bytes: &GenericArray<u8, Self::PkLen>) -> Result<Self::Sk, InternalError> {
+        Scalar::from_canonical_bytes((*bytes).into()).ok_or(InternalError::PointError)
+    }
+}
+
+#[cfg(feature = "ristretto255_voprf")]
+impl voprf::CipherSuite for Ristretto255 {
+    const ID: u16 = voprf::Ristretto255::ID;
+
+    type Group = <voprf::Ristretto255 as voprf::CipherSuite>::Group;
+
+    type Hash = <voprf::Ristretto255 as voprf::CipherSuite>::Hash;
+}
+
+impl Group for Ristretto255 {
+    type Elem = <voprf::Ristretto255 as Group>::Elem;
+
+    type ElemLen = <voprf::Ristretto255 as Group>::ElemLen;
+
+    type Scalar = <voprf::Ristretto255 as Group>::Scalar;
+
+    type ScalarLen = <voprf::Ristretto255 as Group>::ScalarLen;
+
+    fn hash_to_curve<CS: voprf::CipherSuite>(
+        input: &[&[u8]],
+        dst: &[u8],
+    ) -> voprf::Result<Self::Elem, voprf::InternalError>
+    where
+        <CS::Hash as OutputSizeUser>::OutputSize:
+            IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
+    {
+        <voprf::Ristretto255 as Group>::hash_to_curve::<CS>(input, dst)
+    }
+
+    fn hash_to_scalar<CS: voprf::CipherSuite>(
+        input: &[&[u8]],
+        dst: &[u8],
+    ) -> voprf::Result<Self::Scalar, voprf::InternalError>
+    where
+        <CS::Hash as OutputSizeUser>::OutputSize:
+            IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
+    {
+        <voprf::Ristretto255 as Group>::hash_to_scalar::<CS>(input, dst)
+    }
+
+    fn base_elem() -> Self::Elem {
+        <voprf::Ristretto255 as Group>::base_elem()
+    }
+
+    fn identity_elem() -> Self::Elem {
+        <voprf::Ristretto255 as Group>::identity_elem()
+    }
+
+    fn serialize_elem(elem: Self::Elem) -> GenericArray<u8, Self::ElemLen> {
+        <voprf::Ristretto255 as Group>::serialize_elem(elem)
+    }
+
+    fn deserialize_elem(element_bits: &[u8]) -> voprf::Result<Self::Elem> {
+        <voprf::Ristretto255 as Group>::deserialize_elem(element_bits)
+    }
+
+    fn random_scalar<R: RngCore + CryptoRng>(rng: &mut R) -> Self::Scalar {
+        <voprf::Ristretto255 as Group>::random_scalar(rng)
+    }
+
+    fn invert_scalar(scalar: Self::Scalar) -> Self::Scalar {
+        <voprf::Ristretto255 as Group>::invert_scalar(scalar)
+    }
+
+    fn is_zero_scalar(scalar: Self::Scalar) -> subtle::Choice {
+        <voprf::Ristretto255 as Group>::is_zero_scalar(scalar)
     }
 
-    fn to_arr(&self) -> GenericArray<u8, Self::PkLen> {
-        self.compress().to_bytes().into()
+    fn serialize_scalar(scalar: Self::Scalar) -> GenericArray<u8, Self::ScalarLen> {
+        <voprf::Ristretto255 as Group>::serialize_scalar(scalar)
     }
 
-    fn diffie_hellman(&self, sk: &GenericArray<u8, Self::SkLen>) -> GenericArray<u8, Self::SkLen> {
-        (self * Scalar::from_bits(*sk.as_ref())).to_arr()
+    fn deserialize_scalar(scalar_bits: &[u8]) -> voprf::Result<Self::Scalar> {
+        <voprf::Ristretto255 as Group>::deserialize_scalar(scalar_bits)
     }
 }
diff --git a/src/key_exchange/group/x25519.rs b/src/key_exchange/group/x25519.rs
index 18d2067d..690b4d05 100644
--- a/src/key_exchange/group/x25519.rs
+++ b/src/key_exchange/group/x25519.rs
@@ -7,47 +7,97 @@
 
 //! Key Exchange group implementation for X25519
 
-use generic_array::typenum::U32;
+use curve25519_dalek_3::scalar::Scalar;
+use digest::core_api::BlockSizeUser;
+use digest::Digest;
+use elliptic_curve::hash2curve::{ExpandMsg, ExpandMsgXmd, Expander};
+use generic_array::typenum::{IsLess, IsLessOrEqual, U256, U32, U64};
 use generic_array::GenericArray;
 use rand::{CryptoRng, RngCore};
 use x25519_dalek::{PublicKey, StaticSecret};
+use zeroize::Zeroize;
 
 use super::KeGroup;
 use crate::errors::InternalError;
 
+/// Implementation for X25519.
+pub struct X25519;
+
 /// The implementation of such a subgroup for Ristretto
-impl KeGroup for PublicKey {
+impl KeGroup for X25519 {
+    type Pk = PublicKey;
     type PkLen = U32;
+    type Sk = StaticSecret;
     type SkLen = U32;
 
-    fn from_pk_slice(element_bits: &GenericArray<u8, Self::PkLen>) -> Result<Self, InternalError> {
-        Ok(Self::from(<[u8; 32]>::from(*element_bits)))
+    fn serialize_pk(pk: &Self::Pk) -> GenericArray<u8, Self::PkLen> {
+        pk.to_bytes().into()
+    }
+
+    fn deserialize_pk(bytes: &GenericArray<u8, Self::PkLen>) -> Result<Self::Pk, InternalError> {
+        if **bytes == [0; 32] {
+            Err(InternalError::PointError)
+        } else {
+            Ok(PublicKey::from(<[_; 32]>::from(*bytes)))
+        }
     }
 
-    fn random_sk<R: RngCore + CryptoRng>(rng: &mut R) -> GenericArray<u8, Self::SkLen> {
+    fn random_sk<R: RngCore + CryptoRng>(rng: &mut R) -> Self::Sk {
         let mut scalar_bytes = [0u8; 32];
 
         loop {
             rng.fill_bytes(&mut scalar_bytes);
 
             if scalar_bytes != [0u8; 32] {
-                break StaticSecret::from(scalar_bytes).to_bytes().into();
+                break StaticSecret::from(scalar_bytes);
             }
         }
     }
 
-    fn public_key(sk: &GenericArray<u8, Self::SkLen>) -> Self {
-        Self::from(&StaticSecret::from(<[u8; 32]>::from(*sk)))
+    // Implements the `HashToScalar()` function from
+    // https://www.ietf.org/archive/id/draft-irtf-cfrg-voprf-08.html#section-4.1
+    fn hash_to_scalar<'a, H>(input: &[&[u8]], dst: &[u8]) -> Result<Self::Sk, InternalError>
+    where
+        H: Digest + BlockSizeUser,
+        H::OutputSize: IsLess<U256> + IsLessOrEqual<H::BlockSize>,
+    {
+        let mut uniform_bytes = GenericArray::<_, U64>::default();
+        ExpandMsgXmd::<H>::expand_message(input, dst, 64)
+            .map_err(|_| InternalError::HashToScalar)?
+            .fill_bytes(&mut uniform_bytes);
+
+        Ok(StaticSecret::from(
+            Scalar::from_bytes_mod_order_wide(&uniform_bytes.into()).to_bytes(),
+        ))
+    }
+
+    fn public_key(sk: &Self::Sk) -> Self::Pk {
+        PublicKey::from(sk)
     }
 
-    fn to_arr(&self) -> GenericArray<u8, Self::PkLen> {
-        self.to_bytes().into()
+    fn diffie_hellman(pk: &Self::Pk, sk: &Self::Sk) -> GenericArray<u8, Self::PkLen> {
+        sk.diffie_hellman(pk).to_bytes().into()
     }
 
-    fn diffie_hellman(&self, sk: &GenericArray<u8, Self::SkLen>) -> GenericArray<u8, Self::SkLen> {
-        StaticSecret::from(<[u8; 32]>::from(*sk))
-            .diffie_hellman(self)
-            .to_bytes()
-            .into()
+    fn zeroize_sk_on_drop(sk: &mut Self::Sk) {
+        sk.zeroize()
+    }
+
+    fn serialize_sk(sk: &Self::Sk) -> GenericArray<u8, Self::SkLen> {
+        sk.to_bytes().into()
+    }
+
+    fn deserialize_sk(bytes: &GenericArray<u8, Self::PkLen>) -> Result<Self::Sk, InternalError> {
+        if **bytes == [0; 32] {
+            Err(InternalError::PointError)
+        } else {
+            let sk = StaticSecret::from(<[u8; 32]>::from(*bytes));
+
+            if sk.to_bytes() == **bytes {
+                Ok(sk)
+            } else {
+                Err(InternalError::PointError)
+            }
+        }
     }
 }
diff --git a/src/key_exchange/traits.rs b/src/key_exchange/traits.rs
old mode 100755
new mode 100644
index 4e2bfe8b..0889a4f7
--- a/src/key_exchange/traits.rs
+++ b/src/key_exchange/traits.rs
@@ -10,9 +10,9 @@ use digest::Output;
 use generic_array::typenum::{IsLess, Le, NonZero, U256};
 use generic_array::{ArrayLength, GenericArray};
 use rand::{CryptoRng, RngCore};
-use zeroize::Zeroize;
+use zeroize::ZeroizeOnDrop;
 
-use crate::ciphersuite::CipherSuite;
+use crate::ciphersuite::{CipherSuite, OprfHash};
 use crate::errors::ProtocolError;
 use crate::hash::{Hash, ProxyHash};
 use crate::key_exchange::group::KeGroup;
@@ -46,17 +46,17 @@ where
     <D::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
     Le<<D::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
 {
-    type KE1State: FromBytes + ToBytes + Zeroize + Clone;
-    type KE2State: FromBytes + ToBytes + Zeroize + Clone;
-    type KE1Message: FromBytes + ToBytes + Zeroize + Clone;
-    type KE2Message: FromBytes + ToBytes + Clone;
-    type KE3Message: FromBytes + ToBytes + Clone;
+    type KE1State: FromBytes + ToBytes + ZeroizeOnDrop + Clone;
+    type KE2State: FromBytes + ToBytes + ZeroizeOnDrop + Clone;
+    type KE1Message: FromBytes + ToBytes + ZeroizeOnDrop + Clone;
+    type KE2Message: FromBytes + ToBytes + ZeroizeOnDrop + Clone;
+    type KE3Message: FromBytes + ToBytes + ZeroizeOnDrop + Clone;
 
     fn generate_ke1<R: RngCore + CryptoRng>(
         rng: &mut R,
     ) -> Result<(Self::KE1State, Self::KE1Message), ProtocolError>;
 
-    #[allow(clippy::too_many_arguments, clippy::type_complexity)]
+    #[allow(clippy::too_many_arguments)]
     fn generate_ke2<'a, 'b, 'c, 'd, R: RngCore + CryptoRng, S: SecretKey<G>>(
         rng: &mut R,
         l1_bytes: impl Iterator<Item = &'a [u8]>,
@@ -69,7 +69,7 @@ where
         context: &[u8],
     ) -> Result<GenerateKe2Result<Self, D, G>, ProtocolError<S::Error>>;
 
-    #[allow(clippy::too_many_arguments, clippy::type_complexity)]
+    #[allow(clippy::too_many_arguments)]
     fn generate_ke3<'a, 'b, 'c, 'd>(
         l2_component: impl Iterator<Item = &'a [u8]>,
         ke2_message: Self::KE2Message,
@@ -82,13 +82,10 @@ where
         context: &[u8],
     ) -> Result<GenerateKe3Result<Self, D, G>, ProtocolError>;
 
-    #[allow(clippy::type_complexity)]
     fn finish_ke(
         ke3_message: Self::KE3Message,
         ke2_state: &Self::KE2State,
     ) -> Result<Output<D>, ProtocolError>;
-
-    fn ke2_message_size() -> usize;
 }
 
 pub trait FromBytes: Sized {
@@ -101,14 +98,13 @@ pub trait ToBytes {
     fn to_bytes(&self) -> GenericArray<u8, Self::Len>;
 }
 
-#[allow(dead_code)]
 pub type Ke1StateLen<CS: CipherSuite> =
-    <<CS::KeyExchange as KeyExchange<CS::Hash, CS::KeGroup>>::KE1State as ToBytes>::Len;
+    <<CS::KeyExchange as KeyExchange<OprfHash<CS>, CS::KeGroup>>::KE1State as ToBytes>::Len;
 pub type Ke1MessageLen<CS: CipherSuite> =
-    <<CS::KeyExchange as KeyExchange<CS::Hash, CS::KeGroup>>::KE1Message as ToBytes>::Len;
+    <<CS::KeyExchange as KeyExchange<OprfHash<CS>, CS::KeGroup>>::KE1Message as ToBytes>::Len;
 pub type Ke2StateLen<CS: CipherSuite> =
-    <<CS::KeyExchange as KeyExchange<CS::Hash, CS::KeGroup>>::KE2State as ToBytes>::Len;
+    <<CS::KeyExchange as KeyExchange<OprfHash<CS>, CS::KeGroup>>::KE2State as ToBytes>::Len;
 pub type Ke2MessageLen<CS: CipherSuite> =
-    <<CS::KeyExchange as KeyExchange<CS::Hash, CS::KeGroup>>::KE2Message as ToBytes>::Len;
+    <<CS::KeyExchange as KeyExchange<OprfHash<CS>, CS::KeGroup>>::KE2Message as ToBytes>::Len;
 pub type Ke3MessageLen<CS: CipherSuite> =
-    <<CS::KeyExchange as KeyExchange<CS::Hash, CS::KeGroup>>::KE3Message as ToBytes>::Len;
+    <<CS::KeyExchange as KeyExchange<OprfHash<CS>, CS::KeGroup>>::KE3Message as ToBytes>::Len;
diff --git a/src/key_exchange/tripledh.rs b/src/key_exchange/tripledh.rs
old mode 100755
new mode 100644
index 65935f18..797bbefd
--- a/src/key_exchange/tripledh.rs
+++ b/src/key_exchange/tripledh.rs
@@ -9,7 +9,7 @@
 use core::convert::TryFrom;
 use core::ops::Add;
 
-use derive_where::DeriveWhere;
+use derive_where::derive_where;
 use digest::core_api::BlockSizeUser;
 use digest::{Digest, Output};
 use generic_array::sequence::Concat;
@@ -18,6 +18,7 @@ use generic_array::{ArrayLength, GenericArray};
 use hkdf::{Hkdf, HkdfExtract};
 use hmac::{Hmac, Mac};
 use rand::{CryptoRng, RngCore};
+use zeroize::{Zeroize, ZeroizeOnDrop};
 
 use crate::errors::utils::{check_slice_size, check_slice_size_atleast};
 use crate::errors::{InternalError, ProtocolError};
@@ -55,36 +56,63 @@ pub struct TripleDH;
 #[cfg_attr(
     feature = "serde",
     derive(serde_::Deserialize, serde_::Serialize),
-    serde(bound = "", crate = "serde_")
+    serde(
+        bound(
+            deserialize = "KG::Sk: serde_::Deserialize<'de>",
+            serialize = "KG::Sk: serde_::Serialize",
+        ),
+        crate = "serde_"
+    )
 )]
-#[derive(DeriveWhere)]
-#[derive_where(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Zeroize(drop))]
+#[derive_where(Clone)]
+#[derive_where(Debug, Eq, Hash, Ord, PartialEq, PartialOrd; KG::Sk)]
 pub struct Ke1State<KG: KeGroup> {
     client_e_sk: PrivateKey<KG>,
     client_nonce: GenericArray<u8, NonceLen>,
 }
 
+impl<KG: KeGroup> Drop for Ke1State<KG> {
+    fn drop(&mut self) {
+        self.client_nonce.zeroize();
+    }
+}
+
+impl<KG: KeGroup> ZeroizeOnDrop for Ke1State<KG> {}
+
 /// The first key exchange message
 #[cfg_attr(
     feature = "serde",
     derive(serde_::Deserialize, serde_::Serialize),
-    serde(bound = "", crate = "serde_")
+    serde(
+        bound(
+            deserialize = "KG::Pk: serde_::Deserialize<'de>",
+            serialize = "KG::Pk: serde_::Serialize",
+        ),
+        crate = "serde_"
+    )
 )]
-#[derive(DeriveWhere)]
-#[derive_where(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Zeroize)]
+#[derive_where(Clone)]
+#[derive_where(Debug, Eq, Hash, Ord, PartialEq, PartialOrd; KG::Pk)]
 pub struct Ke1Message<KG: KeGroup> {
     pub(crate) client_nonce: GenericArray<u8, NonceLen>,
     pub(crate) client_e_pk: PublicKey<KG>,
 }
 
+impl<KG: KeGroup> Drop for Ke1Message<KG> {
+    fn drop(&mut self) {
+        self.client_nonce.zeroize();
+    }
+}
+
+impl<KG: KeGroup> ZeroizeOnDrop for Ke1Message<KG> {}
+
 /// The server state produced after the second key exchange message
 #[cfg_attr(
     feature = "serde",
     derive(serde_::Deserialize, serde_::Serialize),
     serde(bound = "", crate = "serde_")
 )]
-#[derive(DeriveWhere)]
-#[derive_where(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Zeroize(drop))]
+#[derive_where(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
 pub struct Ke2State<D: Hash>
 where
     D::Core: ProxyHash,
@@ -96,14 +124,41 @@ where
     session_key: Output<D>,
 }
 
+impl<D: Hash> Drop for Ke2State<D>
+where
+    D::Core: ProxyHash,
+    <D::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
+    Le<<D::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
+{
+    fn drop(&mut self) {
+        self.km3.zeroize();
+        self.hashed_transcript.zeroize();
+        self.session_key.zeroize();
+    }
+}
+
+impl<D: Hash> ZeroizeOnDrop for Ke2State<D>
+where
+    D::Core: ProxyHash,
+    <D::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
+    Le<<D::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
+{
+}
+
 /// The second key exchange message
 #[cfg_attr(
     feature = "serde",
     derive(serde_::Deserialize, serde_::Serialize),
-    serde(bound = "", crate = "serde_")
+    serde(
+        bound(
+            deserialize = "KG::Pk: serde_::Deserialize<'de>",
+            serialize = "KG::Pk: serde_::Serialize",
+        ),
+        crate = "serde_"
+    )
 )]
-#[derive(DeriveWhere)]
-#[derive_where(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
+#[derive_where(Clone)]
+#[derive_where(Debug, Eq, Hash, Ord, PartialEq, PartialOrd; KG::Pk)]
 pub struct Ke2Message<D: Hash, KG: KeGroup>
 where
     D::Core: ProxyHash,
@@ -115,13 +170,32 @@ where
     mac: Output<D>,
 }
 
+impl<D: Hash, KG: KeGroup> Drop for Ke2Message<D, KG>
+where
+    D::Core: ProxyHash,
+    <D::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
+    Le<<D::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
+{
+    fn drop(&mut self) {
+        self.server_nonce.zeroize();
+        self.mac.zeroize();
+    }
+}
+
+impl<D: Hash, KG: KeGroup> ZeroizeOnDrop for Ke2Message<D, KG>
+where
+    D::Core: ProxyHash,
+    <D::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
+    Le<<D::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
+{
+}
+
 /// The third key exchange message
 #[cfg_attr(
     feature = "serde",
     derive(serde_::Deserialize, serde_::Serialize),
     serde(bound = "", crate = "serde_")
 )]
-#[derive(DeriveWhere)]
 #[derive_where(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
 pub struct Ke3Message<D: Hash>
 where
@@ -132,6 +206,25 @@ where
     mac: Output<D>,
 }
 
+impl<D: Hash> Drop for Ke3Message<D>
+where
+    D::Core: ProxyHash,
+    <D::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
+    Le<<D::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
+{
+    fn drop(&mut self) {
+        self.mac.zeroize();
+    }
+}
+
+impl<D: Hash> ZeroizeOnDrop for Ke3Message<D>
+where
+    D::Core: ProxyHash,
+    <D::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
+    Le<<D::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
+{
+}
+
 ////////////////////////////////
 // High-level Implementations //
 // ========================== //
@@ -210,13 +303,13 @@ where
             .chain_iter(id_s.into_iter())
             .chain_iter(l2_bytes)
             .chain(server_nonce)
-            .chain(&server_e_kp.public().to_arr());
+            .chain(&server_e_kp.public().to_bytes());
 
         let result = derive_3dh_keys::<D, KG, S>(
             TripleDHComponents {
                 pk1: ke1_message.client_e_pk.clone(),
                 sk1: server_e_kp.private().clone(),
-                pk2: ke1_message.client_e_pk,
+                pk2: ke1_message.client_e_pk.clone(),
                 sk2: server_s_sk,
                 pk3: client_s_pk,
                 sk3: server_e_kp.private().clone(),
@@ -268,7 +361,7 @@ where
             .chain_iter(serialized_credential_request)
             .chain_iter(id_s)
             .chain_iter(l2_component)
-            .chain_iter(ke2_message.to_bytes_without_info_or_mac());
+            .chain(ke2_message.to_bytes_without_mac());
 
         let result = derive_3dh_keys::<D, KG, PrivateKey<KG>>(
             TripleDHComponents {
@@ -322,10 +415,6 @@ where
 
         Ok(ke2_state.session_key.clone())
     }
-
-    fn ke2_message_size() -> usize {
-        NonceLen::USIZE + <KG as KeGroup>::PkLen::USIZE + OutputSize::<D>::USIZE
-    }
 }
 
 /////////////////////////
@@ -489,7 +578,7 @@ impl<KG: KeGroup> FromBytes for Ke1State<KG> {
         let checked_bytes = check_slice_size_atleast(bytes, key_len + nonce_len, "ke1_state")?;
 
         Ok(Self {
-            client_e_sk: PrivateKey::from_bytes(&checked_bytes[..key_len])?,
+            client_e_sk: PrivateKey::deserialize(&checked_bytes[..key_len])?,
             client_nonce: GenericArray::clone_from_slice(
                 &checked_bytes[key_len..key_len + nonce_len],
             ),
@@ -506,7 +595,7 @@ where
     type Len = Sum<KG::SkLen, NonceLen>;
 
     fn to_bytes(&self) -> GenericArray<u8, Self::Len> {
-        self.client_e_sk.to_arr().concat(self.client_nonce)
+        self.client_e_sk.serialize().concat(self.client_nonce)
     }
 }
 
@@ -521,7 +610,7 @@ impl<KG: KeGroup> FromBytes for Ke1Message<KG> {
 
         Ok(Self {
             client_nonce: GenericArray::clone_from_slice(&checked_nonce[..nonce_len]),
-            client_e_pk: PublicKey::from_bytes(&checked_nonce[nonce_len..])?,
+            client_e_pk: PublicKey::deserialize(&checked_nonce[nonce_len..])?,
         })
     }
 }
@@ -535,7 +624,7 @@ where
     type Len = Sum<NonceLen, KG::PkLen>;
 
     fn to_bytes(&self) -> GenericArray<u8, Self::Len> {
-        self.client_nonce.concat(self.client_e_pk.to_arr())
+        self.client_nonce.concat(self.client_e_pk.to_bytes())
     }
 }
 
@@ -602,13 +691,11 @@ where
         )?;
 
         // Check the public key bytes
-        let server_e_pk = KeyPair::<KG>::check_public_key(PublicKey::from_bytes(
-            &unchecked_server_e_pk[..key_len],
-        )?)?;
+        let server_e_pk = PublicKey::deserialize(&unchecked_server_e_pk[..key_len])?;
 
         Ok(Self {
             server_nonce: GenericArray::clone_from_slice(&checked_nonce[..nonce_len]),
-            server_e_pk: PublicKey::from_bytes(&server_e_pk)?,
+            server_e_pk,
             mac: GenericArray::clone_from_slice(checked_mac),
         })
     }
@@ -628,7 +715,7 @@ where
 
     fn to_bytes(&self) -> GenericArray<u8, Self::Len> {
         self.server_nonce
-            .concat(self.server_e_pk.to_arr())
+            .concat(self.server_e_pk.to_bytes())
             .concat(self.mac.clone())
     }
 }
@@ -638,9 +725,11 @@ where
     D::Core: ProxyHash,
     <D::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
     Le<<D::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
+    NonceLen: Add<KG::PkLen>,
+    Sum<NonceLen, KG::PkLen>: ArrayLength<u8>,
 {
-    fn to_bytes_without_info_or_mac(&self) -> impl Iterator<Item = &[u8]> {
-        [self.server_nonce.as_slice(), self.server_e_pk.as_slice()].into_iter()
+    fn to_bytes_without_mac(&self) -> GenericArray<u8, Sum<NonceLen, KG::PkLen>> {
+        self.server_nonce.concat(self.server_e_pk.to_bytes())
     }
 }
 
diff --git a/src/keypair.rs b/src/keypair.rs
index 40fa8c87..3c133051 100644
--- a/src/keypair.rs
+++ b/src/keypair.rs
@@ -9,16 +9,14 @@
 
 #![allow(unsafe_code)]
 
-use core::ops::Deref;
-
-use derive_where::DeriveWhere;
-use generic_array::typenum::Unsigned;
+use derive_where::derive_where;
 use generic_array::{ArrayLength, GenericArray};
 use rand::{CryptoRng, RngCore};
-use zeroize::Zeroize;
+use zeroize::ZeroizeOnDrop;
 
 use crate::errors::{InternalError, ProtocolError};
 use crate::key_exchange::group::KeGroup;
+use crate::serialization::GenericArrayExt;
 
 /// A Keypair trait with public-private verification
 #[cfg_attr(
@@ -26,15 +24,14 @@ use crate::key_exchange::group::KeGroup;
     derive(serde_::Deserialize, serde_::Serialize),
     serde(
         bound(
-            deserialize = "S: serde_::Deserialize<'de>",
-            serialize = "S: serde_::Serialize"
+            deserialize = "KG::Pk: serde_::Deserialize<'de>, S: serde_::Deserialize<'de>",
+            serialize = "KG::Pk: serde_::Serialize, S: serde_::Serialize"
         ),
         crate = "serde_"
     )
 )]
-#[derive(DeriveWhere)]
-#[derive_where(Clone, Zeroize(drop))]
-#[derive_where(Debug, Eq, Hash, Ord, PartialEq, PartialOrd; S)]
+#[derive_where(Clone)]
+#[derive_where(Debug, Eq, Hash, Ord, PartialEq, PartialOrd; KG::Pk, S)]
 pub struct KeyPair<KG: KeGroup, S: SecretKey<KG> = PrivateKey<KG>> {
     pk: PublicKey<KG>,
     sk: S,
@@ -51,14 +48,6 @@ impl<KG: KeGroup, S: SecretKey<KG>> KeyPair<KG, S> {
         &self.sk
     }
 
-    /// Check whether a public key is valid. This is meant to be applied on
-    /// material provided through the network which fits the key representation
-    /// (i.e. can be mapped to a curve point), but presents some risk - e.g.
-    /// small subgroup check
-    pub(crate) fn check_public_key(key: PublicKey<KG>) -> Result<PublicKey<KG>, InternalError> {
-        KG::from_pk_slice(GenericArray::from_slice(&key.0)).map(|_| key)
-    }
-
     /// Obtains a KeyPair from a slice representing the private key
     pub fn from_private_key_slice(input: &[u8]) -> Result<Self, ProtocolError<S::Error>> {
         Self::from_private_key(S::deserialize(input)?)
@@ -77,14 +66,18 @@ impl<KG: KeGroup> KeyPair<KG> {
         let sk = KG::random_sk(rng);
         let pk = KG::public_key(&sk);
         Self {
-            pk: PublicKey(Key(pk.to_arr())),
-            sk: PrivateKey(Key(sk)),
+            pk: PublicKey(pk),
+            sk: PrivateKey(sk),
         }
     }
 }
 
 #[cfg(test)]
-impl<KG: KeGroup> KeyPair<KG> {
+impl<KG: KeGroup> KeyPair<KG>
+where
+    KG::Pk: std::fmt::Debug,
+    KG::Sk: std::fmt::Debug,
+{
     /// Test-only strategy returning a proptest Strategy based on
     /// generate_random
     fn uniform_keypair_strategy() -> proptest::prelude::BoxedStrategy<Self> {
@@ -104,70 +97,39 @@ impl<KG: KeGroup> KeyPair<KG> {
     }
 }
 
-/// A minimalist key type built around a \[u8; 32\]
-#[cfg_attr(
-    feature = "serde",
-    derive(serde_::Deserialize, serde_::Serialize),
-    serde(bound = "", crate = "serde_")
-)]
-#[derive(DeriveWhere)]
-#[derive_where(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Zeroize(drop))]
-pub struct Key<L: ArrayLength<u8>>(GenericArray<u8, L>);
-
-impl<L: ArrayLength<u8>> Deref for Key<L> {
-    type Target = GenericArray<u8, L>;
-
-    fn deref(&self) -> &Self::Target {
-        &self.0
-    }
-}
-
-// Don't make it implement SizedBytes so that it's not constructible outside of
-// this module.
-impl<L: ArrayLength<u8>> Key<L> {
-    /// Convert to bytes
-    pub fn to_arr(&self) -> GenericArray<u8, L> {
-        self.0.clone()
-    }
-}
-
 /// Wrapper around a Key to enforce that it's a private one.
 #[cfg_attr(
     feature = "serde",
     derive(serde_::Deserialize, serde_::Serialize),
-    serde(bound = "", crate = "serde_")
+    serde(
+        bound(
+            deserialize = "KG::Sk: serde_::Deserialize<'de>",
+            serialize = "KG::Sk: serde_::Serialize"
+        ),
+        crate = "serde_"
+    )
 )]
-#[derive(DeriveWhere)]
-#[derive_where(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Zeroize(drop))]
-pub struct PrivateKey<KG: KeGroup>(Key<KG::SkLen>);
+#[derive_where(Clone)]
+#[derive_where(Debug, Eq, Hash, Ord, PartialEq, PartialOrd; KG::Sk)]
+pub struct PrivateKey<KG: KeGroup>(KG::Sk);
 
-// This can't be derived because of the use of a generic parameter
-impl<KG: KeGroup> Deref for PrivateKey<KG> {
-    type Target = Key<KG::SkLen>;
-
-    fn deref(&self) -> &Self::Target {
-        &self.0
+impl<KG: KeGroup> Drop for PrivateKey<KG> {
+    fn drop(&mut self) {
+        KG::zeroize_sk_on_drop(&mut self.0)
     }
 }
 
+impl<KG: KeGroup> ZeroizeOnDrop for PrivateKey<KG> {}
+
 impl<KG: KeGroup> PrivateKey<KG> {
     /// Convert from bytes
-    pub fn from_arr(key_bytes: GenericArray<u8, KG::SkLen>) -> Self {
-        PrivateKey(Key(key_bytes))
-    }
-
-    /// Convert from slice
-    pub fn from_bytes(key_bytes: &[u8]) -> Result<Self, InternalError> {
-        if key_bytes.len() == KG::SkLen::USIZE {
-            Ok(Self::from_arr(GenericArray::from_slice(key_bytes).clone()))
-        } else {
-            Err(InternalError::InvalidByteSequence)
-        }
+    pub fn from_bytes(key_bytes: &GenericArray<u8, KG::SkLen>) -> Result<Self, InternalError> {
+        KG::deserialize_sk(key_bytes).map(Self)
     }
 }
 
 /// A trait specifying the requirements for a private key container
-pub trait SecretKey<KG: KeGroup>: Clone + Sized + Zeroize {
+pub trait SecretKey<KG: KeGroup>: Clone + Sized {
     /// Custom error type that can be passed down to `InternalError::Custom`
     type Error;
     /// Serialization size in bytes.
@@ -197,20 +159,19 @@ impl<KG: KeGroup> SecretKey<KG> for PrivateKey<KG> {
         &self,
         pk: PublicKey<KG>,
     ) -> Result<GenericArray<u8, KG::PkLen>, InternalError> {
-        let pk = KG::from_pk_slice(&pk)?;
-        Ok(pk.diffie_hellman(self))
+        Ok(KG::diffie_hellman(&pk.0, &self.0))
     }
 
     fn public_key(&self) -> Result<PublicKey<KG>, InternalError> {
-        Ok(PublicKey(Key(KG::public_key(&self.0).to_arr())))
+        Ok(PublicKey(KG::public_key(&self.0)))
     }
 
     fn serialize(&self) -> GenericArray<u8, Self::Len> {
-        self.to_arr()
+        KG::serialize_sk(&self.0)
     }
 
     fn deserialize(input: &[u8]) -> Result<Self, InternalError> {
-        PrivateKey::from_bytes(input).map_err(InternalError::from)
+        GenericArray::try_from_slice(input).and_then(Self::from_bytes)
     }
 }
 
@@ -218,93 +179,54 @@ impl<KG: KeGroup> SecretKey<KG> for PrivateKey<KG> {
 #[cfg_attr(
     feature = "serde",
     derive(serde_::Deserialize, serde_::Serialize),
-    serde(bound = "", crate = "serde_")
+    serde(
+        bound(
+            deserialize = "KG::Pk: serde_::Deserialize<'de>",
+            serialize = "KG::Pk: serde_::Serialize"
+        ),
+        crate = "serde_"
+    )
 )]
-#[derive(DeriveWhere)]
-#[derive_where(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Zeroize(drop))]
-pub struct PublicKey<KG: KeGroup>(Key<KG::PkLen>);
-
-impl<KG: KeGroup> Deref for PublicKey<KG> {
-    type Target = Key<KG::PkLen>;
-
-    fn deref(&self) -> &Self::Target {
-        &self.0
-    }
-}
+#[derive_where(Clone)]
+#[derive_where(Debug, Eq, Hash, Ord, PartialEq, PartialOrd; KG::Pk)]
+pub struct PublicKey<KG: KeGroup>(KG::Pk);
 
 impl<KG: KeGroup> PublicKey<KG> {
     /// Convert from bytes
-    pub fn from_arr(key_bytes: GenericArray<u8, KG::PkLen>) -> Self {
-        Self(Key(key_bytes))
+    pub fn from_bytes(key_bytes: &GenericArray<u8, KG::PkLen>) -> Result<Self, InternalError> {
+        KG::deserialize_pk(key_bytes).map(Self)
+    }
+
+    /// Convert to bytes
+    pub fn to_bytes(&self) -> GenericArray<u8, KG::PkLen> {
+        KG::serialize_pk(&self.0)
     }
 
     /// Convert from slice
-    pub fn from_bytes(key_bytes: &[u8]) -> Result<Self, InternalError> {
-        if key_bytes.len() == KG::PkLen::USIZE {
-            Ok(Self::from_arr(GenericArray::from_slice(key_bytes).clone()))
-        } else {
-            Err(InternalError::InvalidByteSequence)
-        }
+    pub fn deserialize(input: &[u8]) -> Result<Self, InternalError> {
+        GenericArray::try_from_slice(input).and_then(Self::from_bytes)
     }
 }
 
 #[cfg(test)]
 mod tests {
-    use core::slice::from_raw_parts;
-    use std::vec;
-
-    use generic_array::typenum::Unsigned;
     use rand::rngs::OsRng;
 
     use super::*;
     use crate::errors::*;
+    use crate::util;
 
     #[test]
-    fn test_zeroize_key() -> Result<(), ProtocolError> {
-        fn inner<G: KeGroup>() -> Result<(), ProtocolError> {
-            let key_len = G::PkLen::USIZE;
-            let mut key = Key::<G::PkLen>(GenericArray::clone_from_slice(&vec![1u8; key_len]));
-            let ptr = key.as_ptr();
-
-            Zeroize::zeroize(&mut key);
-
-            let bytes = unsafe { from_raw_parts(ptr, key_len) };
-            assert!(bytes.iter().all(|&x| x == 0));
-
-            Ok(())
-        }
-
-        #[cfg(feature = "ristretto255")]
-        inner::<curve25519_dalek::ristretto::RistrettoPoint>()?;
-        #[cfg(feature = "p256")]
-        inner::<p256_::PublicKey>()?;
-
-        Ok(())
-    }
-
-    #[test]
-    fn test_zeroize_keypair() {
+    fn test_zeroize_key() {
         fn inner<G: KeGroup>() {
             let mut rng = OsRng;
-            let mut keypair = KeyPair::<G>::generate_random(&mut rng);
-            let pk_ptr = keypair.pk.as_ptr();
-            let sk_ptr = keypair.sk.as_ptr();
-            let pk_len = G::PkLen::USIZE;
-            let sk_len = G::SkLen::USIZE;
-
-            Zeroize::zeroize(&mut keypair);
-
-            let pk_bytes = unsafe { from_raw_parts(pk_ptr, pk_len) };
-            let sk_bytes = unsafe { from_raw_parts(sk_ptr, sk_len) };
-
-            assert!(pk_bytes.iter().all(|&x| x == 0));
-            assert!(sk_bytes.iter().all(|&x| x == 0));
+            let mut key = PrivateKey::<G>(G::random_sk(&mut rng));
+            util::test_zeroize_on_drop(&mut key);
         }
 
         #[cfg(feature = "ristretto255")]
-        inner::<curve25519_dalek::ristretto::RistrettoPoint>();
-        #[cfg(feature = "p256")]
-        inner::<p256_::PublicKey>();
+        inner::<crate::Ristretto255>();
+        inner::<::p256::NistP256>();
     }
 
     macro_rules! test {
@@ -317,12 +239,6 @@ mod tests {
                 use super::*;
 
                 proptest! {
-                    #[test]
-                    fn check(kp in KeyPair::<$point>::uniform_keypair_strategy()) {
-                        let pk = kp.public();
-                        prop_assert!(KeyPair::<$point>::check_public_key(pk.clone()).is_ok());
-                    }
-
                     #[test]
                     fn pub_from_priv(kp in KeyPair::<$point>::uniform_keypair_strategy()) {
                         let pk = kp.public();
@@ -342,10 +258,10 @@ mod tests {
 
                     #[test]
                     fn private_key_slice(kp in KeyPair::<$point>::uniform_keypair_strategy()) {
-                        let sk_bytes = kp.private().to_vec();
+                        let sk_bytes = kp.private().serialize().to_vec();
 
                         let kp2 = KeyPair::<$point>::from_private_key_slice(&sk_bytes)?;
-                        let kp2_private_bytes = kp2.private().to_vec();
+                        let kp2_private_bytes = kp2.private().serialize().to_vec();
 
                         prop_assert_eq!(sk_bytes, kp2_private_bytes);
                     }
@@ -355,16 +271,11 @@ mod tests {
     }
 
     #[cfg(feature = "ristretto255")]
-    test!(ristretto, curve25519_dalek::ristretto::RistrettoPoint);
-    #[cfg(feature = "p256")]
-    test!(p256, p256_::PublicKey);
+    test!(ristretto, crate::Ristretto255);
+    test!(p256, ::p256::NistP256);
 
     #[test]
     fn remote_key() {
-        #[cfg(feature = "ristretto255")]
-        use curve25519_dalek::ristretto::RistrettoPoint as KeCurve;
-        #[cfg(not(feature = "ristretto255"))]
-        use p256_::PublicKey as KeCurve;
         use rand::rngs::OsRng;
 
         use crate::{
@@ -379,19 +290,20 @@ mod tests {
 
         impl CipherSuite for Default {
             #[cfg(feature = "ristretto255")]
-            type OprfGroup = KeCurve;
+            type OprfGroup = crate::Ristretto255;
             #[cfg(not(feature = "ristretto255"))]
-            type OprfGroup = p256_::ProjectivePoint;
-            type KeGroup = KeCurve;
-            type KeyExchange = crate::key_exchange::tripledh::TripleDH;
+            type OprfGroup = ::p256::NistP256;
             #[cfg(feature = "ristretto255")]
-            type Hash = sha2::Sha512;
+            type KeGroup = crate::Ristretto255;
             #[cfg(not(feature = "ristretto255"))]
-            type Hash = sha2::Sha256;
+            type KeGroup = ::p256::NistP256;
+            type KeyExchange = crate::key_exchange::tripledh::TripleDH;
             type SlowHash = crate::slow_hash::NoOpHash;
         }
 
-        #[derive(Clone, Zeroize)]
+        type KeCurve = <Default as CipherSuite>::KeGroup;
+
+        #[derive(Clone)]
         struct RemoteKey(PrivateKey<KeCurve>);
 
         impl SecretKey<KeCurve> for RemoteKey {
@@ -422,7 +334,7 @@ mod tests {
         const PASSWORD: &str = "password";
 
         let sk = KeCurve::random_sk(&mut OsRng);
-        let sk = RemoteKey(PrivateKey(Key(sk)));
+        let sk = RemoteKey(PrivateKey(sk));
         let keypair = KeyPair::from_private_key(sk).unwrap();
 
         let server_setup = ServerSetup::<Default, RemoteKey>::new_with_key(&mut OsRng, keypair);
diff --git a/src/lib.rs b/src/lib.rs
index 37645129..6038bbf6 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -14,7 +14,7 @@
 //!
 //! ### Minimum Supported Rust Version
 //!
-//! Rust **1.56** or higher.
+//! Rust **1.57** or higher.
 //!
 //! # Overview
 //!
@@ -33,10 +33,9 @@
 //! use opaque_ke::CipherSuite;
 //! struct Default;
 //! impl CipherSuite for Default {
-//!     type OprfGroup = curve25519_dalek::ristretto::RistrettoPoint;
-//!     type KeGroup = curve25519_dalek::ristretto::RistrettoPoint;
+//!     type OprfGroup = opaque_ke::Ristretto255;
+//!     type KeGroup = opaque_ke::Ristretto255;
 //!     type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDH;
-//!     type Hash = sha2::Sha512;
 //!     type SlowHash = opaque_ke::slow_hash::NoOpHash;
 //! }
 //! ```
@@ -59,18 +58,16 @@
 //! # struct Default;
 //! # #[cfg(feature = "ristretto255")]
 //! # impl CipherSuite for Default {
-//! #     type OprfGroup = curve25519_dalek::ristretto::RistrettoPoint;
-//! #     type KeGroup = curve25519_dalek::ristretto::RistrettoPoint;
+//! #     type OprfGroup = opaque_ke::Ristretto255;
+//! #     type KeGroup = opaque_ke::Ristretto255;
 //! #     type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDH;
-//! #     type Hash = sha2::Sha512;
 //! #     type SlowHash = opaque_ke::slow_hash::NoOpHash;
 //! # }
 //! # #[cfg(not(feature = "ristretto255"))]
 //! # impl CipherSuite for Default {
-//! #     type OprfGroup = p256_::ProjectivePoint;
-//! #     type KeGroup = p256_::PublicKey;
+//! #     type OprfGroup = p256::NistP256;
+//! #     type KeGroup = p256::NistP256;
 //! #     type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDH;
-//! #     type Hash = sha2::Sha256;
 //! #     type SlowHash = opaque_ke::slow_hash::NoOpHash;
 //! # }
 //! use rand::rngs::OsRng;
@@ -110,18 +107,16 @@
 //! # struct Default;
 //! # #[cfg(feature = "ristretto255")]
 //! # impl CipherSuite for Default {
-//! #     type OprfGroup = curve25519_dalek::ristretto::RistrettoPoint;
-//! #     type KeGroup = curve25519_dalek::ristretto::RistrettoPoint;
+//! #     type OprfGroup = opaque_ke::Ristretto255;
+//! #     type KeGroup = opaque_ke::Ristretto255;
 //! #     type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDH;
-//! #     type Hash = sha2::Sha512;
 //! #     type SlowHash = opaque_ke::slow_hash::NoOpHash;
 //! # }
 //! # #[cfg(not(feature = "ristretto255"))]
 //! # impl CipherSuite for Default {
-//! #     type OprfGroup = p256_::ProjectivePoint;
-//! #     type KeGroup = p256_::PublicKey;
+//! #     type OprfGroup = p256::NistP256;
+//! #     type KeGroup = p256::NistP256;
 //! #     type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDH;
-//! #     type Hash = sha2::Sha256;
 //! #     type SlowHash = opaque_ke::slow_hash::NoOpHash;
 //! # }
 //! use opaque_ke::ClientRegistration;
@@ -150,18 +145,16 @@
 //! # struct Default;
 //! # #[cfg(feature = "ristretto255")]
 //! # impl CipherSuite for Default {
-//! #     type OprfGroup = curve25519_dalek::ristretto::RistrettoPoint;
-//! #     type KeGroup = curve25519_dalek::ristretto::RistrettoPoint;
+//! #     type OprfGroup = opaque_ke::Ristretto255;
+//! #     type KeGroup = opaque_ke::Ristretto255;
 //! #     type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDH;
-//! #     type Hash = sha2::Sha512;
 //! #     type SlowHash = opaque_ke::slow_hash::NoOpHash;
 //! # }
 //! # #[cfg(not(feature = "ristretto255"))]
 //! # impl CipherSuite for Default {
-//! #     type OprfGroup = p256_::ProjectivePoint;
-//! #     type KeGroup = p256_::PublicKey;
+//! #     type OprfGroup = p256::NistP256;
+//! #     type KeGroup = p256::NistP256;
 //! #     type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDH;
-//! #     type Hash = sha2::Sha256;
 //! #     type SlowHash = opaque_ke::slow_hash::NoOpHash;
 //! # }
 //! # use rand::{rngs::OsRng, RngCore};
@@ -199,18 +192,16 @@
 //! # struct Default;
 //! # #[cfg(feature = "ristretto255")]
 //! # impl CipherSuite for Default {
-//! #     type OprfGroup = curve25519_dalek::ristretto::RistrettoPoint;
-//! #     type KeGroup = curve25519_dalek::ristretto::RistrettoPoint;
+//! #     type OprfGroup = opaque_ke::Ristretto255;
+//! #     type KeGroup = opaque_ke::Ristretto255;
 //! #     type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDH;
-//! #     type Hash = sha2::Sha512;
 //! #     type SlowHash = opaque_ke::slow_hash::NoOpHash;
 //! # }
 //! # #[cfg(not(feature = "ristretto255"))]
 //! # impl CipherSuite for Default {
-//! #     type OprfGroup = p256_::ProjectivePoint;
-//! #     type KeGroup = p256_::PublicKey;
+//! #     type OprfGroup = p256::NistP256;
+//! #     type KeGroup = p256::NistP256;
 //! #     type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDH;
-//! #     type Hash = sha2::Sha256;
 //! #     type SlowHash = opaque_ke::slow_hash::NoOpHash;
 //! # }
 //! # use rand::{rngs::OsRng, RngCore};
@@ -249,18 +240,16 @@
 //! # struct Default;
 //! # #[cfg(feature = "ristretto255")]
 //! # impl CipherSuite for Default {
-//! #     type OprfGroup = curve25519_dalek::ristretto::RistrettoPoint;
-//! #     type KeGroup = curve25519_dalek::ristretto::RistrettoPoint;
+//! #     type OprfGroup = opaque_ke::Ristretto255;
+//! #     type KeGroup = opaque_ke::Ristretto255;
 //! #     type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDH;
-//! #     type Hash = sha2::Sha512;
 //! #     type SlowHash = opaque_ke::slow_hash::NoOpHash;
 //! # }
 //! # #[cfg(not(feature = "ristretto255"))]
 //! # impl CipherSuite for Default {
-//! #     type OprfGroup = p256_::ProjectivePoint;
-//! #     type KeGroup = p256_::PublicKey;
+//! #     type OprfGroup = p256::NistP256;
+//! #     type KeGroup = p256::NistP256;
 //! #     type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDH;
-//! #     type Hash = sha2::Sha256;
 //! #     type SlowHash = opaque_ke::slow_hash::NoOpHash;
 //! # }
 //! # use rand::{rngs::OsRng, RngCore};
@@ -304,18 +293,16 @@
 //! # struct Default;
 //! # #[cfg(feature = "ristretto255")]
 //! # impl CipherSuite for Default {
-//! #     type OprfGroup = curve25519_dalek::ristretto::RistrettoPoint;
-//! #     type KeGroup = curve25519_dalek::ristretto::RistrettoPoint;
+//! #     type OprfGroup = opaque_ke::Ristretto255;
+//! #     type KeGroup = opaque_ke::Ristretto255;
 //! #     type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDH;
-//! #     type Hash = sha2::Sha512;
 //! #     type SlowHash = opaque_ke::slow_hash::NoOpHash;
 //! # }
 //! # #[cfg(not(feature = "ristretto255"))]
 //! # impl CipherSuite for Default {
-//! #     type OprfGroup = p256_::ProjectivePoint;
-//! #     type KeGroup = p256_::PublicKey;
+//! #     type OprfGroup = p256::NistP256;
+//! #     type KeGroup = p256::NistP256;
 //! #     type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDH;
-//! #     type Hash = sha2::Sha256;
 //! #     type SlowHash = opaque_ke::slow_hash::NoOpHash;
 //! # }
 //! # use rand::{rngs::OsRng, RngCore};
@@ -343,18 +330,16 @@
 //! # struct Default;
 //! # #[cfg(feature = "ristretto255")]
 //! # impl CipherSuite for Default {
-//! #     type OprfGroup = curve25519_dalek::ristretto::RistrettoPoint;
-//! #     type KeGroup = curve25519_dalek::ristretto::RistrettoPoint;
+//! #     type OprfGroup = opaque_ke::Ristretto255;
+//! #     type KeGroup = opaque_ke::Ristretto255;
 //! #     type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDH;
-//! #     type Hash = sha2::Sha512;
 //! #     type SlowHash = opaque_ke::slow_hash::NoOpHash;
 //! # }
 //! # #[cfg(not(feature = "ristretto255"))]
 //! # impl CipherSuite for Default {
-//! #     type OprfGroup = p256_::ProjectivePoint;
-//! #     type KeGroup = p256_::PublicKey;
+//! #     type OprfGroup = p256::NistP256;
+//! #     type KeGroup = p256::NistP256;
 //! #     type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDH;
-//! #     type Hash = sha2::Sha256;
 //! #     type SlowHash = opaque_ke::slow_hash::NoOpHash;
 //! # }
 //! # use rand::{rngs::OsRng, RngCore};
@@ -408,18 +393,16 @@
 //! # struct Default;
 //! # #[cfg(feature = "ristretto255")]
 //! # impl CipherSuite for Default {
-//! #     type OprfGroup = curve25519_dalek::ristretto::RistrettoPoint;
-//! #     type KeGroup = curve25519_dalek::ristretto::RistrettoPoint;
+//! #     type OprfGroup = opaque_ke::Ristretto255;
+//! #     type KeGroup = opaque_ke::Ristretto255;
 //! #     type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDH;
-//! #     type Hash = sha2::Sha512;
 //! #     type SlowHash = opaque_ke::slow_hash::NoOpHash;
 //! # }
 //! # #[cfg(not(feature = "ristretto255"))]
 //! # impl CipherSuite for Default {
-//! #     type OprfGroup = p256_::ProjectivePoint;
-//! #     type KeGroup = p256_::PublicKey;
+//! #     type OprfGroup = p256::NistP256;
+//! #     type KeGroup = p256::NistP256;
 //! #     type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDH;
-//! #     type Hash = sha2::Sha256;
 //! #     type SlowHash = opaque_ke::slow_hash::NoOpHash;
 //! # }
 //! # use rand::{rngs::OsRng, RngCore};
@@ -466,18 +449,16 @@
 //! # struct Default;
 //! # #[cfg(feature = "ristretto255")]
 //! # impl CipherSuite for Default {
-//! #     type OprfGroup = curve25519_dalek::ristretto::RistrettoPoint;
-//! #     type KeGroup = curve25519_dalek::ristretto::RistrettoPoint;
+//! #     type OprfGroup = opaque_ke::Ristretto255;
+//! #     type KeGroup = opaque_ke::Ristretto255;
 //! #     type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDH;
-//! #     type Hash = sha2::Sha512;
 //! #     type SlowHash = opaque_ke::slow_hash::NoOpHash;
 //! # }
 //! # #[cfg(not(feature = "ristretto255"))]
 //! # impl CipherSuite for Default {
-//! #     type OprfGroup = p256_::ProjectivePoint;
-//! #     type KeGroup = p256_::PublicKey;
+//! #     type OprfGroup = p256::NistP256;
+//! #     type KeGroup = p256::NistP256;
 //! #     type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDH;
-//! #     type Hash = sha2::Sha256;
 //! #     type SlowHash = opaque_ke::slow_hash::NoOpHash;
 //! # }
 //! # use rand::{rngs::OsRng, RngCore};
@@ -564,18 +545,16 @@
 //! # struct Default;
 //! # #[cfg(feature = "ristretto255")]
 //! # impl CipherSuite for Default {
-//! #     type OprfGroup = curve25519_dalek::ristretto::RistrettoPoint;
-//! #     type KeGroup = curve25519_dalek::ristretto::RistrettoPoint;
+//! #     type OprfGroup = opaque_ke::Ristretto255;
+//! #     type KeGroup = opaque_ke::Ristretto255;
 //! #     type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDH;
-//! #     type Hash = sha2::Sha512;
 //! #     type SlowHash = opaque_ke::slow_hash::NoOpHash;
 //! # }
 //! # #[cfg(not(feature = "ristretto255"))]
 //! # impl CipherSuite for Default {
-//! #     type OprfGroup = p256_::ProjectivePoint;
-//! #     type KeGroup = p256_::PublicKey;
+//! #     type OprfGroup = p256::NistP256;
+//! #     type KeGroup = p256::NistP256;
 //! #     type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDH;
-//! #     type Hash = sha2::Sha256;
 //! #     type SlowHash = opaque_ke::slow_hash::NoOpHash;
 //! # }
 //! # use rand::{rngs::OsRng, RngCore};
@@ -663,18 +642,16 @@
 //! # struct Default;
 //! # #[cfg(feature = "ristretto255")]
 //! # impl CipherSuite for Default {
-//! #     type OprfGroup = curve25519_dalek::ristretto::RistrettoPoint;
-//! #     type KeGroup = curve25519_dalek::ristretto::RistrettoPoint;
+//! #     type OprfGroup = opaque_ke::Ristretto255;
+//! #     type KeGroup = opaque_ke::Ristretto255;
 //! #     type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDH;
-//! #     type Hash = sha2::Sha512;
 //! #     type SlowHash = opaque_ke::slow_hash::NoOpHash;
 //! # }
 //! # #[cfg(not(feature = "ristretto255"))]
 //! # impl CipherSuite for Default {
-//! #     type OprfGroup = p256_::ProjectivePoint;
-//! #     type KeGroup = p256_::PublicKey;
+//! #     type OprfGroup = p256::NistP256;
+//! #     type KeGroup = p256::NistP256;
 //! #     type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDH;
-//! #     type Hash = sha2::Sha256;
 //! #     type SlowHash = opaque_ke::slow_hash::NoOpHash;
 //! # }
 //! # use rand::{rngs::OsRng, RngCore};
@@ -746,18 +723,16 @@
 //! # struct Default;
 //! # #[cfg(feature = "ristretto255")]
 //! # impl CipherSuite for Default {
-//! #     type OprfGroup = curve25519_dalek::ristretto::RistrettoPoint;
-//! #     type KeGroup = curve25519_dalek::ristretto::RistrettoPoint;
+//! #     type OprfGroup = opaque_ke::Ristretto255;
+//! #     type KeGroup = opaque_ke::Ristretto255;
 //! #     type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDH;
-//! #     type Hash = sha2::Sha512;
 //! #     type SlowHash = opaque_ke::slow_hash::NoOpHash;
 //! # }
 //! # #[cfg(not(feature = "ristretto255"))]
 //! # impl CipherSuite for Default {
-//! #     type OprfGroup = p256_::ProjectivePoint;
-//! #     type KeGroup = p256_::PublicKey;
+//! #     type OprfGroup = p256::NistP256;
+//! #     type KeGroup = p256::NistP256;
 //! #     type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDH;
-//! #     type Hash = sha2::Sha256;
 //! #     type SlowHash = opaque_ke::slow_hash::NoOpHash;
 //! # }
 //! # use rand::{rngs::OsRng, RngCore};
@@ -796,18 +771,16 @@
 //! # struct Default;
 //! # #[cfg(feature = "ristretto255")]
 //! # impl CipherSuite for Default {
-//! #     type OprfGroup = curve25519_dalek::ristretto::RistrettoPoint;
-//! #     type KeGroup = curve25519_dalek::ristretto::RistrettoPoint;
+//! #     type OprfGroup = opaque_ke::Ristretto255;
+//! #     type KeGroup = opaque_ke::Ristretto255;
 //! #     type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDH;
-//! #     type Hash = sha2::Sha512;
 //! #     type SlowHash = opaque_ke::slow_hash::NoOpHash;
 //! # }
 //! # #[cfg(not(feature = "ristretto255"))]
 //! # impl CipherSuite for Default {
-//! #     type OprfGroup = p256_::ProjectivePoint;
-//! #     type KeGroup = p256_::PublicKey;
+//! #     type OprfGroup = p256::NistP256;
+//! #     type KeGroup = p256::NistP256;
 //! #     type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDH;
-//! #     type Hash = sha2::Sha256;
 //! #     type SlowHash = opaque_ke::slow_hash::NoOpHash;
 //! # }
 //! # use rand::{rngs::OsRng, RngCore};
@@ -857,18 +830,16 @@
 //! # struct Default;
 //! # #[cfg(feature = "ristretto255")]
 //! # impl CipherSuite for Default {
-//! #     type OprfGroup = curve25519_dalek::ristretto::RistrettoPoint;
-//! #     type KeGroup = curve25519_dalek::ristretto::RistrettoPoint;
+//! #     type OprfGroup = opaque_ke::Ristretto255;
+//! #     type KeGroup = opaque_ke::Ristretto255;
 //! #     type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDH;
-//! #     type Hash = sha2::Sha512;
 //! #     type SlowHash = opaque_ke::slow_hash::NoOpHash;
 //! # }
 //! # #[cfg(not(feature = "ristretto255"))]
 //! # impl CipherSuite for Default {
-//! #     type OprfGroup = p256_::ProjectivePoint;
-//! #     type KeGroup = p256_::PublicKey;
+//! #     type OprfGroup = p256::NistP256;
+//! #     type KeGroup = p256::NistP256;
 //! #     type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDH;
-//! #     type Hash = sha2::Sha256;
 //! #     type SlowHash = opaque_ke::slow_hash::NoOpHash;
 //! # }
 //! # use rand::{rngs::OsRng, RngCore};
@@ -955,27 +926,25 @@
 //! # struct Default;
 //! # #[cfg(feature = "ristretto255")]
 //! # impl CipherSuite for Default {
-//! #     type OprfGroup = curve25519_dalek::ristretto::RistrettoPoint;
-//! #     type KeGroup = curve25519_dalek::ristretto::RistrettoPoint;
+//! #     type OprfGroup = opaque_ke::Ristretto255;
+//! #     type KeGroup = opaque_ke::Ristretto255;
 //! #     type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDH;
-//! #     type Hash = sha2::Sha512;
 //! #     type SlowHash = opaque_ke::slow_hash::NoOpHash;
 //! # }
 //! # #[cfg(not(feature = "ristretto255"))]
 //! # impl CipherSuite for Default {
-//! #     type OprfGroup = p256_::ProjectivePoint;
-//! #     type KeGroup = p256_::PublicKey;
+//! #     type OprfGroup = p256::NistP256;
+//! #     type KeGroup = p256::NistP256;
 //! #     type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDH;
-//! #     type Hash = sha2::Sha256;
 //! #     type SlowHash = opaque_ke::slow_hash::NoOpHash;
 //! # }
 //! # #[derive(Debug)]
 //! # struct YourRemoteKeyError;
-//! # #[derive(Clone, Zeroize)]
-//! # struct YourRemoteKey(PrivateKey<<Default as CipherSuite>::KeGroup>);
+//! # #[derive(Clone)]
+//! # struct YourRemoteKey(<<Default as CipherSuite>::KeGroup as KeGroup>::Sk);
 //! # impl YourRemoteKey {
 //! #     fn diffie_hellman(&self, pk: &[u8]) -> Result<GenericArray<u8, <<Default as CipherSuite>::KeGroup as KeGroup>::PkLen>, YourRemoteKeyError> { todo!() }
-//! #     fn public_key(&self) -> Result<GenericArray<u8, <<Default as CipherSuite>::KeGroup as KeGroup>::PkLen>, YourRemoteKeyError> { Ok(GenericArray::default()) }
+//! #     fn public_key(&self) -> Result<GenericArray<u8, <<Default as CipherSuite>::KeGroup as KeGroup>::PkLen>, YourRemoteKeyError> { Ok(<<Default as CipherSuite>::KeGroup>::serialize_pk(&<<Default as CipherSuite>::KeGroup>::public_key(&self.0))) }
 //! # }
 //! impl SecretKey<<Default as CipherSuite>::KeGroup> for YourRemoteKey {
 //!     type Error = YourRemoteKeyError;
@@ -985,14 +954,13 @@
 //!         &self,
 //!         pk: PublicKey<<Default as CipherSuite>::KeGroup>,
 //!     ) -> Result<GenericArray<u8, <<Default as CipherSuite>::KeGroup as KeGroup>::PkLen>, InternalError<Self::Error>> {
-//!         YourRemoteKey::diffie_hellman(self, &pk.to_arr()).map_err(InternalError::Custom)
+//!         YourRemoteKey::diffie_hellman(self, &pk.to_bytes()).map_err(InternalError::Custom)
 //!     }
 //!
 //!     fn public_key(
 //!         &self
 //!     ) -> Result<PublicKey<<Default as CipherSuite>::KeGroup>, InternalError<Self::Error>> {
-//!         YourRemoteKey::public_key(self).map(PublicKey::from_arr)
-//!             .map_err(InternalError::Custom)
+//!         PublicKey::from_bytes(&YourRemoteKey::public_key(self).map_err(InternalError::Custom)?).map_err(InternalError::into_custom)
 //!     }
 //!
 //!     fn serialize(&self) -> GenericArray<u8, Self::Len> {
@@ -1006,7 +974,7 @@
 //!     }
 //! }
 //!
-//! # let remote_key = YourRemoteKey(PrivateKey::from_arr(GenericArray::default()));
+//! # let remote_key = YourRemoteKey(<<Default as CipherSuite>::KeGroup>::random_sk(&mut OsRng));
 //! let keypair = KeyPair::from_private_key(remote_key).unwrap();
 //! let server_setup = ServerSetup::<Default, YourRemoteKey>::new_with_key(&mut OsRng, keypair);
 //! ```
@@ -1032,27 +1000,26 @@
 //!   `ristretto255_fiat_u32`. Any `ristretto255_*` backend feature will enable
 //!   the `ristretto255` feature, which can be used too, but keep in mind that
 //!   `curve25519-dalek` will fail to compile without a selected backend. This
-//!   enables the use of `curve25519_dalek::ristretto::RistrettoPoint` as a
-//!   `KeGroup` and `OprfGroup`.
+//!   enables the use of [`Ristretto255`] as a `KeGroup` and `OprfGroup`.
 //!
 //! - The `x25519` feature is similar to the `ristretto255` feature and requires
 //!   to select a backend like `x25519_u64`, other backends are the same as in
-//!   `ristretto255_*`. This enables `x25519_dalek::PublicKey` as a `KeGroup`.
+//!   `ristretto255_*`. This enables [`X25519`] as a `KeGroup`.
 //!
 //! - The `ristretto255_simd` feature is re-exported from [curve25519-dalek](https://doc.dalek.rs/curve25519_dalek/index.html#backends-and-features)
 //!   and enables parallel formulas, using either AVX2 or AVX512-IFMA. This will
 //!   automatically enable the `ristretto255_u64` feature and requires Rust
 //!   nightly.
 //!
-//! - The `p256` feature enables the use of `p256::PublicKey` as a `KeGroup` and
-//!   `p256::ProjectivePoint` as a `OprfGroup` for `CipherSuite`. Note that this
-//!   is currently an experimental feature ⚠️, and is not yet ready for
-//!   production use.
+//! - The `p256` feature enables the use of [`p256::NistP256`] as a `KeGroup`
+//!   and a `OprfGroup` for `CipherSuite`.
 //!
 //! - The `bench` feature is used only for running performance benchmarks for
 //!   this implementation.
+//!
+//! [`p256::NistP256`]: https://docs.rs/p256/latest/p256/struct.NistP256.html
 
-#![deny(unsafe_code)]
+#![cfg_attr(not(test), deny(unsafe_code))]
 #![no_std]
 #![warn(clippy::cargo, missing_docs)]
 #![allow(clippy::multiple_crate_versions, type_alias_bounds)]
@@ -1074,6 +1041,7 @@ mod messages;
 mod opaque;
 mod serialization;
 pub mod slow_hash;
+mod util;
 
 #[cfg(test)]
 mod tests;
@@ -1083,6 +1051,10 @@ mod tests;
 pub use ciphersuite::CipherSuite;
 pub use rand;
 
+#[cfg(feature = "ristretto255")]
+pub use crate::key_exchange::group::ristretto255::Ristretto255;
+#[cfg(feature = "x25519")]
+pub use crate::key_exchange::group::x25519::X25519;
 pub use crate::messages::{
     CredentialFinalization, CredentialFinalizationLen, CredentialRequest, CredentialRequestLen,
     CredentialResponse, CredentialResponseLen, RegistrationRequest, RegistrationRequestLen,
diff --git a/src/messages.rs b/src/messages.rs
old mode 100755
new mode 100644
index c0719977..282e3f6c
--- a/src/messages.rs
+++ b/src/messages.rs
@@ -9,26 +9,28 @@
 
 use core::ops::Add;
 
-use derive_where::DeriveWhere;
+use derive_where::derive_where;
 use digest::core_api::{BlockSizeUser, CoreProxy};
-use digest::Output;
+use digest::{Output, OutputSizeUser};
 use generic_array::sequence::Concat;
-use generic_array::typenum::{IsLess, Le, NonZero, Sum, Unsigned, U256};
+use generic_array::typenum::{IsLess, IsLessOrEqual, Le, NonZero, Sum, Unsigned, U256};
 use generic_array::{ArrayLength, GenericArray};
 use rand::{CryptoRng, RngCore};
+use subtle::ConstantTimeEq;
 use voprf::Group;
+use zeroize::{Zeroize, ZeroizeOnDrop};
 
-use crate::ciphersuite::CipherSuite;
+use crate::ciphersuite::{CipherSuite, OprfGroup, OprfHash};
 use crate::envelope::{Envelope, EnvelopeLen};
 use crate::errors::utils::{check_slice_size, check_slice_size_atleast};
 use crate::errors::ProtocolError;
-use crate::hash::{OutputSize, ProxyHash};
+use crate::hash::{Hash, OutputSize, ProxyHash};
 use crate::key_exchange::group::KeGroup;
 use crate::key_exchange::traits::{
     FromBytes, Ke1MessageLen, Ke2MessageLen, Ke3MessageLen, KeyExchange, ToBytes,
 };
 use crate::key_exchange::tripledh::NonceLen;
-use crate::keypair::{KeyPair, PublicKey, SecretKey};
+use crate::keypair::{PublicKey, SecretKey};
 use crate::opaque::{MaskedResponse, MaskedResponseLen, ServerSetup};
 
 ////////////////////////////
@@ -37,34 +39,38 @@ use crate::opaque::{MaskedResponse, MaskedResponseLen, ServerSetup};
 ////////////////////////////
 
 /// The message sent by the client to the server, to initiate registration
-#[derive(DeriveWhere)]
 #[derive_where(Clone)]
-#[derive_where(Debug, Eq, Hash, Ord, PartialEq, PartialOrd; CS::OprfGroup)]
+#[derive_where(Debug, Eq, Hash, Ord, PartialEq, PartialOrd; voprf::BlindedElement<CS::OprfGroup>)]
 pub struct RegistrationRequest<CS: CipherSuite>
 where
-    <CS::Hash as CoreProxy>::Core: ProxyHash,
-    <<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
-    Le<<<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
+    <OprfHash<CS> as OutputSizeUser>::OutputSize:
+        IsLess<U256> + IsLessOrEqual<<OprfHash<CS> as BlockSizeUser>::BlockSize>,
+    OprfHash<CS>: Hash,
+    <OprfHash<CS> as CoreProxy>::Core: ProxyHash,
+    <<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
+    Le<<<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
 {
     /// blinded password information
-    pub(crate) blinded_element: voprf::BlindedElement<CS::OprfGroup, CS::Hash>,
+    pub(crate) blinded_element: voprf::BlindedElement<CS::OprfGroup>,
 }
 
 impl_serialize_and_deserialize_for!(RegistrationRequest);
 
 /// The answer sent by the server to the user, upon reception of the
 /// registration attempt
-#[derive(DeriveWhere)]
 #[derive_where(Clone)]
-#[derive_where(Debug, Eq, Hash, Ord, PartialEq, PartialOrd; CS::OprfGroup)]
+#[derive_where(Debug, Eq, Hash, Ord, PartialEq, PartialOrd; voprf::EvaluationElement<CS::OprfGroup>, <CS::KeGroup as KeGroup>::Pk)]
 pub struct RegistrationResponse<CS: CipherSuite>
 where
-    <CS::Hash as CoreProxy>::Core: ProxyHash,
-    <<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
-    Le<<<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
+    <OprfHash<CS> as OutputSizeUser>::OutputSize:
+        IsLess<U256> + IsLessOrEqual<<OprfHash<CS> as BlockSizeUser>::BlockSize>,
+    OprfHash<CS>: Hash,
+    <OprfHash<CS> as CoreProxy>::Core: ProxyHash,
+    <<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
+    Le<<<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
 {
     /// The server's oprf output
-    pub(crate) evaluation_element: voprf::EvaluationElement<CS::OprfGroup, CS::Hash>,
+    pub(crate) evaluation_element: voprf::EvaluationElement<CS::OprfGroup>,
     /// Server's static public key
     pub(crate) server_s_pk: PublicKey<CS::KeGroup>,
 }
@@ -73,101 +79,133 @@ impl_serialize_and_deserialize_for!(
     RegistrationResponse
     where
         // RegistrationResponse: KgPk + KePk
-        <CS::OprfGroup as Group>::ElemLen: Add<<CS::KeGroup as KeGroup>::PkLen>,
+        <OprfGroup<CS> as Group>::ElemLen: Add<<CS::KeGroup as KeGroup>::PkLen>,
         RegistrationResponseLen<CS>: ArrayLength<u8>,
 );
 
 /// The final message from the client, containing sealed cryptographic
 /// identifiers
-#[derive(DeriveWhere)]
-#[derive_where(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Zeroize(drop))]
+#[derive_where(Clone)]
+#[derive_where(Debug, Eq, Hash, Ord, PartialEq, PartialOrd; <CS::KeGroup as KeGroup>::Pk)]
 pub struct RegistrationUpload<CS: CipherSuite>
 where
-    <CS::Hash as CoreProxy>::Core: ProxyHash,
-    <<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
-    Le<<<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
+    <OprfHash<CS> as OutputSizeUser>::OutputSize:
+        IsLess<U256> + IsLessOrEqual<<OprfHash<CS> as BlockSizeUser>::BlockSize>,
+    OprfHash<CS>: Hash,
+    <OprfHash<CS> as CoreProxy>::Core: ProxyHash,
+    <<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
+    Le<<<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
 {
     /// The "envelope" generated by the user, containing sealed cryptographic
     /// identifiers
     pub(crate) envelope: Envelope<CS>,
     /// The masking key used to mask the envelope
-    pub(crate) masking_key: Output<CS::Hash>,
+    pub(crate) masking_key: Output<OprfHash<CS>>,
     /// The user's public key
     pub(crate) client_s_pk: PublicKey<CS::KeGroup>,
 }
 
+impl<CS: CipherSuite> Drop for RegistrationUpload<CS>
+where
+    <OprfHash<CS> as OutputSizeUser>::OutputSize:
+        IsLess<U256> + IsLessOrEqual<<OprfHash<CS> as BlockSizeUser>::BlockSize>,
+    OprfHash<CS>: Hash,
+    <OprfHash<CS> as CoreProxy>::Core: ProxyHash,
+    <<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
+    Le<<<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
+{
+    fn drop(&mut self) {
+        self.masking_key.zeroize();
+    }
+}
+
+impl<CS: CipherSuite> ZeroizeOnDrop for RegistrationUpload<CS>
+where
+    <OprfHash<CS> as OutputSizeUser>::OutputSize:
+        IsLess<U256> + IsLessOrEqual<<OprfHash<CS> as BlockSizeUser>::BlockSize>,
+    OprfHash<CS>: Hash,
+    <OprfHash<CS> as CoreProxy>::Core: ProxyHash,
+    <<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
+    Le<<<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
+{
+}
+
 impl_serialize_and_deserialize_for!(
     RegistrationUpload
     where
         // Envelope: Nonce + Hash
-        NonceLen: Add<OutputSize<CS::Hash>>,
+        NonceLen: Add<OutputSize<OprfHash<CS>>>,
         EnvelopeLen<CS>: ArrayLength<u8>,
         // RegistrationUpload: (KePk + Hash) + Envelope
-        <CS::KeGroup as KeGroup>::PkLen: Add<OutputSize<CS::Hash>>,
-        Sum<<CS::KeGroup as KeGroup>::PkLen, OutputSize<CS::Hash>>:
+        <CS::KeGroup as KeGroup>::PkLen: Add<OutputSize<OprfHash<CS>>>,
+        Sum<<CS::KeGroup as KeGroup>::PkLen, OutputSize<OprfHash<CS>>>:
             ArrayLength<u8> | Add<EnvelopeLen<CS>>,
         RegistrationUploadLen<CS>: ArrayLength<u8>,
 );
 
 /// The message sent by the user to the server, to initiate registration
-#[derive(DeriveWhere)]
-#[derive_where(Clone, Zeroize)]
+#[derive_where(Clone, ZeroizeOnDrop)]
 #[derive_where(
     Debug, Eq, Hash, PartialEq;
-    CS::OprfGroup,
-    <CS::KeyExchange as KeyExchange<CS::Hash, CS::KeGroup>>::KE1Message,
+    voprf::BlindedElement<CS::OprfGroup>,
+    <CS::KeyExchange as KeyExchange<OprfHash<CS>, CS::KeGroup>>::KE1Message,
 )]
 pub struct CredentialRequest<CS: CipherSuite>
 where
-    <CS::Hash as CoreProxy>::Core: ProxyHash,
-    <<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
-    Le<<<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
+    <OprfHash<CS> as OutputSizeUser>::OutputSize:
+        IsLess<U256> + IsLessOrEqual<<OprfHash<CS> as BlockSizeUser>::BlockSize>,
+    OprfHash<CS>: Hash,
+    <OprfHash<CS> as CoreProxy>::Core: ProxyHash,
+    <<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
+    Le<<<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
 {
-    pub(crate) blinded_element: voprf::BlindedElement<CS::OprfGroup, CS::Hash>,
-    pub(crate) ke1_message: <CS::KeyExchange as KeyExchange<CS::Hash, CS::KeGroup>>::KE1Message,
+    pub(crate) blinded_element: voprf::BlindedElement<CS::OprfGroup>,
+    pub(crate) ke1_message: <CS::KeyExchange as KeyExchange<OprfHash<CS>, CS::KeGroup>>::KE1Message,
 }
 
 impl_serialize_and_deserialize_for!(
     CredentialRequest
     where
         // CredentialRequest: KgPk + Ke1Message
-        <CS::OprfGroup as Group>::ElemLen: Add<Ke1MessageLen<CS>>,
+        <OprfGroup<CS> as Group>::ElemLen: Add<Ke1MessageLen<CS>>,
         CredentialRequestLen<CS>: ArrayLength<u8>,
 );
 
 /// The answer sent by the server to the user, upon reception of the login
 /// attempt
-#[derive(DeriveWhere)]
 #[derive_where(Clone)]
 #[derive_where(
     Debug, Eq, Hash, PartialEq;
-    CS::OprfGroup,
-    <CS::KeyExchange as KeyExchange<CS::Hash, CS::KeGroup>>::KE2Message,
+    voprf::EvaluationElement<CS::OprfGroup>,
+    <CS::KeyExchange as KeyExchange<OprfHash<CS>, CS::KeGroup>>::KE2Message,
 )]
 pub struct CredentialResponse<CS: CipherSuite>
 where
-    <CS::Hash as CoreProxy>::Core: ProxyHash,
-    <<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
-    Le<<<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
+    <OprfHash<CS> as OutputSizeUser>::OutputSize:
+        IsLess<U256> + IsLessOrEqual<<OprfHash<CS> as BlockSizeUser>::BlockSize>,
+    OprfHash<CS>: Hash,
+    <OprfHash<CS> as CoreProxy>::Core: ProxyHash,
+    <<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
+    Le<<<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
 {
     /// the server's oprf output
-    pub(crate) evaluation_element: voprf::EvaluationElement<CS::OprfGroup, CS::Hash>,
+    pub(crate) evaluation_element: voprf::EvaluationElement<CS::OprfGroup>,
     pub(crate) masking_nonce: GenericArray<u8, NonceLen>,
     pub(crate) masked_response: MaskedResponse<CS>,
-    pub(crate) ke2_message: <CS::KeyExchange as KeyExchange<CS::Hash, CS::KeGroup>>::KE2Message,
+    pub(crate) ke2_message: <CS::KeyExchange as KeyExchange<OprfHash<CS>, CS::KeGroup>>::KE2Message,
 }
 
 impl_serialize_and_deserialize_for!(
     CredentialResponse
     where
         // CredentialResponseWithoutKeLen: (KgPk + Nonce) + MaskedResponse
-        <CS::OprfGroup as Group>::ElemLen: Add<NonceLen>,
-        Sum<<CS::OprfGroup as Group>::ElemLen, NonceLen>:
+        <OprfGroup<CS> as Group>::ElemLen: Add<NonceLen>,
+        Sum<<OprfGroup<CS> as Group>::ElemLen, NonceLen>:
             ArrayLength<u8> | Add<MaskedResponseLen<CS>>,
         CredentialResponseWithoutKeLen<CS>: ArrayLength<u8>,
         // MaskedResponse: (Nonce + Hash) + KePk
-        NonceLen: Add<OutputSize<CS::Hash>>,
-        Sum<NonceLen, OutputSize<CS::Hash>>:
+        NonceLen: Add<OutputSize<OprfHash<CS>>>,
+        Sum<NonceLen, OutputSize<OprfHash<CS>>>:
             ArrayLength<u8> | Add<<CS::KeGroup as KeGroup>::PkLen>,
         MaskedResponseLen<CS>: ArrayLength<u8>,
         // CredentialResponse: CredentialResponseWithoutKeLen + Ke2Message
@@ -177,19 +215,21 @@ impl_serialize_and_deserialize_for!(
 
 /// The answer sent by the client to the server, upon reception of the sealed
 /// envelope
-#[derive(DeriveWhere)]
 #[derive_where(Clone)]
 #[derive_where(
     Debug, Eq, Hash, PartialEq;
-    <CS::KeyExchange as KeyExchange<CS::Hash, CS::KeGroup>>::KE3Message,
+    <CS::KeyExchange as KeyExchange<OprfHash<CS>, CS::KeGroup>>::KE3Message,
 )]
 pub struct CredentialFinalization<CS: CipherSuite>
 where
-    <CS::Hash as CoreProxy>::Core: ProxyHash,
-    <<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
-    Le<<<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
+    <OprfHash<CS> as OutputSizeUser>::OutputSize:
+        IsLess<U256> + IsLessOrEqual<<OprfHash<CS> as BlockSizeUser>::BlockSize>,
+    OprfHash<CS>: Hash,
+    <OprfHash<CS> as CoreProxy>::Core: ProxyHash,
+    <<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
+    Le<<<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
 {
-    pub(crate) ke3_message: <CS::KeyExchange as KeyExchange<CS::Hash, CS::KeGroup>>::KE3Message,
+    pub(crate) ke3_message: <CS::KeyExchange as KeyExchange<OprfHash<CS>, CS::KeGroup>>::KE3Message,
 }
 
 impl_serialize_and_deserialize_for!(CredentialFinalization);
@@ -200,25 +240,26 @@ impl_serialize_and_deserialize_for!(CredentialFinalization);
 ////////////////////////////////
 
 /// Length of [`RegistrationRequest`] in bytes for serialization.
-pub type RegistrationRequestLen<CS: CipherSuite> = <CS::OprfGroup as Group>::ElemLen;
+pub type RegistrationRequestLen<CS: CipherSuite> = <OprfGroup<CS> as Group>::ElemLen;
 
 impl<CS: CipherSuite> RegistrationRequest<CS>
 where
-    <CS::Hash as CoreProxy>::Core: ProxyHash,
-    <<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
-    Le<<<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
+    <OprfHash<CS> as OutputSizeUser>::OutputSize:
+        IsLess<U256> + IsLessOrEqual<<OprfHash<CS> as BlockSizeUser>::BlockSize>,
+    OprfHash<CS>: Hash,
+    <OprfHash<CS> as CoreProxy>::Core: ProxyHash,
+    <<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
+    Le<<<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
 {
     /// Only used for testing purposes
     #[cfg(test)]
-    pub fn get_blinded_element_for_testing(
-        &self,
-    ) -> voprf::BlindedElement<CS::OprfGroup, CS::Hash> {
+    pub fn get_blinded_element_for_testing(&self) -> voprf::BlindedElement<CS::OprfGroup> {
         self.blinded_element.clone()
     }
 
     /// Serialization into bytes
     pub fn serialize(&self) -> GenericArray<u8, RegistrationRequestLen<CS>> {
-        self.blinded_element.value().to_arr()
+        <OprfGroup<CS> as Group>::serialize_elem(self.blinded_element.value())
     }
 
     /// Deserialization from bytes
@@ -231,38 +272,37 @@ where
 
 /// Length of [`RegistrationResponse`] in bytes for serialization.
 pub type RegistrationResponseLen<CS: CipherSuite> =
-    Sum<<CS::OprfGroup as Group>::ElemLen, <CS::KeGroup as KeGroup>::PkLen>;
+    Sum<<OprfGroup<CS> as Group>::ElemLen, <CS::KeGroup as KeGroup>::PkLen>;
 
 impl<CS: CipherSuite> RegistrationResponse<CS>
 where
-    <CS::Hash as CoreProxy>::Core: ProxyHash,
-    <<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
-    Le<<<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
+    <OprfHash<CS> as OutputSizeUser>::OutputSize:
+        IsLess<U256> + IsLessOrEqual<<OprfHash<CS> as BlockSizeUser>::BlockSize>,
+    OprfHash<CS>: Hash,
+    <OprfHash<CS> as CoreProxy>::Core: ProxyHash,
+    <<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
+    Le<<<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
 {
     /// Serialization into bytes
     pub fn serialize(&self) -> GenericArray<u8, RegistrationResponseLen<CS>>
     where
         // RegistrationResponse: KgPk + KePk
-        <CS::OprfGroup as Group>::ElemLen: Add<<CS::KeGroup as KeGroup>::PkLen>,
+        <OprfGroup<CS> as Group>::ElemLen: Add<<CS::KeGroup as KeGroup>::PkLen>,
         RegistrationResponseLen<CS>: ArrayLength<u8>,
     {
-        self.evaluation_element
-            .value()
-            .to_arr()
-            .concat(self.server_s_pk.to_arr())
+        <OprfGroup<CS> as Group>::serialize_elem(self.evaluation_element.value())
+            .concat(self.server_s_pk.to_bytes())
     }
 
     /// Deserialization from bytes
     pub fn deserialize(input: &[u8]) -> Result<Self, ProtocolError> {
-        let elem_len = <CS::OprfGroup as Group>::ElemLen::USIZE;
+        let elem_len = <OprfGroup<CS> as Group>::ElemLen::USIZE;
         let key_len = <CS::KeGroup as KeGroup>::PkLen::USIZE;
         let checked_slice =
             check_slice_size(input, elem_len + key_len, "registration_response_bytes")?;
 
         // Ensure that public key is valid
-        let server_s_pk = KeyPair::<CS::KeGroup>::check_public_key(PublicKey::from_bytes(
-            &checked_slice[elem_len..],
-        )?)?;
+        let server_s_pk = PublicKey::deserialize(&checked_slice[elem_len..])?;
 
         Ok(Self {
             evaluation_element: voprf::EvaluationElement::deserialize(&checked_slice[..elem_len])?,
@@ -273,7 +313,7 @@ where
     #[cfg(test)]
     /// Only used for tests, where we can set the beta value to test for the
     /// reflection error case
-    pub fn set_evaluation_element_for_testing(&self, beta: CS::OprfGroup) -> Self {
+    pub fn set_evaluation_element_for_testing(&self, beta: <OprfGroup<CS> as Group>::Elem) -> Self {
         Self {
             evaluation_element: voprf::EvaluationElement::from_value_unchecked(beta),
             server_s_pk: self.server_s_pk.clone(),
@@ -283,28 +323,31 @@ where
 
 /// Length of [`RegistrationUpload`] in bytes for serialization.
 pub type RegistrationUploadLen<CS: CipherSuite> =
-    Sum<Sum<<CS::KeGroup as KeGroup>::PkLen, OutputSize<CS::Hash>>, EnvelopeLen<CS>>;
+    Sum<Sum<<CS::KeGroup as KeGroup>::PkLen, OutputSize<OprfHash<CS>>>, EnvelopeLen<CS>>;
 
 impl<CS: CipherSuite> RegistrationUpload<CS>
 where
-    <CS::Hash as CoreProxy>::Core: ProxyHash,
-    <<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
-    Le<<<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
+    <OprfHash<CS> as OutputSizeUser>::OutputSize:
+        IsLess<U256> + IsLessOrEqual<<OprfHash<CS> as BlockSizeUser>::BlockSize>,
+    OprfHash<CS>: Hash,
+    <OprfHash<CS> as CoreProxy>::Core: ProxyHash,
+    <<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
+    Le<<<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
 {
     /// Serialization into bytes
     pub fn serialize(&self) -> GenericArray<u8, RegistrationUploadLen<CS>>
     where
         // Envelope: Nonce + Hash
-        NonceLen: Add<OutputSize<CS::Hash>>,
+        NonceLen: Add<OutputSize<OprfHash<CS>>>,
         EnvelopeLen<CS>: ArrayLength<u8>,
         // RegistrationUpload: (KePk + Hash) + Envelope
-        <CS::KeGroup as KeGroup>::PkLen: Add<OutputSize<CS::Hash>>,
-        Sum<<CS::KeGroup as KeGroup>::PkLen, OutputSize<CS::Hash>>:
+        <CS::KeGroup as KeGroup>::PkLen: Add<OutputSize<OprfHash<CS>>>,
+        Sum<<CS::KeGroup as KeGroup>::PkLen, OutputSize<OprfHash<CS>>>:
             ArrayLength<u8> + Add<EnvelopeLen<CS>>,
         RegistrationUploadLen<CS>: ArrayLength<u8>,
     {
         self.client_s_pk
-            .to_arr()
+            .to_bytes()
             .concat(self.masking_key.clone())
             .concat(self.envelope.serialize())
     }
@@ -312,7 +355,7 @@ where
     /// Deserialization from bytes
     pub fn deserialize(input: &[u8]) -> Result<Self, ProtocolError> {
         let key_len = <CS::KeGroup as KeGroup>::PkLen::USIZE;
-        let hash_len = OutputSize::<CS::Hash>::USIZE;
+        let hash_len = OutputSize::<OprfHash<CS>>::USIZE;
         let checked_slice =
             check_slice_size_atleast(input, key_len + hash_len, "registration_upload_bytes")?;
         let envelope = Envelope::<CS>::deserialize(&checked_slice[key_len + hash_len..])?;
@@ -321,9 +364,7 @@ where
             masking_key: GenericArray::clone_from_slice(
                 &checked_slice[key_len..key_len + hash_len],
             ),
-            client_s_pk: KeyPair::<CS::KeGroup>::check_public_key(PublicKey::from_bytes(
-                &checked_slice[..key_len],
-            )?)?,
+            client_s_pk: PublicKey::deserialize(&checked_slice[..key_len])?,
         })
     }
 
@@ -332,7 +373,7 @@ where
         rng: &mut R,
         server_setup: &ServerSetup<CS, S>,
     ) -> Self {
-        let mut masking_key = Output::<CS::Hash>::default();
+        let mut masking_key = Output::<OprfHash<CS>>::default();
         rng.fill_bytes(&mut masking_key);
 
         Self {
@@ -345,29 +386,30 @@ where
 
 /// Length of [`CredentialRequest`] in bytes for serialization.
 pub type CredentialRequestLen<CS: CipherSuite> =
-    Sum<<CS::OprfGroup as Group>::ElemLen, Ke1MessageLen<CS>>;
+    Sum<<OprfGroup<CS> as Group>::ElemLen, Ke1MessageLen<CS>>;
 
 impl<CS: CipherSuite> CredentialRequest<CS>
 where
-    <CS::Hash as CoreProxy>::Core: ProxyHash,
-    <<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
-    Le<<<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
+    <OprfHash<CS> as OutputSizeUser>::OutputSize:
+        IsLess<U256> + IsLessOrEqual<<OprfHash<CS> as BlockSizeUser>::BlockSize>,
+    OprfHash<CS>: Hash,
+    <OprfHash<CS> as CoreProxy>::Core: ProxyHash,
+    <<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
+    Le<<<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
 {
     /// Serialization into bytes
     pub fn serialize(&self) -> GenericArray<u8, CredentialRequestLen<CS>>
     where
         // CredentialRequest: KgPk + Ke1Message
-        <CS::OprfGroup as Group>::ElemLen: Add<Ke1MessageLen<CS>>,
+        <OprfGroup<CS> as Group>::ElemLen: Add<Ke1MessageLen<CS>>,
         CredentialRequestLen<CS>: ArrayLength<u8>,
     {
-        self.blinded_element
-            .value()
-            .to_arr()
+        <OprfGroup<CS> as Group>::serialize_elem(self.blinded_element.value())
             .concat(self.ke1_message.to_bytes())
     }
 
     pub(crate) fn serialize_iter<'a>(
-        blinded_element: &'a GenericArray<u8, <CS::OprfGroup as Group>::ElemLen>,
+        blinded_element: &'a GenericArray<u8, <OprfGroup<CS> as Group>::ElemLen>,
         ke1_message: &'a GenericArray<u8, Ke1MessageLen<CS>>,
     ) -> impl Iterator<Item = &'a [u8]> {
         [blinded_element.as_slice(), ke1_message].into_iter()
@@ -375,23 +417,22 @@ where
 
     /// Deserialization from bytes
     pub fn deserialize(input: &[u8]) -> Result<Self, ProtocolError> {
-        let elem_len = <CS::OprfGroup as Group>::ElemLen::USIZE;
+        let elem_len = <OprfGroup<CS> as Group>::ElemLen::USIZE;
 
         let checked_slice = check_slice_size_atleast(input, elem_len, "login_first_message_bytes")?;
 
         // Check that the message is actually containing an element of the correct
         // subgroup
-        let blinded_element = voprf::BlindedElement::<CS::OprfGroup, CS::Hash>::deserialize(
-            &checked_slice[..elem_len],
-        )?;
+        let blinded_element =
+            voprf::BlindedElement::<CS::OprfGroup>::deserialize(&checked_slice[..elem_len])?;
 
         // Throw an error if the identity group element is encountered
-        if blinded_element.value().is_identity() {
+        if bool::from(<OprfGroup<CS> as Group>::identity_elem().ct_eq(&blinded_element.value())) {
             return Err(ProtocolError::IdentityGroupElementError);
         }
 
         let ke1_message =
-            <CS::KeyExchange as KeyExchange<CS::Hash, CS::KeGroup>>::KE1Message::from_bytes(
+            <CS::KeyExchange as KeyExchange<OprfHash<CS>, CS::KeGroup>>::KE1Message::from_bytes(
                 &checked_slice[elem_len..],
             )?;
 
@@ -403,9 +444,7 @@ where
 
     /// Only used for testing purposes
     #[cfg(test)]
-    pub fn get_blinded_element_for_testing(
-        &self,
-    ) -> voprf::BlindedElement<CS::OprfGroup, CS::Hash> {
+    pub fn get_blinded_element_for_testing(&self) -> voprf::BlindedElement<CS::OprfGroup> {
         self.blinded_element.clone()
     }
 }
@@ -415,40 +454,42 @@ pub type CredentialResponseLen<CS: CipherSuite> =
     Sum<CredentialResponseWithoutKeLen<CS>, Ke2MessageLen<CS>>;
 
 pub(crate) type CredentialResponseWithoutKeLen<CS: CipherSuite> =
-    Sum<Sum<<CS::OprfGroup as Group>::ElemLen, NonceLen>, MaskedResponseLen<CS>>;
+    Sum<Sum<<OprfGroup<CS> as Group>::ElemLen, NonceLen>, MaskedResponseLen<CS>>;
 
 impl<CS: CipherSuite> CredentialResponse<CS>
 where
-    <CS::Hash as CoreProxy>::Core: ProxyHash,
-    <<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
-    Le<<<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
+    <OprfHash<CS> as OutputSizeUser>::OutputSize:
+        IsLess<U256> + IsLessOrEqual<<OprfHash<CS> as BlockSizeUser>::BlockSize>,
+    OprfHash<CS>: Hash,
+    <OprfHash<CS> as CoreProxy>::Core: ProxyHash,
+    <<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
+    Le<<<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
 {
     /// Serialization into bytes
     pub fn serialize(&self) -> GenericArray<u8, CredentialResponseLen<CS>>
     where
         // CredentialResponseWithoutKeLen: (KgPk + Nonce) + MaskedResponse
-        <CS::OprfGroup as Group>::ElemLen: Add<NonceLen>,
-        Sum<<CS::OprfGroup as Group>::ElemLen, NonceLen>:
+        <OprfGroup<CS> as Group>::ElemLen: Add<NonceLen>,
+        Sum<<OprfGroup<CS> as Group>::ElemLen, NonceLen>:
             ArrayLength<u8> + Add<MaskedResponseLen<CS>>,
         CredentialResponseWithoutKeLen<CS>: ArrayLength<u8>,
         // MaskedResponse: (Nonce + Hash) + KePk
-        NonceLen: Add<OutputSize<CS::Hash>>,
-        Sum<NonceLen, OutputSize<CS::Hash>>: ArrayLength<u8> + Add<<CS::KeGroup as KeGroup>::PkLen>,
+        NonceLen: Add<OutputSize<OprfHash<CS>>>,
+        Sum<NonceLen, OutputSize<OprfHash<CS>>>:
+            ArrayLength<u8> + Add<<CS::KeGroup as KeGroup>::PkLen>,
         MaskedResponseLen<CS>: ArrayLength<u8>,
         // CredentialResponse: CredentialResponseWithoutKeLen + Ke2Message
         CredentialResponseWithoutKeLen<CS>: Add<Ke2MessageLen<CS>>,
         CredentialResponseLen<CS>: ArrayLength<u8>,
     {
-        self.evaluation_element
-            .value()
-            .to_arr()
+        <OprfGroup<CS> as Group>::serialize_elem(self.evaluation_element.value())
             .concat(self.masking_nonce)
             .concat(self.masked_response.serialize())
             .concat(self.ke2_message.to_bytes())
     }
 
     pub(crate) fn serialize_without_ke<'a>(
-        beta: &'a GenericArray<u8, <CS::OprfGroup as Group>::ElemLen>,
+        beta: &'a GenericArray<u8, <OprfGroup<CS> as Group>::ElemLen>,
         masking_nonce: &'a GenericArray<u8, NonceLen>,
         masked_response: &'a MaskedResponse<CS>,
     ) -> impl Iterator<Item = &'a [u8]> {
@@ -459,12 +500,12 @@ where
 
     /// Deserialization from bytes
     pub fn deserialize(input: &[u8]) -> Result<Self, ProtocolError> {
-        let elem_len = <CS::OprfGroup as Group>::ElemLen::USIZE;
+        let elem_len = <OprfGroup<CS> as Group>::ElemLen::USIZE;
         let key_len = <CS::KeGroup as KeGroup>::PkLen::USIZE;
-        let nonce_len: usize = 32;
+        let nonce_len = NonceLen::USIZE;
         let envelope_len = Envelope::<CS>::len();
         let masked_response_len = key_len + envelope_len;
-        let ke2_message_len = CS::KeyExchange::ke2_message_size();
+        let ke2_message_len = Ke2MessageLen::<CS>::USIZE;
 
         let checked_slice = check_slice_size_atleast(
             input,
@@ -476,10 +517,11 @@ where
         // subgroup
         let beta_bytes = &checked_slice[..elem_len];
         let evaluation_element =
-            voprf::EvaluationElement::<CS::OprfGroup, CS::Hash>::deserialize(beta_bytes)?;
+            voprf::EvaluationElement::<CS::OprfGroup>::deserialize(beta_bytes)?;
 
         // Throw an error if the identity group element is encountered
-        if evaluation_element.value().is_identity() {
+        if bool::from(<OprfGroup<CS> as Group>::identity_elem().ct_eq(&evaluation_element.value()))
+        {
             return Err(ProtocolError::IdentityGroupElementError);
         }
 
@@ -489,7 +531,7 @@ where
             &checked_slice[elem_len + nonce_len..elem_len + nonce_len + masked_response_len],
         );
         let ke2_message =
-            <CS::KeyExchange as KeyExchange<CS::Hash, CS::KeGroup>>::KE2Message::from_bytes(
+            <CS::KeyExchange as KeyExchange<OprfHash<CS>, CS::KeGroup>>::KE2Message::from_bytes(
                 &checked_slice[elem_len + nonce_len + masked_response_len..],
             )?;
 
@@ -504,7 +546,7 @@ where
     #[cfg(test)]
     /// Only used for tests, where we can set the beta value to test for the
     /// reflection error case
-    pub fn set_evaluation_element_for_testing(&self, beta: CS::OprfGroup) -> Self {
+    pub fn set_evaluation_element_for_testing(&self, beta: <OprfGroup<CS> as Group>::Elem) -> Self {
         Self {
             evaluation_element: voprf::EvaluationElement::from_value_unchecked(beta),
             masking_nonce: self.masking_nonce,
@@ -519,9 +561,12 @@ pub type CredentialFinalizationLen<CS: CipherSuite> = Ke3MessageLen<CS>;
 
 impl<CS: CipherSuite> CredentialFinalization<CS>
 where
-    <CS::Hash as CoreProxy>::Core: ProxyHash,
-    <<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
-    Le<<<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
+    <OprfHash<CS> as OutputSizeUser>::OutputSize:
+        IsLess<U256> + IsLessOrEqual<<OprfHash<CS> as BlockSizeUser>::BlockSize>,
+    OprfHash<CS>: Hash,
+    <OprfHash<CS> as CoreProxy>::Core: ProxyHash,
+    <<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
+    Le<<<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
 {
     /// Serialization into bytes
     pub fn serialize(&self) -> GenericArray<u8, CredentialFinalizationLen<CS>> {
@@ -531,7 +576,9 @@ where
     /// Deserialization from bytes
     pub fn deserialize(input: &[u8]) -> Result<Self, ProtocolError> {
         let ke3_message =
-            <CS::KeyExchange as KeyExchange<CS::Hash, CS::KeGroup>>::KE3Message::from_bytes(input)?;
+            <CS::KeyExchange as KeyExchange<OprfHash<CS>, CS::KeGroup>>::KE3Message::from_bytes(
+                input,
+            )?;
         Ok(Self { ke3_message })
     }
 }
diff --git a/src/opaque.rs b/src/opaque.rs
old mode 100755
new mode 100644
index d3f3911c..03ccbae3
--- a/src/opaque.rs
+++ b/src/opaque.rs
@@ -7,21 +7,20 @@
 
 //! Provides the main OPAQUE API
 
-use core::marker::PhantomData;
 use core::ops::Add;
 
-use derive_where::DeriveWhere;
+use derive_where::derive_where;
 use digest::core_api::{BlockSizeUser, CoreProxy};
-use digest::Output;
+use digest::{Output, OutputSizeUser};
 use generic_array::sequence::Concat;
-use generic_array::typenum::{IsLess, Le, NonZero, Sum, Unsigned, U2, U256};
+use generic_array::typenum::{IsLess, IsLessOrEqual, Le, NonZero, Sum, Unsigned, U2, U256};
 use generic_array::{ArrayLength, GenericArray};
 use hkdf::{Hkdf, HkdfExtract};
 use rand::{CryptoRng, RngCore};
 use subtle::ConstantTimeEq;
 use voprf::Group;
 
-use crate::ciphersuite::CipherSuite;
+use crate::ciphersuite::{CipherSuite, OprfGroup, OprfHash};
 use crate::envelope::{Envelope, EnvelopeLen};
 use crate::errors::utils::check_slice_size;
 use crate::errors::{InternalError, ProtocolError};
@@ -61,126 +60,137 @@ const STR_OPAQUE_DERIVE_KEY_PAIR: &[u8; 20] = b"OPAQUE-DeriveKeyPair";
     derive(serde_::Deserialize, serde_::Serialize),
     serde(
         bound(
-            deserialize = "KeyPair<CS::KeGroup, S>: serde_::Deserialize<'de>",
-            serialize = "KeyPair<CS::KeGroup, S>: serde_::Serialize"
+            deserialize = "<CS::KeGroup as KeGroup>::Pk: serde_::Deserialize<'de>, <CS::KeGroup \
+                           as KeGroup>::Sk: serde_::Deserialize<'de>, S: serde_::Deserialize<'de>",
+            serialize = "<CS::KeGroup as KeGroup>::Pk: serde_::Serialize, <CS::KeGroup as \
+                         KeGroup>::Sk: serde_::Serialize, S: serde_::Serialize"
         ),
         crate = "serde_"
     )
 )]
-#[derive(DeriveWhere)]
 #[derive_where(Clone)]
-#[derive_where(Debug, Eq, Hash, Ord, PartialEq, PartialOrd; S)]
+#[derive_where(Debug, Eq, Hash, Ord, PartialEq, PartialOrd; <CS::KeGroup as KeGroup>::Pk, <CS::KeGroup as KeGroup>::Sk, S)]
 pub struct ServerSetup<
     CS: CipherSuite,
     S: SecretKey<CS::KeGroup> = PrivateKey<<CS as CipherSuite>::KeGroup>,
 > where
-    <CS::Hash as CoreProxy>::Core: ProxyHash,
-    <<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
-    Le<<<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
+    <OprfHash<CS> as OutputSizeUser>::OutputSize:
+        IsLess<U256> + IsLessOrEqual<<OprfHash<CS> as BlockSizeUser>::BlockSize>,
+    OprfHash<CS>: Hash,
+    <OprfHash<CS> as CoreProxy>::Core: ProxyHash,
+    <<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
+    Le<<<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
 {
-    oprf_seed: Output<CS::Hash>,
+    oprf_seed: Output<OprfHash<CS>>,
     keypair: KeyPair<CS::KeGroup, S>,
     pub(crate) fake_keypair: KeyPair<CS::KeGroup>,
 }
 
 /// The state elements the client holds to register itself
-#[derive(DeriveWhere)]
-#[derive_where(Clone, Zeroize(drop))]
+#[derive_where(Clone, ZeroizeOnDrop)]
 #[derive_where(
     Debug, Eq, Hash, PartialEq;
-    voprf::NonVerifiableClient<CS::OprfGroup, CS::Hash>,
-    voprf::BlindedElement<CS::OprfGroup, CS::Hash>,
+    voprf::NonVerifiableClient<CS::OprfGroup>,
+    voprf::BlindedElement<CS::OprfGroup>,
 )]
 pub struct ClientRegistration<CS: CipherSuite>
 where
-    <CS::Hash as CoreProxy>::Core: ProxyHash,
-    <<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
-    Le<<<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
+    <OprfHash<CS> as OutputSizeUser>::OutputSize:
+        IsLess<U256> + IsLessOrEqual<<OprfHash<CS> as BlockSizeUser>::BlockSize>,
+    OprfHash<CS>: Hash,
+    <OprfHash<CS> as CoreProxy>::Core: ProxyHash,
+    <<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
+    Le<<<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
 {
-    pub(crate) oprf_client: voprf::NonVerifiableClient<CS::OprfGroup, CS::Hash>,
-    pub(crate) blinded_element: voprf::BlindedElement<CS::OprfGroup, CS::Hash>,
+    pub(crate) oprf_client: voprf::NonVerifiableClient<CS::OprfGroup>,
+    pub(crate) blinded_element: voprf::BlindedElement<CS::OprfGroup>,
 }
 
 impl_serialize_and_deserialize_for!(
     ClientRegistration
     where
         // ClientRegistration: KgSk + KgPk
-        <CS::OprfGroup as Group>::ScalarLen: Add<<CS::OprfGroup as Group>::ElemLen>,
+        <OprfGroup<CS> as Group>::ScalarLen: Add<<OprfGroup<CS> as Group>::ElemLen>,
         ClientRegistrationLen<CS>: ArrayLength<u8>,
 );
 
 /// The state elements the server holds to record a registration
-#[derive(DeriveWhere)]
-#[derive_where(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Zeroize(drop))]
-pub struct ServerRegistration<CS: CipherSuite>(RegistrationUpload<CS>)
+#[derive_where(Clone, ZeroizeOnDrop)]
+#[derive_where(Debug, Eq, Hash, Ord, PartialEq, PartialOrd; <CS::KeGroup as KeGroup>::Pk)]
+pub struct ServerRegistration<CS: CipherSuite>(pub(crate) RegistrationUpload<CS>)
 where
-    <CS::Hash as CoreProxy>::Core: ProxyHash,
-    <<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
-    Le<<<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero;
+    <OprfHash<CS> as OutputSizeUser>::OutputSize:
+        IsLess<U256> + IsLessOrEqual<<OprfHash<CS> as BlockSizeUser>::BlockSize>,
+    OprfHash<CS>: Hash,
+    <OprfHash<CS> as CoreProxy>::Core: ProxyHash,
+    <<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
+    Le<<<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero;
 
 impl_serialize_and_deserialize_for!(
     ServerRegistration
     where
         // Envelope: Nonce + Hash
-        NonceLen: Add<OutputSize<CS::Hash>>,
+        NonceLen: Add<OutputSize<OprfHash<CS>>>,
         EnvelopeLen<CS>: ArrayLength<u8>,
         // RegistrationUpload: (KePk + Hash) + Envelope
-        <CS::KeGroup as KeGroup>::PkLen: Add<OutputSize<CS::Hash>>,
-        Sum<<CS::KeGroup as KeGroup>::PkLen, OutputSize<CS::Hash>>:
+        <CS::KeGroup as KeGroup>::PkLen: Add<OutputSize<OprfHash<CS>>>,
+        Sum<<CS::KeGroup as KeGroup>::PkLen, OutputSize<OprfHash<CS>>>:
             ArrayLength<u8> | Add<EnvelopeLen<CS>>,
         RegistrationUploadLen<CS>: ArrayLength<u8>,
         // ServerRegistration = RegistrationUpload
 );
 
 /// The state elements the client holds to perform a login
-#[derive(DeriveWhere)]
-#[derive_where(Clone, Zeroize(drop))]
+#[derive_where(Clone, ZeroizeOnDrop)]
 #[derive_where(
     Debug, Eq, Hash, PartialEq;
-    voprf::NonVerifiableClient<CS::OprfGroup, CS::Hash>,
-    <CS::KeyExchange as KeyExchange<CS::Hash, CS::KeGroup>>::KE1State,
+    voprf::NonVerifiableClient<CS::OprfGroup>,
+    <CS::KeyExchange as KeyExchange<OprfHash<CS>, CS::KeGroup>>::KE1State,
     CredentialRequest<CS>,
 )]
 pub struct ClientLogin<CS: CipherSuite>
 where
-    <CS::Hash as CoreProxy>::Core: ProxyHash,
-    <<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
-    Le<<<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
+    <OprfHash<CS> as OutputSizeUser>::OutputSize:
+        IsLess<U256> + IsLessOrEqual<<OprfHash<CS> as BlockSizeUser>::BlockSize>,
+    OprfHash<CS>: Hash,
+    <OprfHash<CS> as CoreProxy>::Core: ProxyHash,
+    <<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
+    Le<<<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
 {
-    oprf_client: voprf::NonVerifiableClient<CS::OprfGroup, CS::Hash>,
-    ke1_state: <CS::KeyExchange as KeyExchange<CS::Hash, CS::KeGroup>>::KE1State,
-    credential_request: CredentialRequest<CS>,
+    pub(crate) oprf_client: voprf::NonVerifiableClient<CS::OprfGroup>,
+    pub(crate) ke1_state: <CS::KeyExchange as KeyExchange<OprfHash<CS>, CS::KeGroup>>::KE1State,
+    pub(crate) credential_request: CredentialRequest<CS>,
 }
 
 impl_serialize_and_deserialize_for!(
     ClientLogin
     where
         // CredentialRequest: KgPk + Ke1Message
-        <CS::OprfGroup as Group>::ElemLen: Add<Ke1MessageLen<CS>>,
+        <OprfGroup<CS> as Group>::ElemLen: Add<Ke1MessageLen<CS>>,
         CredentialRequestLen<CS>: ArrayLength<u8>,
         // ClientLogin: KgSk + CredentialRequest + Ke1State
-        <CS::OprfGroup as Group>::ScalarLen: Add<CredentialRequestLen<CS>>,
-        Sum<<CS::OprfGroup as Group>::ScalarLen, CredentialRequestLen<CS>>:
+        <OprfGroup<CS> as Group>::ScalarLen: Add<CredentialRequestLen<CS>>,
+        Sum<<OprfGroup<CS> as Group>::ScalarLen, CredentialRequestLen<CS>>:
             ArrayLength<u8> | Add<Ke1StateLen<CS>>,
         ClientLoginLen<CS>: ArrayLength<u8>,
 );
 
 /// The state elements the server holds to record a login
-#[derive(DeriveWhere)]
-#[derive_where(Clone, Zeroize(drop))]
+#[derive_where(Clone, ZeroizeOnDrop)]
 #[derive_where(
     Debug, Eq, Hash, PartialEq;
-    <CS::KeyExchange as KeyExchange<CS::Hash, CS::KeGroup>>::KE2State,
+    <CS::KeyExchange as KeyExchange<OprfHash<CS>, CS::KeGroup>>::KE2State,
 )]
 pub struct ServerLogin<CS: CipherSuite>
 where
-    <CS::Hash as CoreProxy>::Core: ProxyHash,
-    <<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
-    Le<<<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
+    <OprfHash<CS> as OutputSizeUser>::OutputSize:
+        IsLess<U256> + IsLessOrEqual<<OprfHash<CS> as BlockSizeUser>::BlockSize>,
+    OprfHash<CS>: Hash,
+    <OprfHash<CS> as CoreProxy>::Core: ProxyHash,
+    <<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
+    Le<<<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
 {
-    ke2_state: <CS::KeyExchange as KeyExchange<CS::Hash, CS::KeGroup>>::KE2State,
-    #[derive_where(skip(Zeroize))]
-    _cs: PhantomData<CS>,
+    ke2_state: <CS::KeyExchange as KeyExchange<OprfHash<CS>, CS::KeGroup>>::KE2State,
 }
 
 impl_serialize_and_deserialize_for!(ServerLogin);
@@ -195,9 +205,12 @@ impl_serialize_and_deserialize_for!(ServerLogin);
 
 impl<CS: CipherSuite> ServerSetup<CS, PrivateKey<CS::KeGroup>>
 where
-    <CS::Hash as CoreProxy>::Core: ProxyHash,
-    <<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
-    Le<<<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
+    <OprfHash<CS> as OutputSizeUser>::OutputSize:
+        IsLess<U256> + IsLessOrEqual<<OprfHash<CS> as BlockSizeUser>::BlockSize>,
+    OprfHash<CS>: Hash,
+    <OprfHash<CS> as CoreProxy>::Core: ProxyHash,
+    <<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
+    Le<<<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
 {
     /// Generate a new instance of server setup
     pub fn new<R: CryptoRng + RngCore>(rng: &mut R) -> Self {
@@ -208,13 +221,16 @@ where
 
 /// Length of [`ServerSetup`] in bytes for serialization.
 pub type ServerSetupLen<CS: CipherSuite, S: SecretKey<CS::KeGroup>> =
-    Sum<Sum<OutputSize<CS::Hash>, S::Len>, <CS::KeGroup as KeGroup>::SkLen>;
+    Sum<Sum<OutputSize<OprfHash<CS>>, S::Len>, <CS::KeGroup as KeGroup>::SkLen>;
 
 impl<CS: CipherSuite, S: SecretKey<CS::KeGroup>> ServerSetup<CS, S>
 where
-    <CS::Hash as CoreProxy>::Core: ProxyHash,
-    <<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
-    Le<<<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
+    <OprfHash<CS> as OutputSizeUser>::OutputSize:
+        IsLess<U256> + IsLessOrEqual<<OprfHash<CS> as BlockSizeUser>::BlockSize>,
+    OprfHash<CS>: Hash,
+    <OprfHash<CS> as CoreProxy>::Core: ProxyHash,
+    <<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
+    Le<<<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
 {
     /// Create [`ServerSetup`] with the given keypair
     pub fn new_with_key<R: CryptoRng + RngCore>(
@@ -235,19 +251,20 @@ where
     pub fn serialize(&self) -> GenericArray<u8, ServerSetupLen<CS, S>>
     where
         // ServerSetup: Hash + KeSk + KeSk
-        OutputSize<CS::Hash>: Add<S::Len>,
-        Sum<OutputSize<CS::Hash>, S::Len>: ArrayLength<u8> + Add<<CS::KeGroup as KeGroup>::SkLen>,
+        OutputSize<OprfHash<CS>>: Add<S::Len>,
+        Sum<OutputSize<OprfHash<CS>>, S::Len>:
+            ArrayLength<u8> + Add<<CS::KeGroup as KeGroup>::SkLen>,
         ServerSetupLen<CS, S>: ArrayLength<u8>,
     {
         self.oprf_seed
             .clone()
             .concat(self.keypair.private().serialize())
-            .concat(self.fake_keypair.private().to_arr())
+            .concat(self.fake_keypair.private().serialize())
     }
 
     /// Deserialization from bytes
     pub fn deserialize(input: &[u8]) -> Result<Self, ProtocolError<S::Error>> {
-        let seed_len = OutputSize::<CS::Hash>::USIZE;
+        let seed_len = OutputSize::<OprfHash<CS>>::USIZE;
         let key_len = <CS::KeGroup as KeGroup>::SkLen::USIZE;
         let checked_slice = check_slice_size(input, seed_len + key_len + key_len, "server_setup")?;
 
@@ -269,19 +286,22 @@ where
 // ============
 
 pub(crate) type ClientRegistrationLen<CS: CipherSuite> =
-    Sum<<CS::OprfGroup as Group>::ScalarLen, <CS::OprfGroup as Group>::ElemLen>;
+    Sum<<OprfGroup<CS> as Group>::ScalarLen, <OprfGroup<CS> as Group>::ElemLen>;
 
 impl<CS: CipherSuite> ClientRegistration<CS>
 where
-    <CS::Hash as CoreProxy>::Core: ProxyHash,
-    <<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
-    Le<<<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
+    <OprfHash<CS> as OutputSizeUser>::OutputSize:
+        IsLess<U256> + IsLessOrEqual<<OprfHash<CS> as BlockSizeUser>::BlockSize>,
+    OprfHash<CS>: Hash,
+    <OprfHash<CS> as CoreProxy>::Core: ProxyHash,
+    <<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
+    Le<<<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
 {
     /// Serialization into bytes
     pub fn serialize(&self) -> GenericArray<u8, ClientRegistrationLen<CS>>
     where
         // ClientRegistration: KgSk + KgPk
-        <CS::OprfGroup as Group>::ScalarLen: Add<<CS::OprfGroup as Group>::ElemLen>,
+        <OprfGroup<CS> as Group>::ScalarLen: Add<<OprfGroup<CS> as Group>::ElemLen>,
         ClientRegistrationLen<CS>: ArrayLength<u8>,
     {
         self.oprf_client
@@ -291,8 +311,8 @@ where
 
     /// Deserialization from bytes
     pub fn deserialize(input: &[u8]) -> Result<Self, ProtocolError> {
-        let client_len = <CS::OprfGroup as Group>::ScalarLen::USIZE;
-        let element_len = <CS::OprfGroup as Group>::ElemLen::USIZE;
+        let client_len = <OprfGroup<CS> as Group>::ScalarLen::USIZE;
+        let element_len = <OprfGroup<CS> as Group>::ElemLen::USIZE;
         let checked_slice =
             check_slice_size(input, client_len + element_len, "client_registration")?;
 
@@ -359,7 +379,7 @@ where
             params.slow_hash,
         )?;
 
-        let mut masking_key = Output::<CS::Hash>::default();
+        let mut masking_key = Output::<OprfHash<CS>>::default();
         randomized_pwd_hasher
             .expand(STR_MASKING_KEY, &mut masking_key)
             .map_err(|_| InternalError::HkdfError)?;
@@ -394,19 +414,22 @@ pub type ServerRegistrationLen<CS> = RegistrationUploadLen<CS>;
 
 impl<CS: CipherSuite> ServerRegistration<CS>
 where
-    <CS::Hash as CoreProxy>::Core: ProxyHash,
-    <<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
-    Le<<<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
+    <OprfHash<CS> as OutputSizeUser>::OutputSize:
+        IsLess<U256> + IsLessOrEqual<<OprfHash<CS> as BlockSizeUser>::BlockSize>,
+    OprfHash<CS>: Hash,
+    <OprfHash<CS> as CoreProxy>::Core: ProxyHash,
+    <<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
+    Le<<<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
 {
     /// Serialization into bytes
     pub fn serialize(&self) -> GenericArray<u8, ServerRegistrationLen<CS>>
     where
         // Envelope: Nonce + Hash
-        NonceLen: Add<OutputSize<CS::Hash>>,
+        NonceLen: Add<OutputSize<OprfHash<CS>>>,
         EnvelopeLen<CS>: ArrayLength<u8>,
         // RegistrationUpload: (KePk + Hash) + Envelope
-        <CS::KeGroup as KeGroup>::PkLen: Add<OutputSize<CS::Hash>>,
-        Sum<<CS::KeGroup as KeGroup>::PkLen, OutputSize<CS::Hash>>:
+        <CS::KeGroup as KeGroup>::PkLen: Add<OutputSize<OprfHash<CS>>>,
+        Sum<<CS::KeGroup as KeGroup>::PkLen, OutputSize<OprfHash<CS>>>:
             ArrayLength<u8> + Add<EnvelopeLen<CS>>,
         RegistrationUploadLen<CS>: ArrayLength<u8>,
         // ServerRegistration = RegistrationUpload
@@ -426,17 +449,15 @@ where
         message: RegistrationRequest<CS>,
         credential_identifier: &[u8],
     ) -> Result<ServerRegistrationStartResult<CS>, ProtocolError> {
-        let oprf_key = oprf_key_from_seed::<CS::OprfGroup, CS::Hash>(
-            &server_setup.oprf_seed,
-            credential_identifier,
-        )?;
+        let oprf_key =
+            oprf_key_from_seed::<CS::OprfGroup>(&server_setup.oprf_seed, credential_identifier)?;
 
         let server = voprf::NonVerifiableServer::new_with_key(&oprf_key)?;
-        let evaluate_result = server.evaluate(&message.blinded_element, None)?;
+        let evaluation_element = server.evaluate(&message.blinded_element, None)?;
 
         Ok(ServerRegistrationStartResult {
             message: RegistrationResponse {
-                evaluation_element: evaluate_result.message,
+                evaluation_element,
                 server_s_pk: server_setup.keypair.public().clone(),
             },
             #[cfg(test)]
@@ -463,23 +484,26 @@ where
 // =====
 
 pub(crate) type ClientLoginLen<CS: CipherSuite> =
-    Sum<Sum<<CS::OprfGroup as Group>::ScalarLen, CredentialRequestLen<CS>>, Ke1StateLen<CS>>;
+    Sum<Sum<<OprfGroup<CS> as Group>::ScalarLen, CredentialRequestLen<CS>>, Ke1StateLen<CS>>;
 
 impl<CS: CipherSuite> ClientLogin<CS>
 where
-    <CS::Hash as CoreProxy>::Core: ProxyHash,
-    <<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
-    Le<<<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
+    <OprfHash<CS> as OutputSizeUser>::OutputSize:
+        IsLess<U256> + IsLessOrEqual<<OprfHash<CS> as BlockSizeUser>::BlockSize>,
+    OprfHash<CS>: Hash,
+    <OprfHash<CS> as CoreProxy>::Core: ProxyHash,
+    <<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
+    Le<<<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
 {
     /// Serialization into bytes
     pub fn serialize(&self) -> GenericArray<u8, ClientLoginLen<CS>>
     where
         // CredentialRequest: KgPk + Ke1Message
-        <CS::OprfGroup as Group>::ElemLen: Add<Ke1MessageLen<CS>>,
+        <OprfGroup<CS> as Group>::ElemLen: Add<Ke1MessageLen<CS>>,
         CredentialRequestLen<CS>: ArrayLength<u8>,
         // ClientLogin: KgSk + CredentialRequest + Ke1State
-        <CS::OprfGroup as Group>::ScalarLen: Add<CredentialRequestLen<CS>>,
-        Sum<<CS::OprfGroup as Group>::ScalarLen, CredentialRequestLen<CS>>:
+        <OprfGroup<CS> as Group>::ScalarLen: Add<CredentialRequestLen<CS>>,
+        Sum<<OprfGroup<CS> as Group>::ScalarLen, CredentialRequestLen<CS>>:
             ArrayLength<u8> + Add<Ke1StateLen<CS>>,
         ClientLoginLen<CS>: ArrayLength<u8>,
     {
@@ -491,14 +515,14 @@ where
 
     /// Deserialization from bytes
     pub fn deserialize(input: &[u8]) -> Result<Self, ProtocolError> {
-        let client_len = <CS::OprfGroup as Group>::ScalarLen::USIZE;
-        let request_len = <CS::OprfGroup as Group>::ElemLen::USIZE + Ke1MessageLen::<CS>::USIZE;
+        let client_len = <OprfGroup<CS> as Group>::ScalarLen::USIZE;
+        let request_len = <OprfGroup<CS> as Group>::ElemLen::USIZE + Ke1MessageLen::<CS>::USIZE;
         let state_len = Ke1StateLen::<CS>::USIZE;
         let checked_slice =
             check_slice_size(input, client_len + request_len + state_len, "client_login")?;
 
         let ke1_state =
-            <CS::KeyExchange as KeyExchange<CS::Hash, CS::KeGroup>>::KE1State::from_bytes(
+            <CS::KeyExchange as KeyExchange<OprfHash<CS>, CS::KeGroup>>::KE1State::from_bytes(
                 &checked_slice[client_len + request_len..],
             )?;
         Ok(Self {
@@ -509,29 +533,16 @@ where
             ke1_state,
         })
     }
-
-    /// Only used for testing zeroize
-    #[cfg(test)]
-    pub(crate) fn to_vec(&self) -> std::vec::Vec<u8>
-    where
-        // CredentialRequest: KgPk + Ke1Message
-        <CS::OprfGroup as Group>::ElemLen: Add<Ke1MessageLen<CS>>,
-        CredentialRequestLen<CS>: ArrayLength<u8>,
-    {
-        [
-            self.oprf_client.serialize().to_vec(),
-            self.credential_request.serialize().to_vec(),
-            self.ke1_state.to_bytes().to_vec(),
-        ]
-        .concat()
-    }
 }
 
 impl<CS: CipherSuite> ClientLogin<CS>
 where
-    <CS::Hash as CoreProxy>::Core: ProxyHash,
-    <<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
-    Le<<<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
+    <OprfHash<CS> as OutputSizeUser>::OutputSize:
+        IsLess<U256> + IsLessOrEqual<<OprfHash<CS> as BlockSizeUser>::BlockSize>,
+    OprfHash<CS>: Hash,
+    <OprfHash<CS> as CoreProxy>::Core: ProxyHash,
+    <<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
+    Le<<<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
 {
     /// Returns an initial "blinded" password request to send to the server, as
     /// well as a ClientLogin
@@ -567,8 +578,9 @@ where
     ) -> Result<ClientLoginFinishResult<CS>, ProtocolError>
     where
         // MaskedResponse: (Nonce + Hash) + KePk
-        NonceLen: Add<OutputSize<CS::Hash>>,
-        Sum<NonceLen, OutputSize<CS::Hash>>: ArrayLength<u8> + Add<<CS::KeGroup as KeGroup>::PkLen>,
+        NonceLen: Add<OutputSize<OprfHash<CS>>>,
+        Sum<NonceLen, OutputSize<OprfHash<CS>>>:
+            ArrayLength<u8> + Add<<CS::KeGroup as KeGroup>::PkLen>,
         MaskedResponseLen<CS>: ArrayLength<u8>,
     {
         // Check if beta value from server is equal to alpha value from client
@@ -589,7 +601,7 @@ where
             params.slow_hash,
         )?;
 
-        let mut masking_key = Output::<CS::Hash>::default();
+        let mut masking_key = Output::<OprfHash<CS>>::default();
         randomized_pwd_hasher
             .expand(STR_MASKING_KEY, &mut masking_key)
             .map_err(|_| InternalError::HkdfError)?;
@@ -617,14 +629,15 @@ where
                 err => err,
             })?;
 
-        let beta = credential_response.evaluation_element.value().to_arr();
+        let beta = OprfGroup::<CS>::serialize_elem(credential_response.evaluation_element.value());
         let credential_response_component = CredentialResponse::<CS>::serialize_without_ke(
             &beta,
             &credential_response.masking_nonce,
             &credential_response.masked_response,
         );
 
-        let blinded_element = self.credential_request.blinded_element.value().to_arr();
+        let blinded_element =
+            OprfGroup::<CS>::serialize_elem(self.credential_request.blinded_element.value());
         let ke1_message = self.credential_request.ke1_message.to_bytes();
         let serialized_credential_request =
             CredentialRequest::<CS>::serialize_iter(&blinded_element, &ke1_message);
@@ -660,9 +673,12 @@ where
 
 impl<CS: CipherSuite> ServerLogin<CS>
 where
-    <CS::Hash as CoreProxy>::Core: ProxyHash,
-    <<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
-    Le<<<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
+    <OprfHash<CS> as OutputSizeUser>::OutputSize:
+        IsLess<U256> + IsLessOrEqual<<OprfHash<CS> as BlockSizeUser>::BlockSize>,
+    OprfHash<CS>: Hash,
+    <OprfHash<CS> as CoreProxy>::Core: ProxyHash,
+    <<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
+    Le<<<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
 {
     /// Serialization into bytes
     pub fn serialize(&self) -> GenericArray<u8, Ke2StateLen<CS>> {
@@ -672,9 +688,8 @@ where
     /// Deserialization from bytes
     pub fn deserialize(bytes: &[u8]) -> Result<Self, ProtocolError> {
         Ok(Self {
-            _cs: PhantomData,
             ke2_state:
-                <CS::KeyExchange as KeyExchange<CS::Hash, CS::KeGroup>>::KE2State::from_bytes(
+                <CS::KeyExchange as KeyExchange<OprfHash<CS>, CS::KeGroup>>::KE2State::from_bytes(
                     bytes,
                 )?,
         })
@@ -695,8 +710,9 @@ where
     ) -> Result<ServerLoginStartResult<CS>, ProtocolError<S::Error>>
     where
         // MaskedResponse: (Nonce + Hash) + KePk
-        NonceLen: Add<OutputSize<CS::Hash>>,
-        Sum<NonceLen, OutputSize<CS::Hash>>: ArrayLength<u8> + Add<<CS::KeGroup as KeGroup>::PkLen>,
+        NonceLen: Add<OutputSize<OprfHash<CS>>>,
+        Sum<NonceLen, OutputSize<OprfHash<CS>>>:
+            ArrayLength<u8> + Add<<CS::KeGroup as KeGroup>::PkLen>,
         MaskedResponseLen<CS>: ArrayLength<u8>,
     {
         let record = match password_file {
@@ -728,29 +744,27 @@ where
 
         let (id_u, id_s) = bytestrings_from_identifiers::<CS::KeGroup>(
             identifiers,
-            client_s_pk.to_arr(),
-            server_s_pk.to_arr(),
+            client_s_pk.to_bytes(),
+            server_s_pk.to_bytes(),
         )
         .map_err(ProtocolError::into_custom)?;
 
-        let blinded_element = credential_request.blinded_element.value().to_arr();
+        let blinded_element =
+            OprfGroup::<CS>::serialize_elem(credential_request.blinded_element.value());
         let ke1_message = credential_request.ke1_message.to_bytes();
         let credential_request_bytes =
             CredentialRequest::<CS>::serialize_iter(&blinded_element, &ke1_message);
 
-        let oprf_key = oprf_key_from_seed::<CS::OprfGroup, CS::Hash>(
-            &server_setup.oprf_seed,
-            credential_identifier,
-        )
-        .map_err(ProtocolError::into_custom)?;
+        let oprf_key =
+            oprf_key_from_seed::<CS::OprfGroup>(&server_setup.oprf_seed, credential_identifier)
+                .map_err(ProtocolError::into_custom)?;
         let server = voprf::NonVerifiableServer::new_with_key(&oprf_key)
             .map_err(|e| ProtocolError::into_custom(e.into()))?;
-        let evaluate_result = server
+        let evaluation_element = server
             .evaluate(&credential_request.blinded_element, None)
             .map_err(|e| ProtocolError::into_custom(e.into()))?;
-        let evaluation_element = evaluate_result.message;
 
-        let beta = evaluation_element.value().to_arr();
+        let beta = OprfGroup::<CS>::serialize_elem(evaluation_element.value());
         let credential_response_component =
             CredentialResponse::<CS>::serialize_without_ke(&beta, &masking_nonce, &masked_response);
 
@@ -758,7 +772,7 @@ where
             rng,
             credential_request_bytes,
             credential_response_component,
-            credential_request.ke1_message,
+            credential_request.ke1_message.clone(),
             client_s_pk,
             server_s_sk.clone(),
             id_u.iter(),
@@ -776,7 +790,6 @@ where
         Ok(ServerLoginStartResult {
             message: credential_response,
             state: Self {
-                _cs: PhantomData,
                 ke2_state: result.0,
             },
             #[cfg(test)]
@@ -794,14 +807,13 @@ where
         self,
         message: CredentialFinalization<CS>,
     ) -> Result<ServerLoginFinishResult<CS>, ProtocolError> {
-        let session_key = <CS::KeyExchange as KeyExchange<CS::Hash, CS::KeGroup>>::finish_ke(
+        let session_key = <CS::KeyExchange as KeyExchange<OprfHash<CS>, CS::KeGroup>>::finish_ke(
             message.ke3_message,
             &self.ke2_state,
         )?;
 
         Ok(ServerLoginFinishResult {
             session_key,
-            _cs: PhantomData,
             #[cfg(test)]
             state: self,
         })
@@ -823,13 +835,15 @@ pub struct Identifiers<'a> {
 }
 
 /// Optional parameters for client registration finish
-#[derive(DeriveWhere)]
 #[derive_where(Clone, Default)]
 pub struct ClientRegistrationFinishParameters<'i, 'h, CS: CipherSuite>
 where
-    <CS::Hash as CoreProxy>::Core: ProxyHash,
-    <<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
-    Le<<<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
+    <OprfHash<CS> as OutputSizeUser>::OutputSize:
+        IsLess<U256> + IsLessOrEqual<<OprfHash<CS> as BlockSizeUser>::BlockSize>,
+    OprfHash<CS>: Hash,
+    <OprfHash<CS> as CoreProxy>::Core: ProxyHash,
+    <<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
+    Le<<<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
 {
     /// Specifying the identifiers idU and idS
     pub identifiers: Identifiers<'i>,
@@ -839,9 +853,12 @@ where
 
 impl<'i, 'h, CS: CipherSuite> ClientRegistrationFinishParameters<'i, 'h, CS>
 where
-    <CS::Hash as CoreProxy>::Core: ProxyHash,
-    <<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
-    Le<<<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
+    <OprfHash<CS> as OutputSizeUser>::OutputSize:
+        IsLess<U256> + IsLessOrEqual<<OprfHash<CS> as BlockSizeUser>::BlockSize>,
+    OprfHash<CS>: Hash,
+    <OprfHash<CS> as CoreProxy>::Core: ProxyHash,
+    <<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
+    Le<<<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
 {
     /// Create a new [`ClientRegistrationFinishParameters`]
     pub fn new(identifiers: Identifiers<'i>, slow_hash: Option<&'h CS::SlowHash>) -> Self {
@@ -853,13 +870,15 @@ where
 }
 
 /// Contains the fields that are returned by a client registration start
-#[derive(DeriveWhere)]
 #[derive_where(Clone)]
 pub struct ClientRegistrationStartResult<CS: CipherSuite>
 where
-    <CS::Hash as CoreProxy>::Core: ProxyHash,
-    <<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
-    Le<<<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
+    <OprfHash<CS> as OutputSizeUser>::OutputSize:
+        IsLess<U256> + IsLessOrEqual<<OprfHash<CS> as BlockSizeUser>::BlockSize>,
+    OprfHash<CS>: Hash,
+    <OprfHash<CS> as CoreProxy>::Core: ProxyHash,
+    <<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
+    Le<<<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
 {
     /// The registration request message to be sent to the server
     pub message: RegistrationRequest<CS>,
@@ -869,18 +888,20 @@ where
 }
 
 /// Contains the fields that are returned by a client registration finish
-#[derive(DeriveWhere)]
 #[derive_where(Clone)]
 pub struct ClientRegistrationFinishResult<CS: CipherSuite>
 where
-    <CS::Hash as CoreProxy>::Core: ProxyHash,
-    <<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
-    Le<<<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
+    <OprfHash<CS> as OutputSizeUser>::OutputSize:
+        IsLess<U256> + IsLessOrEqual<<OprfHash<CS> as BlockSizeUser>::BlockSize>,
+    OprfHash<CS>: Hash,
+    <OprfHash<CS> as CoreProxy>::Core: ProxyHash,
+    <<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
+    Le<<<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
 {
     /// The registration upload message to be sent to the server
     pub message: RegistrationUpload<CS>,
     /// The export key output by client registration
-    pub export_key: Output<CS::Hash>,
+    pub export_key: Output<OprfHash<CS>>,
     /// The server's static public key
     pub server_s_pk: PublicKey<CS::KeGroup>,
     /// Instance of the ClientRegistration, only used in tests for checking
@@ -889,37 +910,41 @@ where
     pub state: ClientRegistration<CS>,
     /// AuthKey, only used in tests
     #[cfg(test)]
-    pub auth_key: Output<CS::Hash>,
+    pub auth_key: Output<OprfHash<CS>>,
     /// Password derived key, only used in tests
     #[cfg(test)]
-    pub randomized_pwd: Output<CS::Hash>,
+    pub randomized_pwd: Output<OprfHash<CS>>,
 }
 
 /// Contains the fields that are returned by a server registration start. Note
 /// that there is no state output in this step
-#[derive(DeriveWhere)]
 #[derive_where(Clone)]
 pub struct ServerRegistrationStartResult<CS: CipherSuite>
 where
-    <CS::Hash as CoreProxy>::Core: ProxyHash,
-    <<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
-    Le<<<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
+    <OprfHash<CS> as OutputSizeUser>::OutputSize:
+        IsLess<U256> + IsLessOrEqual<<OprfHash<CS> as BlockSizeUser>::BlockSize>,
+    OprfHash<CS>: Hash,
+    <OprfHash<CS> as CoreProxy>::Core: ProxyHash,
+    <<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
+    Le<<<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
 {
     /// The registration resposne message to send to the client
     pub message: RegistrationResponse<CS>,
     /// OPRF key, only used in tests
     #[cfg(test)]
-    pub oprf_key: GenericArray<u8, <CS::OprfGroup as Group>::ScalarLen>,
+    pub oprf_key: GenericArray<u8, <OprfGroup<CS> as Group>::ScalarLen>,
 }
 
 /// Contains the fields that are returned by a client login start
-#[derive(DeriveWhere)]
 #[derive_where(Clone)]
 pub struct ClientLoginStartResult<CS: CipherSuite>
 where
-    <CS::Hash as CoreProxy>::Core: ProxyHash,
-    <<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
-    Le<<<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
+    <OprfHash<CS> as OutputSizeUser>::OutputSize:
+        IsLess<U256> + IsLessOrEqual<<OprfHash<CS> as BlockSizeUser>::BlockSize>,
+    OprfHash<CS>: Hash,
+    <OprfHash<CS> as CoreProxy>::Core: ProxyHash,
+    <<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
+    Le<<<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
 {
     /// The message to send to the server to begin the login protocol
     pub message: CredentialRequest<CS>,
@@ -928,13 +953,15 @@ where
 }
 
 /// Optional parameters for client login finish
-#[derive(DeriveWhere)]
 #[derive_where(Clone, Default)]
 pub struct ClientLoginFinishParameters<'c, 'i, 'h, CS: CipherSuite>
 where
-    <CS::Hash as CoreProxy>::Core: ProxyHash,
-    <<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
-    Le<<<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
+    <OprfHash<CS> as OutputSizeUser>::OutputSize:
+        IsLess<U256> + IsLessOrEqual<<OprfHash<CS> as BlockSizeUser>::BlockSize>,
+    OprfHash<CS>: Hash,
+    <OprfHash<CS> as CoreProxy>::Core: ProxyHash,
+    <<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
+    Le<<<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
 {
     /// Specifying a context field that the server must agree on
     pub context: Option<&'c [u8]>,
@@ -947,9 +974,12 @@ where
 
 impl<'c, 'i, 'h, CS: CipherSuite> ClientLoginFinishParameters<'c, 'i, 'h, CS>
 where
-    <CS::Hash as CoreProxy>::Core: ProxyHash,
-    <<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
-    Le<<<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
+    <OprfHash<CS> as OutputSizeUser>::OutputSize:
+        IsLess<U256> + IsLessOrEqual<<OprfHash<CS> as BlockSizeUser>::BlockSize>,
+    OprfHash<CS>: Hash,
+    <OprfHash<CS> as CoreProxy>::Core: ProxyHash,
+    <<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
+    Le<<<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
 {
     /// Create a new [`ClientLoginFinishParameters`]
     pub fn new(
@@ -966,20 +996,22 @@ where
 }
 
 /// Contains the fields that are returned by a client login finish
-#[derive(DeriveWhere)]
 #[derive_where(Clone)]
 pub struct ClientLoginFinishResult<CS: CipherSuite>
 where
-    <CS::Hash as CoreProxy>::Core: ProxyHash,
-    <<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
-    Le<<<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
+    <OprfHash<CS> as OutputSizeUser>::OutputSize:
+        IsLess<U256> + IsLessOrEqual<<OprfHash<CS> as BlockSizeUser>::BlockSize>,
+    OprfHash<CS>: Hash,
+    <OprfHash<CS> as CoreProxy>::Core: ProxyHash,
+    <<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
+    Le<<<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
 {
     /// The message to send to the server to complete the protocol
     pub message: CredentialFinalization<CS>,
     /// The session key
-    pub session_key: Output<CS::Hash>,
+    pub session_key: Output<OprfHash<CS>>,
     /// The client-side export key
-    pub export_key: Output<CS::Hash>,
+    pub export_key: Output<OprfHash<CS>>,
     /// The server's static public key
     pub server_s_pk: PublicKey<CS::KeGroup>,
     /// Instance of the ClientLogin, only used in tests for checking zeroize
@@ -987,26 +1019,27 @@ where
     pub state: ClientLogin<CS>,
     /// Handshake secret, only used in tests
     #[cfg(test)]
-    pub handshake_secret: Output<CS::Hash>,
+    pub handshake_secret: Output<OprfHash<CS>>,
     /// Client MAC key, only used in tests
     #[cfg(test)]
-    pub client_mac_key: Output<CS::Hash>,
+    pub client_mac_key: Output<OprfHash<CS>>,
 }
 
 /// Contains the fields that are returned by a server login finish
-#[derive(DeriveWhere)]
 #[derive_where(Clone)]
 #[cfg_attr(not(test), derive_where(Debug))]
 #[cfg_attr(test, derive_where(Debug; ServerLogin<CS>))]
 pub struct ServerLoginFinishResult<CS: CipherSuite>
 where
-    <CS::Hash as CoreProxy>::Core: ProxyHash,
-    <<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
-    Le<<<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
+    <OprfHash<CS> as OutputSizeUser>::OutputSize:
+        IsLess<U256> + IsLessOrEqual<<OprfHash<CS> as BlockSizeUser>::BlockSize>,
+    OprfHash<CS>: Hash,
+    <OprfHash<CS> as CoreProxy>::Core: ProxyHash,
+    <<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
+    Le<<<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
 {
     /// The session key between client and server
-    pub session_key: Output<CS::Hash>,
-    _cs: PhantomData<CS>,
+    pub session_key: Output<OprfHash<CS>>,
     /// Instance of the ClientRegistration, only used in tests for checking
     /// zeroize
     #[cfg(test)]
@@ -1024,19 +1057,21 @@ pub struct ServerLoginStartParameters<'c, 'i> {
 }
 
 /// Contains the fields that are returned by a server login start
-#[derive(DeriveWhere)]
 #[derive_where(Clone)]
 #[derive_where(
     Debug;
-    CS::OprfGroup,
-    <CS::KeyExchange as KeyExchange<CS::Hash, CS::KeGroup>>::KE2Message,
-    <CS::KeyExchange as KeyExchange<CS::Hash, CS::KeGroup>>::KE2State,
+    voprf::EvaluationElement<CS::OprfGroup>,
+    <CS::KeyExchange as KeyExchange<OprfHash<CS>, CS::KeGroup>>::KE2Message,
+    <CS::KeyExchange as KeyExchange<OprfHash<CS>, CS::KeGroup>>::KE2State,
 )]
 pub struct ServerLoginStartResult<CS: CipherSuite>
 where
-    <CS::Hash as CoreProxy>::Core: ProxyHash,
-    <<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
-    Le<<<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
+    <OprfHash<CS> as OutputSizeUser>::OutputSize:
+        IsLess<U256> + IsLessOrEqual<<OprfHash<CS> as BlockSizeUser>::BlockSize>,
+    OprfHash<CS>: Hash,
+    <OprfHash<CS> as CoreProxy>::Core: ProxyHash,
+    <<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
+    Le<<<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
 {
     /// The message to send back to the client
     pub message: CredentialResponse<CS>,
@@ -1044,13 +1079,13 @@ where
     pub state: ServerLogin<CS>,
     /// Handshake secret, only used in tests
     #[cfg(test)]
-    pub handshake_secret: Output<CS::Hash>,
+    pub handshake_secret: Output<OprfHash<CS>>,
     /// Server MAC key, only used in tests
     #[cfg(test)]
-    pub server_mac_key: Output<CS::Hash>,
+    pub server_mac_key: Output<OprfHash<CS>>,
     /// OPRF key, only used in tests
     #[cfg(test)]
-    pub oprf_key: GenericArray<u8, <CS::OprfGroup as Group>::ScalarLen>,
+    pub oprf_key: GenericArray<u8, <OprfGroup<CS> as Group>::ScalarLen>,
 }
 
 ////////////////////////////////////////////////
@@ -1062,14 +1097,17 @@ where
 #[allow(clippy::type_complexity)]
 fn get_password_derived_key<CS: CipherSuite>(
     input: &[u8],
-    oprf_client: voprf::NonVerifiableClient<CS::OprfGroup, CS::Hash>,
-    evaluation_element: voprf::EvaluationElement<CS::OprfGroup, CS::Hash>,
+    oprf_client: voprf::NonVerifiableClient<CS::OprfGroup>,
+    evaluation_element: voprf::EvaluationElement<CS::OprfGroup>,
     slow_hash: Option<&CS::SlowHash>,
-) -> Result<(Output<CS::Hash>, Hkdf<CS::Hash>), ProtocolError>
+) -> Result<(Output<OprfHash<CS>>, Hkdf<OprfHash<CS>>), ProtocolError>
 where
-    <CS::Hash as CoreProxy>::Core: ProxyHash,
-    <<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
-    Le<<<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
+    <OprfHash<CS> as OutputSizeUser>::OutputSize:
+        IsLess<U256> + IsLessOrEqual<<OprfHash<CS> as BlockSizeUser>::BlockSize>,
+    OprfHash<CS>: Hash,
+    <OprfHash<CS> as CoreProxy>::Core: ProxyHash,
+    <<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
+    Le<<<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
 {
     let oprf_output = oprf_client.finalize(input, &evaluation_element, None)?;
 
@@ -1080,63 +1118,74 @@ where
     }
     .map_err(ProtocolError::from)?;
 
-    let mut hkdf = HkdfExtract::<CS::Hash>::new(None);
+    let mut hkdf = HkdfExtract::<OprfHash<CS>>::new(None);
     hkdf.input_ikm(&oprf_output);
     hkdf.input_ikm(&hardened_output);
     Ok(hkdf.finalize())
 }
 
-fn oprf_key_from_seed<G: Group, D: Hash>(
-    oprf_seed: &Output<D>,
+fn oprf_key_from_seed<CS: voprf::CipherSuite>(
+    oprf_seed: &Output<CS::Hash>,
     credential_identifier: &[u8],
-) -> Result<GenericArray<u8, G::ScalarLen>, ProtocolError>
+) -> Result<GenericArray<u8, <CS::Group as Group>::ScalarLen>, ProtocolError>
 where
-    D::Core: ProxyHash,
-    <D::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
-    Le<<D::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
+    <CS::Hash as OutputSizeUser>::OutputSize:
+        IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
+    CS::Hash: Hash,
+    <CS::Hash as CoreProxy>::Core: ProxyHash,
+    <<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
+    Le<<<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
 {
-    let mut ikm = GenericArray::<_, G::ScalarLen>::default();
-    Hkdf::<D>::from_prk(oprf_seed)
+    let mut ikm = GenericArray::<_, <CS::Group as Group>::ScalarLen>::default();
+    Hkdf::<CS::Hash>::from_prk(oprf_seed)
         .ok()
         .and_then(|hkdf| {
             hkdf.expand_multi_info(&[credential_identifier, STR_OPRF_KEY], &mut ikm)
                 .ok()
         })
         .ok_or(InternalError::HkdfError)?;
-    Ok(G::scalar_as_bytes(G::hash_to_scalar::<D, _, _>(
-        [ikm.as_slice()],
-        GenericArray::from(*STR_OPAQUE_DERIVE_KEY_PAIR),
-    )?))
+    Ok(CS::Group::serialize_scalar(
+        CS::Group::hash_to_scalar::<CS>(
+            &[ikm.as_slice()],
+            &GenericArray::from(*STR_OPAQUE_DERIVE_KEY_PAIR),
+        )?,
+    ))
 }
 
-#[derive(DeriveWhere)]
 #[derive_where(Clone)]
 #[derive_where(Debug, Eq, Hash, PartialEq)]
 pub(crate) struct MaskedResponse<CS: CipherSuite>
 where
-    <CS::Hash as CoreProxy>::Core: ProxyHash,
-    <<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
-    Le<<<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
+    <OprfHash<CS> as OutputSizeUser>::OutputSize:
+        IsLess<U256> + IsLessOrEqual<<OprfHash<CS> as BlockSizeUser>::BlockSize>,
+    OprfHash<CS>: Hash,
+    <OprfHash<CS> as CoreProxy>::Core: ProxyHash,
+    <<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
+    Le<<<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
 {
     pub(crate) nonce: GenericArray<u8, NonceLen>,
-    pub(crate) hash: Output<CS::Hash>,
+    pub(crate) hash: Output<OprfHash<CS>>,
     pub(crate) pk: GenericArray<u8, <CS::KeGroup as KeGroup>::PkLen>,
 }
 
 pub(crate) type MaskedResponseLen<CS: CipherSuite> =
-    Sum<Sum<NonceLen, OutputSize<CS::Hash>>, <CS::KeGroup as KeGroup>::PkLen>;
+    Sum<Sum<NonceLen, OutputSize<OprfHash<CS>>>, <CS::KeGroup as KeGroup>::PkLen>;
 
 impl<CS: CipherSuite> MaskedResponse<CS>
 where
-    <CS::Hash as CoreProxy>::Core: ProxyHash,
-    <<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
-    Le<<<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
+    <OprfHash<CS> as OutputSizeUser>::OutputSize:
+        IsLess<U256> + IsLessOrEqual<<OprfHash<CS> as BlockSizeUser>::BlockSize>,
+    OprfHash<CS>: Hash,
+    <OprfHash<CS> as CoreProxy>::Core: ProxyHash,
+    <<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
+    Le<<<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
 {
     pub(crate) fn serialize(&self) -> GenericArray<u8, MaskedResponseLen<CS>>
     where
         // MaskedResponse: (Nonce + Hash) + KePk
-        NonceLen: Add<OutputSize<CS::Hash>>,
-        Sum<NonceLen, OutputSize<CS::Hash>>: ArrayLength<u8> + Add<<CS::KeGroup as KeGroup>::PkLen>,
+        NonceLen: Add<OutputSize<OprfHash<CS>>>,
+        Sum<NonceLen, OutputSize<OprfHash<CS>>>:
+            ArrayLength<u8> + Add<<CS::KeGroup as KeGroup>::PkLen>,
         MaskedResponseLen<CS>: ArrayLength<u8>,
     {
         self.nonce.concat(self.hash.clone()).concat(self.pk.clone())
@@ -1144,7 +1193,7 @@ where
 
     pub(crate) fn deserialize(bytes: &[u8]) -> Self {
         let nonce = NonceLen::USIZE;
-        let hash = nonce + OutputSize::<CS::Hash>::USIZE;
+        let hash = nonce + OutputSize::<OprfHash<CS>>::USIZE;
         let pk = hash + <CS::KeGroup as KeGroup>::PkLen::USIZE;
 
         Self {
@@ -1166,25 +1215,27 @@ fn mask_response<CS: CipherSuite>(
     envelope: &Envelope<CS>,
 ) -> Result<MaskedResponse<CS>, ProtocolError>
 where
-    <CS::Hash as CoreProxy>::Core: ProxyHash,
-    <<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
-    Le<<<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
-
+    <OprfHash<CS> as OutputSizeUser>::OutputSize:
+        IsLess<U256> + IsLessOrEqual<<OprfHash<CS> as BlockSizeUser>::BlockSize>,
+    OprfHash<CS>: Hash,
+    <OprfHash<CS> as CoreProxy>::Core: ProxyHash,
+    <<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
+    Le<<<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
     // MaskedResponse: (Nonce + Hash) + KePk
-    NonceLen: Add<OutputSize<CS::Hash>>,
-    Sum<NonceLen, OutputSize<CS::Hash>>: ArrayLength<u8> + Add<<CS::KeGroup as KeGroup>::PkLen>,
+    NonceLen: Add<OutputSize<OprfHash<CS>>>,
+    Sum<NonceLen, OutputSize<OprfHash<CS>>>: ArrayLength<u8> + Add<<CS::KeGroup as KeGroup>::PkLen>,
     MaskedResponseLen<CS>: ArrayLength<u8>,
 {
     let mut xor_pad = GenericArray::<_, MaskedResponseLen<CS>>::default();
 
-    Hkdf::<CS::Hash>::from_prk(masking_key)
+    Hkdf::<OprfHash<CS>>::from_prk(masking_key)
         .map_err(|_| InternalError::HkdfError)?
         .expand_multi_info(&[masking_nonce, STR_CREDENTIAL_RESPONSE_PAD], &mut xor_pad)
         .map_err(|_| InternalError::HkdfError)?;
 
     for (x1, x2) in xor_pad.iter_mut().zip(
         server_s_pk
-            .to_arr()
+            .to_bytes()
             .as_slice()
             .iter()
             .chain(envelope.serialize().iter()),
@@ -1201,17 +1252,20 @@ fn unmask_response<CS: CipherSuite>(
     masked_response: &MaskedResponse<CS>,
 ) -> Result<(PublicKey<CS::KeGroup>, Envelope<CS>), ProtocolError>
 where
-    <CS::Hash as CoreProxy>::Core: ProxyHash,
-    <<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
-    Le<<<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
+    <OprfHash<CS> as OutputSizeUser>::OutputSize:
+        IsLess<U256> + IsLessOrEqual<<OprfHash<CS> as BlockSizeUser>::BlockSize>,
+    OprfHash<CS>: Hash,
+    <OprfHash<CS> as CoreProxy>::Core: ProxyHash,
+    <<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
+    Le<<<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
     // MaskedResponse: (Nonce + Hash) + KePk
-    NonceLen: Add<OutputSize<CS::Hash>>,
-    Sum<NonceLen, OutputSize<CS::Hash>>: ArrayLength<u8> + Add<<CS::KeGroup as KeGroup>::PkLen>,
+    NonceLen: Add<OutputSize<OprfHash<CS>>>,
+    Sum<NonceLen, OutputSize<OprfHash<CS>>>: ArrayLength<u8> + Add<<CS::KeGroup as KeGroup>::PkLen>,
     MaskedResponseLen<CS>: ArrayLength<u8>,
 {
     let mut xor_pad = GenericArray::<_, MaskedResponseLen<CS>>::default();
 
-    Hkdf::<CS::Hash>::from_prk(masking_key)
+    Hkdf::<OprfHash<CS>>::from_prk(masking_key)
         .map_err(|_| InternalError::HkdfError)?
         .expand_multi_info(&[masking_nonce, STR_CREDENTIAL_RESPONSE_PAD], &mut xor_pad)
         .map_err(|_| InternalError::HkdfError)?;
@@ -1221,12 +1275,9 @@ where
     }
 
     let key_len = <CS::KeGroup as KeGroup>::PkLen::USIZE;
-    let unchecked_server_s_pk = PublicKey::from_bytes(&xor_pad[..key_len])?;
-    let envelope = Envelope::deserialize(&xor_pad[key_len..])?;
-
-    // Ensure that public key is valid
-    let server_s_pk = KeyPair::<CS::KeGroup>::check_public_key(unchecked_server_s_pk)
+    let server_s_pk = PublicKey::deserialize(&xor_pad[..key_len])
         .map_err(|_| ProtocolError::SerializationError)?;
+    let envelope = Envelope::deserialize(&xor_pad[key_len..])?;
 
     Ok((server_s_pk, envelope))
 }
@@ -1257,27 +1308,25 @@ pub(crate) fn bytestrings_from_identifiers<KG: KeGroup>(
 fn blind<CS: CipherSuite, R: RngCore + CryptoRng>(
     rng: &mut R,
     password: &[u8],
-) -> Result<voprf::NonVerifiableClientBlindResult<CS::OprfGroup, CS::Hash>, voprf::Error>
+) -> Result<voprf::NonVerifiableClientBlindResult<CS::OprfGroup>, voprf::Error>
 where
-    <CS::Hash as CoreProxy>::Core: ProxyHash,
-    <<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
-    Le<<<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
+    <OprfHash<CS> as OutputSizeUser>::OutputSize:
+        IsLess<U256> + IsLessOrEqual<<OprfHash<CS> as BlockSizeUser>::BlockSize>,
+    OprfHash<CS>: Hash,
+    <OprfHash<CS> as CoreProxy>::Core: ProxyHash,
+    <<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
+    Le<<<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
 {
     #[cfg(not(test))]
     let result = voprf::NonVerifiableClient::blind(password, rng)?;
 
     #[cfg(test)]
     let result = {
-        let mut blind_bytes = GenericArray::default();
+        let mut blind_bytes = GenericArray::<_, <OprfGroup<CS> as Group>::ScalarLen>::default();
         let blind = loop {
             rng.fill_bytes(&mut blind_bytes);
-            let scalar = <CS::OprfGroup as Group>::from_scalar_slice_unchecked(&blind_bytes)?;
-            match scalar
-                .ct_eq(&<CS::OprfGroup as Group>::scalar_zero())
-                .into()
-            {
-                false => break scalar,
-                true => (),
+            if let Ok(scalar) = <OprfGroup<CS> as Group>::deserialize_scalar(&blind_bytes) {
+                break scalar;
             }
         };
         voprf::NonVerifiableClient::deterministic_blind_unchecked(password, blind)?
diff --git a/src/serialization/mod.rs b/src/serialization/mod.rs
index 10f31fd5..81060f90 100644
--- a/src/serialization/mod.rs
+++ b/src/serialization/mod.rs
@@ -12,7 +12,7 @@ use generic_array::typenum::{U0, U2};
 use generic_array::{ArrayLength, GenericArray};
 use hmac::Mac;
 
-use crate::errors::ProtocolError;
+use crate::errors::{InternalError, ProtocolError};
 
 // Corresponds to the I2OSP() function from RFC8017
 pub(crate) fn i2osp<L: ArrayLength<u8>>(
@@ -157,6 +157,20 @@ impl<T: Mac> MacExt for T {
     }
 }
 
+pub(crate) trait GenericArrayExt {
+    fn try_from_slice(slice: &[u8]) -> Result<&Self, InternalError>;
+}
+
+impl<L: ArrayLength<u8>> GenericArrayExt for GenericArray<u8, L> {
+    fn try_from_slice(slice: &[u8]) -> Result<&Self, InternalError> {
+        if slice.len() == L::USIZE {
+            Ok(Self::from_slice(slice))
+        } else {
+            Err(InternalError::InvalidByteSequence)
+        }
+    }
+}
+
 #[cfg(test)]
 mod tests;
 
diff --git a/src/slow_hash.rs b/src/slow_hash.rs
index 522d923c..7ee63a21 100644
--- a/src/slow_hash.rs
+++ b/src/slow_hash.rs
@@ -7,48 +7,39 @@
 
 //! Trait specifying a slow hashing function
 
-use digest::core_api::BlockSizeUser;
-use digest::Output;
-use generic_array::typenum::{IsLess, Le, NonZero, U256};
+use generic_array::{ArrayLength, GenericArray};
 
 use crate::errors::InternalError;
-use crate::hash::{Hash, ProxyHash};
 
 /// Used for the slow hashing function in OPAQUE
-pub trait SlowHash<D: Hash>: Default
-where
-    D::Core: ProxyHash,
-    <D::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
-    Le<<D::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
-{
+pub trait SlowHash: Default {
     /// Computes the slow hashing function
-    fn hash(&self, input: Output<D>) -> Result<Output<D>, InternalError>;
+    fn hash<L: ArrayLength<u8>>(
+        &self,
+        input: GenericArray<u8, L>,
+    ) -> Result<GenericArray<u8, L>, InternalError>;
 }
 
 /// A no-op hash which simply returns its input
 #[derive(Default)]
 pub struct NoOpHash;
 
-impl<D: Hash> SlowHash<D> for NoOpHash
-where
-    D::Core: ProxyHash,
-    <D::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
-    Le<<D::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
-{
-    fn hash(&self, input: Output<D>) -> Result<Output<D>, InternalError> {
+impl SlowHash for NoOpHash {
+    fn hash<L: ArrayLength<u8>>(
+        &self,
+        input: GenericArray<u8, L>,
+    ) -> Result<GenericArray<u8, L>, InternalError> {
         Ok(input)
     }
 }
 
 #[cfg(feature = "slow-hash")]
-impl<D: Hash> SlowHash<D> for argon2::Argon2<'_>
-where
-    D::Core: ProxyHash,
-    <D::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
-    Le<<D::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
-{
-    fn hash(&self, input: Output<D>) -> Result<Output<D>, InternalError> {
-        let mut output = Output::<D>::default();
+impl SlowHash for argon2::Argon2<'_> {
+    fn hash<L: ArrayLength<u8>>(
+        &self,
+        input: GenericArray<u8, L>,
+    ) -> Result<GenericArray<u8, L>, InternalError> {
+        let mut output = GenericArray::default();
         self.hash_password_into(&input, &[0; argon2::MIN_SALT_LEN], &mut output)
             .map_err(|_| InternalError::SlowHashError)?;
         Ok(output)
diff --git a/src/util.rs b/src/util.rs
new file mode 100644
index 00000000..2aaff7d9
--- /dev/null
+++ b/src/util.rs
@@ -0,0 +1,41 @@
+// Copyright (c) Facebook, Inc. and its affiliates.
+//
+// This source code is licensed under both the MIT license found in the
+// LICENSE-MIT file in the root directory of this source tree and the Apache
+// License, Version 2.0 found in the LICENSE-APACHE file in the root directory
+// of this source tree.
+
+//! Utility functions.
+
+#[cfg(test)]
+pub(crate) fn test_zeroize_on_drop<T: Sized>(value: &mut T) {
+    drop_manually(value);
+
+    test_zeroized(value);
+}
+
+#[cfg(test)]
+pub(crate) fn test_zeroized<T: Sized>(value: &mut T) {
+    use std::{mem, slice, vec};
+
+    let test =
+        unsafe { slice::from_raw_parts(value as *const _ as *const u8, mem::size_of::<T>()) };
+
+    assert_eq!(test, vec![0; mem::size_of::<T>()]);
+}
+
+#[cfg(test)]
+pub(crate) fn drop_manually<T: Sized>(value: &mut T) {
+    use std::{mem, ptr, vec};
+
+    assert!(mem::needs_drop::<T>());
+    let mut test_holder = vec![value];
+    let ptr = &mut *test_holder[0] as *mut T;
+
+    unsafe {
+        test_holder.set_len(0);
+        ptr::drop_in_place(ptr);
+    }
+
+    assert_eq!(test_holder.capacity(), 1);
+}


Test patch:
diff --git a/src/serialization/tests.rs b/src/serialization/tests.rs
old mode 100755
new mode 100644
index 2a2e3e9c..4d68dbd4
--- a/src/serialization/tests.rs
+++ b/src/serialization/tests.rs
@@ -10,8 +10,8 @@ use std::vec;
 use std::vec::Vec;
 
 use digest::core_api::{BlockSizeUser, CoreProxy};
-use digest::Output;
-use generic_array::typenum::{IsLess, Le, NonZero, Sum, Unsigned, U256};
+use digest::{Output, OutputSizeUser};
+use generic_array::typenum::{IsLess, IsLessOrEqual, Le, NonZero, Sum, Unsigned, U256};
 use generic_array::ArrayLength;
 use proptest::collection::vec;
 use proptest::prelude::*;
@@ -19,16 +19,16 @@ use rand::rngs::OsRng;
 use rand::RngCore;
 use voprf::Group;
 
-use crate::ciphersuite::CipherSuite;
+use crate::ciphersuite::{CipherSuite, OprfGroup, OprfHash};
 use crate::envelope::{Envelope, EnvelopeLen, InnerEnvelopeMode};
 use crate::errors::*;
-use crate::hash::{OutputSize, ProxyHash};
+use crate::hash::{Hash, OutputSize, ProxyHash};
 use crate::key_exchange::group::KeGroup;
 use crate::key_exchange::traits::{
     FromBytes, Ke1MessageLen, Ke1StateLen, Ke2MessageLen, KeyExchange, ToBytes,
 };
 use crate::key_exchange::tripledh::{NonceLen, TripleDH};
-use crate::keypair::KeyPair;
+use crate::keypair::{KeyPair, SecretKey};
 use crate::messages::CredentialResponseWithoutKeLen;
 use crate::opaque::{ClientLoginLen, ClientRegistrationLen, MaskedResponseLen};
 use crate::serialization::{i2osp, os2ip};
@@ -36,31 +36,32 @@ use crate::*;
 
 #[cfg(feature = "ristretto255")]
 struct Ristretto255;
+
 #[cfg(feature = "ristretto255")]
 impl CipherSuite for Ristretto255 {
-    type OprfGroup = curve25519_dalek::ristretto::RistrettoPoint;
-    type KeGroup = curve25519_dalek::ristretto::RistrettoPoint;
+    type OprfGroup = crate::Ristretto255;
+    type KeGroup = crate::Ristretto255;
     type KeyExchange = TripleDH;
-    type Hash = sha2::Sha512;
     type SlowHash = crate::slow_hash::NoOpHash;
 }
 
-#[cfg(feature = "p256")]
 struct P256;
-#[cfg(feature = "p256")]
+
 impl CipherSuite for P256 {
-    type OprfGroup = p256_::ProjectivePoint;
-    type KeGroup = p256_::PublicKey;
+    type OprfGroup = ::p256::NistP256;
+    type KeGroup = ::p256::NistP256;
     type KeyExchange = TripleDH;
-    type Hash = sha2::Sha256;
     type SlowHash = crate::slow_hash::NoOpHash;
 }
 
-fn random_point<CS: CipherSuite>() -> CS::KeGroup
+fn random_point<CS: CipherSuite>() -> <CS::KeGroup as KeGroup>::Pk
 where
-    <CS::Hash as CoreProxy>::Core: ProxyHash,
-    <<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
-    Le<<<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
+    <OprfHash<CS> as OutputSizeUser>::OutputSize:
+        IsLess<U256> + IsLessOrEqual<<OprfHash<CS> as BlockSizeUser>::BlockSize>,
+    OprfHash<CS>: Hash,
+    <OprfHash<CS> as CoreProxy>::Core: ProxyHash,
+    <<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
+    Le<<<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
 {
     let mut rng = OsRng;
     let sk = CS::KeGroup::random_sk(&mut rng);
@@ -71,18 +72,20 @@ where
 fn client_registration_roundtrip() -> Result<(), ProtocolError> {
     fn inner<CS: CipherSuite>() -> Result<(), ProtocolError>
     where
-        <CS::Hash as CoreProxy>::Core: ProxyHash,
-        <<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
-        Le<<<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
+        <OprfHash<CS> as OutputSizeUser>::OutputSize:
+            IsLess<U256> + IsLessOrEqual<<OprfHash<CS> as BlockSizeUser>::BlockSize>,
+        OprfHash<CS>: Hash,
+        <OprfHash<CS> as CoreProxy>::Core: ProxyHash,
+        <<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
+        Le<<<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
         // ClientRegistration: KgSk + KgPk
-        <CS::OprfGroup as Group>::ScalarLen: Add<<CS::OprfGroup as Group>::ElemLen>,
+        <OprfGroup<CS> as Group>::ScalarLen: Add<<OprfGroup<CS> as Group>::ElemLen>,
         ClientRegistrationLen<CS>: ArrayLength<u8>,
     {
         let pw = b"hunter2";
         let mut rng = OsRng;
 
-        let blind_result =
-            &voprf::NonVerifiableClient::<CS::OprfGroup, CS::Hash>::blind(pw, &mut rng)?;
+        let blind_result = &voprf::NonVerifiableClient::<CS::OprfGroup>::blind(pw, &mut rng)?;
 
         let bytes: Vec<u8> = blind_result
             .state
@@ -100,7 +103,6 @@ fn client_registration_roundtrip() -> Result<(), ProtocolError> {
 
     #[cfg(feature = "ristretto255")]
     inner::<Ristretto255>()?;
-    #[cfg(feature = "p256")]
     inner::<P256>()?;
 
     Ok(())
@@ -110,22 +112,25 @@ fn client_registration_roundtrip() -> Result<(), ProtocolError> {
 fn server_registration_roundtrip() -> Result<(), ProtocolError> {
     fn inner<CS: CipherSuite>() -> Result<(), ProtocolError>
     where
-        <CS::Hash as CoreProxy>::Core: ProxyHash,
-        <<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
-        Le<<<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
+        <OprfHash<CS> as OutputSizeUser>::OutputSize:
+            IsLess<U256> + IsLessOrEqual<<OprfHash<CS> as BlockSizeUser>::BlockSize>,
+        OprfHash<CS>: Hash,
+        <OprfHash<CS> as CoreProxy>::Core: ProxyHash,
+        <<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
+        Le<<<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
         // Envelope: Nonce + Hash
-        NonceLen: Add<OutputSize<CS::Hash>>,
+        NonceLen: Add<OutputSize<OprfHash<CS>>>,
         EnvelopeLen<CS>: ArrayLength<u8>,
         // RegistrationUpload: (KePk + Hash) + Envelope
-        <CS::KeGroup as KeGroup>::PkLen: Add<OutputSize<CS::Hash>>,
-        Sum<<CS::KeGroup as KeGroup>::PkLen, OutputSize<CS::Hash>>:
+        <CS::KeGroup as KeGroup>::PkLen: Add<OutputSize<OprfHash<CS>>>,
+        Sum<<CS::KeGroup as KeGroup>::PkLen, OutputSize<OprfHash<CS>>>:
             ArrayLength<u8> + Add<EnvelopeLen<CS>>,
         RegistrationUploadLen<CS>: ArrayLength<u8>,
         // ServerRegistration = RegistrationUpload
     {
         // If we don't have envelope and client_pk, the server registration just
         let mut rng = OsRng;
-        let mut masking_key = Output::<CS::Hash>::default();
+        let mut masking_key = Output::<OprfHash<CS>>::default();
         rng.fill_bytes(&mut masking_key);
 
         // Construct a mock envelope
@@ -135,12 +140,12 @@ fn server_registration_roundtrip() -> Result<(), ProtocolError> {
         // ciphertext which is an encrypted private key
         //mock_envelope_bytes.extend_from_slice(&ciphertext);
         // length-MAC_SIZE hmac
-        mock_envelope_bytes.extend_from_slice(&Output::<CS::Hash>::default());
+        mock_envelope_bytes.extend_from_slice(&Output::<OprfHash<CS>>::default());
 
         let mock_client_kp = KeyPair::<CS::KeGroup>::generate_random(&mut rng);
         // serialization order: oprf_key, public key, envelope
         let mut bytes = Vec::<u8>::new();
-        bytes.extend_from_slice(&mock_client_kp.public().to_arr());
+        bytes.extend_from_slice(&mock_client_kp.public().to_bytes());
         bytes.extend_from_slice(&masking_key);
         bytes.extend_from_slice(&mock_envelope_bytes);
         let reg = ServerRegistration::<CS>::deserialize(&bytes)?;
@@ -151,7 +156,6 @@ fn server_registration_roundtrip() -> Result<(), ProtocolError> {
 
     #[cfg(feature = "ristretto255")]
     inner::<Ristretto255>()?;
-    #[cfg(feature = "p256")]
     inner::<P256>()?;
 
     Ok(())
@@ -161,12 +165,15 @@ fn server_registration_roundtrip() -> Result<(), ProtocolError> {
 fn registration_request_roundtrip() -> Result<(), ProtocolError> {
     fn inner<CS: CipherSuite>() -> Result<(), ProtocolError>
     where
-        <CS::Hash as CoreProxy>::Core: ProxyHash,
-        <<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
-        Le<<<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
+        <OprfHash<CS> as OutputSizeUser>::OutputSize:
+            IsLess<U256> + IsLessOrEqual<<OprfHash<CS> as BlockSizeUser>::BlockSize>,
+        OprfHash<CS>: Hash,
+        <OprfHash<CS> as CoreProxy>::Core: ProxyHash,
+        <<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
+        Le<<<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
     {
         let pt = random_point::<CS>();
-        let pt_bytes = pt.to_arr().to_vec();
+        let pt_bytes = CS::KeGroup::serialize_pk(&pt);
 
         let mut input = Vec::new();
         input.extend_from_slice(&pt_bytes);
@@ -176,13 +183,13 @@ fn registration_request_roundtrip() -> Result<(), ProtocolError> {
         assert_eq!(input, *r1_bytes);
 
         // Assert that identity group element is rejected
-        let identity = CS::OprfGroup::identity();
-        let identity_bytes = identity.to_arr().to_vec();
+        let identity = OprfGroup::<CS>::identity_elem();
+        let identity_bytes = OprfGroup::<CS>::serialize_elem(identity).to_vec();
 
         assert!(matches!(
             RegistrationRequest::<CS>::deserialize(&identity_bytes),
             Err(ProtocolError::LibraryError(InternalError::OprfError(
-                voprf::Error::PointError,
+                voprf::Error::Deserialization,
             )))
         ));
 
@@ -191,7 +198,6 @@ fn registration_request_roundtrip() -> Result<(), ProtocolError> {
 
     #[cfg(feature = "ristretto255")]
     inner::<Ristretto255>()?;
-    #[cfg(feature = "p256")]
     inner::<P256>()?;
 
     Ok(())
@@ -201,18 +207,21 @@ fn registration_request_roundtrip() -> Result<(), ProtocolError> {
 fn registration_response_roundtrip() -> Result<(), ProtocolError> {
     fn inner<CS: CipherSuite>() -> Result<(), ProtocolError>
     where
-        <CS::Hash as CoreProxy>::Core: ProxyHash,
-        <<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
-        Le<<<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
+        <OprfHash<CS> as OutputSizeUser>::OutputSize:
+            IsLess<U256> + IsLessOrEqual<<OprfHash<CS> as BlockSizeUser>::BlockSize>,
+        OprfHash<CS>: Hash,
+        <OprfHash<CS> as CoreProxy>::Core: ProxyHash,
+        <<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
+        Le<<<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
         // RegistrationResponse: KgPk + KePk
-        <CS::OprfGroup as Group>::ElemLen: Add<<CS::KeGroup as KeGroup>::PkLen>,
+        <OprfGroup<CS> as Group>::ElemLen: Add<<CS::KeGroup as KeGroup>::PkLen>,
         RegistrationResponseLen<CS>: ArrayLength<u8>,
     {
         let pt = random_point::<CS>();
-        let beta_bytes = pt.to_arr();
+        let beta_bytes = CS::KeGroup::serialize_pk(&pt);
         let mut rng = OsRng;
         let skp = KeyPair::<CS::KeGroup>::generate_random(&mut rng);
-        let pubkey_bytes = skp.public().to_arr();
+        let pubkey_bytes = skp.public().to_bytes();
 
         let mut input = Vec::new();
         input.extend_from_slice(&beta_bytes);
@@ -223,15 +232,15 @@ fn registration_response_roundtrip() -> Result<(), ProtocolError> {
         assert_eq!(input, *r2_bytes);
 
         // Assert that identity group element is rejected
-        let identity = CS::OprfGroup::identity();
-        let identity_bytes = identity.to_arr().to_vec();
+        let identity = OprfGroup::<CS>::identity_elem();
+        let identity_bytes = OprfGroup::<CS>::serialize_elem(identity).to_vec();
 
         assert!(matches!(
             RegistrationResponse::<CS>::deserialize(
                 &[identity_bytes, pubkey_bytes.to_vec()].concat()
             ),
             Err(ProtocolError::LibraryError(InternalError::OprfError(
-                voprf::Error::PointError,
+                voprf::Error::Deserialization,
             )))
         ));
 
@@ -240,7 +249,6 @@ fn registration_response_roundtrip() -> Result<(), ProtocolError> {
 
     #[cfg(feature = "ristretto255")]
     inner::<Ristretto255>()?;
-    #[cfg(feature = "p256")]
     inner::<P256>()?;
 
     Ok(())
@@ -250,28 +258,31 @@ fn registration_response_roundtrip() -> Result<(), ProtocolError> {
 fn registration_upload_roundtrip() -> Result<(), ProtocolError> {
     fn inner<CS: CipherSuite>() -> Result<(), ProtocolError>
     where
-        <CS::Hash as CoreProxy>::Core: ProxyHash,
-        <<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
-        Le<<<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
+        <OprfHash<CS> as OutputSizeUser>::OutputSize:
+            IsLess<U256> + IsLessOrEqual<<OprfHash<CS> as BlockSizeUser>::BlockSize>,
+        OprfHash<CS>: Hash,
+        <OprfHash<CS> as CoreProxy>::Core: ProxyHash,
+        <<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
+        Le<<<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
         // Envelope: Nonce + Hash
-        NonceLen: Add<OutputSize<CS::Hash>>,
+        NonceLen: Add<OutputSize<OprfHash<CS>>>,
         EnvelopeLen<CS>: ArrayLength<u8>,
         // RegistrationUpload: (KePk + Hash) + Envelope
-        <CS::KeGroup as KeGroup>::PkLen: Add<OutputSize<CS::Hash>>,
-        Sum<<CS::KeGroup as KeGroup>::PkLen, OutputSize<CS::Hash>>:
+        <CS::KeGroup as KeGroup>::PkLen: Add<OutputSize<OprfHash<CS>>>,
+        Sum<<CS::KeGroup as KeGroup>::PkLen, OutputSize<OprfHash<CS>>>:
             ArrayLength<u8> + Add<EnvelopeLen<CS>>,
         RegistrationUploadLen<CS>: ArrayLength<u8>,
     {
         let mut rng = OsRng;
         let skp = KeyPair::<CS::KeGroup>::generate_random(&mut rng);
-        let pubkey_bytes = skp.public().to_arr();
+        let pubkey_bytes = skp.public().to_bytes();
 
         let mut key = [0u8; 32];
         rng.fill_bytes(&mut key);
         let mut nonce = [0u8; NonceLen::USIZE];
         rng.fill_bytes(&mut nonce);
 
-        let mut masking_key = Output::<CS::Hash>::default();
+        let mut masking_key = Output::<OprfHash<CS>>::default();
         rng.fill_bytes(&mut masking_key);
 
         let randomized_pwd_hasher = hkdf::Hkdf::new(None, &key);
@@ -299,7 +310,6 @@ fn registration_upload_roundtrip() -> Result<(), ProtocolError> {
 
     #[cfg(feature = "ristretto255")]
     inner::<Ristretto255>()?;
-    #[cfg(feature = "p256")]
     inner::<P256>()?;
 
     Ok(())
@@ -309,22 +319,29 @@ fn registration_upload_roundtrip() -> Result<(), ProtocolError> {
 fn credential_request_roundtrip() -> Result<(), ProtocolError> {
     fn inner<CS: CipherSuite>() -> Result<(), ProtocolError>
     where
-        <CS::Hash as CoreProxy>::Core: ProxyHash,
-        <<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
-        Le<<<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
+        <OprfHash<CS> as OutputSizeUser>::OutputSize:
+            IsLess<U256> + IsLessOrEqual<<OprfHash<CS> as BlockSizeUser>::BlockSize>,
+        OprfHash<CS>: Hash,
+        <OprfHash<CS> as CoreProxy>::Core: ProxyHash,
+        <<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
+        Le<<<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
         // CredentialRequest: KgPk + Ke1Message
-        <CS::OprfGroup as Group>::ElemLen: Add<Ke1MessageLen<CS>>,
+        <OprfGroup<CS> as Group>::ElemLen: Add<Ke1MessageLen<CS>>,
         CredentialRequestLen<CS>: ArrayLength<u8>,
     {
         let mut rng = OsRng;
         let alpha = random_point::<CS>();
-        let alpha_bytes = alpha.to_arr();
+        let alpha_bytes = CS::KeGroup::serialize_pk(&alpha);
 
         let client_e_kp = KeyPair::<CS::KeGroup>::generate_random(&mut rng);
         let mut client_nonce = [0u8; NonceLen::USIZE];
         rng.fill_bytes(&mut client_nonce);
 
-        let ke1m: Vec<u8> = [client_nonce.as_ref(), client_e_kp.public()].concat();
+        let ke1m: Vec<u8> = [
+            client_nonce.as_ref(),
+            client_e_kp.public().to_bytes().as_ref(),
+        ]
+        .concat();
 
         let mut input = Vec::new();
         input.extend_from_slice(&alpha_bytes);
@@ -335,13 +352,13 @@ fn credential_request_roundtrip() -> Result<(), ProtocolError> {
         assert_eq!(input, *l1_bytes);
 
         // Assert that identity group element is rejected
-        let identity = CS::OprfGroup::identity();
-        let identity_bytes = identity.to_arr().to_vec();
+        let identity = OprfGroup::<CS>::identity_elem();
+        let identity_bytes = OprfGroup::<CS>::serialize_elem(identity).to_vec();
 
         assert!(matches!(
             CredentialRequest::<CS>::deserialize(&[identity_bytes, ke1m.to_vec()].concat()),
             Err(ProtocolError::LibraryError(InternalError::OprfError(
-                voprf::Error::PointError,
+                voprf::Error::Deserialization,
             )))
         ));
 
@@ -350,7 +367,6 @@ fn credential_request_roundtrip() -> Result<(), ProtocolError> {
 
     #[cfg(feature = "ristretto255")]
     inner::<Ristretto255>()?;
-    #[cfg(feature = "p256")]
     inner::<P256>()?;
 
     Ok(())
@@ -360,24 +376,28 @@ fn credential_request_roundtrip() -> Result<(), ProtocolError> {
 fn credential_response_roundtrip() -> Result<(), ProtocolError> {
     fn inner<CS: CipherSuite>() -> Result<(), ProtocolError>
     where
-        <CS::Hash as CoreProxy>::Core: ProxyHash,
-        <<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
-        Le<<<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
+        <OprfHash<CS> as OutputSizeUser>::OutputSize:
+            IsLess<U256> + IsLessOrEqual<<OprfHash<CS> as BlockSizeUser>::BlockSize>,
+        OprfHash<CS>: Hash,
+        <OprfHash<CS> as CoreProxy>::Core: ProxyHash,
+        <<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
+        Le<<<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
         // CredentialResponseWithoutKeLen: (KgPk + Nonce) + MaskedResponse
-        <CS::OprfGroup as Group>::ElemLen: Add<NonceLen>,
-        Sum<<CS::OprfGroup as Group>::ElemLen, NonceLen>:
+        <OprfGroup<CS> as Group>::ElemLen: Add<NonceLen>,
+        Sum<<OprfGroup<CS> as Group>::ElemLen, NonceLen>:
             ArrayLength<u8> + Add<MaskedResponseLen<CS>>,
         CredentialResponseWithoutKeLen<CS>: ArrayLength<u8>,
         // MaskedResponse: (Nonce + Hash) + KePk
-        NonceLen: Add<OutputSize<CS::Hash>>,
-        Sum<NonceLen, OutputSize<CS::Hash>>: ArrayLength<u8> + Add<<CS::KeGroup as KeGroup>::PkLen>,
+        NonceLen: Add<OutputSize<OprfHash<CS>>>,
+        Sum<NonceLen, OutputSize<OprfHash<CS>>>:
+            ArrayLength<u8> + Add<<CS::KeGroup as KeGroup>::PkLen>,
         MaskedResponseLen<CS>: ArrayLength<u8>,
         // CredentialResponse: CredentialResponseWithoutKeLen + Ke2Message
         CredentialResponseWithoutKeLen<CS>: Add<Ke2MessageLen<CS>>,
         CredentialResponseLen<CS>: ArrayLength<u8>,
     {
         let pt = random_point::<CS>();
-        let pt_bytes = pt.to_arr();
+        let pt_bytes = CS::KeGroup::serialize_pk(&pt);
 
         let mut rng = OsRng;
 
@@ -385,16 +405,21 @@ fn credential_response_roundtrip() -> Result<(), ProtocolError> {
         rng.fill_bytes(&mut masking_nonce);
 
         let mut masked_response =
-            vec![0u8; <CS::OprfGroup as Group>::ElemLen::USIZE + Envelope::<CS>::len()];
+            vec![0u8; <OprfGroup<CS> as Group>::ElemLen::USIZE + Envelope::<CS>::len()];
         rng.fill_bytes(&mut masked_response);
 
         let server_e_kp = KeyPair::<CS::KeGroup>::generate_random(&mut rng);
-        let mut mac = Output::<CS::Hash>::default();
+        let mut mac = Output::<OprfHash<CS>>::default();
         rng.fill_bytes(&mut mac);
         let mut server_nonce = [0u8; NonceLen::USIZE];
         rng.fill_bytes(&mut server_nonce);
 
-        let ke2m: Vec<u8> = [server_nonce.as_ref(), server_e_kp.public(), &mac].concat();
+        let ke2m: Vec<u8> = [
+            server_nonce.as_ref(),
+            server_e_kp.public().to_bytes().as_ref(),
+            &mac,
+        ]
+        .concat();
 
         let mut input = Vec::new();
         input.extend_from_slice(&pt_bytes);
@@ -407,8 +432,8 @@ fn credential_response_roundtrip() -> Result<(), ProtocolError> {
         assert_eq!(input, *l2_bytes);
 
         // Assert that identity group element is rejected
-        let identity = CS::OprfGroup::identity();
-        let identity_bytes = identity.to_arr().to_vec();
+        let identity = OprfGroup::<CS>::identity_elem();
+        let identity_bytes = OprfGroup::<CS>::serialize_elem(identity).to_vec();
 
         assert!(matches!(
             CredentialResponse::<CS>::deserialize(
@@ -421,7 +446,7 @@ fn credential_response_roundtrip() -> Result<(), ProtocolError> {
                 .concat()
             ),
             Err(ProtocolError::LibraryError(InternalError::OprfError(
-                voprf::Error::PointError,
+                voprf::Error::Deserialization,
             )))
         ));
 
@@ -430,7 +455,6 @@ fn credential_response_roundtrip() -> Result<(), ProtocolError> {
 
     #[cfg(feature = "ristretto255")]
     inner::<Ristretto255>()?;
-    #[cfg(feature = "p256")]
     inner::<P256>()?;
 
     Ok(())
@@ -440,12 +464,15 @@ fn credential_response_roundtrip() -> Result<(), ProtocolError> {
 fn credential_finalization_roundtrip() -> Result<(), ProtocolError> {
     fn inner<CS: CipherSuite>() -> Result<(), ProtocolError>
     where
-        <CS::Hash as CoreProxy>::Core: ProxyHash,
-        <<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
-        Le<<<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
+        <OprfHash<CS> as OutputSizeUser>::OutputSize:
+            IsLess<U256> + IsLessOrEqual<<OprfHash<CS> as BlockSizeUser>::BlockSize>,
+        OprfHash<CS>: Hash,
+        <OprfHash<CS> as CoreProxy>::Core: ProxyHash,
+        <<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
+        Le<<<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
     {
         let mut rng = OsRng;
-        let mut mac = Output::<CS::Hash>::default();
+        let mut mac = Output::<OprfHash<CS>>::default();
         rng.fill_bytes(&mut mac);
 
         let input = mac;
@@ -459,7 +486,6 @@ fn credential_finalization_roundtrip() -> Result<(), ProtocolError> {
 
     #[cfg(feature = "ristretto255")]
     inner::<Ristretto255>()?;
-    #[cfg(feature = "p256")]
     inner::<P256>()?;
 
     Ok(())
@@ -469,15 +495,18 @@ fn credential_finalization_roundtrip() -> Result<(), ProtocolError> {
 fn client_login_roundtrip() -> Result<(), ProtocolError> {
     fn inner<CS: CipherSuite>() -> Result<(), ProtocolError>
     where
-        <CS::Hash as CoreProxy>::Core: ProxyHash,
-        <<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
-        Le<<<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
+        <OprfHash<CS> as OutputSizeUser>::OutputSize:
+            IsLess<U256> + IsLessOrEqual<<OprfHash<CS> as BlockSizeUser>::BlockSize>,
+        OprfHash<CS>: Hash,
+        <OprfHash<CS> as CoreProxy>::Core: ProxyHash,
+        <<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
+        Le<<<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
         // CredentialRequest: KgPk + Ke1Message
-        <CS::OprfGroup as Group>::ElemLen: Add<Ke1MessageLen<CS>>,
+        <OprfGroup<CS> as Group>::ElemLen: Add<Ke1MessageLen<CS>>,
         CredentialRequestLen<CS>: ArrayLength<u8>,
         // ClientLogin: KgSk + CredentialRequest + Ke1State
-        <CS::OprfGroup as Group>::ScalarLen: Add<CredentialRequestLen<CS>>,
-        Sum<<CS::OprfGroup as Group>::ScalarLen, CredentialRequestLen<CS>>:
+        <OprfGroup<CS> as Group>::ScalarLen: Add<CredentialRequestLen<CS>>,
+        Sum<<OprfGroup<CS> as Group>::ScalarLen, CredentialRequestLen<CS>>:
             ArrayLength<u8> + Add<Ke1StateLen<CS>>,
         ClientLoginLen<CS>: ArrayLength<u8>,
     {
@@ -489,19 +518,22 @@ fn client_login_roundtrip() -> Result<(), ProtocolError> {
         rng.fill_bytes(&mut client_nonce);
 
         let l1_data = [
-            client_e_kp.private().to_arr().to_vec(),
+            client_e_kp.private().serialize().to_vec(),
             client_nonce.to_vec(),
         ]
         .concat();
 
-        let blind_result =
-            voprf::NonVerifiableClient::<CS::OprfGroup, CS::Hash>::blind(pw, &mut rng)?;
+        let blind_result = voprf::NonVerifiableClient::<CS::OprfGroup>::blind(pw, &mut rng)?;
 
         let credential_request = CredentialRequest::<CS> {
             blinded_element: blind_result.message,
             ke1_message:
-                <CS::KeyExchange as KeyExchange<CS::Hash, CS::KeGroup>>::KE1Message::from_bytes(
-                    &[client_nonce.as_ref(), client_e_kp.public()].concat(),
+                <CS::KeyExchange as KeyExchange<OprfHash<CS>, CS::KeGroup>>::KE1Message::from_bytes(
+                    &[
+                        client_nonce.as_ref(),
+                        client_e_kp.public().to_bytes().as_ref(),
+                    ]
+                    .concat(),
                 )?,
         };
 
@@ -521,7 +553,6 @@ fn client_login_roundtrip() -> Result<(), ProtocolError> {
 
     #[cfg(feature = "ristretto255")]
     inner::<Ristretto255>()?;
-    #[cfg(feature = "p256")]
     inner::<P256>()?;
 
     Ok(())
@@ -531,9 +562,12 @@ fn client_login_roundtrip() -> Result<(), ProtocolError> {
 fn ke1_message_roundtrip() -> Result<(), ProtocolError> {
     fn inner<CS: CipherSuite>() -> Result<(), ProtocolError>
     where
-        <CS::Hash as CoreProxy>::Core: ProxyHash,
-        <<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
-        Le<<<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
+        <OprfHash<CS> as OutputSizeUser>::OutputSize:
+            IsLess<U256> + IsLessOrEqual<<OprfHash<CS> as BlockSizeUser>::BlockSize>,
+        OprfHash<CS>: Hash,
+        <OprfHash<CS> as CoreProxy>::Core: ProxyHash,
+        <<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
+        Le<<<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
     {
         let mut rng = OsRng;
 
@@ -541,9 +575,15 @@ fn ke1_message_roundtrip() -> Result<(), ProtocolError> {
         let mut client_nonce = vec![0u8; NonceLen::USIZE];
         rng.fill_bytes(&mut client_nonce);
 
-        let ke1m = [client_nonce.as_slice(), client_e_kp.public()].concat();
+        let ke1m = [
+            client_nonce.as_slice(),
+            client_e_kp.public().to_bytes().as_ref(),
+        ]
+        .concat();
         let reg =
-            <CS::KeyExchange as KeyExchange<CS::Hash, CS::KeGroup>>::KE1Message::from_bytes(&ke1m)?;
+            <CS::KeyExchange as KeyExchange<OprfHash<CS>, CS::KeGroup>>::KE1Message::from_bytes(
+                &ke1m,
+            )?;
         let reg_bytes = reg.to_bytes();
         assert_eq!(*reg_bytes, ke1m);
 
@@ -552,7 +592,6 @@ fn ke1_message_roundtrip() -> Result<(), ProtocolError> {
 
     #[cfg(feature = "ristretto255")]
     inner::<Ristretto255>()?;
-    #[cfg(feature = "p256")]
     inner::<P256>()?;
 
     Ok(())
@@ -562,22 +601,32 @@ fn ke1_message_roundtrip() -> Result<(), ProtocolError> {
 fn ke2_message_roundtrip() -> Result<(), ProtocolError> {
     fn inner<CS: CipherSuite>() -> Result<(), ProtocolError>
     where
-        <CS::Hash as CoreProxy>::Core: ProxyHash,
-        <<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
-        Le<<<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
+        <OprfHash<CS> as OutputSizeUser>::OutputSize:
+            IsLess<U256> + IsLessOrEqual<<OprfHash<CS> as BlockSizeUser>::BlockSize>,
+        OprfHash<CS>: Hash,
+        <OprfHash<CS> as CoreProxy>::Core: ProxyHash,
+        <<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
+        Le<<<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
     {
         let mut rng = OsRng;
 
         let server_e_kp = KeyPair::<CS::KeGroup>::generate_random(&mut rng);
-        let mut mac = Output::<CS::Hash>::default();
+        let mut mac = Output::<OprfHash<CS>>::default();
         rng.fill_bytes(&mut mac);
         let mut server_nonce = vec![0u8; NonceLen::USIZE];
         rng.fill_bytes(&mut server_nonce);
 
-        let ke2m: Vec<u8> = [server_nonce.as_slice(), server_e_kp.public(), &mac].concat();
+        let ke2m: Vec<u8> = [
+            server_nonce.as_slice(),
+            server_e_kp.public().to_bytes().as_ref(),
+            &mac,
+        ]
+        .concat();
 
         let reg =
-            <CS::KeyExchange as KeyExchange<CS::Hash, CS::KeGroup>>::KE2Message::from_bytes(&ke2m)?;
+            <CS::KeyExchange as KeyExchange<OprfHash<CS>, CS::KeGroup>>::KE2Message::from_bytes(
+                &ke2m,
+            )?;
         let reg_bytes = reg.to_bytes();
         assert_eq!(*reg_bytes, ke2m);
 
@@ -586,7 +635,6 @@ fn ke2_message_roundtrip() -> Result<(), ProtocolError> {
 
     #[cfg(feature = "ristretto255")]
     inner::<Ristretto255>()?;
-    #[cfg(feature = "p256")]
     inner::<P256>()?;
 
     Ok(())
@@ -596,18 +644,23 @@ fn ke2_message_roundtrip() -> Result<(), ProtocolError> {
 fn ke3_message_roundtrip() -> Result<(), ProtocolError> {
     fn inner<CS: CipherSuite>() -> Result<(), ProtocolError>
     where
-        <CS::Hash as CoreProxy>::Core: ProxyHash,
-        <<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
-        Le<<<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
+        <OprfHash<CS> as OutputSizeUser>::OutputSize:
+            IsLess<U256> + IsLessOrEqual<<OprfHash<CS> as BlockSizeUser>::BlockSize>,
+        OprfHash<CS>: Hash,
+        <OprfHash<CS> as CoreProxy>::Core: ProxyHash,
+        <<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
+        Le<<<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
     {
         let mut rng = OsRng;
-        let mut mac = Output::<CS::Hash>::default();
+        let mut mac = Output::<OprfHash<CS>>::default();
         rng.fill_bytes(&mut mac);
 
         let ke3m: Vec<u8> = [mac].concat();
 
         let reg =
-            <CS::KeyExchange as KeyExchange<CS::Hash, CS::KeGroup>>::KE3Message::from_bytes(&ke3m)?;
+            <CS::KeyExchange as KeyExchange<OprfHash<CS>, CS::KeGroup>>::KE3Message::from_bytes(
+                &ke3m,
+            )?;
         let reg_bytes = reg.to_bytes();
         assert_eq!(*reg_bytes, ke3m);
 
@@ -616,7 +669,6 @@ fn ke3_message_roundtrip() -> Result<(), ProtocolError> {
 
     #[cfg(feature = "ristretto255")]
     inner::<Ristretto255>()?;
-    #[cfg(feature = "p256")]
     inner::<P256>()?;
 
     Ok(())
@@ -707,5 +759,4 @@ macro_rules! test {
 
 #[cfg(feature = "ristretto255")]
 test!(ristretto255, Ristretto255);
-#[cfg(feature = "p256")]
 test!(p256, P256);
diff --git a/src/tests/full_test.rs b/src/tests/full_test.rs
old mode 100755
new mode 100644
index 5644f679..fe3a00b4
--- a/src/tests/full_test.rs
+++ b/src/tests/full_test.rs
@@ -8,27 +8,27 @@
 #![allow(unsafe_code)]
 
 use core::ops::Add;
-use std::string::{String, ToString};
+use std::string::String;
 use std::vec::Vec;
-use std::{format, println, vec};
+use std::{format, println, ptr, vec};
 
 use digest::core_api::{BlockSizeUser, CoreProxy};
-use digest::Output;
-use generic_array::typenum::{IsLess, Le, NonZero, Sum, Unsigned, U256};
+use digest::{Output, OutputSizeUser};
+use generic_array::typenum::{IsLess, IsLessOrEqual, Le, NonZero, Sum, Unsigned, U256};
 use generic_array::ArrayLength;
 use rand::rngs::OsRng;
 use serde_json::Value;
 use subtle::ConstantTimeEq;
 use voprf::Group;
-use zeroize::Zeroize;
 
-use crate::ciphersuite::CipherSuite;
+use crate::ciphersuite::{CipherSuite, OprfGroup, OprfHash};
 use crate::envelope::EnvelopeLen;
 use crate::errors::*;
-use crate::hash::{OutputSize, ProxyHash};
+use crate::hash::{Hash, OutputSize, ProxyHash};
 use crate::key_exchange::group::KeGroup;
 use crate::key_exchange::traits::{Ke1MessageLen, Ke1StateLen, Ke2MessageLen};
 use crate::key_exchange::tripledh::{NonceLen, TripleDH};
+use crate::keypair::SecretKey;
 use crate::messages::{
     CredentialRequestLen, CredentialResponseLen, CredentialResponseWithoutKeLen,
     RegistrationResponseLen, RegistrationUploadLen,
@@ -43,45 +43,43 @@ use crate::*;
 
 #[cfg(feature = "ristretto255")]
 struct Ristretto255;
+
 #[cfg(feature = "ristretto255")]
 impl CipherSuite for Ristretto255 {
-    type OprfGroup = curve25519_dalek::ristretto::RistrettoPoint;
-    type KeGroup = curve25519_dalek::ristretto::RistrettoPoint;
+    type OprfGroup = crate::Ristretto255;
+    type KeGroup = crate::Ristretto255;
     type KeyExchange = TripleDH;
-    type Hash = sha2::Sha512;
     type SlowHash = NoOpHash;
 }
 
-#[cfg(feature = "p256")]
 struct P256;
-#[cfg(feature = "p256")]
+
 impl CipherSuite for P256 {
-    type OprfGroup = p256_::ProjectivePoint;
-    type KeGroup = p256_::PublicKey;
+    type OprfGroup = p256::NistP256;
+    type KeGroup = p256::NistP256;
     type KeyExchange = TripleDH;
-    type Hash = sha2::Sha256;
     type SlowHash = NoOpHash;
 }
 
 #[cfg(all(feature = "x25519", feature = "ristretto255"))]
 struct X25519Ristretto255;
+
 #[cfg(all(feature = "x25519", feature = "ristretto255"))]
 impl CipherSuite for X25519Ristretto255 {
-    type OprfGroup = curve25519_dalek::ristretto::RistrettoPoint;
-    type KeGroup = x25519_dalek::PublicKey;
+    type OprfGroup = crate::Ristretto255;
+    type KeGroup = crate::X25519;
     type KeyExchange = TripleDH;
-    type Hash = sha2::Sha512;
     type SlowHash = NoOpHash;
 }
 
-#[cfg(all(feature = "x25519", feature = "p256"))]
+#[cfg(feature = "x25519")]
 struct X25519P256;
-#[cfg(all(feature = "x25519", feature = "p256"))]
+
+#[cfg(feature = "x25519")]
 impl CipherSuite for X25519P256 {
-    type OprfGroup = p256_::ProjectivePoint;
-    type KeGroup = x25519_dalek::PublicKey;
+    type OprfGroup = p256::NistP256;
+    type KeGroup = crate::X25519;
     type KeyExchange = TripleDH;
-    type Hash = sha2::Sha256;
     type SlowHash = NoOpHash;
 }
 
@@ -162,7 +160,6 @@ static TEST_VECTOR_RISTRETTO255: &str = r#"
 }
 "#;
 
-#[cfg(feature = "p256")]
 static TEST_VECTOR_P256: &str = r#"
 {
     "client_s_pk": "02ec5dd688a3aa66022860d4bfed2dcb01a5da07a0b4ff0c84d9f8749bd478c293",
@@ -203,83 +200,81 @@ static TEST_VECTOR_P256: &str = r#"
 #[cfg(all(feature = "x25519", feature = "ristretto255"))]
 static TEST_VECTOR_X25519_RISTRETTO255: &str = r#"
 {
-    "client_s_pk": "d6ea34b61fa4625c1197f8f9fd51bc7023d4dfb0e17a95cf0ec38488ffff072c",
-    "client_s_sk": "f8fe7d4c525bd238c501c78a7b6ab26e076bb22c6b409ca08e34875ee4055850",
-    "client_e_pk": "7b0d1002539befa86f0cdf2a281f538842ec685bad21e9057a92390846ca0047",
-    "client_e_sk": "30e51bf7c5734fd3d1465b20affb65dc342f06513df999822832aa464aa29c5a",
-    "server_s_pk": "b6791c7cad7775b6cbc0bfa580319a1de159981771c59b0b86afeeff2767365a",
-    "server_s_sk": "68bfdb4e00e93059fb35e90db641ae1ef7af0fc8a7e013e2990431cf4c708563",
-    "server_e_pk": "c68d13eacc23578e731d78d2ccc37e2ff8e7cfdac3f76ee54d9ae40dd1167325",
-    "server_e_sk": "68b6d213f11d303e61929d299ca2424947e136d5a56b1400dda6286eaf5e4278",
-    "fake_sk": "c8f71d7e7864a25ee4e786744c5059ca268b7cf7a7610b4b3d763f368fae6972",
+    "client_s_pk": "4df1ab49521829e233f34b412d48598d046afa20156f117898b7809b070ab526",
+    "client_s_sk": "9055cba6082b86d8856b8ac3720c16a7ca07ff3303874149a0b5656e391bb05a",
+    "client_e_pk": "9bbea5c768383168e385d895007f3eba4ecfbcadef26305449a76b2440465e3d",
+    "client_e_sk": "388fac72f9c2e9aeeb189963015f7af1692a4a31fefaf06b4ecb66d066d9fe43",
+    "server_s_pk": "f2f02607a409b5de08ee6e1e51ae674462397997682ea7c48c2d3c762378075b",
+    "server_s_sk": "c06fed7591d65aa5f6a23cda5d53bde59707bc2d62c2ba61382380b77cae497a",
+    "server_e_pk": "ee5251c72b241bfa4d926fed7d5082ee31a406e622731550912ac0fce105515f",
+    "server_e_sk": "b018aa7067155ce9bb6d2fdbc163e706888d8e0cdff1e47f45073556abc8da5a",
+    "fake_sk": "b8c3c7caec7ef8985a8c84dc183d98e2dbc9c30bd13b86e9c37c810fa0e1cc5e",
     "credential_identifier": "637265644964656e746966696572",
     "id_u": "696455",
     "id_s": "696453",
     "password": "70617373776f7264",
-    "blinding_factor": "fa0bbeb200bef1802f3317c0e6b92590d9431fb6f5cb7f579d0865950172e40f",
-    "oprf_seed": "8bbe6e550d125d9169342b5683b085be3aee7e6414fe2a4f6db2aa3493b16a9b75f109725d6d92c13f3f2814dec17f83e2fd20cf8b922ca1d928e8bf476f8154",
-    "masking_nonce": "885aa518b13d78757415f8839e1505a4ae8b5f04b6904ce9aff6de6d156d94756f3cb40352bbdafe521da49bf9a57bcde3597114161b023cfbf3b79051d2ee0d",
-    "envelope_nonce": "e5c5bb34123bb9b08eb961c6f3a94c6b627f1c5bcd3527d46a1652f662e078ac",
-    "client_nonce": "7fb4b8f81eefe57a8b1cf5d75465d557e04b21ed7800356713ecc63b0ebbfca5",
-    "server_nonce": "9e3f8ceb7174fdd7ceb2f1e37d4cd483e28e18fa60457416d2d5c468ae7501e7",
+    "blinding_factor": "d36ed47358b72a28796b5b0fc4f9301a294659da94d394f227e6e9b8ea277c0b",
+    "oprf_seed": "b430182519702493f1991bbf7506c335b5e6e3899e5f1266a7f4c99dd16bf2efab3355a7e75423c2d0e54ee7cc1c16d3fc8975e47946f4bea95a77a06bfeea7c",
+    "masking_nonce": "ce8202d2438b73cfe23d5f7bccc172ee6a354d690c1770ef07f4f269978bb0dbd6df845d9d9cb969ec8bf41dadef3835ba1d1dd4360b15ba5ad6cb77b9a53879",
+    "envelope_nonce": "04c9e2914f5874089839e78eb29d342d3c472c0bdb62e1abedf74bbf505fec79",
+    "client_nonce": "c01061951b0f6acc314a0cfa2f45b8f2e2125e0f1896fa63c1d97d8f74245c16",
+    "server_nonce": "50fe3f0de7db78443da1eadb15d36d6645ea3f94ffcc57b862befda2a9bd10a5",
     "context": "636f6e74657874",
-    "registration_request": "8eda5caa002e6574e677636eeba5967ec25505a125ea12d9857c0d8c3beb3551",
-    "registration_response": "ecf14ba8fb208a8d5b263170ffd84a9d21751810f52e539938ed22e71f66cb02b6791c7cad7775b6cbc0bfa580319a1de159981771c59b0b86afeeff2767365a",
-    "registration_upload": "020689ce9ffba0f198d9e63e23902ff1600656fdd604aa47c289d5d073cc472354e891e7c4f89058fd270813d320e0cb97a745722eb8038eec062e5aed5e6b40b88148d43e68b7f457c352c3b6523e0bc79795f651487d007574cbaf7dba317ff8fe7d4c525bd238c501c78a7b6ab26e076bb22c6b409ca08e34875ee4055850ea0fcb59720fdb42fcbddbccbaed60a0aec01d72403c63075468e8a03c9efc25206776154291cbce05d7567a82a10d8303f991b8689643c5727b1886c478a9b8",
-    "credential_request": "8eda5caa002e6574e677636eeba5967ec25505a125ea12d9857c0d8c3beb35517fb4b8f81eefe57a8b1cf5d75465d557e04b21ed7800356713ecc63b0ebbfca57b0d1002539befa86f0cdf2a281f538842ec685bad21e9057a92390846ca0047",
-    "credential_response": "ecf14ba8fb208a8d5b263170ffd84a9d21751810f52e539938ed22e71f66cb02885aa518b13d78757415f8839e1505a4ae8b5f04b6904ce9aff6de6d156d94756060ede0b8a8d061eaa376b4578fd39ec064f9f36c7a0602894690da72727b427148a2970ce50d06d93ed74dde1143f7d092f19541e8738c509dd5d34f4411d753959bc295083bc96a0b6e1d9a12a369c83fc2a50fab84276f83a2a63e5d5ba6dbb0b23752b7819c238123f0e69cbccc777fa6c35484608f324a3c30b307ad6068b6d213f11d303e61929d299ca2424947e136d5a56b1400dda6286eaf5e4278549bc184dd7a99b0749dfb1acb8ca862b649567d04b056c0530755d453e4c466d5728608562883eea4c922c313455999720cbbf3c6511d3666365132e39c1b6f094d05e73a75e15fb0869661337c3569fcfca75a9373d8fac9ff29c61be49a0d",
-    "credential_finalization": "6061b34bc847481034908047ded2b7e08450793091f96b9a425e4e4e24e65810596b2556e1ea1ae57e3f6bf234ba33fc393fe4fa98de984760df870a454fcdc9",
-    "client_registration_state": "fa0bbeb200bef1802f3317c0e6b92590d9431fb6f5cb7f579d0865950172e40f8eda5caa002e6574e677636eeba5967ec25505a125ea12d9857c0d8c3beb3551",
-    "client_login_state": "fa0bbeb200bef1802f3317c0e6b92590d9431fb6f5cb7f579d0865950172e40f8eda5caa002e6574e677636eeba5967ec25505a125ea12d9857c0d8c3beb35517fb4b8f81eefe57a8b1cf5d75465d557e04b21ed7800356713ecc63b0ebbfca57b0d1002539befa86f0cdf2a281f538842ec685bad21e9057a92390846ca004730e51bf7c5734fd3d1465b20affb65dc342f06513df999822832aa464aa29c5a7fb4b8f81eefe57a8b1cf5d75465d557e04b21ed7800356713ecc63b0ebbfca5",
-    "server_login_state": "d58815ef0bd83afafea536fe17c23f5292a5484d5748e7164b678bb4edbbfd99a31fc1b8e78ce8aab0908db29832e2c9644b4af4e3a167ab9a4f5e82c0a068eb8df23068244e4c7256c48abb28bc02529c3fbc2217c042fcc7f54e50f1516dde8f9f25c694e34604c9bea4b94090c3c911c22db5c74a79abbecf0354dc8d475575d17a1f85b7efdc2dd8b6e2fa9529bfe49d7cfa2563451fe6b9d5cd7344377c3730e197b550030c012b01466b142c9022b869fe5ea7c59412e5b6685db19997",
-    "password_file": "020689ce9ffba0f198d9e63e23902ff1600656fdd604aa47c289d5d073cc472354e891e7c4f89058fd270813d320e0cb97a745722eb8038eec062e5aed5e6b40b88148d43e68b7f457c352c3b6523e0bc79795f651487d007574cbaf7dba317ff8fe7d4c525bd238c501c78a7b6ab26e076bb22c6b409ca08e34875ee4055850ea0fcb59720fdb42fcbddbccbaed60a0aec01d72403c63075468e8a03c9efc25206776154291cbce05d7567a82a10d8303f991b8689643c5727b1886c478a9b8",
-    "export_key": "0527421cb5c31aeba8fddff1af3a673e94cbd7af57999eaed47e23ea3562d68362416d69afb3bcf450db76b4fea504d5d065e0a104d9a1848f20e64ff04f3cfd",
-    "session_key": "75d17a1f85b7efdc2dd8b6e2fa9529bfe49d7cfa2563451fe6b9d5cd7344377c3730e197b550030c012b01466b142c9022b869fe5ea7c59412e5b6685db19997"
+    "registration_request": "b889d6957f21a33951637b12f12007efc50d7b87811c96b8cc0072a605f1d24c",
+    "registration_response": "200bcd11d44e9a10541a465ed3b130d4d10632dd7433965578e33d181e321113f2f02607a409b5de08ee6e1e51ae674462397997682ea7c48c2d3c762378075b",
+    "registration_upload": "79d14f4164e722a8dcabffd4a341104e217d1597eeb414d4e4597d7c4376bf3b23ed2104a05b3ff4a12160f73172bbb195a3a399489bc4092e471e181a524b6c7fb6f955cacb8f8d0db54b89287b496e48b34b1a6204960925469d712c793bd89055cba6082b86d8856b8ac3720c16a7ca07ff3303874149a0b5656e391bb05a9490086c367c3705a0a46a497c797de7e67c0c7d1803e17e1cfd78d949c7b8558fa46da9fb3445bdef5e285a561e04518919387726a1b73660e687bda8a14256",
+    "credential_request": "b889d6957f21a33951637b12f12007efc50d7b87811c96b8cc0072a605f1d24cc01061951b0f6acc314a0cfa2f45b8f2e2125e0f1896fa63c1d97d8f74245c169bbea5c768383168e385d895007f3eba4ecfbcadef26305449a76b2440465e3d",
+    "credential_response": "200bcd11d44e9a10541a465ed3b130d4d10632dd7433965578e33d181e321113ce8202d2438b73cfe23d5f7bccc172ee6a354d690c1770ef07f4f269978bb0dbcc6f726f243b5a331e2a5b420866413d0e8b82f391bc4183361508a9f6c1b700e5f182630556f732e466976c2797cc5b05163ee4bb6ce39b1db500f03fe365009e359199926fad26726e438722c45f11beccc0e32e45fc24732f85d1aa9543e1d7c30248a20aad34efe89f8af3e8578071aa362564b5faba808fba9a66a78a26b018aa7067155ce9bb6d2fdbc163e706888d8e0cdff1e47f45073556abc8da5a3b9470912a8c10a2bede335ddbc1198d706acca69ae6ff0a72e195d2575f5b27b32d6099439ef55837e6e817def5ee8230ffb6fcd66d0f23f7169ab49eecfa8d134f2d6db9de03c51034b3a129f84c261234429cc34dfb7afdf3e392b7962b78",
+    "credential_finalization": "f4133b65451a2b8f7a9cbfaca3f7c06211912fc669ffbe7cd554905b9f4e82a0e841cc97c70c8b964e02d6f665a136db0ad83b249d4f36be4c4944c4596ac696",
+    "client_registration_state": "d36ed47358b72a28796b5b0fc4f9301a294659da94d394f227e6e9b8ea277c0bb889d6957f21a33951637b12f12007efc50d7b87811c96b8cc0072a605f1d24c",
+    "client_login_state": "d36ed47358b72a28796b5b0fc4f9301a294659da94d394f227e6e9b8ea277c0bb889d6957f21a33951637b12f12007efc50d7b87811c96b8cc0072a605f1d24cc01061951b0f6acc314a0cfa2f45b8f2e2125e0f1896fa63c1d97d8f74245c169bbea5c768383168e385d895007f3eba4ecfbcadef26305449a76b2440465e3d388fac72f9c2e9aeeb189963015f7af1692a4a31fefaf06b4ecb66d066d9fe43c01061951b0f6acc314a0cfa2f45b8f2e2125e0f1896fa63c1d97d8f74245c16",
+    "server_login_state": "b87047afce3cb9166d7f96a992d423f3eebb931413775c5373dc18abd6a42f919de37ca5643454b07d84440d77d364c7dd4c9029fb481aa7e34d91f061c38c2b5ba9c3260c4eb1bdfd6ef65e1415b8e227631985d979f0c5860d3076bd8f22a0e009dd6efb4359c414257678101fe91560f15b16461ca79618c09d26f346b0e9776d5589e16dc1d9466d5aa901db05d5cbeb73c2672da3b954046b170f514ce50282b8bccc1d542e902af8486fd37f886f555e56b2f34cc91d658996651eb8b8",
+    "password_file": "79d14f4164e722a8dcabffd4a341104e217d1597eeb414d4e4597d7c4376bf3b23ed2104a05b3ff4a12160f73172bbb195a3a399489bc4092e471e181a524b6c7fb6f955cacb8f8d0db54b89287b496e48b34b1a6204960925469d712c793bd89055cba6082b86d8856b8ac3720c16a7ca07ff3303874149a0b5656e391bb05a9490086c367c3705a0a46a497c797de7e67c0c7d1803e17e1cfd78d949c7b8558fa46da9fb3445bdef5e285a561e04518919387726a1b73660e687bda8a14256",
+    "export_key": "9b728dbe104caf7ea3d52a167c74408770b89f883c4ee280ee053fd9ad5aabc62bbe78cbc6bc9230819819ea75d40b8411f914095d4101c1e0d92c71b4e5b29f",
+    "session_key": "776d5589e16dc1d9466d5aa901db05d5cbeb73c2672da3b954046b170f514ce50282b8bccc1d542e902af8486fd37f886f555e56b2f34cc91d658996651eb8b8"
 }
 "#;
 
-#[cfg(all(feature = "x25519", feature = "p256"))]
+#[cfg(feature = "x25519")]
 static TEST_VECTOR_X25519_P256: &str = r#"
 {
-    "client_s_pk": "ee6282a908e24291fcd1e7ce0a6fc244cf9b6371889e31a908d1919cdd756776",
-    "client_s_sk": "785f65307f77f78cc4b20565a42c1954a30763d881528749f376b90a13ca2b73",
-    "client_e_pk": "c6e0310d186d3c869b384418a6574cd9fff826e3a91ac46d05ce0ab56e25b978",
-    "client_e_sk": "e889400a32d355cd203d6a1ee195a787217db28075de794d0c39ca29c44f1776",
-    "server_s_pk": "1c840f081ecaa88b6eff81536d28b3220cc7101e6e90b998461cc80ead285808",
-    "server_s_sk": "10138ce9d5660b1b23aaf520e1ec948bd1f318b571356aa3f9becf3db34daf7a",
-    "server_e_pk": "152d47f6ac15e7c2c11f29bd513d182db4eed09ef3974a6e4438b72ec9aa2d0c",
-    "server_e_sk": "589db8b32e957d97134b10f8b2fa107b908f88eb2a4620f2ae25d61107bf9e6f",
-    "fake_sk": "088a857c0f23ea4896cb067420a5264e8ea22f13d6b471cc4518cdf520de817b",
+    "client_s_pk": "54ad295308e561e9549d2662a79a18228784bca512b50a6fd161595dbfa18f54",
+    "client_s_sk": "20f9d6b90a9463e8432780c647b2dd7f4becf8d174c92a8023f395d01f16515b",
+    "client_e_pk": "07cb52b7d96fcdcdec4ec1da39dab5210c6abca838af870886a391caf1cc9d7a",
+    "client_e_sk": "20bdb5e9d5d98fcd9ba5634e03eba34fc728753369ce0234c9826aa88047c259",
+    "server_s_pk": "4923a63bd059263cb2a69fd3a528191986d9c1f470bfe240d470af628adec277",
+    "server_s_sk": "88b9fbacb0d38394f61c35408fe05c6734a1a63c5e6a16684095d9cc2f82cb4d",
+    "server_e_pk": "e8a420fd6ff116cf865035321f91e211609b4a0f013ffd0798ea68bd04267c7a",
+    "server_e_sk": "20eed40939abf897bdba6d7649893a617762fb3b94e04c247a086dc48448f361",
+    "fake_sk": "4847b46ae57fa21f9bdf38bb4ee08ed12f64141b21707743ce3bf203cf36416b",
     "credential_identifier": "637265644964656e746966696572",
     "id_u": "696455",
     "id_s": "696453",
     "password": "70617373776f7264",
-    "blinding_factor": "40b461844231f2f890fc68aa0da838e25f40af01ec1b212fdf6bad07db170757",
-    "oprf_seed": "b25a0265e824656034824b935d49d7f844d26acf1a7d8c6c40d635543a2e6d33",
-    "masking_nonce": "005cf982b3ddbb28ee252d729c83c4b9d74a54ac72f25325f7f21824530649fbf165383559cce8a4734d6fcd56e45f866828008d3d4c56dd57659c80bc3e094f",
-    "envelope_nonce": "0c9954390a2bac0bf09c083a2152bfce397281e7a5408c08b0b18d56c0ec686c",
-    "client_nonce": "b389ed08bf4bfbf895ea6706c3d967b5ec7c4af96c207ebe816c50b9615ab06e",
-    "server_nonce": "a920b7dbb1607caea2a3d7577531fb30173d0123a2353cf4151bea9e71caaab2",
+    "blinding_factor": "1ca386b0d0a0c390c68d8eab341787260fa3ee2a94059d7887ec687eba80ceee",
+    "oprf_seed": "b78f0138ef05a3df5aef410e7dda3b4972600e4d9f1e3ffa9376778cf31337ad",
+    "masking_nonce": "669d7a5f0b410805b4a48d81d86e821d925341c5a634581d43225b1f0820d3c13a3633d70a1eb6982b12dbca41db7718f12db87a0896034bc4c3c7c54ac4d647",
+    "envelope_nonce": "b57ed3206fb154fcfccb1e5effd873e9bce098af031674524c24572116061cc1",
+    "client_nonce": "f7e2cf7311f3bd310e331dc24a94f319c0dc5f1ae2d3a039b5da60bac9ef6099",
+    "server_nonce": "daad4ea374a85431b05253e53c5c69f67e0fc8067eaa1d492f8ff045659fe377",
     "context": "636f6e74657874",
-    "registration_request": "02c1f758572663d7bb1fa5dbc8cf426b867a9936bc741e9acc8a31b18bf0e5bd33",
-    "registration_response": "03068d5d3fe0d6b3361c2c728b85dce104df42d3d11c2079392ab894ace50ff4001c840f081ecaa88b6eff81536d28b3220cc7101e6e90b998461cc80ead285808",
-    "registration_upload": "f49b790e8f2e36ca511957263868d1ee897b2936b1ae4922ac2ad0b7a0e38f3c2606360d0008b6ed33e4ca48d31f3992875445e3a53f42e4eee661aad96e2c50785f65307f77f78cc4b20565a42c1954a30763d881528749f376b90a13ca2b7374d474b4232682bf9033067a42d35f2b9936822372cd9ea4f9b8c38b14948ff5",
-    "credential_request": "02c1f758572663d7bb1fa5dbc8cf426b867a9936bc741e9acc8a31b18bf0e5bd33b389ed08bf4bfbf895ea6706c3d967b5ec7c4af96c207ebe816c50b9615ab06ec6e0310d186d3c869b384418a6574cd9fff826e3a91ac46d05ce0ab56e25b978",
-    "credential_response": "03068d5d3fe0d6b3361c2c728b85dce104df42d3d11c2079392ab894ace50ff400005cf982b3ddbb28ee252d729c83c4b9d74a54ac72f25325f7f21824530649fbed3e1ab6eb243095958829896b0286515a108213900d019d88d8578eb7919aec225c95400fd737bdfa72a8dfcfc12958fb794383b8da8f49d13cd554569097e0f9ed325ae363061f593e3cf7a2d28ac87e5e0d2a2f344e7286cf7f38e006908b589db8b32e957d97134b10f8b2fa107b908f88eb2a4620f2ae25d61107bf9e6f3691d795831920a6728252782da676dc34b5d069d92af4d306f8b13a81185b5e8f648ff448ba49b2fbe98d0cac15fce9f60b965e79e0903bdee7528549d4241b",
-    "credential_finalization": "1e7f17932a85f0d16ca1a40c1233695dd23a087f17f470df4eb51e9ff24cbe43",
-    "client_registration_state": "40b461844231f2f890fc68aa0da838e25f40af01ec1b212fdf6bad07db17075702c1f758572663d7bb1fa5dbc8cf426b867a9936bc741e9acc8a31b18bf0e5bd33",
-    "client_login_state": "40b461844231f2f890fc68aa0da838e25f40af01ec1b212fdf6bad07db17075702c1f758572663d7bb1fa5dbc8cf426b867a9936bc741e9acc8a31b18bf0e5bd33b389ed08bf4bfbf895ea6706c3d967b5ec7c4af96c207ebe816c50b9615ab06ec6e0310d186d3c869b384418a6574cd9fff826e3a91ac46d05ce0ab56e25b978e889400a32d355cd203d6a1ee195a787217db28075de794d0c39ca29c44f1776b389ed08bf4bfbf895ea6706c3d967b5ec7c4af96c207ebe816c50b9615ab06e",
-    "server_login_state": "9a2ccb3a4690792ee4c290ae12a84049b941ac7d6c5288c300f3af40b34fef6dc40347b218e9f65fe3c67d07173dcc9d73b7bcd5e2a128e7565e4c2fcaada2c631b54a2472ce398831783222437e916804e19a67771d900cde8733e890305ce3",
-    "password_file": "f49b790e8f2e36ca511957263868d1ee897b2936b1ae4922ac2ad0b7a0e38f3c2606360d0008b6ed33e4ca48d31f3992875445e3a53f42e4eee661aad96e2c50785f65307f77f78cc4b20565a42c1954a30763d881528749f376b90a13ca2b7374d474b4232682bf9033067a42d35f2b9936822372cd9ea4f9b8c38b14948ff5",
-    "export_key": "2b867ad9909d31946cb3c3738fb1c1e51ba09d768e83d6b03eb909e0e4298003",
-    "session_key": "31b54a2472ce398831783222437e916804e19a67771d900cde8733e890305ce3"
+    "registration_request": "023741a9f45d763159b728738cde140058e6bc3ead289e74b9df6cf2317dc50a36",
+    "registration_response": "023e084f135edff464f60468dcc18c779ea7cea99daec254499ed082ed086bfe7d4923a63bd059263cb2a69fd3a528191986d9c1f470bfe240d470af628adec277",
+    "registration_upload": "fee1e2438d269807b7e8b07c5b078f553bab065deb2943ac95119ec04857dc2a8a2e3934463798e276d13a0b3456eb2a98c87968d90c4ae0a51d311fe1655d5f20f9d6b90a9463e8432780c647b2dd7f4becf8d174c92a8023f395d01f16515bc4ddd82e83589cbe9c3b6fdea12acedef29aa7784a2ce0b65685dc352b66c142",
+    "credential_request": "023741a9f45d763159b728738cde140058e6bc3ead289e74b9df6cf2317dc50a36f7e2cf7311f3bd310e331dc24a94f319c0dc5f1ae2d3a039b5da60bac9ef609907cb52b7d96fcdcdec4ec1da39dab5210c6abca838af870886a391caf1cc9d7a",
+    "credential_response": "023e084f135edff464f60468dcc18c779ea7cea99daec254499ed082ed086bfe7d669d7a5f0b410805b4a48d81d86e821d925341c5a634581d43225b1f0820d3c15a700199e8446adf51b7f027b5c59ef0847b6e43ab72a6d6de4cf0eb3fe3a2093bdf8d3a50013795f094d651dcd7e9f597904e4940a3629166c2f3f4905e88c3e1f6db8f5244feebd435e5664bd395e9ba78aa93bd7e93863aa052765d4911b420eed40939abf897bdba6d7649893a617762fb3b94e04c247a086dc48448f3618e2b6d1e406e0695a0a7f32f296e45990186c54ab76a3562e7d70faf66ff99759118020c29faab346283f6e0221f7de81def8dc79c6904b5075b098a35c6a50e",
+    "credential_finalization": "4a6e76c08153d2c07ab695030c6fd3a6f531338e3dc85f75b0af56ee54874e2e",
+    "client_registration_state": "1ca386b0d0a0c390c68d8eab341787260fa3ee2a94059d7887ec687eba80ceee023741a9f45d763159b728738cde140058e6bc3ead289e74b9df6cf2317dc50a36",
+    "client_login_state": "1ca386b0d0a0c390c68d8eab341787260fa3ee2a94059d7887ec687eba80ceee023741a9f45d763159b728738cde140058e6bc3ead289e74b9df6cf2317dc50a36f7e2cf7311f3bd310e331dc24a94f319c0dc5f1ae2d3a039b5da60bac9ef609907cb52b7d96fcdcdec4ec1da39dab5210c6abca838af870886a391caf1cc9d7a20bdb5e9d5d98fcd9ba5634e03eba34fc728753369ce0234c9826aa88047c259f7e2cf7311f3bd310e331dc24a94f319c0dc5f1ae2d3a039b5da60bac9ef6099",
+    "server_login_state": "a4177b3ddccd8634ea0ad95dc9d7287f701fc5354ff53c84cb6205bff831fdeb49b5196d9330ab791ca1eb475a8eb04937430ef87776a7d1c7c4102231e7a1a89c599288d589338b0c99688a2db39f693d5d6f57cee46dc77f3d66af027fed3e",
+    "password_file": "fee1e2438d269807b7e8b07c5b078f553bab065deb2943ac95119ec04857dc2a8a2e3934463798e276d13a0b3456eb2a98c87968d90c4ae0a51d311fe1655d5f20f9d6b90a9463e8432780c647b2dd7f4becf8d174c92a8023f395d01f16515bc4ddd82e83589cbe9c3b6fdea12acedef29aa7784a2ce0b65685dc352b66c142",
+    "export_key": "fae7cd4aa438eeaa5f83b0a181ce29d18c9de4eecfd778a42511014c8b536f89",
+    "session_key": "9c599288d589338b0c99688a2db39f693d5d6f57cee46dc77f3d66af027fed3e"
 }
 "#;
 
 fn decode(values: &Value, key: &str) -> Option<Vec<u8>> {
-    values[key]
-        .as_str()
-        .and_then(|s| hex::decode(&s.to_string()).ok())
+    values[key].as_str().and_then(|s| hex::decode(&s).ok())
 }
 
 fn populate_test_vectors(values: &Value) -> TestVectorParameters {
@@ -444,21 +439,24 @@ fn stringify_test_vectors(p: &TestVectorParameters) -> String {
 
 fn generate_parameters<CS: CipherSuite>() -> Result<TestVectorParameters, ProtocolError>
 where
-    <CS::Hash as CoreProxy>::Core: ProxyHash,
-    <<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
-    Le<<<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
+    <OprfHash<CS> as OutputSizeUser>::OutputSize:
+        IsLess<U256> + IsLessOrEqual<<OprfHash<CS> as BlockSizeUser>::BlockSize>,
+    OprfHash<CS>: Hash,
+    <OprfHash<CS> as CoreProxy>::Core: ProxyHash,
+    <<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
+    Le<<<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
     // ClientRegistration: KgSk + KgPk
-    <CS::OprfGroup as Group>::ScalarLen: Add<<CS::OprfGroup as Group>::ElemLen>,
+    <OprfGroup<CS> as Group>::ScalarLen: Add<<OprfGroup<CS> as Group>::ElemLen>,
     ClientRegistrationLen<CS>: ArrayLength<u8>,
     // RegistrationResponse: KgPk + KePk
-    <CS::OprfGroup as Group>::ElemLen: Add<<CS::KeGroup as KeGroup>::PkLen>,
+    <OprfGroup<CS> as Group>::ElemLen: Add<<CS::KeGroup as KeGroup>::PkLen>,
     RegistrationResponseLen<CS>: ArrayLength<u8>,
     // Envelope: Nonce + Hash
-    NonceLen: Add<OutputSize<CS::Hash>>,
+    NonceLen: Add<OutputSize<OprfHash<CS>>>,
     EnvelopeLen<CS>: ArrayLength<u8>,
     // RegistrationUpload: (KePk + Hash) + Envelope
-    <CS::KeGroup as KeGroup>::PkLen: Add<OutputSize<CS::Hash>>,
-    Sum<<CS::KeGroup as KeGroup>::PkLen, OutputSize<CS::Hash>>:
+    <CS::KeGroup as KeGroup>::PkLen: Add<OutputSize<OprfHash<CS>>>,
+    Sum<<CS::KeGroup as KeGroup>::PkLen, OutputSize<OprfHash<CS>>>:
         ArrayLength<u8> + Add<EnvelopeLen<CS>>,
     RegistrationUploadLen<CS>: ArrayLength<u8>,
     // ServerRegistration = RegistrationUpload
@@ -466,24 +464,24 @@ where
     NonceLen: Add<<CS::KeGroup as KeGroup>::PkLen>,
     Ke1MessageLen<CS>: ArrayLength<u8>,
     // CredentialRequest: KgPk + Ke1Message
-    <CS::OprfGroup as Group>::ElemLen: Add<Ke1MessageLen<CS>>,
+    <OprfGroup<CS> as Group>::ElemLen: Add<Ke1MessageLen<CS>>,
     CredentialRequestLen<CS>: ArrayLength<u8>,
     // ClientLogin: KgSk + CredentialRequest + Ke1State
-    <CS::OprfGroup as Group>::ScalarLen: Add<CredentialRequestLen<CS>>,
-    Sum<<CS::OprfGroup as Group>::ScalarLen, CredentialRequestLen<CS>>:
+    <OprfGroup<CS> as Group>::ScalarLen: Add<CredentialRequestLen<CS>>,
+    Sum<<OprfGroup<CS> as Group>::ScalarLen, CredentialRequestLen<CS>>:
         ArrayLength<u8> + Add<Ke1StateLen<CS>>,
     ClientLoginLen<CS>: ArrayLength<u8>,
     // MaskedResponse: (Nonce + Hash) + KePk
-    NonceLen: Add<OutputSize<CS::Hash>>,
-    Sum<NonceLen, OutputSize<CS::Hash>>: ArrayLength<u8> + Add<<CS::KeGroup as KeGroup>::PkLen>,
+    NonceLen: Add<OutputSize<OprfHash<CS>>>,
+    Sum<NonceLen, OutputSize<OprfHash<CS>>>: ArrayLength<u8> + Add<<CS::KeGroup as KeGroup>::PkLen>,
     MaskedResponseLen<CS>: ArrayLength<u8>,
     // CredentialResponseWithoutKeLen: (KgPk + Nonce) + MaskedResponse
-    <CS::OprfGroup as Group>::ElemLen: Add<NonceLen>,
-    Sum<<CS::OprfGroup as Group>::ElemLen, NonceLen>: ArrayLength<u8> + Add<MaskedResponseLen<CS>>,
+    <OprfGroup<CS> as Group>::ElemLen: Add<NonceLen>,
+    Sum<<OprfGroup<CS> as Group>::ElemLen, NonceLen>: ArrayLength<u8> + Add<MaskedResponseLen<CS>>,
     CredentialResponseWithoutKeLen<CS>: ArrayLength<u8>,
     // Ke2Message: (Nonce + KePk) + Hash
     NonceLen: Add<<CS::KeGroup as KeGroup>::PkLen>,
-    Sum<NonceLen, <CS::KeGroup as KeGroup>::PkLen>: ArrayLength<u8> + Add<OutputSize<CS::Hash>>,
+    Sum<NonceLen, <CS::KeGroup as KeGroup>::PkLen>: ArrayLength<u8> + Add<OutputSize<OprfHash<CS>>>,
     Ke2MessageLen<CS>: ArrayLength<u8>,
     // CredentialResponse: CredentialResponseWithoutKeLen + Ke2Message
     CredentialResponseWithoutKeLen<CS>: Add<Ke2MessageLen<CS>>,
@@ -506,7 +504,7 @@ where
     let id_s = b"idS";
     let password = b"password";
     let context = b"context";
-    let mut oprf_seed = Output::<CS::Hash>::default();
+    let mut oprf_seed = Output::<OprfHash<CS>>::default();
     rng.fill_bytes(&mut oprf_seed);
     let mut masking_nonce = [0u8; 64];
     rng.fill_bytes(&mut masking_nonce);
@@ -517,24 +515,24 @@ where
     let mut server_nonce = [0u8; NonceLen::USIZE];
     rng.fill_bytes(&mut server_nonce);
 
-    let fake_sk: Vec<u8> = fake_kp.private().to_vec();
+    let fake_sk: Vec<u8> = fake_kp.private().serialize().to_vec();
     let server_setup = ServerSetup::<CS>::deserialize(
         &[
             oprf_seed.as_ref(),
-            &server_s_kp.private().to_arr(),
+            &server_s_kp.private().serialize(),
             &fake_sk,
         ]
         .concat(),
     )
     .unwrap();
 
-    let blinding_factor = CS::OprfGroup::random_nonzero_scalar(&mut rng);
-    let blinding_factor_bytes = CS::OprfGroup::scalar_as_bytes(blinding_factor);
+    let blinding_factor = <OprfGroup<CS> as Group>::random_scalar(&mut rng);
+    let blinding_factor_bytes = OprfGroup::<CS>::serialize_scalar(blinding_factor);
 
     let mut blinding_factor_registration_rng = CycleRng::new(blinding_factor_bytes.to_vec());
     let client_registration_start_result =
         ClientRegistration::<CS>::start(&mut blinding_factor_registration_rng, password).unwrap();
-    let blinding_factor_bytes_returned = CS::OprfGroup::scalar_as_bytes(
+    let blinding_factor_bytes_returned = OprfGroup::<CS>::serialize_scalar(
         client_registration_start_result
             .state
             .oprf_client
@@ -557,7 +555,7 @@ where
     let registration_response_bytes = server_registration_start_result.message.serialize();
 
     let mut client_s_sk_and_nonce: Vec<u8> = Vec::new();
-    client_s_sk_and_nonce.extend_from_slice(&client_s_kp.private().to_arr());
+    client_s_sk_and_nonce.extend_from_slice(&client_s_kp.private().serialize());
     client_s_sk_and_nonce.extend_from_slice(&envelope_nonce);
 
     let mut finish_registration_rng = CycleRng::new(client_s_sk_and_nonce);
@@ -583,7 +581,7 @@ where
 
     let mut client_login_start: Vec<u8> = Vec::new();
     client_login_start.extend_from_slice(&blinding_factor_bytes);
-    client_login_start.extend_from_slice(&client_e_kp.private().to_arr());
+    client_login_start.extend_from_slice(&client_e_kp.private().serialize());
     client_login_start.extend_from_slice(&client_nonce);
 
     let mut client_login_start_rng = CycleRng::new(client_login_start);
@@ -595,7 +593,7 @@ where
     let mut server_e_sk_and_nonce_rng = CycleRng::new(
         [
             masking_nonce.to_vec(),
-            server_e_kp.private().to_arr().to_vec(),
+            server_e_kp.private().serialize().to_vec(),
             server_nonce.to_vec(),
         ]
         .concat(),
@@ -636,14 +634,14 @@ where
     let credential_finalization_bytes = client_login_finish_result.message.serialize();
 
     Ok(TestVectorParameters {
-        client_s_pk: client_s_kp.public().to_arr().to_vec(),
-        client_s_sk: client_s_kp.private().to_arr().to_vec(),
-        client_e_pk: client_e_kp.public().to_arr().to_vec(),
-        client_e_sk: client_e_kp.private().to_arr().to_vec(),
-        server_s_pk: server_s_kp.public().to_arr().to_vec(),
-        server_s_sk: server_s_kp.private().to_arr().to_vec(),
-        server_e_pk: server_e_kp.public().to_arr().to_vec(),
-        server_e_sk: server_e_kp.private().to_arr().to_vec(),
+        client_s_pk: client_s_kp.public().to_bytes().to_vec(),
+        client_s_sk: client_s_kp.private().serialize().to_vec(),
+        client_e_pk: client_e_kp.public().to_bytes().to_vec(),
+        client_e_sk: client_e_kp.private().serialize().to_vec(),
+        server_s_pk: server_s_kp.public().to_bytes().to_vec(),
+        server_s_sk: server_s_kp.private().serialize().to_vec(),
+        server_e_pk: server_e_kp.public().to_bytes().to_vec(),
+        server_e_sk: server_e_kp.private().serialize().to_vec(),
         fake_sk,
         credential_identifier: credential_identifier.to_vec(),
         id_u: id_u.to_vec(),
@@ -678,11 +676,10 @@ fn generate_test_vectors() -> Result<(), ProtocolError> {
         let parameters = generate_parameters::<Ristretto255>()?;
         println!("Ristretto255: {}", stringify_test_vectors(&parameters));
     }
-    #[cfg(feature = "p256")]
-    {
-        let parameters = generate_parameters::<P256>()?;
-        println!("P-256: {}", stringify_test_vectors(&parameters));
-    }
+
+    let parameters = generate_parameters::<P256>()?;
+    println!("P-256: {}", stringify_test_vectors(&parameters));
+
     #[cfg(all(feature = "x25519", feature = "ristretto255"))]
     {
         let parameters = generate_parameters::<X25519Ristretto255>()?;
@@ -691,7 +688,8 @@ fn generate_test_vectors() -> Result<(), ProtocolError> {
             stringify_test_vectors(&parameters)
         );
     }
-    #[cfg(all(feature = "x25519", feature = "p256"))]
+
+    #[cfg(feature = "x25519")]
     {
         let parameters = generate_parameters::<X25519P256>()?;
         println!("X25519 P-256: {}", stringify_test_vectors(&parameters));
@@ -704,11 +702,14 @@ fn generate_test_vectors() -> Result<(), ProtocolError> {
 fn test_registration_request() -> Result<(), ProtocolError> {
     fn inner<CS: CipherSuite>(test_vector: &str) -> Result<(), ProtocolError>
     where
-        <CS::Hash as CoreProxy>::Core: ProxyHash,
-        <<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
-        Le<<<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
+        <OprfHash<CS> as OutputSizeUser>::OutputSize:
+            IsLess<U256> + IsLessOrEqual<<OprfHash<CS> as BlockSizeUser>::BlockSize>,
+        OprfHash<CS>: Hash,
+        <OprfHash<CS> as CoreProxy>::Core: ProxyHash,
+        <<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
+        Le<<<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
         // ClientRegistration: KgSk + KgPk
-        <CS::OprfGroup as Group>::ScalarLen: Add<<CS::OprfGroup as Group>::ElemLen>,
+        <OprfGroup<CS> as Group>::ScalarLen: Add<<OprfGroup<CS> as Group>::ElemLen>,
         ClientRegistrationLen<CS>: ArrayLength<u8>,
     {
         let parameters = populate_test_vectors(&serde_json::from_str(test_vector).unwrap());
@@ -728,11 +729,10 @@ fn test_registration_request() -> Result<(), ProtocolError> {
 
     #[cfg(feature = "ristretto255")]
     inner::<Ristretto255>(TEST_VECTOR_RISTRETTO255)?;
-    #[cfg(feature = "p256")]
     inner::<P256>(TEST_VECTOR_P256)?;
     #[cfg(all(feature = "x25519", feature = "ristretto255"))]
     inner::<X25519Ristretto255>(TEST_VECTOR_X25519_RISTRETTO255)?;
-    #[cfg(all(feature = "x25519", feature = "p256"))]
+    #[cfg(feature = "x25519")]
     inner::<X25519P256>(TEST_VECTOR_X25519_P256)?;
 
     Ok(())
@@ -745,9 +745,12 @@ fn test_serialization() -> Result<(), ProtocolError> {
 
     fn inner<CS: CipherSuite>(test_vector: &str) -> Result<(), ProtocolError>
     where
-        <CS::Hash as CoreProxy>::Core: ProxyHash,
-        <<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
-        Le<<<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
+        <OprfHash<CS> as OutputSizeUser>::OutputSize:
+            IsLess<U256> + IsLessOrEqual<<OprfHash<CS> as BlockSizeUser>::BlockSize>,
+        OprfHash<CS>: Hash,
+        <OprfHash<CS> as CoreProxy>::Core: ProxyHash,
+        <<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
+        Le<<<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
     {
         let parameters = populate_test_vectors(&serde_json::from_str(test_vector).unwrap());
         let mut rng = CycleRng::new(parameters.blinding_factor.to_vec());
@@ -773,11 +776,10 @@ fn test_serialization() -> Result<(), ProtocolError> {
 
     #[cfg(feature = "ristretto255")]
     inner::<Ristretto255>(TEST_VECTOR_RISTRETTO255)?;
-    #[cfg(feature = "p256")]
     inner::<P256>(TEST_VECTOR_P256)?;
     #[cfg(all(feature = "x25519", feature = "ristretto255"))]
     inner::<X25519Ristretto255>(TEST_VECTOR_X25519_RISTRETTO255)?;
-    #[cfg(all(feature = "x25519", feature = "p256"))]
+    #[cfg(feature = "x25519")]
     inner::<X25519P256>(TEST_VECTOR_X25519_P256)?;
 
     Ok(())
@@ -787,11 +789,14 @@ fn test_serialization() -> Result<(), ProtocolError> {
 fn test_registration_response() -> Result<(), ProtocolError> {
     fn inner<CS: CipherSuite>(test_vector: &str) -> Result<(), ProtocolError>
     where
-        <CS::Hash as CoreProxy>::Core: ProxyHash,
-        <<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
-        Le<<<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
+        <OprfHash<CS> as OutputSizeUser>::OutputSize:
+            IsLess<U256> + IsLessOrEqual<<OprfHash<CS> as BlockSizeUser>::BlockSize>,
+        OprfHash<CS>: Hash,
+        <OprfHash<CS> as CoreProxy>::Core: ProxyHash,
+        <<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
+        Le<<<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
         // RegistrationResponse: KgPk + KePk
-        <CS::OprfGroup as Group>::ElemLen: Add<<CS::KeGroup as KeGroup>::PkLen>,
+        <OprfGroup<CS> as Group>::ElemLen: Add<<CS::KeGroup as KeGroup>::PkLen>,
         RegistrationResponseLen<CS>: ArrayLength<u8>,
     {
         let parameters = populate_test_vectors(
@@ -821,11 +826,10 @@ fn test_registration_response() -> Result<(), ProtocolError> {
 
     #[cfg(feature = "ristretto255")]
     inner::<Ristretto255>(TEST_VECTOR_RISTRETTO255)?;
-    #[cfg(feature = "p256")]
     inner::<P256>(TEST_VECTOR_P256)?;
     #[cfg(all(feature = "x25519", feature = "ristretto255"))]
     inner::<X25519Ristretto255>(TEST_VECTOR_X25519_RISTRETTO255)?;
-    #[cfg(all(feature = "x25519", feature = "p256"))]
+    #[cfg(feature = "x25519")]
     inner::<X25519P256>(TEST_VECTOR_X25519_P256)?;
 
     Ok(())
@@ -835,15 +839,18 @@ fn test_registration_response() -> Result<(), ProtocolError> {
 fn test_registration_upload() -> Result<(), ProtocolError> {
     fn inner<CS: CipherSuite>(test_vector: &str) -> Result<(), ProtocolError>
     where
-        <CS::Hash as CoreProxy>::Core: ProxyHash,
-        <<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
-        Le<<<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
+        <OprfHash<CS> as OutputSizeUser>::OutputSize:
+            IsLess<U256> + IsLessOrEqual<<OprfHash<CS> as BlockSizeUser>::BlockSize>,
+        OprfHash<CS>: Hash,
+        <OprfHash<CS> as CoreProxy>::Core: ProxyHash,
+        <<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
+        Le<<<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
         // Envelope: Nonce + Hash
-        NonceLen: Add<OutputSize<CS::Hash>>,
+        NonceLen: Add<OutputSize<OprfHash<CS>>>,
         EnvelopeLen<CS>: ArrayLength<u8>,
         // RegistrationUpload: (KePk + Hash) + Envelope
-        <CS::KeGroup as KeGroup>::PkLen: Add<OutputSize<CS::Hash>>,
-        Sum<<CS::KeGroup as KeGroup>::PkLen, OutputSize<CS::Hash>>:
+        <CS::KeGroup as KeGroup>::PkLen: Add<OutputSize<OprfHash<CS>>>,
+        Sum<<CS::KeGroup as KeGroup>::PkLen, OutputSize<OprfHash<CS>>>:
             ArrayLength<u8> + Add<EnvelopeLen<CS>>,
         RegistrationUploadLen<CS>: ArrayLength<u8>,
     {
@@ -874,7 +881,7 @@ fn test_registration_upload() -> Result<(), ProtocolError> {
         );
         assert_eq!(
             hex::encode(parameters.export_key),
-            hex::encode(result.export_key.to_vec())
+            hex::encode(result.export_key)
         );
 
         Ok(())
@@ -882,11 +889,10 @@ fn test_registration_upload() -> Result<(), ProtocolError> {
 
     #[cfg(feature = "ristretto255")]
     inner::<Ristretto255>(TEST_VECTOR_RISTRETTO255)?;
-    #[cfg(feature = "p256")]
     inner::<P256>(TEST_VECTOR_P256)?;
     #[cfg(all(feature = "x25519", feature = "ristretto255"))]
     inner::<X25519Ristretto255>(TEST_VECTOR_X25519_RISTRETTO255)?;
-    #[cfg(all(feature = "x25519", feature = "p256"))]
+    #[cfg(feature = "x25519")]
     inner::<X25519P256>(TEST_VECTOR_X25519_P256)?;
 
     Ok(())
@@ -896,15 +902,18 @@ fn test_registration_upload() -> Result<(), ProtocolError> {
 fn test_password_file() -> Result<(), ProtocolError> {
     fn inner<CS: CipherSuite>(test_vector: &str) -> Result<(), ProtocolError>
     where
-        <CS::Hash as CoreProxy>::Core: ProxyHash,
-        <<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
-        Le<<<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
+        <OprfHash<CS> as OutputSizeUser>::OutputSize:
+            IsLess<U256> + IsLessOrEqual<<OprfHash<CS> as BlockSizeUser>::BlockSize>,
+        OprfHash<CS>: Hash,
+        <OprfHash<CS> as CoreProxy>::Core: ProxyHash,
+        <<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
+        Le<<<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
         // Envelope: Nonce + Hash
-        NonceLen: Add<OutputSize<CS::Hash>>,
+        NonceLen: Add<OutputSize<OprfHash<CS>>>,
         EnvelopeLen<CS>: ArrayLength<u8>,
         // RegistrationUpload: (KePk + Hash) + Envelope
-        <CS::KeGroup as KeGroup>::PkLen: Add<OutputSize<CS::Hash>>,
-        Sum<<CS::KeGroup as KeGroup>::PkLen, OutputSize<CS::Hash>>:
+        <CS::KeGroup as KeGroup>::PkLen: Add<OutputSize<OprfHash<CS>>>,
+        Sum<<CS::KeGroup as KeGroup>::PkLen, OutputSize<OprfHash<CS>>>:
             ArrayLength<u8> + Add<EnvelopeLen<CS>>,
         RegistrationUploadLen<CS>: ArrayLength<u8>,
         // ServerRegistration = RegistrationUpload
@@ -924,11 +933,10 @@ fn test_password_file() -> Result<(), ProtocolError> {
 
     #[cfg(feature = "ristretto255")]
     inner::<Ristretto255>(TEST_VECTOR_RISTRETTO255)?;
-    #[cfg(feature = "p256")]
     inner::<P256>(TEST_VECTOR_P256)?;
     #[cfg(all(feature = "x25519", feature = "ristretto255"))]
     inner::<X25519Ristretto255>(TEST_VECTOR_X25519_RISTRETTO255)?;
-    #[cfg(all(feature = "x25519", feature = "p256"))]
+    #[cfg(feature = "x25519")]
     inner::<X25519P256>(TEST_VECTOR_X25519_P256)?;
 
     Ok(())
@@ -938,15 +946,18 @@ fn test_password_file() -> Result<(), ProtocolError> {
 fn test_credential_request() -> Result<(), ProtocolError> {
     fn inner<CS: CipherSuite>(test_vector: &str) -> Result<(), ProtocolError>
     where
-        <CS::Hash as CoreProxy>::Core: ProxyHash,
-        <<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
-        Le<<<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
+        <OprfHash<CS> as OutputSizeUser>::OutputSize:
+            IsLess<U256> + IsLessOrEqual<<OprfHash<CS> as BlockSizeUser>::BlockSize>,
+        OprfHash<CS>: Hash,
+        <OprfHash<CS> as CoreProxy>::Core: ProxyHash,
+        <<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
+        Le<<<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
         // CredentialRequest: KgPk + Ke1Message
-        <CS::OprfGroup as Group>::ElemLen: Add<Ke1MessageLen<CS>>,
+        <OprfGroup<CS> as Group>::ElemLen: Add<Ke1MessageLen<CS>>,
         CredentialRequestLen<CS>: ArrayLength<u8>,
         // ClientLogin: KgSk + CredentialRequest + Ke1State
-        <CS::OprfGroup as Group>::ScalarLen: Add<CredentialRequestLen<CS>>,
-        Sum<<CS::OprfGroup as Group>::ScalarLen, CredentialRequestLen<CS>>:
+        <OprfGroup<CS> as Group>::ScalarLen: Add<CredentialRequestLen<CS>>,
+        Sum<<OprfGroup<CS> as Group>::ScalarLen, CredentialRequestLen<CS>>:
             ArrayLength<u8> + Add<Ke1StateLen<CS>>,
         ClientLoginLen<CS>: ArrayLength<u8>,
     {
@@ -974,11 +985,10 @@ fn test_credential_request() -> Result<(), ProtocolError> {
 
     #[cfg(feature = "ristretto255")]
     inner::<Ristretto255>(TEST_VECTOR_RISTRETTO255)?;
-    #[cfg(feature = "p256")]
     inner::<P256>(TEST_VECTOR_P256)?;
     #[cfg(all(feature = "x25519", feature = "ristretto255"))]
     inner::<X25519Ristretto255>(TEST_VECTOR_X25519_RISTRETTO255)?;
-    #[cfg(all(feature = "x25519", feature = "p256"))]
+    #[cfg(feature = "x25519")]
     inner::<X25519P256>(TEST_VECTOR_X25519_P256)?;
 
     Ok(())
@@ -988,16 +998,20 @@ fn test_credential_request() -> Result<(), ProtocolError> {
 fn test_credential_response() -> Result<(), ProtocolError> {
     fn inner<CS: CipherSuite>(test_vector: &str) -> Result<(), ProtocolError>
     where
-        <CS::Hash as CoreProxy>::Core: ProxyHash,
-        <<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
-        Le<<<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
+        <OprfHash<CS> as OutputSizeUser>::OutputSize:
+            IsLess<U256> + IsLessOrEqual<<OprfHash<CS> as BlockSizeUser>::BlockSize>,
+        OprfHash<CS>: Hash,
+        <OprfHash<CS> as CoreProxy>::Core: ProxyHash,
+        <<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
+        Le<<<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
         // MaskedResponse: (Nonce + Hash) + KePk
-        NonceLen: Add<OutputSize<CS::Hash>>,
-        Sum<NonceLen, OutputSize<CS::Hash>>: ArrayLength<u8> + Add<<CS::KeGroup as KeGroup>::PkLen>,
+        NonceLen: Add<OutputSize<OprfHash<CS>>>,
+        Sum<NonceLen, OutputSize<OprfHash<CS>>>:
+            ArrayLength<u8> + Add<<CS::KeGroup as KeGroup>::PkLen>,
         MaskedResponseLen<CS>: ArrayLength<u8>,
         // CredentialResponseWithoutKeLen: (KgPk + Nonce) + MaskedResponse
-        <CS::OprfGroup as Group>::ElemLen: Add<NonceLen>,
-        Sum<<CS::OprfGroup as Group>::ElemLen, NonceLen>:
+        <OprfGroup<CS> as Group>::ElemLen: Add<NonceLen>,
+        Sum<<OprfGroup<CS> as Group>::ElemLen, NonceLen>:
             ArrayLength<u8> + Add<MaskedResponseLen<CS>>,
         CredentialResponseWithoutKeLen<CS>: ArrayLength<u8>,
         // CredentialResponse: CredentialResponseWithoutKeLen + Ke2Message
@@ -1050,11 +1064,10 @@ fn test_credential_response() -> Result<(), ProtocolError> {
 
     #[cfg(feature = "ristretto255")]
     inner::<Ristretto255>(TEST_VECTOR_RISTRETTO255)?;
-    #[cfg(feature = "p256")]
     inner::<P256>(TEST_VECTOR_P256)?;
     #[cfg(all(feature = "x25519", feature = "ristretto255"))]
     inner::<X25519Ristretto255>(TEST_VECTOR_X25519_RISTRETTO255)?;
-    #[cfg(all(feature = "x25519", feature = "p256"))]
+    #[cfg(feature = "x25519")]
     inner::<X25519P256>(TEST_VECTOR_X25519_P256)?;
 
     Ok(())
@@ -1064,12 +1077,16 @@ fn test_credential_response() -> Result<(), ProtocolError> {
 fn test_credential_finalization() -> Result<(), ProtocolError> {
     fn inner<CS: CipherSuite>(test_vector: &str) -> Result<(), ProtocolError>
     where
-        <CS::Hash as CoreProxy>::Core: ProxyHash,
-        <<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
-        Le<<<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
+        <OprfHash<CS> as OutputSizeUser>::OutputSize:
+            IsLess<U256> + IsLessOrEqual<<OprfHash<CS> as BlockSizeUser>::BlockSize>,
+        OprfHash<CS>: Hash,
+        <OprfHash<CS> as CoreProxy>::Core: ProxyHash,
+        <<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
+        Le<<<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
         // MaskedResponse: (Nonce + Hash) + KePk
-        NonceLen: Add<OutputSize<CS::Hash>>,
-        Sum<NonceLen, OutputSize<CS::Hash>>: ArrayLength<u8> + Add<<CS::KeGroup as KeGroup>::PkLen>,
+        NonceLen: Add<OutputSize<OprfHash<CS>>>,
+        Sum<NonceLen, OutputSize<OprfHash<CS>>>:
+            ArrayLength<u8> + Add<<CS::KeGroup as KeGroup>::PkLen>,
         MaskedResponseLen<CS>: ArrayLength<u8>,
     {
         let parameters = populate_test_vectors(&serde_json::from_str(test_vector).unwrap());
@@ -1090,7 +1107,7 @@ fn test_credential_finalization() -> Result<(), ProtocolError> {
 
         assert_eq!(
             hex::encode(&parameters.server_s_pk),
-            hex::encode(&client_login_finish_result.server_s_pk.to_arr().to_vec())
+            hex::encode(&client_login_finish_result.server_s_pk.to_bytes())
         );
         assert_eq!(
             hex::encode(&parameters.session_key),
@@ -1110,11 +1127,10 @@ fn test_credential_finalization() -> Result<(), ProtocolError> {
 
     #[cfg(feature = "ristretto255")]
     inner::<Ristretto255>(TEST_VECTOR_RISTRETTO255)?;
-    #[cfg(feature = "p256")]
     inner::<P256>(TEST_VECTOR_P256)?;
     #[cfg(all(feature = "x25519", feature = "ristretto255"))]
     inner::<X25519Ristretto255>(TEST_VECTOR_X25519_RISTRETTO255)?;
-    #[cfg(all(feature = "x25519", feature = "p256"))]
+    #[cfg(feature = "x25519")]
     inner::<X25519P256>(TEST_VECTOR_X25519_P256)?;
 
     Ok(())
@@ -1124,9 +1140,12 @@ fn test_credential_finalization() -> Result<(), ProtocolError> {
 fn test_server_login_finish() -> Result<(), ProtocolError> {
     fn inner<CS: CipherSuite>(test_vector: &str) -> Result<(), ProtocolError>
     where
-        <CS::Hash as CoreProxy>::Core: ProxyHash,
-        <<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
-        Le<<<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
+        <OprfHash<CS> as OutputSizeUser>::OutputSize:
+            IsLess<U256> + IsLessOrEqual<<OprfHash<CS> as BlockSizeUser>::BlockSize>,
+        OprfHash<CS>: Hash,
+        <OprfHash<CS> as CoreProxy>::Core: ProxyHash,
+        <<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
+        Le<<<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
     {
         let parameters = populate_test_vectors(&serde_json::from_str(test_vector).unwrap());
 
@@ -1145,11 +1164,10 @@ fn test_server_login_finish() -> Result<(), ProtocolError> {
 
     #[cfg(feature = "ristretto255")]
     inner::<Ristretto255>(TEST_VECTOR_RISTRETTO255)?;
-    #[cfg(feature = "p256")]
     inner::<P256>(TEST_VECTOR_P256)?;
     #[cfg(all(feature = "x25519", feature = "ristretto255"))]
     inner::<X25519Ristretto255>(TEST_VECTOR_X25519_RISTRETTO255)?;
-    #[cfg(all(feature = "x25519", feature = "p256"))]
+    #[cfg(feature = "x25519")]
     inner::<X25519P256>(TEST_VECTOR_X25519_P256)?;
 
     Ok(())
@@ -1160,12 +1178,15 @@ fn test_complete_flow<CS: CipherSuite>(
     login_password: &[u8],
 ) -> Result<(), ProtocolError>
 where
-    <CS::Hash as CoreProxy>::Core: ProxyHash,
-    <<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
-    Le<<<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
+    <OprfHash<CS> as OutputSizeUser>::OutputSize:
+        IsLess<U256> + IsLessOrEqual<<OprfHash<CS> as BlockSizeUser>::BlockSize>,
+    OprfHash<CS>: Hash,
+    <OprfHash<CS> as CoreProxy>::Core: ProxyHash,
+    <<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
+    Le<<<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
     // MaskedResponse: (Nonce + Hash) + KePk
-    NonceLen: Add<OutputSize<CS::Hash>>,
-    Sum<NonceLen, OutputSize<CS::Hash>>: ArrayLength<u8> + Add<<CS::KeGroup as KeGroup>::PkLen>,
+    NonceLen: Add<OutputSize<OprfHash<CS>>>,
+    Sum<NonceLen, OutputSize<OprfHash<CS>>>: ArrayLength<u8> + Add<<CS::KeGroup as KeGroup>::PkLen>,
     MaskedResponseLen<CS>: ArrayLength<u8>,
 {
     let credential_identifier = b"credentialIdentifier";
@@ -1230,11 +1251,10 @@ where
 fn test_complete_flow_success() -> Result<(), ProtocolError> {
     #[cfg(feature = "ristretto255")]
     test_complete_flow::<Ristretto255>(b"good password", b"good password")?;
-    #[cfg(feature = "p256")]
     test_complete_flow::<P256>(b"good password", b"good password")?;
     #[cfg(all(feature = "x25519", feature = "ristretto255"))]
     test_complete_flow::<X25519Ristretto255>(b"good password", b"good password")?;
-    #[cfg(all(feature = "x25519", feature = "p256"))]
+    #[cfg(feature = "x25519")]
     test_complete_flow::<X25519P256>(b"good password", b"good password")?;
 
     Ok(())
@@ -1244,11 +1264,10 @@ fn test_complete_flow_success() -> Result<(), ProtocolError> {
 fn test_complete_flow_fail() -> Result<(), ProtocolError> {
     #[cfg(feature = "ristretto255")]
     test_complete_flow::<Ristretto255>(b"good password", b"bad password")?;
-    #[cfg(feature = "p256")]
     test_complete_flow::<P256>(b"good password", b"bad password")?;
     #[cfg(all(feature = "x25519", feature = "ristretto255"))]
     test_complete_flow::<X25519Ristretto255>(b"good password", b"bad password")?;
-    #[cfg(all(feature = "x25519", feature = "p256"))]
+    #[cfg(feature = "x25519")]
     test_complete_flow::<X25519P256>(b"good password", b"bad password")?;
 
     Ok(())
@@ -1260,16 +1279,19 @@ fn test_complete_flow_fail() -> Result<(), ProtocolError> {
 fn test_zeroize_client_registration_start() -> Result<(), ProtocolError> {
     fn inner<CS: CipherSuite>() -> Result<(), ProtocolError>
     where
-        <CS::Hash as CoreProxy>::Core: ProxyHash,
-        <<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
-        Le<<<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
+        <OprfHash<CS> as OutputSizeUser>::OutputSize:
+            IsLess<U256> + IsLessOrEqual<<OprfHash<CS> as BlockSizeUser>::BlockSize>,
+        OprfHash<CS>: Hash,
+        <OprfHash<CS> as CoreProxy>::Core: ProxyHash,
+        <<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
+        Le<<<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
     {
         let mut client_rng = OsRng;
         let client_registration_start_result =
             ClientRegistration::<CS>::start(&mut client_rng, STR_PASSWORD.as_bytes())?;
 
         let mut state = client_registration_start_result.state;
-        Zeroize::zeroize(&mut state);
+        unsafe { ptr::drop_in_place(&mut state) };
         for byte in state.to_vec() {
             assert_eq!(byte, 0);
         }
@@ -1279,11 +1301,10 @@ fn test_zeroize_client_registration_start() -> Result<(), ProtocolError> {
 
     #[cfg(feature = "ristretto255")]
     inner::<Ristretto255>()?;
-    #[cfg(feature = "p256")]
     inner::<P256>()?;
     #[cfg(all(feature = "x25519", feature = "ristretto255"))]
     inner::<X25519Ristretto255>()?;
-    #[cfg(all(feature = "x25519", feature = "p256"))]
+    #[cfg(feature = "x25519")]
     inner::<X25519P256>()?;
 
     Ok(())
@@ -1293,9 +1314,12 @@ fn test_zeroize_client_registration_start() -> Result<(), ProtocolError> {
 fn test_zeroize_client_registration_finish() -> Result<(), ProtocolError> {
     fn inner<CS: CipherSuite>() -> Result<(), ProtocolError>
     where
-        <CS::Hash as CoreProxy>::Core: ProxyHash,
-        <<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
-        Le<<<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
+        <OprfHash<CS> as OutputSizeUser>::OutputSize:
+            IsLess<U256> + IsLessOrEqual<<OprfHash<CS> as BlockSizeUser>::BlockSize>,
+        OprfHash<CS>: Hash,
+        <OprfHash<CS> as CoreProxy>::Core: ProxyHash,
+        <<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
+        Le<<<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
     {
         let mut client_rng = OsRng;
         let mut server_rng = OsRng;
@@ -1315,7 +1339,7 @@ fn test_zeroize_client_registration_finish() -> Result<(), ProtocolError> {
         )?;
 
         let mut state = client_registration_finish_result.state;
-        Zeroize::zeroize(&mut state);
+        unsafe { ptr::drop_in_place(&mut state) };
         for byte in state.to_vec() {
             assert_eq!(byte, 0);
         }
@@ -1325,11 +1349,10 @@ fn test_zeroize_client_registration_finish() -> Result<(), ProtocolError> {
 
     #[cfg(feature = "ristretto255")]
     inner::<Ristretto255>()?;
-    #[cfg(feature = "p256")]
     inner::<P256>()?;
     #[cfg(all(feature = "x25519", feature = "ristretto255"))]
     inner::<X25519Ristretto255>()?;
-    #[cfg(all(feature = "x25519", feature = "p256"))]
+    #[cfg(feature = "x25519")]
     inner::<X25519P256>()?;
 
     Ok(())
@@ -1339,15 +1362,18 @@ fn test_zeroize_client_registration_finish() -> Result<(), ProtocolError> {
 fn test_zeroize_server_registration_finish() -> Result<(), ProtocolError> {
     fn inner<CS: CipherSuite>() -> Result<(), ProtocolError>
     where
-        <CS::Hash as CoreProxy>::Core: ProxyHash,
-        <<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
-        Le<<<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
+        <OprfHash<CS> as OutputSizeUser>::OutputSize:
+            IsLess<U256> + IsLessOrEqual<<OprfHash<CS> as BlockSizeUser>::BlockSize>,
+        OprfHash<CS>: Hash,
+        <OprfHash<CS> as CoreProxy>::Core: ProxyHash,
+        <<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
+        Le<<<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
         // Envelope: Nonce + Hash
-        NonceLen: Add<OutputSize<CS::Hash>>,
+        NonceLen: Add<OutputSize<OprfHash<CS>>>,
         EnvelopeLen<CS>: ArrayLength<u8>,
         // RegistrationUpload: (KePk + Hash) + Envelope
-        <CS::KeGroup as KeGroup>::PkLen: Add<OutputSize<CS::Hash>>,
-        Sum<<CS::KeGroup as KeGroup>::PkLen, OutputSize<CS::Hash>>:
+        <CS::KeGroup as KeGroup>::PkLen: Add<OutputSize<OprfHash<CS>>>,
+        Sum<<CS::KeGroup as KeGroup>::PkLen, OutputSize<OprfHash<CS>>>:
             ArrayLength<u8> + Add<EnvelopeLen<CS>>,
         RegistrationUploadLen<CS>: ArrayLength<u8>,
         // ServerRegistration = RegistrationUpload
@@ -1371,21 +1397,19 @@ fn test_zeroize_server_registration_finish() -> Result<(), ProtocolError> {
         let p_file = ServerRegistration::finish(client_registration_finish_result.message);
 
         let mut state = p_file;
-        Zeroize::zeroize(&mut state);
-        for byte in state.serialize() {
-            assert_eq!(byte, 0);
-        }
+        util::drop_manually(&mut state);
+        util::test_zeroized(&mut state.0.envelope.mode);
+        util::test_zeroized(&mut state.0.masking_key);
 
         Ok(())
     }
 
     #[cfg(feature = "ristretto255")]
     inner::<Ristretto255>()?;
-    #[cfg(feature = "p256")]
     inner::<P256>()?;
     #[cfg(all(feature = "x25519", feature = "ristretto255"))]
     inner::<X25519Ristretto255>()?;
-    #[cfg(all(feature = "x25519", feature = "p256"))]
+    #[cfg(feature = "x25519")]
     inner::<X25519P256>()?;
 
     Ok(())
@@ -1393,35 +1417,55 @@ fn test_zeroize_server_registration_finish() -> Result<(), ProtocolError> {
 
 #[test]
 fn test_zeroize_client_login_start() -> Result<(), ProtocolError> {
-    fn inner<CS: CipherSuite>() -> Result<(), ProtocolError>
+    fn inner<CS: CipherSuite<KeyExchange = TripleDH>>() -> Result<(), ProtocolError>
     where
-        <CS::Hash as CoreProxy>::Core: ProxyHash,
-        <<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
-        Le<<<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
+        <OprfHash<CS> as OutputSizeUser>::OutputSize:
+            IsLess<U256> + IsLessOrEqual<<OprfHash<CS> as BlockSizeUser>::BlockSize>,
+        OprfHash<CS>: Hash,
+        <OprfHash<CS> as CoreProxy>::Core: ProxyHash,
+        <<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
+        Le<<<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
         // CredentialRequest: KgPk + Ke1Message
-        <CS::OprfGroup as Group>::ElemLen: Add<Ke1MessageLen<CS>>,
+        <OprfGroup<CS> as Group>::ElemLen: Add<Sum<NonceLen, <CS::KeGroup as KeGroup>::PkLen>>,
         CredentialRequestLen<CS>: ArrayLength<u8>,
+        // Ke1State: KeSk + Nonce
+        <CS::KeGroup as KeGroup>::SkLen: Add<NonceLen>,
+        Sum<<CS::KeGroup as KeGroup>::SkLen, NonceLen>: ArrayLength<u8>,
+        // Ke1Message: Nonce + KePk
+        NonceLen: Add<<CS::KeGroup as KeGroup>::PkLen>,
+        Sum<NonceLen, <CS::KeGroup as KeGroup>::PkLen>: ArrayLength<u8>,
+        // Ke2State: (Hash + Hash) + Hash
+        OutputSize<OprfHash<CS>>: Add<OutputSize<OprfHash<CS>>>,
+        Sum<OutputSize<OprfHash<CS>>, OutputSize<OprfHash<CS>>>:
+            ArrayLength<u8> + Add<OutputSize<OprfHash<CS>>>,
+        Sum<Sum<OutputSize<OprfHash<CS>>, OutputSize<OprfHash<CS>>>, OutputSize<OprfHash<CS>>>:
+            ArrayLength<u8>,
+        // Ke2Message: (Nonce + KePk) + Hash
+        NonceLen: Add<<CS::KeGroup as KeGroup>::PkLen>,
+        Sum<NonceLen, <CS::KeGroup as KeGroup>::PkLen>:
+            ArrayLength<u8> + Add<OutputSize<OprfHash<CS>>>,
+        Sum<Sum<NonceLen, <CS::KeGroup as KeGroup>::PkLen>, OutputSize<OprfHash<CS>>>:
+            ArrayLength<u8>,
     {
         let mut client_rng = OsRng;
         let client_login_start_result =
             ClientLogin::<CS>::start(&mut client_rng, STR_PASSWORD.as_bytes())?;
 
         let mut state = client_login_start_result.state;
-        Zeroize::zeroize(&mut state);
-        for byte in state.to_vec() {
-            assert_eq!(byte, 0);
-        }
+        util::drop_manually(&mut state);
+        util::test_zeroized(&mut state.oprf_client);
+        util::test_zeroized(&mut state.ke1_state);
+        util::test_zeroized(&mut state.credential_request.ke1_message.client_nonce);
 
         Ok(())
     }
 
     #[cfg(feature = "ristretto255")]
     inner::<Ristretto255>()?;
-    #[cfg(feature = "p256")]
     inner::<P256>()?;
     #[cfg(all(feature = "x25519", feature = "ristretto255"))]
     inner::<X25519Ristretto255>()?;
-    #[cfg(all(feature = "x25519", feature = "p256"))]
+    #[cfg(feature = "x25519")]
     inner::<X25519P256>()?;
 
     Ok(())
@@ -1431,12 +1475,16 @@ fn test_zeroize_client_login_start() -> Result<(), ProtocolError> {
 fn test_zeroize_server_login_start() -> Result<(), ProtocolError> {
     fn inner<CS: CipherSuite>() -> Result<(), ProtocolError>
     where
-        <CS::Hash as CoreProxy>::Core: ProxyHash,
-        <<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
-        Le<<<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
+        <OprfHash<CS> as OutputSizeUser>::OutputSize:
+            IsLess<U256> + IsLessOrEqual<<OprfHash<CS> as BlockSizeUser>::BlockSize>,
+        OprfHash<CS>: Hash,
+        <OprfHash<CS> as CoreProxy>::Core: ProxyHash,
+        <<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
+        Le<<<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
         // MaskedResponse: (Nonce + Hash) + KePk
-        NonceLen: Add<OutputSize<CS::Hash>>,
-        Sum<NonceLen, OutputSize<CS::Hash>>: ArrayLength<u8> + Add<<CS::KeGroup as KeGroup>::PkLen>,
+        NonceLen: Add<OutputSize<OprfHash<CS>>>,
+        Sum<NonceLen, OutputSize<OprfHash<CS>>>:
+            ArrayLength<u8> + Add<<CS::KeGroup as KeGroup>::PkLen>,
         MaskedResponseLen<CS>: ArrayLength<u8>,
     {
         let mut client_rng = OsRng;
@@ -1468,7 +1516,7 @@ fn test_zeroize_server_login_start() -> Result<(), ProtocolError> {
         )?;
 
         let mut state = server_login_start_result.state;
-        Zeroize::zeroize(&mut state);
+        unsafe { ptr::drop_in_place(&mut state) };
         for byte in state.serialize() {
             assert_eq!(byte, 0);
         }
@@ -1478,11 +1526,10 @@ fn test_zeroize_server_login_start() -> Result<(), ProtocolError> {
 
     #[cfg(feature = "ristretto255")]
     inner::<Ristretto255>()?;
-    #[cfg(feature = "p256")]
     inner::<P256>()?;
     #[cfg(all(feature = "x25519", feature = "ristretto255"))]
     inner::<X25519Ristretto255>()?;
-    #[cfg(all(feature = "x25519", feature = "p256"))]
+    #[cfg(feature = "x25519")]
     inner::<X25519P256>()?;
 
     Ok(())
@@ -1490,18 +1537,40 @@ fn test_zeroize_server_login_start() -> Result<(), ProtocolError> {
 
 #[test]
 fn test_zeroize_client_login_finish() -> Result<(), ProtocolError> {
-    fn inner<CS: CipherSuite>() -> Result<(), ProtocolError>
+    fn inner<CS: CipherSuite<KeyExchange = TripleDH>>() -> Result<(), ProtocolError>
     where
-        <CS::Hash as CoreProxy>::Core: ProxyHash,
-        <<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
-        Le<<<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
+        <OprfHash<CS> as OutputSizeUser>::OutputSize:
+            IsLess<U256> + IsLessOrEqual<<OprfHash<CS> as BlockSizeUser>::BlockSize>,
+        OprfHash<CS>: Hash,
+        <OprfHash<CS> as CoreProxy>::Core: ProxyHash,
+        <<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
+        Le<<<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
         // MaskedResponse: (Nonce + Hash) + KePk
-        NonceLen: Add<OutputSize<CS::Hash>>,
-        Sum<NonceLen, OutputSize<CS::Hash>>: ArrayLength<u8> + Add<<CS::KeGroup as KeGroup>::PkLen>,
+        NonceLen: Add<OutputSize<OprfHash<CS>>>,
+        Sum<NonceLen, OutputSize<OprfHash<CS>>>:
+            ArrayLength<u8> + Add<<CS::KeGroup as KeGroup>::PkLen>,
         MaskedResponseLen<CS>: ArrayLength<u8>,
         // CredentialRequest: KgPk + Ke1Message
-        <CS::OprfGroup as Group>::ElemLen: Add<Ke1MessageLen<CS>>,
+        <OprfGroup<CS> as Group>::ElemLen: Add<Sum<NonceLen, <CS::KeGroup as KeGroup>::PkLen>>,
         CredentialRequestLen<CS>: ArrayLength<u8>,
+        // Ke1State: KeSk + Nonce
+        <CS::KeGroup as KeGroup>::SkLen: Add<NonceLen>,
+        Sum<<CS::KeGroup as KeGroup>::SkLen, NonceLen>: ArrayLength<u8>,
+        // Ke1Message: Nonce + KePk
+        NonceLen: Add<<CS::KeGroup as KeGroup>::PkLen>,
+        Sum<NonceLen, <CS::KeGroup as KeGroup>::PkLen>: ArrayLength<u8>,
+        // Ke2State: (Hash + Hash) + Hash
+        OutputSize<OprfHash<CS>>: Add<OutputSize<OprfHash<CS>>>,
+        Sum<OutputSize<OprfHash<CS>>, OutputSize<OprfHash<CS>>>:
+            ArrayLength<u8> + Add<OutputSize<OprfHash<CS>>>,
+        Sum<Sum<OutputSize<OprfHash<CS>>, OutputSize<OprfHash<CS>>>, OutputSize<OprfHash<CS>>>:
+            ArrayLength<u8>,
+        // Ke2Message: (Nonce + KePk) + Hash
+        NonceLen: Add<<CS::KeGroup as KeGroup>::PkLen>,
+        Sum<NonceLen, <CS::KeGroup as KeGroup>::PkLen>:
+            ArrayLength<u8> + Add<OutputSize<OprfHash<CS>>>,
+        Sum<Sum<NonceLen, <CS::KeGroup as KeGroup>::PkLen>, OutputSize<OprfHash<CS>>>:
+            ArrayLength<u8>,
     {
         let mut client_rng = OsRng;
         let mut server_rng = OsRng;
@@ -1537,21 +1606,20 @@ fn test_zeroize_client_login_finish() -> Result<(), ProtocolError> {
         )?;
 
         let mut state = client_login_finish_result.state;
-        Zeroize::zeroize(&mut state);
-        for byte in state.to_vec() {
-            assert_eq!(byte, 0);
-        }
+        util::drop_manually(&mut state);
+        util::test_zeroized(&mut state.oprf_client);
+        util::test_zeroized(&mut state.ke1_state);
+        util::test_zeroized(&mut state.credential_request.ke1_message.client_nonce);
 
         Ok(())
     }
 
     #[cfg(feature = "ristretto255")]
     inner::<Ristretto255>()?;
-    #[cfg(feature = "p256")]
     inner::<P256>()?;
     #[cfg(all(feature = "x25519", feature = "ristretto255"))]
     inner::<X25519Ristretto255>()?;
-    #[cfg(all(feature = "x25519", feature = "p256"))]
+    #[cfg(feature = "x25519")]
     inner::<X25519P256>()?;
 
     Ok(())
@@ -1561,12 +1629,16 @@ fn test_zeroize_client_login_finish() -> Result<(), ProtocolError> {
 fn test_zeroize_server_login_finish() -> Result<(), ProtocolError> {
     fn inner<CS: CipherSuite>() -> Result<(), ProtocolError>
     where
-        <CS::Hash as CoreProxy>::Core: ProxyHash,
-        <<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
-        Le<<<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
+        <OprfHash<CS> as OutputSizeUser>::OutputSize:
+            IsLess<U256> + IsLessOrEqual<<OprfHash<CS> as BlockSizeUser>::BlockSize>,
+        OprfHash<CS>: Hash,
+        <OprfHash<CS> as CoreProxy>::Core: ProxyHash,
+        <<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
+        Le<<<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
         // MaskedResponse: (Nonce + Hash) + KePk
-        NonceLen: Add<OutputSize<CS::Hash>>,
-        Sum<NonceLen, OutputSize<CS::Hash>>: ArrayLength<u8> + Add<<CS::KeGroup as KeGroup>::PkLen>,
+        NonceLen: Add<OutputSize<OprfHash<CS>>>,
+        Sum<NonceLen, OutputSize<OprfHash<CS>>>:
+            ArrayLength<u8> + Add<<CS::KeGroup as KeGroup>::PkLen>,
         MaskedResponseLen<CS>: ArrayLength<u8>,
     {
         let mut client_rng = OsRng;
@@ -1606,7 +1678,7 @@ fn test_zeroize_server_login_finish() -> Result<(), ProtocolError> {
             .finish(client_login_finish_result.message)?;
 
         let mut state = server_login_finish_result.state;
-        Zeroize::zeroize(&mut state);
+        unsafe { ptr::drop_in_place(&mut state) };
         for byte in state.serialize() {
             assert_eq!(byte, 0);
         }
@@ -1616,11 +1688,10 @@ fn test_zeroize_server_login_finish() -> Result<(), ProtocolError> {
 
     #[cfg(feature = "ristretto255")]
     inner::<Ristretto255>()?;
-    #[cfg(feature = "p256")]
     inner::<P256>()?;
     #[cfg(all(feature = "x25519", feature = "ristretto255"))]
     inner::<X25519Ristretto255>()?;
-    #[cfg(all(feature = "x25519", feature = "p256"))]
+    #[cfg(feature = "x25519")]
     inner::<X25519P256>()?;
 
     Ok(())
@@ -1630,9 +1701,12 @@ fn test_zeroize_server_login_finish() -> Result<(), ProtocolError> {
 fn test_scalar_always_nonzero() -> Result<(), ProtocolError> {
     fn inner<CS: CipherSuite>() -> Result<(), ProtocolError>
     where
-        <CS::Hash as CoreProxy>::Core: ProxyHash,
-        <<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
-        Le<<<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
+        <OprfHash<CS> as OutputSizeUser>::OutputSize:
+            IsLess<U256> + IsLessOrEqual<<OprfHash<CS> as BlockSizeUser>::BlockSize>,
+        OprfHash<CS>: Hash,
+        <OprfHash<CS> as CoreProxy>::Core: ProxyHash,
+        <<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
+        Le<<<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
     {
         // Start out with a bunch of zeros to force resampling of scalar
         let mut client_registration_rng = CycleRng::new([vec![0u8; 128], vec![1u8; 128]].concat());
@@ -1640,7 +1714,7 @@ fn test_scalar_always_nonzero() -> Result<(), ProtocolError> {
             ClientRegistration::<CS>::start(&mut client_registration_rng, STR_PASSWORD.as_bytes())?;
 
         assert!(!bool::from(
-            CS::OprfGroup::identity().ct_eq(
+            OprfGroup::<CS>::identity_elem().ct_eq(
                 &client_registration_start_result
                     .message
                     .get_blinded_element_for_testing()
@@ -1654,7 +1728,7 @@ fn test_scalar_always_nonzero() -> Result<(), ProtocolError> {
             ClientLogin::<CS>::start(&mut client_login_rng, STR_PASSWORD.as_bytes())?;
 
         assert!(!bool::from(
-            CS::OprfGroup::identity().ct_eq(
+            OprfGroup::<CS>::identity_elem().ct_eq(
                 &client_login_start_result
                     .message
                     .get_blinded_element_for_testing()
@@ -1667,11 +1741,10 @@ fn test_scalar_always_nonzero() -> Result<(), ProtocolError> {
 
     #[cfg(feature = "ristretto255")]
     inner::<Ristretto255>()?;
-    #[cfg(feature = "p256")]
     inner::<P256>()?;
     #[cfg(all(feature = "x25519", feature = "ristretto255"))]
     inner::<X25519Ristretto255>()?;
-    #[cfg(all(feature = "x25519", feature = "p256"))]
+    #[cfg(feature = "x25519")]
     inner::<X25519P256>()?;
 
     Ok(())
@@ -1681,9 +1754,12 @@ fn test_scalar_always_nonzero() -> Result<(), ProtocolError> {
 fn test_reflected_value_error_registration() -> Result<(), ProtocolError> {
     fn inner<CS: CipherSuite>() -> Result<(), ProtocolError>
     where
-        <CS::Hash as CoreProxy>::Core: ProxyHash,
-        <<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
-        Le<<<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
+        <OprfHash<CS> as OutputSizeUser>::OutputSize:
+            IsLess<U256> + IsLessOrEqual<<OprfHash<CS> as BlockSizeUser>::BlockSize>,
+        OprfHash<CS>: Hash,
+        <OprfHash<CS> as CoreProxy>::Core: ProxyHash,
+        <<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
+        Le<<<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
     {
         let credential_identifier = b"credentialIdentifier";
         let password = b"password";
@@ -1723,11 +1799,10 @@ fn test_reflected_value_error_registration() -> Result<(), ProtocolError> {
 
     #[cfg(feature = "ristretto255")]
     inner::<Ristretto255>()?;
-    #[cfg(feature = "p256")]
     inner::<P256>()?;
     #[cfg(all(feature = "x25519", feature = "ristretto255"))]
     inner::<X25519Ristretto255>()?;
-    #[cfg(all(feature = "x25519", feature = "p256"))]
+    #[cfg(feature = "x25519")]
     inner::<X25519P256>()?;
 
     Ok(())
@@ -1737,12 +1812,16 @@ fn test_reflected_value_error_registration() -> Result<(), ProtocolError> {
 fn test_reflected_value_error_login() -> Result<(), ProtocolError> {
     fn inner<CS: CipherSuite>() -> Result<(), ProtocolError>
     where
-        <CS::Hash as CoreProxy>::Core: ProxyHash,
-        <<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
-        Le<<<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
+        <OprfHash<CS> as OutputSizeUser>::OutputSize:
+            IsLess<U256> + IsLessOrEqual<<OprfHash<CS> as BlockSizeUser>::BlockSize>,
+        OprfHash<CS>: Hash,
+        <OprfHash<CS> as CoreProxy>::Core: ProxyHash,
+        <<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
+        Le<<<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
         // MaskedResponse: (Nonce + Hash) + KePk
-        NonceLen: Add<OutputSize<CS::Hash>>,
-        Sum<NonceLen, OutputSize<CS::Hash>>: ArrayLength<u8> + Add<<CS::KeGroup as KeGroup>::PkLen>,
+        NonceLen: Add<OutputSize<OprfHash<CS>>>,
+        Sum<NonceLen, OutputSize<OprfHash<CS>>>:
+            ArrayLength<u8> + Add<<CS::KeGroup as KeGroup>::PkLen>,
         MaskedResponseLen<CS>: ArrayLength<u8>,
     {
         let credential_identifier = b"credentialIdentifier";
@@ -1797,11 +1876,10 @@ fn test_reflected_value_error_login() -> Result<(), ProtocolError> {
 
     #[cfg(feature = "ristretto255")]
     inner::<Ristretto255>()?;
-    #[cfg(feature = "p256")]
     inner::<P256>()?;
     #[cfg(all(feature = "x25519", feature = "ristretto255"))]
     inner::<X25519Ristretto255>()?;
-    #[cfg(all(feature = "x25519", feature = "p256"))]
+    #[cfg(feature = "x25519")]
     inner::<X25519P256>()?;
 
     Ok(())
diff --git a/src/tests/test_opaque_vectors.rs b/src/tests/test_opaque_vectors.rs
old mode 100755
new mode 100644
index 7c2e9a0a..c194790c
--- a/src/tests/test_opaque_vectors.rs
+++ b/src/tests/test_opaque_vectors.rs
@@ -6,20 +6,22 @@
 // of this source tree.
 
 use core::ops::Add;
-use std::string::ToString;
 use std::vec::Vec;
 use std::{println, vec};
 
 use digest::core_api::{BlockSizeUser, CoreProxy};
-use generic_array::typenum::{IsLess, Le, NonZero, Sum, U256};
-use generic_array::ArrayLength;
+use digest::OutputSizeUser;
+use generic_array::typenum::{IsLess, IsLessOrEqual, Le, NonZero, Sum, U256};
+use generic_array::{ArrayLength, GenericArray};
 use json::JsonValue;
+use rand::rngs::OsRng;
+use rand::RngCore;
 use voprf::Group;
 
-use crate::ciphersuite::CipherSuite;
+use crate::ciphersuite::{CipherSuite, OprfGroup, OprfHash};
 use crate::envelope::EnvelopeLen;
 use crate::errors::*;
-use crate::hash::{OutputSize, ProxyHash};
+use crate::hash::{Hash, OutputSize, ProxyHash};
 use crate::key_exchange::group::KeGroup;
 use crate::key_exchange::traits::{Ke1MessageLen, Ke2MessageLen};
 use crate::key_exchange::tripledh::{NonceLen, TripleDH};
@@ -89,30 +91,39 @@ macro_rules! parse_default {
     };
 }
 
-/// If no entry is found, default to filling a random buffer of a specified size
-macro_rules! parse_default_random {
-    ( $v:ident, $s:expr, $size:expr ) => {
-        parse_default!($v, $s, {
-            use rand::rngs::OsRng;
-            use rand::RngCore;
-            let mut rng = OsRng;
-            let mut v = vec![0u8; $size];
-            rng.fill_bytes(&mut v);
-            v
-        })
-    };
-}
-
 fn decode(values: &JsonValue, key: &str) -> Option<Vec<u8>> {
-    values[key]
-        .as_str()
-        .and_then(|s| hex::decode(&s.to_string()).ok())
+    values[key].as_str().and_then(|s| hex::decode(&s).ok())
 }
 
-fn populate_test_vectors(values: &JsonValue) -> OpaqueTestVectorParameters {
+fn populate_test_vectors<CS: CipherSuite>(values: &JsonValue) -> OpaqueTestVectorParameters
+where
+    <OprfHash<CS> as OutputSizeUser>::OutputSize:
+        IsLess<U256> + IsLessOrEqual<<OprfHash<CS> as BlockSizeUser>::BlockSize>,
+    OprfHash<CS>: Hash,
+    <OprfHash<CS> as CoreProxy>::Core: ProxyHash,
+    <<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
+    Le<<<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
+{
+    let mut rng = OsRng;
+
     OpaqueTestVectorParameters {
-        dummy_private_key: parse_default_random!(values, "client_private_key", 32),
-        dummy_masking_key: parse_default_random!(values, "masking_key", 64),
+        dummy_private_key: {
+            match decode(values, "client_private_key") {
+                Some(value) => value,
+                None => CS::KeGroup::serialize_sk(&CS::KeGroup::random_sk(&mut OsRng)).to_vec(),
+            }
+        },
+        dummy_masking_key: {
+            match decode(values, "masking_key") {
+                Some(value) => value,
+                None => {
+                    let mut bytes =
+                        GenericArray::<u8, <OprfHash<CS> as OutputSizeUser>::OutputSize>::default();
+                    rng.fill_bytes(&mut bytes);
+                    bytes.to_vec()
+                }
+            }
+        },
         context: parse!(values, "Context"),
         client_private_key: decode(values, "client_private_key"),
         client_keyshare: parse!(values, "client_keyshare"),
@@ -153,15 +164,18 @@ fn populate_test_vectors(values: &JsonValue) -> OpaqueTestVectorParameters {
 
 fn get_password_file_bytes<CS: CipherSuite>(parameters: &OpaqueTestVectorParameters) -> Vec<u8>
 where
-    <CS::Hash as CoreProxy>::Core: ProxyHash,
-    <<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
-    Le<<<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
+    <OprfHash<CS> as OutputSizeUser>::OutputSize:
+        IsLess<U256> + IsLessOrEqual<<OprfHash<CS> as BlockSizeUser>::BlockSize>,
+    OprfHash<CS>: Hash,
+    <OprfHash<CS> as CoreProxy>::Core: ProxyHash,
+    <<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
+    Le<<<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
     // Envelope: Nonce + Hash
-    NonceLen: Add<OutputSize<CS::Hash>>,
+    NonceLen: Add<OutputSize<OprfHash<CS>>>,
     EnvelopeLen<CS>: ArrayLength<u8>,
     // RegistrationUpload: (KePk + Hash) + Envelope
-    <CS::KeGroup as KeGroup>::PkLen: Add<OutputSize<CS::Hash>>,
-    Sum<<CS::KeGroup as KeGroup>::PkLen, OutputSize<CS::Hash>>:
+    <CS::KeGroup as KeGroup>::PkLen: Add<OutputSize<OprfHash<CS>>>,
+    Sum<<CS::KeGroup as KeGroup>::PkLen, OutputSize<OprfHash<CS>>>:
         ArrayLength<u8> + Add<EnvelopeLen<CS>>,
     RegistrationUploadLen<CS>: ArrayLength<u8>,
     // ServerRegistration = RegistrationUpload
@@ -174,12 +188,12 @@ where
 }
 
 macro_rules! json_to_test_vectors {
-    ( $v:ident, $vector_type:expr, $cs:expr, ) => {
+    ( $v:ident, $vector_type:expr, $cs:expr, $cs_ty:ty) => {
         $v[$vector_type]
             .members()
             .filter_map(|x| {
                 if x.has_key($cs) {
-                    Some(populate_test_vectors(&x[$cs]))
+                    Some(populate_test_vectors::<$cs_ty>(&x[$cs]))
                 } else {
                     None
                 }
@@ -195,24 +209,33 @@ fn tests() -> Result<(), ProtocolError> {
 
     #[cfg(feature = "ristretto255")]
     {
-        let ristretto_real_tvs = json_to_test_vectors!(rfc, "Real", "ristretto255, SHA512",);
+        struct Ristretto255Sha512NoSlowHash;
+        impl CipherSuite for Ristretto255Sha512NoSlowHash {
+            type OprfGroup = crate::Ristretto255;
+            type KeGroup = crate::Ristretto255;
+            type KeyExchange = TripleDH;
+            type SlowHash = NoOpHash;
+        }
+
+        let ristretto_real_tvs = json_to_test_vectors!(
+            rfc,
+            "Real",
+            "ristretto255, SHA512",
+            Ristretto255Sha512NoSlowHash
+        );
 
-        let ristretto_fake_tvs = json_to_test_vectors!(rfc, "Fake", "ristretto255, SHA512",);
+        let ristretto_fake_tvs = json_to_test_vectors!(
+            rfc,
+            "Fake",
+            "ristretto255, SHA512",
+            Ristretto255Sha512NoSlowHash
+        );
 
         assert!(
             !(ristretto_real_tvs.is_empty() || ristretto_fake_tvs.is_empty()),
             "Parsing error"
         );
 
-        struct Ristretto255Sha512NoSlowHash;
-        impl CipherSuite for Ristretto255Sha512NoSlowHash {
-            type OprfGroup = curve25519_dalek::ristretto::RistrettoPoint;
-            type KeGroup = curve25519_dalek::ristretto::RistrettoPoint;
-            type KeyExchange = TripleDH;
-            type Hash = sha2::Sha512;
-            type SlowHash = NoOpHash;
-        }
-
         test_registration_request::<Ristretto255Sha512NoSlowHash>(&ristretto_real_tvs)?;
         test_registration_response::<Ristretto255Sha512NoSlowHash>(&ristretto_real_tvs)?;
         test_registration_upload::<Ristretto255Sha512NoSlowHash>(&ristretto_real_tvs)?;
@@ -223,36 +246,40 @@ fn tests() -> Result<(), ProtocolError> {
         test_fake_vectors::<Ristretto255Sha512NoSlowHash>(&ristretto_fake_tvs)?;
     }
 
-    #[cfg(feature = "p256")]
-    {
-        let p256_real_tvs =
-            json_to_test_vectors!(rfc, "Real", "P256_XMD:SHA-256_SSWU_RO_, SHA256",);
-        let p256_fake_tvs =
-            json_to_test_vectors!(rfc, "Fake", "P256_XMD:SHA-256_SSWU_RO_, SHA256",);
+    struct P256Sha256NoSlowHash;
+    impl CipherSuite for P256Sha256NoSlowHash {
+        type OprfGroup = p256::NistP256;
+        type KeGroup = p256::NistP256;
+        type KeyExchange = TripleDH;
+        type SlowHash = NoOpHash;
+    }
 
-        assert!(
-            !(p256_real_tvs.is_empty() || p256_fake_tvs.is_empty()),
-            "Parsing error"
-        );
+    let p256_real_tvs = json_to_test_vectors!(
+        rfc,
+        "Real",
+        "P256_XMD:SHA-256_SSWU_RO_, SHA256",
+        P256Sha256NoSlowHash
+    );
+    let p256_fake_tvs = json_to_test_vectors!(
+        rfc,
+        "Fake",
+        "P256_XMD:SHA-256_SSWU_RO_, SHA256",
+        P256Sha256NoSlowHash
+    );
 
-        struct P256Sha256NoSlowHash;
-        impl CipherSuite for P256Sha256NoSlowHash {
-            type OprfGroup = p256_::ProjectivePoint;
-            type KeGroup = p256_::PublicKey;
-            type KeyExchange = TripleDH;
-            type Hash = sha2::Sha256;
-            type SlowHash = NoOpHash;
-        }
+    assert!(
+        !(p256_real_tvs.is_empty() || p256_fake_tvs.is_empty()),
+        "Parsing error"
+    );
 
-        test_registration_request::<P256Sha256NoSlowHash>(&p256_real_tvs)?;
-        test_registration_response::<P256Sha256NoSlowHash>(&p256_real_tvs)?;
-        test_registration_upload::<P256Sha256NoSlowHash>(&p256_real_tvs)?;
-        test_ke1::<P256Sha256NoSlowHash>(&p256_real_tvs)?;
-        test_ke2::<P256Sha256NoSlowHash>(&p256_real_tvs)?;
-        test_ke3::<P256Sha256NoSlowHash>(&p256_real_tvs)?;
-        test_server_login_finish::<P256Sha256NoSlowHash>(&p256_real_tvs)?;
-        test_fake_vectors::<P256Sha256NoSlowHash>(&p256_fake_tvs)?;
-    }
+    test_registration_request::<P256Sha256NoSlowHash>(&p256_real_tvs)?;
+    test_registration_response::<P256Sha256NoSlowHash>(&p256_real_tvs)?;
+    test_registration_upload::<P256Sha256NoSlowHash>(&p256_real_tvs)?;
+    test_ke1::<P256Sha256NoSlowHash>(&p256_real_tvs)?;
+    test_ke2::<P256Sha256NoSlowHash>(&p256_real_tvs)?;
+    test_ke3::<P256Sha256NoSlowHash>(&p256_real_tvs)?;
+    test_server_login_finish::<P256Sha256NoSlowHash>(&p256_real_tvs)?;
+    test_fake_vectors::<P256Sha256NoSlowHash>(&p256_fake_tvs)?;
 
     Ok(())
 }
@@ -261,9 +288,12 @@ fn test_registration_request<CS: CipherSuite>(
     tvs: &[OpaqueTestVectorParameters],
 ) -> Result<(), ProtocolError>
 where
-    <CS::Hash as CoreProxy>::Core: ProxyHash,
-    <<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
-    Le<<<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
+    <OprfHash<CS> as OutputSizeUser>::OutputSize:
+        IsLess<U256> + IsLessOrEqual<<OprfHash<CS> as BlockSizeUser>::BlockSize>,
+    OprfHash<CS>: Hash,
+    <OprfHash<CS> as CoreProxy>::Core: ProxyHash,
+    <<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
+    Le<<<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
 {
     for parameters in tvs {
         let mut rng = CycleRng::new(parameters.blind_registration.to_vec());
@@ -281,11 +311,14 @@ fn test_registration_response<CS: CipherSuite>(
     tvs: &[OpaqueTestVectorParameters],
 ) -> Result<(), ProtocolError>
 where
-    <CS::Hash as CoreProxy>::Core: ProxyHash,
-    <<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
-    Le<<<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
+    <OprfHash<CS> as OutputSizeUser>::OutputSize:
+        IsLess<U256> + IsLessOrEqual<<OprfHash<CS> as BlockSizeUser>::BlockSize>,
+    OprfHash<CS>: Hash,
+    <OprfHash<CS> as CoreProxy>::Core: ProxyHash,
+    <<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
+    Le<<<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
     // RegistrationResponse: KgPk + KePk
-    <CS::OprfGroup as Group>::ElemLen: Add<<CS::KeGroup as KeGroup>::PkLen>,
+    <OprfGroup<CS> as Group>::ElemLen: Add<<CS::KeGroup as KeGroup>::PkLen>,
     RegistrationResponseLen<CS>: ArrayLength<u8>,
 {
     for parameters in tvs {
@@ -318,15 +351,18 @@ fn test_registration_upload<CS: CipherSuite>(
     tvs: &[OpaqueTestVectorParameters],
 ) -> Result<(), ProtocolError>
 where
-    <CS::Hash as CoreProxy>::Core: ProxyHash,
-    <<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
-    Le<<<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
+    <OprfHash<CS> as OutputSizeUser>::OutputSize:
+        IsLess<U256> + IsLessOrEqual<<OprfHash<CS> as BlockSizeUser>::BlockSize>,
+    OprfHash<CS>: Hash,
+    <OprfHash<CS> as CoreProxy>::Core: ProxyHash,
+    <<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
+    Le<<<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
     // Envelope: Nonce + Hash
-    NonceLen: Add<OutputSize<CS::Hash>>,
+    NonceLen: Add<OutputSize<OprfHash<CS>>>,
     EnvelopeLen<CS>: ArrayLength<u8>,
     // RegistrationUpload: (KePk + Hash) + Envelope
-    <CS::KeGroup as KeGroup>::PkLen: Add<OutputSize<CS::Hash>>,
-    Sum<<CS::KeGroup as KeGroup>::PkLen, OutputSize<CS::Hash>>:
+    <CS::KeGroup as KeGroup>::PkLen: Add<OutputSize<OprfHash<CS>>>,
+    Sum<<CS::KeGroup as KeGroup>::PkLen, OutputSize<OprfHash<CS>>>:
         ArrayLength<u8> + Add<EnvelopeLen<CS>>,
     RegistrationUploadLen<CS>: ArrayLength<u8>,
 {
@@ -362,7 +398,7 @@ where
         );
         assert_eq!(
             hex::encode(&parameters.export_key),
-            hex::encode(result.export_key.to_vec())
+            hex::encode(result.export_key)
         );
     }
 
@@ -371,11 +407,14 @@ where
 
 fn test_ke1<CS: CipherSuite>(tvs: &[OpaqueTestVectorParameters]) -> Result<(), ProtocolError>
 where
-    <CS::Hash as CoreProxy>::Core: ProxyHash,
-    <<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
-    Le<<<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
+    <OprfHash<CS> as OutputSizeUser>::OutputSize:
+        IsLess<U256> + IsLessOrEqual<<OprfHash<CS> as BlockSizeUser>::BlockSize>,
+    OprfHash<CS>: Hash,
+    <OprfHash<CS> as CoreProxy>::Core: ProxyHash,
+    <<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
+    Le<<<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
     // CredentialRequest: KgPk + Ke1Message
-    <CS::OprfGroup as Group>::ElemLen: Add<Ke1MessageLen<CS>>,
+    <OprfGroup<CS> as Group>::ElemLen: Add<Ke1MessageLen<CS>>,
     CredentialRequestLen<CS>: ArrayLength<u8>,
 {
     for parameters in tvs {
@@ -404,29 +443,32 @@ where
 
 fn test_ke2<CS: CipherSuite>(tvs: &[OpaqueTestVectorParameters]) -> Result<(), ProtocolError>
 where
-    <CS::Hash as CoreProxy>::Core: ProxyHash,
-    <<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
-    Le<<<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
+    <OprfHash<CS> as OutputSizeUser>::OutputSize:
+        IsLess<U256> + IsLessOrEqual<<OprfHash<CS> as BlockSizeUser>::BlockSize>,
+    OprfHash<CS>: Hash,
+    <OprfHash<CS> as CoreProxy>::Core: ProxyHash,
+    <<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
+    Le<<<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
     // Envelope: Nonce + Hash
-    NonceLen: Add<OutputSize<CS::Hash>>,
+    NonceLen: Add<OutputSize<OprfHash<CS>>>,
     EnvelopeLen<CS>: ArrayLength<u8>,
     // RegistrationUpload: (KePk + Hash) + Envelope
-    <CS::KeGroup as KeGroup>::PkLen: Add<OutputSize<CS::Hash>>,
-    Sum<<CS::KeGroup as KeGroup>::PkLen, OutputSize<CS::Hash>>:
+    <CS::KeGroup as KeGroup>::PkLen: Add<OutputSize<OprfHash<CS>>>,
+    Sum<<CS::KeGroup as KeGroup>::PkLen, OutputSize<OprfHash<CS>>>:
         ArrayLength<u8> + Add<EnvelopeLen<CS>>,
     RegistrationUploadLen<CS>: ArrayLength<u8>,
     // ServerRegistration = RegistrationUpload
     // MaskedResponse: (Nonce + Hash) + KePk
-    NonceLen: Add<OutputSize<CS::Hash>>,
-    Sum<NonceLen, OutputSize<CS::Hash>>: ArrayLength<u8> + Add<<CS::KeGroup as KeGroup>::PkLen>,
+    NonceLen: Add<OutputSize<OprfHash<CS>>>,
+    Sum<NonceLen, OutputSize<OprfHash<CS>>>: ArrayLength<u8> + Add<<CS::KeGroup as KeGroup>::PkLen>,
     MaskedResponseLen<CS>: ArrayLength<u8>,
     // CredentialResponseWithoutKeLen: (KgPk + Nonce) + MaskedResponse
-    <CS::OprfGroup as Group>::ElemLen: Add<NonceLen>,
-    Sum<<CS::OprfGroup as Group>::ElemLen, NonceLen>: ArrayLength<u8> + Add<MaskedResponseLen<CS>>,
+    <OprfGroup<CS> as Group>::ElemLen: Add<NonceLen>,
+    Sum<<OprfGroup<CS> as Group>::ElemLen, NonceLen>: ArrayLength<u8> + Add<MaskedResponseLen<CS>>,
     CredentialResponseWithoutKeLen<CS>: ArrayLength<u8>,
     // MaskedResponse: (Nonce + Hash) + KePk
-    NonceLen: Add<OutputSize<CS::Hash>>,
-    Sum<NonceLen, OutputSize<CS::Hash>>: ArrayLength<u8> + Add<<CS::KeGroup as KeGroup>::PkLen>,
+    NonceLen: Add<OutputSize<OprfHash<CS>>>,
+    Sum<NonceLen, OutputSize<OprfHash<CS>>>: ArrayLength<u8> + Add<<CS::KeGroup as KeGroup>::PkLen>,
     MaskedResponseLen<CS>: ArrayLength<u8>,
     // CredentialResponse: CredentialResponseWithoutKeLen + Ke2Message
     CredentialResponseWithoutKeLen<CS>: Add<Ke2MessageLen<CS>>,
@@ -489,12 +531,15 @@ where
 
 fn test_ke3<CS: CipherSuite>(tvs: &[OpaqueTestVectorParameters]) -> Result<(), ProtocolError>
 where
-    <CS::Hash as CoreProxy>::Core: ProxyHash,
-    <<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
-    Le<<<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
+    <OprfHash<CS> as OutputSizeUser>::OutputSize:
+        IsLess<U256> + IsLessOrEqual<<OprfHash<CS> as BlockSizeUser>::BlockSize>,
+    OprfHash<CS>: Hash,
+    <OprfHash<CS> as CoreProxy>::Core: ProxyHash,
+    <<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
+    Le<<<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
     // MaskedResponse: (Nonce + Hash) + KePk
-    NonceLen: Add<OutputSize<CS::Hash>>,
-    Sum<NonceLen, OutputSize<CS::Hash>>: ArrayLength<u8> + Add<<CS::KeGroup as KeGroup>::PkLen>,
+    NonceLen: Add<OutputSize<OprfHash<CS>>>,
+    Sum<NonceLen, OutputSize<OprfHash<CS>>>: ArrayLength<u8> + Add<<CS::KeGroup as KeGroup>::PkLen>,
     MaskedResponseLen<CS>: ArrayLength<u8>,
 {
     for parameters in tvs {
@@ -549,21 +594,24 @@ fn test_server_login_finish<CS: CipherSuite>(
     tvs: &[OpaqueTestVectorParameters],
 ) -> Result<(), ProtocolError>
 where
-    <CS::Hash as CoreProxy>::Core: ProxyHash,
-    <<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
-    Le<<<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
+    <OprfHash<CS> as OutputSizeUser>::OutputSize:
+        IsLess<U256> + IsLessOrEqual<<OprfHash<CS> as BlockSizeUser>::BlockSize>,
+    OprfHash<CS>: Hash,
+    <OprfHash<CS> as CoreProxy>::Core: ProxyHash,
+    <<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
+    Le<<<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
     // Envelope: Nonce + Hash
-    NonceLen: Add<OutputSize<CS::Hash>>,
+    NonceLen: Add<OutputSize<OprfHash<CS>>>,
     EnvelopeLen<CS>: ArrayLength<u8>,
     // RegistrationUpload: (KePk + Hash) + Envelope
-    <CS::KeGroup as KeGroup>::PkLen: Add<OutputSize<CS::Hash>>,
-    Sum<<CS::KeGroup as KeGroup>::PkLen, OutputSize<CS::Hash>>:
+    <CS::KeGroup as KeGroup>::PkLen: Add<OutputSize<OprfHash<CS>>>,
+    Sum<<CS::KeGroup as KeGroup>::PkLen, OutputSize<OprfHash<CS>>>:
         ArrayLength<u8> + Add<EnvelopeLen<CS>>,
     RegistrationUploadLen<CS>: ArrayLength<u8>,
     // ServerRegistration = RegistrationUpload
     // MaskedResponse: (Nonce + Hash) + KePk
-    NonceLen: Add<OutputSize<CS::Hash>>,
-    Sum<NonceLen, OutputSize<CS::Hash>>: ArrayLength<u8> + Add<<CS::KeGroup as KeGroup>::PkLen>,
+    NonceLen: Add<OutputSize<OprfHash<CS>>>,
+    Sum<NonceLen, OutputSize<OprfHash<CS>>>: ArrayLength<u8> + Add<<CS::KeGroup as KeGroup>::PkLen>,
     MaskedResponseLen<CS>: ArrayLength<u8>,
 {
     for parameters in tvs {
@@ -618,16 +666,19 @@ fn test_fake_vectors<CS: CipherSuite>(
     tvs: &[OpaqueTestVectorParameters],
 ) -> Result<(), ProtocolError>
 where
-    <CS::Hash as CoreProxy>::Core: ProxyHash,
-    <<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
-    Le<<<CS::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
+    <OprfHash<CS> as OutputSizeUser>::OutputSize:
+        IsLess<U256> + IsLessOrEqual<<OprfHash<CS> as BlockSizeUser>::BlockSize>,
+    OprfHash<CS>: Hash,
+    <OprfHash<CS> as CoreProxy>::Core: ProxyHash,
+    <<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
+    Le<<<OprfHash<CS> as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
     // MaskedResponse: (Nonce + Hash) + KePk
-    NonceLen: Add<OutputSize<CS::Hash>>,
-    Sum<NonceLen, OutputSize<CS::Hash>>: ArrayLength<u8> + Add<<CS::KeGroup as KeGroup>::PkLen>,
+    NonceLen: Add<OutputSize<OprfHash<CS>>>,
+    Sum<NonceLen, OutputSize<OprfHash<CS>>>: ArrayLength<u8> + Add<<CS::KeGroup as KeGroup>::PkLen>,
     MaskedResponseLen<CS>: ArrayLength<u8>,
     // CredentialResponseWithoutKeLen: (KgPk + Nonce) + MaskedResponse
-    <CS::OprfGroup as Group>::ElemLen: Add<NonceLen>,
-    Sum<<CS::OprfGroup as Group>::ElemLen, NonceLen>: ArrayLength<u8> + Add<MaskedResponseLen<CS>>,
+    <OprfGroup<CS> as Group>::ElemLen: Add<NonceLen>,
+    Sum<<OprfGroup<CS> as Group>::ElemLen, NonceLen>: ArrayLength<u8> + Add<MaskedResponseLen<CS>>,
     CredentialResponseWithoutKeLen<CS>: ArrayLength<u8>,
     // CredentialResponse: CredentialResponseWithoutKeLen + Ke2Message
     CredentialResponseWithoutKeLen<CS>: Add<Ke2MessageLen<CS>>,