Repository: novifinancial/opaque-ke
Pull request #50 (2020-09-19 23:26:29)

Simplifications and Normalizations
This PR rationalizes some of our (complex) uses of generics and types:
- TripleDH converts back-and-forth to and from `GenericArray` both on storage (in the `KE[1-4](State|Message)` types) and on entering / exiting the KE (`(to|from)_bytes`). The later is justified, but the former is not and this PR corrects it.
- We transform the hidden panics from `foo.copy_from_slice(x)` where `foo` is a `[u8; N]` into explicit `TryInto` instances (followed by expect + a justification).
- `LoginSecondMessage` now uses the `CipherSuite` instance rather than 4 different types.
- the `KeyExchange` trait now uses a single `KeyFormat` parameter for its 3 functions (just like `Hash`) instead of 3 generic functions (for which having distinct instantiations would make no sense).
- `Hash` defined two parameters `OutputSize` and `BlockSize`, the later unused, and the former without relationship with the one defined by the required supertrait of `FixedOutput` (hence introducing errors), we eliminate them.
- `sized_bytes_using_constant_and_try_from` was unused and is removed.



Patch:
diff --git a/src/ciphersuite.rs b/src/ciphersuite.rs
index ad7b1ac..63f20dc 100644
--- a/src/ciphersuite.rs
+++ b/src/ciphersuite.rs
@@ -34,7 +34,7 @@ pub trait CipherSuite {
     /// A keypair type composed of public and private components
     type KeyFormat: KeyPair<Repr = Key> + PartialEq;
     /// A key exchange protocol
-    type KeyExchange: KeyExchange<Self::Hash>;
+    type KeyExchange: KeyExchange<Self::Hash, Self::KeyFormat>;
     /// The main hash function use (for HKDF computations and hashing transcripts)
     type Hash: Hash;
     /// A slow hashing function, typically used for password hashing
diff --git a/src/elligator/mod.rs b/src/elligator/mod.rs
index 2df11f7..164020d 100644
--- a/src/elligator/mod.rs
+++ b/src/elligator/mod.rs
@@ -58,6 +58,7 @@ pub fn hash_to_point(bytes: &[u8]) -> EdwardsPoint {
 #[cfg(test)]
 mod tests {
     use super::*;
+    use std::convert::TryInto;
 
     ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
     // Signal tests from                                                                                                              //
@@ -73,8 +74,8 @@ mod tests {
     #[test]
     fn elligator_correct() {
         let bytes: Vec<u8> = (0u8..32u8).collect();
-        let mut bits_in = [0u8; 32];
-        bits_in.copy_from_slice(&bytes);
+        let bits_in: [u8; 32] = (&bytes[..]).try_into().expect("Range invariant broken");
+
         let fe = FieldElement51::from_bytes(&bits_in);
         let eg = elligator_signal(&fe);
         assert_eq!(eg.to_bytes(), ELLIGATOR_CORRECT_OUTPUT);
diff --git a/src/group.rs b/src/group.rs
index 88fba28..7a4ed55 100644
--- a/src/group.rs
+++ b/src/group.rs
@@ -87,7 +87,7 @@ impl Group for RistrettoPoint {
     ) -> Result<Self, InternalPakeError> {
         CompressedRistretto::from_slice(element_bits)
             .decompress()
-            .ok_or_else(|| InternalPakeError::PointError)
+            .ok_or(InternalPakeError::PointError)
     }
     // serialization of a group element
     fn to_arr(&self) -> GenericArray<u8, Self::ElemLen> {
@@ -97,9 +97,12 @@ impl Group for RistrettoPoint {
 
     type UniformBytesLen = U64;
     fn hash_to_curve(uniform_bytes: &GenericArray<u8, Self::UniformBytesLen>) -> Self {
-        let mut bits = [0u8; 64];
-        bits.copy_from_slice(&uniform_bytes);
-
+        // https://caniuse.rs/features/array_gt_32_impls
+        let bits: [u8; 64] = {
+            let mut bytes = [0u8; 64];
+            bytes.copy_from_slice(uniform_bytes);
+            bytes
+        };
         RistrettoPoint::from_uniform_bytes(&bits)
     }
 }
@@ -132,7 +135,7 @@ impl Group for EdwardsPoint {
     ) -> Result<Self, InternalPakeError> {
         let point = CompressedEdwardsY::from_slice(element_bits)
             .decompress()
-            .ok_or_else(|| InternalPakeError::PointError)?;
+            .ok_or(InternalPakeError::PointError)?;
 
         if point.is_small_order() {
             return Err(InternalPakeError::SubGroupError);
@@ -155,6 +158,7 @@ impl Group for EdwardsPoint {
 mod tests {
     use super::*;
     use anyhow::{anyhow, Result};
+    use std::convert::TryInto;
 
     const EIGHT_TORSION: [[u8; 32]; 8] = [
         [
@@ -192,8 +196,9 @@ mod tests {
     ];
 
     fn deserialize_point(pt: &[u8]) -> Result<EdwardsPoint> {
-        let mut bytes = [0u8; 32];
-        bytes.copy_from_slice(&pt[..32]);
+        let bytes: [u8; 32] = (&pt[..32])
+            .try_into()
+            .expect("Slice pattern invariant broken");
 
         curve25519_dalek::edwards::CompressedEdwardsY(bytes)
             .decompress()
diff --git a/src/hash.rs b/src/hash.rs
index dfe6f9e..03a591e 100644
--- a/src/hash.rs
+++ b/src/hash.rs
@@ -4,19 +4,10 @@
 // LICENSE file in the root directory of this source tree.
 
 use digest::{BlockInput, FixedOutput, Reset, Update};
-use generic_array::ArrayLength;
 
 /// Trait inheriting the requirements from digest::Digest for compatibility with HKDF and HMAC
 // Associated types could be simplified when they are made as defaults:
 // https://github.com/rust-lang/rust/issues/29661
-pub trait Hash: Update + BlockInput + FixedOutput + Reset + Default + Clone {
-    /// The block size for the hash function
-    type BlockSize: ArrayLength<u8>;
-    /// The output size of the hash function
-    type OutputSize: ArrayLength<u8>;
-}
+pub trait Hash: Update + BlockInput + FixedOutput + Reset + Default + Clone {}
 
-impl<T: Update + BlockInput + FixedOutput + Reset + Default + Clone> Hash for T {
-    type BlockSize = T::BlockSize;
-    type OutputSize = T::OutputSize;
-}
+impl<T: Update + BlockInput + FixedOutput + Reset + Default + Clone> Hash for T {}
diff --git a/src/key_exchange/traits.rs b/src/key_exchange/traits.rs
index 543df0f..1786bec 100644
--- a/src/key_exchange/traits.rs
+++ b/src/key_exchange/traits.rs
@@ -12,19 +12,19 @@ use rand_core::{CryptoRng, RngCore};
 
 use std::convert::TryFrom;
 
-pub trait KeyExchange<D: Hash> {
+pub trait KeyExchange<D: Hash, KeyFormat: KeyPair<Repr = Key>> {
     type KE1State: TryFrom<Vec<u8>, Error = InternalPakeError> + ToBytes;
     type KE2State: TryFrom<Vec<u8>, Error = ProtocolError> + ToBytes;
     type KE1Message: TryFrom<Vec<u8>, Error = InternalPakeError> + ToBytes;
     type KE2Message: TryFrom<Vec<u8>, Error = ProtocolError> + ToBytes;
     type KE3Message: TryFrom<Vec<u8>, Error = ProtocolError> + ToBytes;
 
-    fn generate_ke1<R: RngCore + CryptoRng, KeyFormat: KeyPair<Repr = Key>>(
+    fn generate_ke1<R: RngCore + CryptoRng>(
         l1_component: Vec<u8>,
         rng: &mut R,
     ) -> Result<(Self::KE1State, Self::KE1Message), ProtocolError>;
 
-    fn generate_ke2<R: RngCore + CryptoRng, KeyFormat: KeyPair<Repr = Key>>(
+    fn generate_ke2<R: RngCore + CryptoRng>(
         rng: &mut R,
         l1_bytes: Vec<u8>,
         l2_bytes: Vec<u8>,
@@ -33,7 +33,7 @@ pub trait KeyExchange<D: Hash> {
         server_s_sk: KeyFormat::Repr,
     ) -> Result<(Self::KE2State, Self::KE2Message), ProtocolError>;
 
-    fn generate_ke3<KeyFormat: KeyPair<Repr = Key>>(
+    fn generate_ke3(
         l2_component: Vec<u8>,
         ke2_message: Self::KE2Message,
         ke1_state: &Self::KE1State,
diff --git a/src/key_exchange/tripledh.rs b/src/key_exchange/tripledh.rs
index 138c69f..ad4ec97 100644
--- a/src/key_exchange/tripledh.rs
+++ b/src/key_exchange/tripledh.rs
@@ -9,12 +9,11 @@ use crate::{
     hash::Hash,
     key_exchange::traits::{KeyExchange, ToBytes},
     keypair::{Key, KeyPair, SizedBytes},
-    sized_bytes_using_constant_and_try_from,
 };
-use digest::Digest;
+use digest::{Digest, FixedOutput};
 use generic_array::{
-    typenum::{U64, U96},
-    GenericArray,
+    typenum::{Unsigned, U32},
+    ArrayLength, GenericArray,
 };
 use hkdf::Hkdf;
 use hmac::{Hmac, Mac, NewMac};
@@ -24,31 +23,34 @@ use std::convert::TryFrom;
 
 const KEY_LEN: usize = 32;
 pub(crate) const NONCE_LEN: usize = 32;
+pub(crate) type NonceLen = U32;
 const KE1_STATE_LEN: usize = KEY_LEN + KEY_LEN + NONCE_LEN;
-const KE2_MESSAGE_LEN: usize = NONCE_LEN + 2 * KEY_LEN;
 
 static STR_3DH: &[u8] = b"3DH keys";
 
 /// The Triple Diffie-Hellman key exchange implementation
 pub struct TripleDH;
 
-impl<D: Hash> KeyExchange<D> for TripleDH {
-    type KE1State = KE1State;
-    type KE2State = KE2State;
+impl<D: Hash, KeyFormat: KeyPair<Repr = Key>> KeyExchange<D, KeyFormat> for TripleDH {
+    type KE1State = KE1State<<D as FixedOutput>::OutputSize>;
+    type KE2State = KE2State<<D as FixedOutput>::OutputSize>;
     type KE1Message = KE1Message;
-    type KE2Message = KE2Message;
-    type KE3Message = KE3Message;
+    type KE2Message = KE2Message<<D as FixedOutput>::OutputSize>;
+    type KE3Message = KE3Message<<D as FixedOutput>::OutputSize>;
 
-    fn generate_ke1<R: RngCore + CryptoRng, KeyFormat: KeyPair<Repr = Key>>(
+    fn generate_ke1<R: RngCore + CryptoRng>(
         l1_component: Vec<u8>,
         rng: &mut R,
     ) -> Result<(Self::KE1State, Self::KE1Message), ProtocolError> {
         let client_e_kp = KeyFormat::generate_random(rng)?;
-        let mut client_nonce = [0u8; NONCE_LEN];
-        rng.fill_bytes(&mut client_nonce);
+        let client_nonce: GenericArray<u8, NonceLen> = {
+            let mut client_nonce_bytes = [0u8; NONCE_LEN];
+            rng.fill_bytes(&mut client_nonce_bytes);
+            client_nonce_bytes.into()
+        };
 
         let ke1_message = KE1Message {
-            client_nonce: client_nonce.to_vec(),
+            client_nonce,
             client_e_pk: client_e_kp.public().clone(),
         };
 
@@ -60,14 +62,14 @@ impl<D: Hash> KeyExchange<D> for TripleDH {
         Ok((
             KE1State {
                 client_e_sk: client_e_kp.private().clone(),
-                client_nonce: client_nonce.to_vec(),
-                hashed_l1: hashed_l1.to_vec(),
+                client_nonce,
+                hashed_l1,
             },
             ke1_message,
         ))
     }
 
-    fn generate_ke2<R: RngCore + CryptoRng, KeyFormat: KeyPair<Repr = Key>>(
+    fn generate_ke2<R: RngCore + CryptoRng>(
         rng: &mut R,
         l1_bytes: Vec<u8>,
         l2_bytes: Vec<u8>,
@@ -76,8 +78,11 @@ impl<D: Hash> KeyExchange<D> for TripleDH {
         server_s_sk: KeyFormat::Repr,
     ) -> Result<(Self::KE2State, Self::KE2Message), ProtocolError> {
         let server_e_kp = KeyFormat::generate_random(rng)?;
-        let mut server_nonce = [0u8; NONCE_LEN];
-        rng.fill_bytes(&mut server_nonce);
+        let server_nonce: GenericArray<u8, NonceLen> = {
+            let mut server_nonce_bytes = [0u8; NONCE_LEN];
+            rng.fill_bytes(&mut server_nonce_bytes);
+            server_nonce_bytes.into()
+        };
 
         let (shared_secret, km2, km3) = derive_3dh_keys::<KeyFormat, D>(
             TripleDHComponents {
@@ -115,19 +120,19 @@ impl<D: Hash> KeyExchange<D> for TripleDH {
 
         Ok((
             KE2State {
-                km3: km3.to_vec(),
-                hashed_transcript: hashed_transcript.to_vec(),
-                shared_secret: shared_secret.to_vec(),
+                km3,
+                hashed_transcript,
+                shared_secret,
             },
             KE2Message {
-                server_nonce: server_nonce.to_vec(),
+                server_nonce,
                 server_e_pk: server_e_kp.public().clone(),
-                mac: mac.finalize().into_bytes().to_vec(),
+                mac: mac.finalize().into_bytes(),
             },
         ))
     }
 
-    fn generate_ke3<KeyFormat: KeyPair<Repr = Key>>(
+    fn generate_ke3(
         l2_component: Vec<u8>,
         ke2_message: Self::KE2Message,
         ke1_state: &Self::KE1State,
@@ -165,7 +170,7 @@ impl<D: Hash> KeyExchange<D> for TripleDH {
             Hmac::<D>::new_varkey(&km2).map_err(|_| InternalPakeError::HmacError)?;
         server_mac.update(&hashed_transcript);
 
-        if ke2_message.mac != server_mac.finalize().into_bytes().to_vec() {
+        if ke2_message.mac != server_mac.finalize().into_bytes() {
             return Err(ProtocolError::VerificationError(
                 PakeError::KeyExchangeMacValidationError,
             ));
@@ -178,7 +183,7 @@ impl<D: Hash> KeyExchange<D> for TripleDH {
         Ok((
             shared_secret.to_vec(),
             KE3Message {
-                mac: client_mac.finalize().into_bytes().to_vec(),
+                mac: client_mac.finalize().into_bytes(),
             },
         ))
     }
@@ -191,7 +196,7 @@ impl<D: Hash> KeyExchange<D> for TripleDH {
             Hmac::<D>::new_varkey(&ke2_state.km3).map_err(|_| InternalPakeError::HmacError)?;
         client_mac.update(&ke2_state.hashed_transcript);
 
-        if ke3_message.mac != client_mac.finalize().into_bytes().to_vec() {
+        if ke3_message.mac != client_mac.finalize().into_bytes() {
             return Err(ProtocolError::VerificationError(
                 PakeError::KeyExchangeMacValidationError,
             ));
@@ -205,40 +210,46 @@ impl<D: Hash> KeyExchange<D> for TripleDH {
     }
 
     fn ke2_message_size() -> usize {
-        KE2_MESSAGE_LEN
+        NONCE_LEN + KEY_LEN + <<D as FixedOutput>::OutputSize as Unsigned>::to_usize()
     }
 }
 
 /// The client state produced after the first key exchange message
 #[derive(PartialEq, Eq)]
-pub struct KE1State {
+pub struct KE1State<HashLen: ArrayLength<u8>> {
     client_e_sk: Key,
-    client_nonce: Vec<u8>,
-    hashed_l1: Vec<u8>,
+    client_nonce: GenericArray<u8, NonceLen>,
+    hashed_l1: GenericArray<u8, HashLen>,
 }
 
 /// The first key exchange message
 #[derive(PartialEq, Eq)]
 pub struct KE1Message {
-    pub(crate) client_nonce: Vec<u8>,
+    pub(crate) client_nonce: GenericArray<u8, NonceLen>,
     pub(crate) client_e_pk: Key,
 }
 
-impl TryFrom<Vec<u8>> for KE1State {
+impl<HashLen: ArrayLength<u8>> TryFrom<Vec<u8>> for KE1State<HashLen> {
     type Error = InternalPakeError;
 
     fn try_from(bytes: Vec<u8>) -> Result<Self, Self::Error> {
-        let checked_bytes = check_slice_size(&bytes, KE1_STATE_LEN, "ke1_state")?;
+        let checked_bytes = check_slice_size(
+            &bytes,
+            KEY_LEN + NONCE_LEN + HashLen::to_usize(),
+            "ke1_state",
+        )?;
 
         Ok(Self {
             client_e_sk: Key::from_bytes(&checked_bytes[..KEY_LEN])?,
-            client_nonce: checked_bytes[KEY_LEN..KEY_LEN + NONCE_LEN].to_vec(),
-            hashed_l1: checked_bytes[KEY_LEN + NONCE_LEN..].to_vec(),
+            client_nonce: GenericArray::clone_from_slice(
+                &checked_bytes[KEY_LEN..KEY_LEN + NONCE_LEN],
+            ),
+            hashed_l1: GenericArray::clone_from_slice(&checked_bytes[KEY_LEN + NONCE_LEN..]),
         })
     }
 }
 
-impl ToBytes for KE1State {
+impl<HashLen: ArrayLength<u8>> ToBytes for KE1State<HashLen> {
     fn to_bytes(&self) -> Vec<u8> {
         let output: Vec<u8> = [
             &self.client_e_sk.to_arr(),
@@ -250,8 +261,6 @@ impl ToBytes for KE1State {
     }
 }
 
-sized_bytes_using_constant_and_try_from!(KE1State, U96);
-
 impl ToBytes for KE1Message {
     fn to_bytes(&self) -> Vec<u8> {
         [&self.client_nonce[..], &self.client_e_pk.to_arr()].concat()
@@ -266,29 +275,27 @@ impl TryFrom<Vec<u8>> for KE1Message {
             check_slice_size(&ke1_message_bytes, NONCE_LEN + KEY_LEN, "ke1_message")?;
 
         Ok(Self {
-            client_nonce: checked_bytes[..NONCE_LEN].to_vec(),
+            client_nonce: GenericArray::clone_from_slice(&checked_bytes[..NONCE_LEN]),
             client_e_pk: Key::from_bytes(&checked_bytes[NONCE_LEN..])?,
         })
     }
 }
 
-sized_bytes_using_constant_and_try_from!(KE1Message, U64);
-
 /// The server state produced after the second key exchange message
-pub struct KE2State {
-    km3: Vec<u8>,
-    hashed_transcript: Vec<u8>,
-    shared_secret: Vec<u8>,
+pub struct KE2State<HashLen: ArrayLength<u8>> {
+    km3: GenericArray<u8, HashLen>,
+    hashed_transcript: GenericArray<u8, HashLen>,
+    shared_secret: GenericArray<u8, HashLen>,
 }
 
 /// The second key exchange message
-pub struct KE2Message {
-    server_nonce: Vec<u8>,
+pub struct KE2Message<HashLen: ArrayLength<u8>> {
+    server_nonce: GenericArray<u8, NonceLen>,
     server_e_pk: Key,
-    mac: Vec<u8>,
+    mac: GenericArray<u8, HashLen>,
 }
 
-impl ToBytes for KE2State {
+impl<HashLen: ArrayLength<u8>> ToBytes for KE2State<HashLen> {
     fn to_bytes(&self) -> Vec<u8> {
         let output: Vec<u8> = [
             &self.km3[..],
@@ -300,21 +307,21 @@ impl ToBytes for KE2State {
     }
 }
 
-impl TryFrom<Vec<u8>> for KE2State {
+impl<HashLen: ArrayLength<u8>> TryFrom<Vec<u8>> for KE2State<HashLen> {
     type Error = ProtocolError;
 
     fn try_from(ke1_message_bytes: Vec<u8>) -> Result<Self, Self::Error> {
         let checked_bytes = check_slice_size(&ke1_message_bytes, 3 * KEY_LEN, "ke2_state")?;
 
         Ok(Self {
-            km3: checked_bytes[..KEY_LEN].to_vec(),
-            hashed_transcript: checked_bytes[KEY_LEN..2 * KEY_LEN].to_vec(),
-            shared_secret: checked_bytes[2 * KEY_LEN..].to_vec(),
+            km3: GenericArray::clone_from_slice(&checked_bytes[..KEY_LEN]),
+            hashed_transcript: GenericArray::clone_from_slice(&checked_bytes[KEY_LEN..2 * KEY_LEN]),
+            shared_secret: GenericArray::clone_from_slice(&checked_bytes[2 * KEY_LEN..]),
         })
     }
 }
 
-impl ToBytes for KE2Message {
+impl<HashLen: ArrayLength<u8>> ToBytes for KE2Message<HashLen> {
     fn to_bytes(&self) -> Vec<u8> {
         let output: Vec<u8> = [
             &self.server_nonce[..],
@@ -326,16 +333,17 @@ impl ToBytes for KE2Message {
     }
 }
 
-impl TryFrom<Vec<u8>> for KE2Message {
+impl<HashLen: ArrayLength<u8>> TryFrom<Vec<u8>> for KE2Message<HashLen> {
     type Error = ProtocolError;
 
     fn try_from(ke2_message_bytes: Vec<u8>) -> Result<Self, Self::Error> {
-        let checked_bytes = check_slice_size(&ke2_message_bytes, KE2_MESSAGE_LEN, "ke2_message")?;
+        let ke2_message_len = NONCE_LEN + KEY_LEN + HashLen::to_usize();
+        let checked_bytes = check_slice_size(&ke2_message_bytes, ke2_message_len, "ke2_message")?;
 
         Ok(Self {
-            server_nonce: checked_bytes[..NONCE_LEN].to_vec(),
+            server_nonce: GenericArray::clone_from_slice(&checked_bytes[..NONCE_LEN]),
             server_e_pk: Key::from_bytes(&checked_bytes[NONCE_LEN..NONCE_LEN + KEY_LEN])?,
-            mac: checked_bytes[NONCE_LEN + KEY_LEN..].to_vec(),
+            mac: GenericArray::clone_from_slice(&checked_bytes[NONCE_LEN + KEY_LEN..]),
         })
     }
 }
@@ -352,17 +360,17 @@ struct TripleDHComponents {
 
 // Consists of a shared secret, followed by two mac keys
 type TripleDHDerivationResult<D> = (
-    GenericArray<u8, <D as Hash>::OutputSize>,
-    GenericArray<u8, <D as Hash>::OutputSize>,
-    GenericArray<u8, <D as Hash>::OutputSize>,
+    GenericArray<u8, <D as FixedOutput>::OutputSize>,
+    GenericArray<u8, <D as FixedOutput>::OutputSize>,
+    GenericArray<u8, <D as FixedOutput>::OutputSize>,
 );
 
 // Internal function which takes the public and private components of the client and server keypairs, along
 // with some auxiliary metadata, to produce the shared secret and two MAC keys
 fn derive_3dh_keys<KeyFormat: KeyPair<Repr = Key>, D: Hash>(
     dh: TripleDHComponents,
-    client_nonce: &[u8],
-    server_nonce: &[u8],
+    client_nonce: &GenericArray<u8, NonceLen>,
+    server_nonce: &GenericArray<u8, NonceLen>,
     client_s_pk: KeyFormat::Repr,
     server_s_pk: KeyFormat::Repr,
 ) -> Result<TripleDHDerivationResult<D>, ProtocolError> {
@@ -395,24 +403,24 @@ fn derive_3dh_keys<KeyFormat: KeyPair<Repr = Key>, D: Hash>(
 }
 
 /// The third key exchange message
-pub struct KE3Message {
-    mac: Vec<u8>,
+pub struct KE3Message<HashLen: ArrayLength<u8>> {
+    mac: GenericArray<u8, HashLen>,
 }
 
-impl ToBytes for KE3Message {
+impl<HashLen: ArrayLength<u8>> ToBytes for KE3Message<HashLen> {
     fn to_bytes(&self) -> Vec<u8> {
-        self.mac.clone()
+        self.mac.to_vec()
     }
 }
 
-impl TryFrom<Vec<u8>> for KE3Message {
+impl<HashLen: ArrayLength<u8>> TryFrom<Vec<u8>> for KE3Message<HashLen> {
     type Error = ProtocolError;
 
     fn try_from(bytes: Vec<u8>) -> Result<Self, Self::Error> {
         let checked_bytes = check_slice_size(&bytes, KEY_LEN, "ke3_message")?;
 
         Ok(Self {
-            mac: checked_bytes.to_vec(),
+            mac: GenericArray::clone_from_slice(&checked_bytes),
         })
     }
 }
diff --git a/src/keypair.rs b/src/keypair.rs
index d68db58..0c6bd8b 100644
--- a/src/keypair.rs
+++ b/src/keypair.rs
@@ -16,6 +16,7 @@ use proptest::prelude::*;
 #[cfg(test)]
 use rand::{rngs::StdRng, SeedableRng};
 use rand_core::{CryptoRng, RngCore};
+use std::convert::TryInto;
 use std::fmt::Debug;
 use x25519_dalek::{PublicKey, StaticSecret};
 
@@ -91,35 +92,6 @@ trait KeyPairExt: KeyPair + Debug {
 #[cfg(test)]
 impl<KP> KeyPairExt for KP where KP: KeyPair + Debug {}
 
-/// This assumes you have defined:
-/// - an `impl TryFrom<&[u8b], Error = InternalPakeError>` for a non-generic `T`
-/// - an `fn to_bytes(&self) -> Vec<u8>` in an `impl T` block
-/// and it both of the above to produce a sensible SizedBytes implementation
-///
-/// Because SizedBytes has a strong notion of size, and TryFrom/to_bytes does
-/// not, it's better to use the macro below rather than this one, where possible.
-#[macro_export]
-macro_rules! sized_bytes_using_constant_and_try_from {
-    ($sized_type: ident, $len: ident) => {
-        impl SizedBytes for $sized_type {
-            type Len = $len;
-
-            fn to_arr(&self) -> generic_array::GenericArray<u8, Self::Len> {
-                generic_array::GenericArray::clone_from_slice(&self.to_bytes())
-            }
-
-            fn from_bytes(bytes: &[u8]) -> Result<Self, InternalPakeError> {
-                let checked_bytes = check_slice_size(
-                    bytes,
-                    <Self::Len as generic_array::typenum::Unsigned>::to_usize(),
-                    "bytes",
-                )?;
-                std::convert::TryFrom::try_from(checked_bytes.to_vec())
-            }
-        }
-    };
-}
-
 /// This assumes you have defined a SizedBytes instance for a `T`, and defines:
 /// - an `impl TryFrom<&[u8b], Error = InternalPakeError>` for a non-generic `T`
 /// - an `fn to_bytes(&self) -> Vec<u8>` in an `impl T` block
@@ -245,15 +217,15 @@ impl KeyPair for X25519KeyPair {
     }
 
     fn public_from_private(secret: &Self::Repr) -> Self::Repr {
-        let mut secret_data = [0u8; 32];
-        secret_data.copy_from_slice(&secret.0[..]);
+        let secret_data: [u8; 32] = (&secret.0[..])
+            .try_into()
+            .expect("Keypair::Repr invariant broken");
         let base_data = ::x25519_dalek::X25519_BASEPOINT_BYTES;
         Key(::x25519_dalek::x25519(secret_data, base_data).to_vec())
     }
 
     fn check_public_key(key: Self::Repr) -> Result<Self::Repr, InternalPakeError> {
-        let mut key_bytes = [0u8; 32];
-        key_bytes.copy_from_slice(&key);
+        let key_bytes: [u8; 32] = (&key[..]).try_into().expect("Key invariant broken");
         let point = ::curve25519_dalek::montgomery::MontgomeryPoint(key_bytes)
             .to_edwards(1)
             .ok_or(InternalPakeError::PointError)?;
diff --git a/src/map_to_curve.rs b/src/map_to_curve.rs
index 3d8819c..a7667db 100644
--- a/src/map_to_curve.rs
+++ b/src/map_to_curve.rs
@@ -9,7 +9,6 @@
 use crate::group::Group;
 use curve25519_dalek::{edwards::EdwardsPoint, ristretto::RistrettoPoint};
 
-use generic_array::GenericArray;
 use hkdf::Hkdf;
 use sha2::{Sha256, Sha512};
 
@@ -22,13 +21,13 @@ pub trait GroupWithMapToCurve: Group {
 impl GroupWithMapToCurve for RistrettoPoint {
     fn map_to_curve(password: &[u8], pepper: Option<&[u8]>) -> Self {
         let (hashed_input, _) = Hkdf::<Sha512>::extract(pepper, password);
-        <Self as Group>::hash_to_curve(GenericArray::from_slice(&hashed_input))
+        <Self as Group>::hash_to_curve(&hashed_input)
     }
 }
 
 impl GroupWithMapToCurve for EdwardsPoint {
     fn map_to_curve(password: &[u8], pepper: Option<&[u8]>) -> Self {
         let (hashed_input, _) = Hkdf::<Sha256>::extract(pepper, password);
-        <Self as Group>::hash_to_curve(GenericArray::from_slice(&hashed_input))
+        <Self as Group>::hash_to_curve(&hashed_input)
     }
 }
diff --git a/src/opaque.rs b/src/opaque.rs
index d98eacb..39b2922 100644
--- a/src/opaque.rs
+++ b/src/opaque.rs
@@ -141,7 +141,7 @@ where
 pub struct LoginFirstMessage<CS: CipherSuite> {
     /// blinded password information
     alpha: CS::Group,
-    ke1_message: <CS::KeyExchange as KeyExchange<CS::Hash>>::KE1Message,
+    ke1_message: <CS::KeyExchange as KeyExchange<CS::Hash, CS::KeyFormat>>::KE1Message,
 }
 
 impl<CS: CipherSuite> TryFrom<&[u8]> for LoginFirstMessage<CS> {
@@ -163,9 +163,10 @@ impl<CS: CipherSuite> TryFrom<&[u8]> for LoginFirstMessage<CS> {
         let arr = GenericArray::from_slice(&checked_slice[..elem_len]);
         let alpha = CS::Group::from_element_slice(arr)?;
 
-        let ke1_message = <CS::KeyExchange as KeyExchange<CS::Hash>>::KE1Message::try_from(
-            checked_slice[elem_len..].to_vec(),
-        )?;
+        let ke1_message =
+            <CS::KeyExchange as KeyExchange<CS::Hash, CS::KeyFormat>>::KE1Message::try_from(
+                checked_slice[elem_len..].to_vec(),
+            )?;
         Ok(Self { alpha, ke1_message })
     }
 }
@@ -179,28 +180,15 @@ impl<CS: CipherSuite> LoginFirstMessage<CS> {
 
 /// The answer sent by the server to the user, upon reception of the
 /// login attempt.
-pub struct LoginSecondMessage<Grp, KeyFormat, KE, D>
-where
-    KeyFormat: KeyPair,
-    KE: KeyExchange<D>,
-    D: Hash,
-{
-    _key_format: PhantomData<KeyFormat>,
-    _key_exchange: PhantomData<KE>,
+pub struct LoginSecondMessage<CS: CipherSuite> {
     /// the server's oprf output
-    beta: Grp,
+    beta: CS::Group,
     /// the user's sealed information,
-    envelope: Envelope<D>,
-    ke2_message: KE::KE2Message,
+    envelope: Envelope<CS::Hash>,
+    ke2_message: <CS::KeyExchange as KeyExchange<CS::Hash, CS::KeyFormat>>::KE2Message,
 }
 
-impl<Grp, KeyFormat, KE, D> LoginSecondMessage<Grp, KeyFormat, KE, D>
-where
-    Grp: Group,
-    KeyFormat: KeyPair,
-    KE: KeyExchange<D>,
-    D: Hash,
-{
+impl<CS: CipherSuite> LoginSecondMessage<CS> {
     /// byte representation for the login response
     pub fn to_bytes(&self) -> Vec<u8> {
         [
@@ -212,19 +200,13 @@ where
     }
 }
 
-impl<Grp, KeyFormat, KE, D> TryFrom<&[u8]> for LoginSecondMessage<Grp, KeyFormat, KE, D>
-where
-    Grp: Group,
-    KeyFormat: KeyPair,
-    KE: KeyExchange<D>,
-    D: Hash,
-{
+impl<CS: CipherSuite> TryFrom<&[u8]> for LoginSecondMessage<CS> {
     type Error = ProtocolError;
     fn try_from(second_message_bytes: &[u8]) -> Result<Self, Self::Error> {
-        let key_len = <KeyFormat::Repr as SizedBytes>::Len::to_usize();
-        let envelope_size = key_len + Envelope::<D>::additional_size();
-        let elem_len = Grp::ElemLen::to_usize();
-        let ke2_message_size = KE::ke2_message_size();
+        let key_len = <<CS::KeyFormat as KeyPair>::Repr as SizedBytes>::Len::to_usize();
+        let envelope_size = key_len + Envelope::<CS::Hash>::additional_size();
+        let elem_len = <CS::Group as Group>::ElemLen::to_usize();
+        let ke2_message_size = CS::KeyExchange::ke2_message_size();
         let checked_slice = check_slice_size(
             second_message_bytes,
             elem_len + envelope_size + ke2_message_size,
@@ -235,17 +217,17 @@ where
         // correct subgroup
         let beta_bytes = &checked_slice[..elem_len];
         let arr = GenericArray::from_slice(beta_bytes);
-        let beta = Grp::from_element_slice(arr)?;
+        let beta = CS::Group::from_element_slice(arr)?;
 
         let envelope =
-            Envelope::<D>::from_bytes(&checked_slice[elem_len..elem_len + envelope_size])?;
+            Envelope::<CS::Hash>::from_bytes(&checked_slice[elem_len..elem_len + envelope_size])?;
 
         let ke2_message =
-            KE::KE2Message::try_from(checked_slice[elem_len + envelope_size..].to_vec())?;
+            <CS::KeyExchange as KeyExchange<CS::Hash, CS::KeyFormat>>::KE2Message::try_from(
+                checked_slice[elem_len + envelope_size..].to_vec(),
+            )?;
 
         Ok(Self {
-            _key_format: PhantomData,
-            _key_exchange: PhantomData,
             beta,
             envelope,
             ke2_message,
@@ -256,7 +238,7 @@ where
 /// The answer sent by the client to the server, upon reception of the
 /// sealed envelope
 pub struct LoginThirdMessage<CS: CipherSuite> {
-    ke3_message: <CS::KeyExchange as KeyExchange<CS::Hash>>::KE3Message,
+    ke3_message: <CS::KeyExchange as KeyExchange<CS::Hash, CS::KeyFormat>>::KE3Message,
 }
 
 impl<CS: CipherSuite> TryFrom<&[u8]> for LoginThirdMessage<CS> {
@@ -264,7 +246,9 @@ impl<CS: CipherSuite> TryFrom<&[u8]> for LoginThirdMessage<CS> {
 
     fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
         let ke3_message =
-            <CS::KeyExchange as KeyExchange<CS::Hash>>::KE3Message::try_from(bytes.to_vec())?;
+            <CS::KeyExchange as KeyExchange<CS::Hash, CS::KeyFormat>>::KE3Message::try_from(
+                bytes.to_vec(),
+            )?;
         Ok(Self { ke3_message })
     }
 }
@@ -533,14 +517,12 @@ where
     /// byte representation for the server's registration state
     pub fn to_bytes(&self) -> Vec<u8> {
         let mut output: Vec<u8> = CS::Group::scalar_as_bytes(&self.oprf_key).to_vec();
-        match &self.client_s_pk {
-            Some(v) => output.extend_from_slice(&v.to_arr()),
-            None => {}
-        };
-        match &self.envelope {
-            Some(v) => output.extend_from_slice(&v.to_bytes()),
-            None => {}
-        };
+        self.client_s_pk
+            .iter()
+            .for_each(|v| output.extend_from_slice(&v));
+        self.envelope
+            .iter()
+            .for_each(|v| output.extend_from_slice(&v.to_bytes()));
         output
     }
 
@@ -641,21 +623,20 @@ where
 
 /// The state elements the client holds to perform a login
 pub struct ClientLogin<CS: CipherSuite> {
-    /// A choice of the keypair type
-    _key_format: PhantomData<CS::KeyFormat>,
     /// A blinding factor, which is used to mask (and unmask) secret
     /// information before transmission
     blinding_factor: <CS::Group as Group>::Scalar,
     /// The user's password
     password: Vec<u8>,
-    ke1_state: <CS::KeyExchange as KeyExchange<CS::Hash>>::KE1State,
+    ke1_state: <CS::KeyExchange as KeyExchange<CS::Hash, CS::KeyFormat>>::KE1State,
 }
 
 impl<CS: CipherSuite> TryFrom<&[u8]> for ClientLogin<CS> {
     type Error = ProtocolError;
     fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
         let scalar_len = <CS::Group as Group>::ScalarLen::to_usize();
-        let ke1_state_size = <CS::KeyExchange as KeyExchange<CS::Hash>>::ke1_state_size();
+        let ke1_state_size =
+            <CS::KeyExchange as KeyExchange<CS::Hash, CS::KeyFormat>>::ke1_state_size();
 
         let min_expected_len = scalar_len + ke1_state_size;
         let checked_slice = (if bytes.len() <= min_expected_len {
@@ -670,12 +651,12 @@ impl<CS: CipherSuite> TryFrom<&[u8]> for ClientLogin<CS> {
 
         let blinding_factor_bytes = GenericArray::from_slice(&checked_slice[..scalar_len]);
         let blinding_factor = CS::Group::from_scalar_slice(blinding_factor_bytes)?;
-        let ke1_state = <CS::KeyExchange as KeyExchange<CS::Hash>>::KE1State::try_from(
-            checked_slice[scalar_len..scalar_len + ke1_state_size].to_vec(),
-        )?;
+        let ke1_state =
+            <CS::KeyExchange as KeyExchange<CS::Hash, CS::KeyFormat>>::KE1State::try_from(
+                checked_slice[scalar_len..scalar_len + ke1_state_size].to_vec(),
+            )?;
         let password = bytes[scalar_len + ke1_state_size..].to_vec();
         Ok(Self {
-            _key_format: PhantomData,
             blinding_factor,
             password,
             ke1_state,
@@ -737,15 +718,13 @@ impl<CS: CipherSuite> ClientLogin<CS> {
             blinding_factor,
         } = oprf::generate_oprf1::<R, CS::Group>(&password, pepper, rng)?;
 
-        let (ke1_state, ke1_message) =
-            CS::KeyExchange::generate_ke1::<_, CS::KeyFormat>(alpha.to_arr().to_vec(), rng)?;
+        let (ke1_state, ke1_message) = CS::KeyExchange::generate_ke1(alpha.to_arr().to_vec(), rng)?;
 
         let l1 = LoginFirstMessage { alpha, ke1_message };
 
         Ok((
             l1,
             Self {
-                _key_format: PhantomData,
                 blinding_factor,
                 password: password.to_vec(),
                 ke1_state,
@@ -790,7 +769,7 @@ impl<CS: CipherSuite> ClientLogin<CS> {
     /// ```
     pub fn finish<R: RngCore + CryptoRng>(
         self,
-        l2: LoginSecondMessage<CS::Group, CS::KeyFormat, CS::KeyExchange, CS::Hash>,
+        l2: LoginSecondMessage<CS>,
         server_s_pk: &<<CS as CipherSuite>::KeyFormat as KeyPair>::Repr,
         _client_e_sk_rng: &mut R,
     ) -> Result<ClientLoginFinishResult<CS>, ProtocolError> {
@@ -810,7 +789,7 @@ impl<CS: CipherSuite> ClientLogin<CS> {
                 err => PakeError::from(err),
             })?;
 
-        let (shared_secret, ke3_message) = CS::KeyExchange::generate_ke3::<CS::KeyFormat>(
+        let (shared_secret, ke3_message) = CS::KeyExchange::generate_ke3(
             l2_bytes,
             l2.ke2_message,
             &self.ke1_state,
@@ -828,7 +807,7 @@ impl<CS: CipherSuite> ClientLogin<CS> {
 
 /// The state elements the server holds to record a login
 pub struct ServerLogin<CS: CipherSuite> {
-    ke2_state: <CS::KeyExchange as KeyExchange<CS::Hash>>::KE2State,
+    ke2_state: <CS::KeyExchange as KeyExchange<CS::Hash, CS::KeyFormat>>::KE2State,
     _cs: PhantomData<CS>,
 }
 
@@ -837,22 +816,15 @@ impl<CS: CipherSuite> TryFrom<&[u8]> for ServerLogin<CS> {
     fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
         Ok(Self {
             _cs: PhantomData,
-            ke2_state: <CS::KeyExchange as KeyExchange<CS::Hash>>::KE2State::try_from(
-                bytes.to_vec(),
-            )?,
+            ke2_state:
+                <CS::KeyExchange as KeyExchange<CS::Hash, CS::KeyFormat>>::KE2State::try_from(
+                    bytes.to_vec(),
+                )?,
         })
     }
 }
 
-type ServerLoginStartResult<CS> = (
-    LoginSecondMessage<
-        <CS as CipherSuite>::Group,
-        <CS as CipherSuite>::KeyFormat,
-        <CS as CipherSuite>::KeyExchange,
-        <CS as CipherSuite>::Hash,
-    >,
-    ServerLogin<CS>,
-);
+type ServerLoginStartResult<CS> = (LoginSecondMessage<CS>, ServerLogin<CS>);
 
 impl<CS: CipherSuite> ServerLogin<CS> {
     /// byte representation for the server's login state
@@ -911,7 +883,7 @@ impl<CS: CipherSuite> ServerLogin<CS> {
 
         let l2_component: Vec<u8> = [&beta.to_arr()[..], &envelope.to_bytes()].concat();
 
-        let (ke2_state, ke2_message) = CS::KeyExchange::generate_ke2::<_, CS::KeyFormat>(
+        let (ke2_state, ke2_message) = CS::KeyExchange::generate_ke2(
             rng,
             l1_bytes.to_vec(),
             l2_component,
@@ -921,8 +893,6 @@ impl<CS: CipherSuite> ServerLogin<CS> {
         )?;
 
         let l2 = LoginSecondMessage {
-            _key_format: PhantomData,
-            _key_exchange: PhantomData,
             beta,
             envelope,
             ke2_message,
@@ -975,18 +945,20 @@ impl<CS: CipherSuite> ServerLogin<CS> {
     /// # Ok::<(), ProtocolError>(())
     /// ```
     pub fn finish(&self, message: LoginThirdMessage<CS>) -> Result<Vec<u8>, ProtocolError> {
-        <CS::KeyExchange as KeyExchange<CS::Hash>>::finish_ke(message.ke3_message, &self.ke2_state)
-            .map_err(|e| match e {
-                ProtocolError::VerificationError(PakeError::KeyExchangeMacValidationError) => {
-                    ProtocolError::VerificationError(PakeError::InvalidLoginError)
-                }
-                err => err,
-            })
+        <CS::KeyExchange as KeyExchange<CS::Hash, CS::KeyFormat>>::finish_ke(
+            message.ke3_message,
+            &self.ke2_state,
+        )
+        .map_err(|e| match e {
+            ProtocolError::VerificationError(PakeError::KeyExchangeMacValidationError) => {
+                ProtocolError::VerificationError(PakeError::InvalidLoginError)
+            }
+            err => err,
+        })
     }
 }
 
 // Helper functions
-
 fn get_password_derived_key<G: Group, SH: SlowHash<D>, D: Hash>(
     password: Vec<u8>,
     beta: G,


Test patch:
diff --git a/src/tests/opaque_ke_test.rs b/src/tests/opaque_ke_test.rs
index dc984a8..15f66ba 100644
--- a/src/tests/opaque_ke_test.rs
+++ b/src/tests/opaque_ke_test.rs
@@ -471,19 +471,15 @@ fn test_l3() -> Result<(), PakeError> {
     let parameters = populate_test_vectors(&serde_json::from_str(TEST_VECTOR).unwrap());
 
     let mut client_e_sk_rng = CycleRng::new(parameters.client_e_sk.to_vec());
-    let (l3, shared_secret, export_key_login) = ClientLogin::<X255193dhNoSlowHash>::try_from(
-        &parameters.client_login_state[..],
-    )
-    .unwrap()
-    .finish(
-        LoginSecondMessage::<EdwardsPoint, X25519KeyPair, TripleDH, sha2::Sha256>::try_from(
-            &parameters.l2[..],
-        )
-        .unwrap(),
-        &Key::try_from(&parameters.server_s_pk[..])?,
-        &mut client_e_sk_rng,
-    )
-    .unwrap();
+    let (l3, shared_secret, export_key_login) =
+        ClientLogin::<X255193dhNoSlowHash>::try_from(&parameters.client_login_state[..])
+            .unwrap()
+            .finish(
+                LoginSecondMessage::<X255193dhNoSlowHash>::try_from(&parameters.l2[..]).unwrap(),
+                &Key::try_from(&parameters.server_s_pk[..])?,
+                &mut client_e_sk_rng,
+            )
+            .unwrap();
 
     assert_eq!(
         hex::encode(&parameters.shared_secret),
@@ -558,10 +554,12 @@ fn test_complete_flow(
             hex::encode(login_export_key)
         );
     } else {
-        let res = match client_login_result {
-            Err(ProtocolError::VerificationError(PakeError::InvalidLoginError)) => true,
-            _ => false,
-        };
+        let res = matches!(
+            client_login_result,
+            Err(ProtocolError::VerificationError(
+                PakeError::InvalidLoginError
+            ))
+        );
         assert!(res);
     }
 
diff --git a/src/tests/serialization.rs b/src/tests/serialization.rs
index 47ead1e..9988901 100644
--- a/src/tests/serialization.rs
+++ b/src/tests/serialization.rs
@@ -167,8 +167,10 @@ fn login_first_message_roundtrip() {
     rng.fill_bytes(&mut client_nonce);
 
     let ke1m: Vec<u8> = [&client_nonce[..], &client_e_kp.public()].concat();
-    let reg =
-        <TripleDH as KeyExchange<sha2::Sha256>>::KE1Message::try_from(ke1m[..].to_vec()).unwrap();
+    let reg = <TripleDH as KeyExchange<sha2::Sha256, crate::keypair::X25519KeyPair>>::KE1Message::try_from(
+        ke1m[..].to_vec(),
+    )
+    .unwrap();
     let reg_bytes = reg.to_bytes();
     assert_eq!(reg_bytes, ke1m);
 }
@@ -197,7 +199,7 @@ proptest! {
 
     #[test]
     fn test_nocrash_login_second_message(bytes in vec(any::<u8>(), 0..500)) {
-        LoginSecondMessage::<RistrettoPoint, crate::keypair::X25519KeyPair, TripleDH, sha2::Sha512>::try_from(&bytes[..]).map_or(true, |_| true);
+        LoginSecondMessage::<Default>::try_from(&bytes[..]).map_or(true, |_| true);
     }
 
     #[test]