Repository: novifinancial/opaque-ke
Pull request #120 (2021-01-26 01:50:08)

Aligning test vectors with RFC generated ones
Aligns with the RFC generated test vectors, fixing a bunch of errors along the way


Patch:
diff --git a/src/envelope.rs b/src/envelope.rs
index f26a915..216b2d5 100644
--- a/src/envelope.rs
+++ b/src/envelope.rs
@@ -4,26 +4,22 @@
 // LICENSE file in the root directory of this source tree.
 
 use crate::{
-    errors::{InternalPakeError, PakeError, ProtocolError},
+    errors::{utils::check_slice_size_atleast, InternalPakeError, PakeError, ProtocolError},
     hash::Hash,
     serialization::{serialize, tokenize},
 };
 use digest::Digest;
-use generic_array::{
-    typenum::{Unsigned, U32},
-    GenericArray,
-};
+use generic_array::{typenum::Unsigned, GenericArray};
 use hkdf::Hkdf;
 use hmac::{Hmac, Mac, NewMac};
 use rand_core::{CryptoRng, RngCore};
 use std::convert::TryFrom;
 
 // Constant string used as salt for HKDF computation
-const STR_ENVU: &[u8] = b"EnvU";
-
-/// The length of the "export key" output by the client registration
-/// and login finish steps
-pub(crate) type ExportKeySize = U32;
+const STR_RWDU: &[u8] = b"rwdU";
+const STR_PAD: &[u8] = b"Pad";
+const STR_AUTH_KEY: &[u8] = b"AuthKey";
+const STR_EXPORT_KEY: &[u8] = b"ExportKey";
 
 const NONCE_LEN: usize = 32;
 
@@ -44,6 +40,51 @@ impl TryFrom<u8> for InnerEnvelopeMode {
     }
 }
 
+pub(crate) struct InnerEnvelope {
+    mode: InnerEnvelopeMode,
+    nonce: Vec<u8>,
+    ciphertext: Vec<u8>,
+}
+
+impl InnerEnvelope {
+    pub(crate) fn serialize(&self) -> Vec<u8> {
+        [
+            &[self.mode as u8],
+            &self.nonce[..],
+            &serialize(&self.ciphertext, 2)[..],
+        ]
+        .concat()
+    }
+
+    pub(crate) fn deserialize(input: &[u8]) -> Result<(Self, Vec<u8>), ProtocolError> {
+        if input.is_empty() {
+            return Err(ProtocolError::VerificationError(
+                PakeError::SerializationError,
+            ));
+        }
+        let mode = InnerEnvelopeMode::try_from(input[0])?;
+
+        let bytes = &input[1..];
+        if bytes.len() < NONCE_LEN {
+            return Err(ProtocolError::VerificationError(
+                PakeError::SerializationError,
+            ));
+        }
+
+        let nonce = &bytes[..NONCE_LEN];
+        let (ciphertext, remainder) = tokenize(&bytes[NONCE_LEN..], 2)?;
+
+        Ok((
+            Self {
+                mode,
+                nonce: nonce.to_vec(),
+                ciphertext,
+            },
+            remainder,
+        ))
+    }
+}
+
 /// This struct is an instantiation of the envelope as described in
 /// https://tools.ietf.org/html/draft-krawczyk-cfrg-opaque-06#section-4
 ///
@@ -55,21 +96,18 @@ impl TryFrom<u8> for InnerEnvelopeMode {
 /// an XOR-based approach without compromising on security, and to avoid
 /// the confusion around the implementation of an RKR-secure encryption.
 pub(crate) struct Envelope<D: Hash> {
-    mode: InnerEnvelopeMode,
-    nonce: Vec<u8>,
-    ciphertext: Vec<u8>,
-    auth_data: Vec<u8>,
+    inner_envelope: InnerEnvelope,
     hmac: GenericArray<u8, <D as Digest>::OutputSize>,
 }
 
-pub(crate) struct OpenedEnvelope {
+pub(crate) struct OpenedEnvelope<D: Hash> {
     pub(crate) client_s_sk: Vec<u8>,
-    pub(crate) export_key: GenericArray<u8, ExportKeySize>,
+    pub(crate) export_key: GenericArray<u8, <D as Digest>::OutputSize>,
 }
 
-pub(crate) struct OpenedInnerEnvelope {
+pub(crate) struct OpenedInnerEnvelope<D: Hash> {
     pub(crate) plaintext: Vec<u8>,
-    pub(crate) export_key: GenericArray<u8, ExportKeySize>,
+    pub(crate) export_key: GenericArray<u8, <D as Digest>::OutputSize>,
 }
 
 impl<D: Hash> Envelope<D> {
@@ -83,27 +121,11 @@ impl<D: Hash> Envelope<D> {
     }
 
     fn export_key_size() -> usize {
-        ExportKeySize::to_usize()
+        <D as Digest>::OutputSize::to_usize()
     }
 
     pub(crate) fn get_mode(&self) -> InnerEnvelopeMode {
-        self.mode
-    }
-
-    pub(crate) fn new(
-        mode: InnerEnvelopeMode,
-        nonce: Vec<u8>,
-        ciphertext: Vec<u8>,
-        auth_data: Vec<u8>,
-        hmac: GenericArray<u8, <D as Digest>::OutputSize>,
-    ) -> Self {
-        Self {
-            mode,
-            nonce,
-            ciphertext,
-            auth_data,
-            hmac,
-        }
+        self.inner_envelope.mode
     }
 
     /// The format of the output is:
@@ -123,44 +145,22 @@ impl<D: Hash> Envelope<D> {
     }
 
     pub(crate) fn serialize(&self) -> Vec<u8> {
-        [
-            &[self.mode as u8],
-            &self.nonce[..],
-            &serialize(&self.ciphertext, 2)[..],
-            &serialize(&self.auth_data, 2)[..],
-            &serialize(&self.hmac, 2)[..],
-        ]
-        .concat()
+        [&self.inner_envelope.serialize(), &self.hmac[..]].concat()
     }
 
     pub(crate) fn deserialize(input: &[u8]) -> Result<(Self, Vec<u8>), ProtocolError> {
-        if input.is_empty() {
-            return Err(ProtocolError::VerificationError(
-                PakeError::SerializationError,
-            ));
-        }
-        let mode = InnerEnvelopeMode::try_from(input[0])?;
+        let (inner_envelope, remainder) = InnerEnvelope::deserialize(input)?;
 
-        let bytes = &input[1..];
-        if bytes.len() < NONCE_LEN {
-            return Err(ProtocolError::VerificationError(
-                PakeError::SerializationError,
-            ));
-        }
+        let hmac_key_size = Self::hmac_key_size();
+        let hmac_and_remainder =
+            check_slice_size_atleast(&remainder, hmac_key_size, "hmac_key_size")?;
 
-        let nonce = &bytes[..NONCE_LEN];
-        let (ciphertext, remainder) = tokenize(&bytes[NONCE_LEN..], 2)?;
-        let (auth_data, remainder) = tokenize(&remainder, 2)?;
-        let (hmac, remainder) = tokenize(&remainder, 2)?;
         Ok((
-            Self::new(
-                mode,
-                nonce.to_vec(),
-                ciphertext,
-                auth_data,
-                GenericArray::clone_from_slice(&hmac[..]),
-            ),
-            remainder,
+            Self {
+                inner_envelope,
+                hmac: GenericArray::clone_from_slice(&hmac_and_remainder[..hmac_key_size]),
+            },
+            hmac_and_remainder[hmac_key_size..].to_vec(),
         ))
     }
 
@@ -170,7 +170,7 @@ impl<D: Hash> Envelope<D> {
         client_s_sk: &[u8],
         server_s_pk: &[u8],
         optional_ids: Option<(Vec<u8>, Vec<u8>)>,
-    ) -> Result<(Self, GenericArray<u8, ExportKeySize>), InternalPakeError> {
+    ) -> Result<(Self, GenericArray<u8, <D as Digest>::OutputSize>), InternalPakeError> {
         let plaintext = serialize(&client_s_sk, 2);
         let aad = construct_aad(server_s_pk, &optional_ids);
         Self::seal_raw(rng, key, &plaintext, &aad, mode_from_ids(&optional_ids))
@@ -184,17 +184,27 @@ impl<D: Hash> Envelope<D> {
         plaintext: &[u8],
         aad: &[u8],
         mode: InnerEnvelopeMode,
-    ) -> Result<(Self, GenericArray<u8, ExportKeySize>), InternalPakeError> {
+    ) -> Result<(Self, GenericArray<u8, <D as Digest>::OutputSize>), InternalPakeError> {
         let mut nonce = vec![0u8; NONCE_LEN];
         rng.fill_bytes(&mut nonce);
 
-        let h = Hkdf::<D>::new(Some(&nonce), &key);
-        let mut okm = vec![0u8; plaintext.len() + Self::hmac_key_size() + Self::export_key_size()];
-        h.expand(STR_ENVU, &mut okm)
+        let h = Hkdf::<D>::new(Some(STR_RWDU), &key);
+        let mut xor_key = vec![0u8; plaintext.len()];
+        let mut hmac_key = vec![0u8; Self::hmac_key_size()];
+        let mut export_key = vec![0u8; Self::export_key_size()];
+
+        h.expand(&[nonce.clone(), STR_PAD.to_vec()].concat(), &mut xor_key)
             .map_err(|_| InternalPakeError::HkdfError)?;
-        let xor_key = &okm[..plaintext.len()];
-        let hmac_key = &okm[plaintext.len()..plaintext.len() + Self::hmac_key_size()];
-        let export_key = &okm[plaintext.len() + Self::hmac_key_size()..];
+        h.expand(
+            &[nonce.clone(), STR_AUTH_KEY.to_vec()].concat(),
+            &mut hmac_key,
+        )
+        .map_err(|_| InternalPakeError::HkdfError)?;
+        h.expand(
+            &[nonce.clone(), STR_EXPORT_KEY.to_vec()].concat(),
+            &mut export_key,
+        )
+        .map_err(|_| InternalPakeError::HkdfError)?;
 
         let ciphertext: Vec<u8> = xor_key
             .iter()
@@ -202,21 +212,25 @@ impl<D: Hash> Envelope<D> {
             .map(|(&x1, &x2)| x1 ^ x2)
             .collect();
 
+        let inner_envelope = InnerEnvelope {
+            mode,
+            nonce,
+            ciphertext,
+        };
+
         let mut hmac =
             Hmac::<D>::new_varkey(&hmac_key).map_err(|_| InternalPakeError::HmacError)?;
-        hmac.update(&nonce);
-        hmac.update(&ciphertext);
+        hmac.update(&inner_envelope.serialize());
         hmac.update(&aad);
 
+        let hmac_bytes = hmac.finalize().into_bytes();
+
         Ok((
-            Self::new(
-                mode,
-                nonce,
-                ciphertext.to_vec(),
-                aad.to_vec(),
-                hmac.finalize().into_bytes(),
-            ),
-            *GenericArray::from_slice(&export_key),
+            Self {
+                inner_envelope,
+                hmac: hmac_bytes,
+            },
+            GenericArray::clone_from_slice(&export_key),
         ))
     }
 
@@ -225,9 +239,9 @@ impl<D: Hash> Envelope<D> {
         key: &[u8],
         server_s_pk: &[u8],
         optional_ids: &Option<(Vec<u8>, Vec<u8>)>,
-    ) -> Result<OpenedEnvelope, InternalPakeError> {
+    ) -> Result<OpenedEnvelope<D>, InternalPakeError> {
         // First, check that mode matches
-        if self.mode != mode_from_ids(optional_ids) {
+        if self.inner_envelope.mode != mode_from_ids(optional_ids) {
             return Err(InternalPakeError::IncompatibleEnvelopeModeError);
         }
 
@@ -253,20 +267,31 @@ impl<D: Hash> Envelope<D> {
         &self,
         key: &[u8],
         aad: &[u8],
-    ) -> Result<OpenedInnerEnvelope, InternalPakeError> {
-        let h = Hkdf::<D>::new(Some(&self.nonce), &key);
-        let mut okm =
-            vec![0u8; self.ciphertext.len() + Self::hmac_key_size() + Self::export_key_size()];
-        h.expand(STR_ENVU, &mut okm)
-            .map_err(|_| InternalPakeError::HkdfError)?;
-        let xor_key = &okm[..self.ciphertext.len()];
-        let hmac_key = &okm[self.ciphertext.len()..self.ciphertext.len() + Self::hmac_key_size()];
-        let export_key = &okm[self.ciphertext.len() + Self::hmac_key_size()..];
+    ) -> Result<OpenedInnerEnvelope<D>, InternalPakeError> {
+        let h = Hkdf::<D>::new(Some(STR_RWDU), &key);
+        let mut xor_key = vec![0u8; self.inner_envelope.ciphertext.len()];
+        let mut hmac_key = vec![0u8; Self::hmac_key_size()];
+        let mut export_key = vec![0u8; Self::export_key_size()];
+
+        h.expand(
+            &[self.inner_envelope.nonce.clone(), STR_PAD.to_vec()].concat(),
+            &mut xor_key,
+        )
+        .map_err(|_| InternalPakeError::HkdfError)?;
+        h.expand(
+            &[self.inner_envelope.nonce.clone(), STR_AUTH_KEY.to_vec()].concat(),
+            &mut hmac_key,
+        )
+        .map_err(|_| InternalPakeError::HkdfError)?;
+        h.expand(
+            &[self.inner_envelope.nonce.clone(), STR_EXPORT_KEY.to_vec()].concat(),
+            &mut export_key,
+        )
+        .map_err(|_| InternalPakeError::HkdfError)?;
 
         let mut hmac =
             Hmac::<D>::new_varkey(&hmac_key).map_err(|_| InternalPakeError::HmacError)?;
-        hmac.update(&self.nonce);
-        hmac.update(&self.ciphertext);
+        hmac.update(&self.inner_envelope.serialize());
         hmac.update(aad);
         if hmac.verify(&self.hmac).is_err() {
             return Err(InternalPakeError::SealOpenHmacError);
@@ -274,12 +299,14 @@ impl<D: Hash> Envelope<D> {
 
         let plaintext: Vec<u8> = xor_key
             .iter()
-            .zip(self.ciphertext.iter())
+            .zip(self.inner_envelope.ciphertext.iter())
             .map(|(&x1, &x2)| x1 ^ x2)
             .collect();
         Ok(OpenedInnerEnvelope {
             plaintext,
-            export_key: *GenericArray::from_slice(&export_key),
+            export_key: GenericArray::<u8, <D as Digest>::OutputSize>::clone_from_slice(
+                &export_key,
+            ),
         })
     }
 }
diff --git a/src/key_exchange/traits.rs b/src/key_exchange/traits.rs
index 26b0ac9..089b322 100644
--- a/src/key_exchange/traits.rs
+++ b/src/key_exchange/traits.rs
@@ -56,8 +56,6 @@ pub trait KeyExchange<D: Hash, G: Group> {
         ke2_state: &Self::KE2State,
     ) -> Result<Vec<u8>, ProtocolError>;
 
-    fn ke1_state_size() -> usize;
-
     fn ke2_message_size() -> usize;
 }
 
diff --git a/src/key_exchange/tripledh.rs b/src/key_exchange/tripledh.rs
index d434f1e..71e4ac1 100644
--- a/src/key_exchange/tripledh.rs
+++ b/src/key_exchange/tripledh.rs
@@ -44,14 +44,14 @@ static STR_OPAQUE: &[u8] = b"OPAQUE ";
 pub struct TripleDH;
 
 impl<D: Hash, G: Group> KeyExchange<D, G> for TripleDH {
-    type KE1State = KE1State<<D as FixedOutput>::OutputSize>;
+    type KE1State = KE1State;
     type KE2State = KE2State<<D as FixedOutput>::OutputSize>;
     type KE1Message = KE1Message;
     type KE2Message = KE2Message<<D as FixedOutput>::OutputSize>;
     type KE3Message = KE3Message<<D as FixedOutput>::OutputSize>;
 
     fn generate_ke1<R: RngCore + CryptoRng>(
-        l1_component: Vec<u8>,
+        alpha_bytes: Vec<u8>,
         info: Vec<u8>,
         rng: &mut R,
     ) -> Result<(Self::KE1State, Self::KE1Message), ProtocolError> {
@@ -68,16 +68,14 @@ impl<D: Hash, G: Group> KeyExchange<D, G> for TripleDH {
             client_e_pk: client_e_kp.public().clone(),
         };
 
-        let l1_data: Vec<u8> = [&l1_component[..], &ke1_message.to_bytes()].concat();
-        let mut hasher = D::new();
-        hasher.update(&l1_data);
-        let hashed_l1 = hasher.finalize();
+        // TODO: must match the serialization of a credential request, could be done more cleanly
+        let serialized_credential_request = [alpha_bytes, ke1_message.to_bytes()].concat();
 
         Ok((
             KE1State {
                 client_e_sk: client_e_kp.private().clone(),
                 client_nonce,
-                hashed_l1,
+                serialized_credential_request,
             },
             ke1_message,
         ))
@@ -86,7 +84,7 @@ impl<D: Hash, G: Group> KeyExchange<D, G> for TripleDH {
     #[allow(clippy::type_complexity)]
     fn generate_ke2<R: RngCore + CryptoRng>(
         rng: &mut R,
-        l1_bytes: Vec<u8>,
+        serialized_credential_request: Vec<u8>,
         l2_bytes: Vec<u8>,
         ke1_message: Self::KE1Message,
         client_s_pk: Key,
@@ -118,7 +116,7 @@ impl<D: Hash, G: Group> KeyExchange<D, G> for TripleDH {
         )?;
 
         // Compute encryption of e_info
-        let h = Hkdf::<D>::new(None, &ke2);
+        let h = Hkdf::<D>::from_prk(&ke2).map_err(|_| InternalPakeError::HkdfError)?;
         let mut encryption_pad = vec![0u8; e_info.len()];
         h.expand(STR_ENCRYPTION_PAD, &mut encryption_pad)
             .map_err(|_| InternalPakeError::HkdfError)?;
@@ -128,12 +126,8 @@ impl<D: Hash, G: Group> KeyExchange<D, G> for TripleDH {
             .map(|(&x1, &x2)| x1 ^ x2)
             .collect();
 
-        let mut hasher = D::new();
-        hasher.update(&l1_bytes);
-        let hashed_l1 = hasher.finalize();
-
         let transcript2: Vec<u8> = [
-            &hashed_l1[..],
+            &serialized_credential_request[..],
             &l2_bytes[..],
             &server_nonce[..],
             &server_e_kp.public().to_arr(),
@@ -152,7 +146,6 @@ impl<D: Hash, G: Group> KeyExchange<D, G> for TripleDH {
 
         let mut hasher3 = D::new();
         hasher3.update(&transcript2);
-        hasher3.update(mac.clone());
         let hashed_transcript = hasher3.finalize();
 
         Ok((
@@ -197,7 +190,7 @@ impl<D: Hash, G: Group> KeyExchange<D, G> for TripleDH {
         )?;
 
         let transcript: Vec<u8> = [
-            &ke1_state.hashed_l1[..],
+            &ke1_state.serialized_credential_request[..],
             &l2_component[..],
             &ke2_message.to_bytes_without_mac(),
         ]
@@ -219,7 +212,7 @@ impl<D: Hash, G: Group> KeyExchange<D, G> for TripleDH {
 
         let mut hasher2 = D::new();
         hasher2.update(transcript);
-        hasher2.update(ke2_message.mac.to_vec());
+        // hasher2.update(ke2_message.mac.to_vec()); // FIXME, sync with @caw on including this
         let hashed_transcript = hasher2.finalize();
 
         let mut client_mac =
@@ -227,7 +220,7 @@ impl<D: Hash, G: Group> KeyExchange<D, G> for TripleDH {
         client_mac.update(&hashed_transcript);
 
         // Compute decryption of e_info
-        let h = Hkdf::<D>::new(None, &ke2);
+        let h = Hkdf::<D>::from_prk(&ke2).map_err(|_| InternalPakeError::HkdfError)?;
         let mut encryption_pad = vec![0u8; ke2_message.e_info.len()];
         h.expand(STR_ENCRYPTION_PAD, &mut encryption_pad)
             .map_err(|_| InternalPakeError::HkdfError)?;
@@ -264,10 +257,6 @@ impl<D: Hash, G: Group> KeyExchange<D, G> for TripleDH {
         Ok(ke2_state.session_secret.to_vec())
     }
 
-    fn ke1_state_size() -> usize {
-        NONCE_LEN + KEY_LEN + <<D as FixedOutput>::OutputSize as Unsigned>::to_usize()
-    }
-
     fn ke2_message_size() -> usize {
         NONCE_LEN + KEY_LEN + <<D as FixedOutput>::OutputSize as Unsigned>::to_usize()
     }
@@ -275,10 +264,10 @@ impl<D: Hash, G: Group> KeyExchange<D, G> for TripleDH {
 
 /// The client state produced after the first key exchange message
 #[derive(PartialEq, Eq)]
-pub struct KE1State<HashLen: ArrayLength<u8>> {
+pub struct KE1State {
     client_e_sk: Key,
     client_nonce: GenericArray<u8, NonceLen>,
-    hashed_l1: GenericArray<u8, HashLen>,
+    serialized_credential_request: Vec<u8>,
 }
 
 /// The first key exchange message
@@ -289,32 +278,28 @@ pub struct KE1Message {
     pub(crate) client_e_pk: Key,
 }
 
-impl<HashLen: ArrayLength<u8>> TryFrom<&[u8]> for KE1State<HashLen> {
+impl TryFrom<&[u8]> for KE1State {
     type Error = PakeError;
 
     fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
-        let checked_bytes = check_slice_size(
-            bytes,
-            KEY_LEN + NONCE_LEN + HashLen::to_usize(),
-            "ke1_state",
-        )?;
+        let checked_bytes = check_slice_size_atleast(bytes, KEY_LEN + NONCE_LEN, "ke1_state")?;
 
         Ok(Self {
             client_e_sk: Key::from_bytes(&checked_bytes[..KEY_LEN])?,
             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..]),
+            serialized_credential_request: checked_bytes[KEY_LEN + NONCE_LEN..].to_vec(),
         })
     }
 }
 
-impl<HashLen: ArrayLength<u8>> ToBytes for KE1State<HashLen> {
+impl ToBytes for KE1State {
     fn to_bytes(&self) -> Vec<u8> {
         let output: Vec<u8> = [
             &self.client_e_sk.to_arr(),
             &self.client_nonce[..],
-            &self.hashed_l1[..],
+            &self.serialized_credential_request[..],
         ]
         .concat();
         output
@@ -505,6 +490,7 @@ fn derive_3dh_keys<D: Hash, G: Group>(
     let extracted_ikm = Hkdf::<D>::new(None, &ikm);
     let handshake_secret = derive_secrets::<D>(&extracted_ikm, &STR_HANDSHAKE_SECRET, &info)?;
     let session_secret = derive_secrets::<D>(&extracted_ikm, &STR_SESSION_SECRET, &info)?;
+
     let km2 = hkdf_expand_label::<D>(
         &handshake_secret,
         &STR_SERVER_MAC,
@@ -538,7 +524,7 @@ fn hkdf_expand_label<D: Hash>(
     context: &[u8],
     length: usize,
 ) -> Result<Vec<u8>, ProtocolError> {
-    let h = Hkdf::<D>::new(None, secret);
+    let h = Hkdf::<D>::from_prk(secret).map_err(|_| InternalPakeError::HkdfError)?;
     hkdf_expand_label_extracted(&h, label, context, length)
 }
 
diff --git a/src/keypair.rs b/src/keypair.rs
index 9c9b24c..25b7b2a 100644
--- a/src/keypair.rs
+++ b/src/keypair.rs
@@ -17,7 +17,6 @@ use rand::{rngs::StdRng, SeedableRng};
 use rand_core::{CryptoRng, RngCore};
 use std::fmt::Debug;
 use std::marker::PhantomData;
-
 use std::ops::Deref;
 
 // Pub(crate) convenience extension trait of SizedBytes for our purposes
diff --git a/src/messages.rs b/src/messages.rs
index 109ce9f..531714d 100644
--- a/src/messages.rs
+++ b/src/messages.rs
@@ -54,22 +54,13 @@ impl<Grp: Group> RegistrationRequest<Grp> {
 
     /// Serialization into bytes
     pub fn serialize(&self) -> Vec<u8> {
-        serialize(&self.alpha.to_arr(), 2)
+        self.alpha.to_arr().to_vec()
     }
 
     /// Deserialization from bytes
     pub fn deserialize(input: &[u8]) -> Result<Self, ProtocolError> {
-        let (alpha_bytes, remainder) = tokenize(&input, 2)?;
-
-        if !remainder.is_empty() {
-            return Err(PakeError::SerializationError.into());
-        }
-
-        let checked_slice = check_slice_size(
-            &alpha_bytes,
-            Grp::ElemLen::to_usize(),
-            "first_message_bytes",
-        )?;
+        let checked_slice =
+            check_slice_size(&input, Grp::ElemLen::to_usize(), "first_message_bytes")?;
         // Check that the message is actually containing an element of the
         // correct subgroup
         let arr = GenericArray::from_slice(checked_slice);
@@ -122,29 +113,26 @@ where
     /// Serialization into bytes
     pub fn serialize(&self) -> Vec<u8> {
         let mut registration_response: Vec<u8> = Vec::new();
-        registration_response.extend_from_slice(&serialize(&self.beta.to_arr(), 2));
+        registration_response.extend_from_slice(&self.beta.to_arr());
         registration_response.extend_from_slice(&serialize(&self.server_s_pk, 2));
         registration_response
     }
 
     /// Deserialization from bytes
     pub fn deserialize(input: &[u8]) -> Result<Self, ProtocolError> {
-        let (beta_bytes, remainder) = tokenize(&input, 2)?;
-        let (server_s_pk, remainder) = tokenize(&remainder, 2)?;
+        let checked_slice =
+            check_slice_size_atleast(&input, Grp::ElemLen::to_usize(), "second_message_bytes")?;
+
+        // Check that the message is actually containing an element of the
+        // correct subgroup
+        let arr = GenericArray::from_slice(&checked_slice[..Grp::ElemLen::to_usize()]);
+        let beta = Grp::from_element_slice(arr)?;
 
+        let (server_s_pk, remainder) = tokenize(&checked_slice[Grp::ElemLen::to_usize()..], 2)?;
         if !remainder.is_empty() {
             return Err(PakeError::SerializationError.into());
         }
 
-        let checked_slice = check_slice_size(
-            &beta_bytes,
-            Grp::ElemLen::to_usize(),
-            "second_message_bytes",
-        )?;
-        // Check that the message is actually containing an element of the
-        // correct subgroup
-        let arr = GenericArray::from_slice(&checked_slice);
-        let beta = Grp::from_element_slice(arr)?;
         Ok(Self { server_s_pk, beta })
     }
 }
@@ -186,15 +174,15 @@ impl<D: Hash, G: Group> RegistrationUpload<D, G> {
     /// Serialization into bytes
     pub fn serialize(&self) -> Vec<u8> {
         let mut message: Vec<u8> = Vec::new();
-        message.extend_from_slice(&self.envelope.serialize());
         message.extend_from_slice(&serialize(&self.client_s_pk.to_arr(), 2));
+        message.extend_from_slice(&self.envelope.serialize());
         message
     }
 
     /// Deserialization from bytes
     pub fn deserialize(input: &[u8]) -> Result<Self, ProtocolError> {
-        let (envelope, remainder) = Envelope::<D>::deserialize(&input)?;
-        let (client_s_pk, remainder) = tokenize(&remainder, 2)?;
+        let (client_s_pk, remainder) = tokenize(&input, 2)?;
+        let (envelope, remainder) = Envelope::<D>::deserialize(&remainder)?;
 
         if !remainder.is_empty() {
             return Err(PakeError::SerializationError.into());
@@ -231,22 +219,27 @@ impl<CS: CipherSuite> CredentialRequest<CS> {
     /// Serialization into bytes
     pub fn serialize(&self) -> Vec<u8> {
         let mut credential_request: Vec<u8> = Vec::new();
-        credential_request.extend_from_slice(&serialize(&self.alpha.to_arr(), 2));
+        credential_request.extend_from_slice(&self.alpha.to_arr());
         credential_request.extend_from_slice(&self.ke1_message.to_bytes());
         credential_request
     }
 
     /// Deserialization from bytes
     pub fn deserialize(input: &[u8]) -> Result<Self, ProtocolError> {
-        let (alpha_bytes, ke1m) = tokenize(&input, 2)?;
-
         let elem_len = <CS::Group as Group>::ElemLen::to_usize();
-        let checked_slice = check_slice_size(&alpha_bytes, elem_len, "login_first_message_bytes")?;
+
+        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 arr = GenericArray::from_slice(&checked_slice[..elem_len]);
-        let alpha = <CS::Group as Group>::from_element_slice(arr)?;
+        let alpha = CS::Group::from_element_slice(arr)?;
 
         let ke1_message =
-            <CS::KeyExchange as KeyExchange<CS::Hash, CS::Group>>::KE1Message::try_from(&ke1m[..])?;
+            <CS::KeyExchange as KeyExchange<CS::Hash, CS::Group>>::KE1Message::try_from(
+                &checked_slice[elem_len..],
+            )?;
 
         Ok(Self { alpha, ke1_message })
     }
@@ -267,7 +260,7 @@ impl<CS: CipherSuite> CredentialResponse<CS> {
     /// Serialization into bytes
     pub fn serialize(&self) -> Vec<u8> {
         let mut credential_response: Vec<u8> = Vec::new();
-        credential_response.extend_from_slice(&serialize(&self.beta.to_arr(), 2));
+        credential_response.extend_from_slice(&self.beta.to_arr());
         credential_response.extend_from_slice(&serialize(&self.server_s_pk.to_arr().to_vec(), 2));
         credential_response.extend_from_slice(&self.envelope.to_bytes());
         credential_response.extend_from_slice(&self.ke2_message.to_bytes());
@@ -276,13 +269,7 @@ impl<CS: CipherSuite> CredentialResponse<CS> {
 
     /// Deserialization from bytes
     pub fn deserialize(input: &[u8]) -> Result<Self, ProtocolError> {
-        let (beta_bytes, server_s_pk_and_envelope_and_ke2m_bytes) = tokenize(&input, 2)?;
-        let concatenated = [
-            &beta_bytes[..],
-            &server_s_pk_and_envelope_and_ke2m_bytes[..],
-        ]
-        .concat();
-        Self::try_from(&concatenated[..])
+        Self::try_from(input)
     }
 }
 
diff --git a/src/opaque.rs b/src/opaque.rs
index de34493..923f0ed 100644
--- a/src/opaque.rs
+++ b/src/opaque.rs
@@ -7,7 +7,7 @@
 
 use crate::{
     ciphersuite::CipherSuite,
-    envelope::{mode_from_ids, Envelope, ExportKeySize},
+    envelope::{mode_from_ids, Envelope},
     errors::{utils::check_slice_size_atleast, InternalPakeError, PakeError, ProtocolError},
     group::Group,
     hash::Hash,
@@ -15,18 +15,19 @@ use crate::{
     keypair::{Key, KeyPair, SizedBytesExt},
     map_to_curve::GroupWithMapToCurve,
     oprf,
-    serialization::serialize,
+    serialization::{serialize, tokenize},
     slow_hash::SlowHash,
     CredentialFinalization, CredentialRequest, CredentialResponse, RegistrationRequest,
     RegistrationResponse, RegistrationUpload,
 };
+use digest::Digest;
 use generic_array::{typenum::Unsigned, GenericArray};
 use generic_bytes::SizedBytes;
 use rand_core::{CryptoRng, RngCore};
 use std::{convert::TryFrom, marker::PhantomData};
 use zeroize::Zeroize;
 
-static STR_OPAQUE_VERSION: &[u8] = b"OPAQUE00";
+static STR_OPAQUE_VERSION: &[u8] = b"OPAQUE01";
 
 // Registration
 // ============
@@ -142,7 +143,7 @@ pub struct ClientRegistrationFinishResult<D: Hash, G: Group> {
     /// The registration upload message to be sent to the server
     pub message: RegistrationUpload<D, G>,
     /// The export key output by client registration
-    pub export_key: GenericArray<u8, ExportKeySize>,
+    pub export_key: GenericArray<u8, <D as Digest>::OutputSize>,
     _g: PhantomData<G>,
 }
 
@@ -413,14 +414,10 @@ impl<CS: CipherSuite> TryFrom<&[u8]> for ClientLogin<CS> {
     type Error = ProtocolError;
     fn try_from(input: &[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, CS::Group>>::ke1_state_size();
-
-        let min_expected_len = scalar_len + ke1_state_size;
-        let checked_slice = (if input.len() <= min_expected_len {
+        let checked_slice = (if input.len() <= scalar_len {
             Err(InternalPakeError::SizeError {
                 name: "client_login_bytes",
-                len: min_expected_len,
+                len: scalar_len,
                 actual_len: input.len(),
             })
         } else {
@@ -429,10 +426,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_bytes, password) = tokenize(&checked_slice[scalar_len..], 2)?;
+
         let ke1_state = <CS::KeyExchange as KeyExchange<CS::Hash, CS::Group>>::KE1State::try_from(
-            &checked_slice[scalar_len..scalar_len + ke1_state_size],
+            &ke1_state_bytes[..],
         )?;
-        let password = input[scalar_len + ke1_state_size..].to_vec();
         Ok(Self {
             token: oprf::Token {
                 data: password,
@@ -448,7 +447,7 @@ impl<CS: CipherSuite> ClientLogin<CS> {
     pub fn to_bytes(&self) -> Vec<u8> {
         let output: Vec<u8> = [
             &CS::Group::scalar_as_bytes(&self.token.blind)[..],
-            &self.ke1_state.to_bytes(),
+            &serialize(&self.ke1_state.to_bytes(), 2),
             &self.token.data,
         ]
         .concat();
@@ -497,7 +496,7 @@ pub struct ClientLoginFinishResult<CS: CipherSuite> {
     /// The shared session secret
     pub shared_secret: Vec<u8>,
     /// The client-side export key
-    pub export_key: GenericArray<u8, ExportKeySize>,
+    pub export_key: GenericArray<u8, <CS::Hash as Digest>::OutputSize>,
     /// The server's static public key
     pub server_s_pk: Key,
     /// The confidential info sent by the client
@@ -597,7 +596,6 @@ impl<CS: CipherSuite> ClientLogin<CS> {
 
         let password_derived_key =
             get_password_derived_key::<CS::Group, CS::SlowHash, CS::Hash>(&self.token, l2.beta)?;
-
         let opened_envelope = &l2
             .envelope
             .open(&password_derived_key, &server_s_pk_bytes, &optional_ids)
@@ -619,9 +617,9 @@ impl<CS: CipherSuite> ClientLogin<CS> {
         };
 
         let l2_bytes: Vec<u8> = [
-            serialize(&l2_beta_bytes, 2),
-            serialize(&server_s_pk_bytes, 2),
-            l2.envelope.to_bytes(),
+            l2_beta_bytes,
+            &serialize(&server_s_pk_bytes, 2),
+            &l2.envelope.to_bytes(),
         ]
         .concat();
 
@@ -639,7 +637,7 @@ impl<CS: CipherSuite> ClientLogin<CS> {
             confidential_info,
             message: CredentialFinalization { ke3_message },
             shared_secret,
-            export_key: opened_envelope.export_key,
+            export_key: opened_envelope.export_key.clone(),
             server_s_pk: l2.server_s_pk,
         })
     }
@@ -777,10 +775,11 @@ impl<CS: CipherSuite> ServerLogin<CS> {
 
         let server_s_pk = KeyPair::<CS::Group>::public_from_private(&server_s_sk);
 
+        // TODO: must match serialization of credential response, could be done more cleanly
         let l2_component: Vec<u8> = [
-            serialize(&beta.to_arr()[..], 2),
-            serialize(&server_s_pk.to_arr()[..], 2),
-            envelope.to_bytes(),
+            &beta.to_arr()[..],
+            &serialize(&server_s_pk.to_arr()[..], 2),
+            &envelope.to_bytes()[..],
         ]
         .concat();
 


Test patch:
diff --git a/src/serialization/tests.rs b/src/serialization/tests.rs
index 83cb7b2..05b142f 100644
--- a/src/serialization/tests.rs
+++ b/src/serialization/tests.rs
@@ -21,7 +21,7 @@ use generic_bytes::SizedBytes;
 use proptest::{collection::vec, prelude::*};
 use rand_core::{OsRng, RngCore};
 
-use sha2::{Digest, Sha512};
+use sha2::Digest;
 use std::convert::TryFrom;
 
 struct Default;
@@ -81,10 +81,7 @@ fn server_registration_roundtrip() {
     mock_envelope_bytes.extend_from_slice(&[0; 1]); // mode = 0
     mock_envelope_bytes.extend_from_slice(&[0; NONCE_LEN]); // empty nonce
     mock_envelope_bytes.extend_from_slice(&[0, 0]); // empty ciphertext
-    mock_envelope_bytes.extend_from_slice(&[0, 0]); // empty auth_data
-                                                    // length-MAC_SIZE hmac
-    mock_envelope_bytes.extend_from_slice(&[0, MAC_SIZE as u8]);
-    mock_envelope_bytes.extend_from_slice(&[0; MAC_SIZE]);
+    mock_envelope_bytes.extend_from_slice(&[0; MAC_SIZE]); // length-MAC_SIZE hmac
 
     let mock_client_kp = Default::generate_random_keypair(&mut rng);
     // serialization order: oprf_key, public key, envelope
@@ -102,10 +99,7 @@ fn register_first_message_roundtrip() {
     let pt = random_ristretto_point();
     let pt_bytes = pt.to_arr().to_vec();
 
-    let alpha_length: usize = 32;
-
     let mut input = Vec::new();
-    input.extend_from_slice(&alpha_length.to_be_bytes()[std::mem::size_of::<usize>() - 2..]);
     input.extend_from_slice(pt_bytes.as_slice());
 
     let r1 = RegistrationRequest::<RistrettoPoint>::deserialize(input.as_slice()).unwrap();
@@ -121,11 +115,9 @@ fn register_second_message_roundtrip() {
     let skp = Default::generate_random_keypair(&mut rng);
     let pubkey_bytes = skp.public().to_arr();
 
-    let beta_length: usize = beta_bytes.len();
     let pubkey_length: usize = pubkey_bytes.len();
 
     let mut input = Vec::new();
-    input.extend_from_slice(&beta_length.to_be_bytes()[std::mem::size_of::<usize>() - 2..]);
     input.extend_from_slice(beta_bytes.as_slice());
     input.extend_from_slice(&pubkey_length.to_be_bytes()[std::mem::size_of::<usize>() - 2..]);
     input.extend_from_slice(&pubkey_bytes.as_slice());
@@ -160,9 +152,9 @@ fn register_third_message_roundtrip() {
     let pubkey_length: usize = pubkey_bytes.len();
 
     let mut input = Vec::new();
-    input.extend_from_slice(&envelope_bytes);
     input.extend_from_slice(&pubkey_length.to_be_bytes()[std::mem::size_of::<usize>() - 2..]);
     input.extend_from_slice(&pubkey_bytes[..]);
+    input.extend_from_slice(&envelope_bytes);
 
     let r3 = RegistrationUpload::<sha2::Sha512, RistrettoPoint>::deserialize(&input[..]).unwrap();
     let r3_bytes = r3.serialize();
@@ -189,10 +181,7 @@ fn login_first_message_roundtrip() {
     ]
     .concat();
 
-    let alpha_length = alpha_bytes.len();
-
     let mut input = Vec::new();
-    input.extend_from_slice(&alpha_length.to_be_bytes()[std::mem::size_of::<usize>() - 2..]);
     input.extend_from_slice(&alpha_bytes);
     input.extend_from_slice(&ke1m[..]);
 
@@ -244,7 +233,6 @@ fn login_second_message_roundtrip() {
     .concat();
 
     let mut input = Vec::new();
-    input.extend_from_slice(&pt_bytes.len().to_be_bytes()[std::mem::size_of::<usize>() - 2..]);
     input.extend_from_slice(pt_bytes.as_slice());
     input.extend_from_slice(&pubkey_length.to_be_bytes()[std::mem::size_of::<usize>() - 2..]);
     input.extend_from_slice(&pubkey_bytes.as_slice());
@@ -280,19 +268,9 @@ fn client_login_roundtrip() {
     rng.fill_bytes(&mut client_nonce);
 
     let l1_data = [&sc.to_bytes()[..], &client_nonce, client_e_kp.public()].concat();
-    let mut hasher = Sha512::new();
-    hasher.update(l1_data);
-    let hashed_l1 = hasher.finalize();
 
     // serialization order: scalar, password, ke1_state
-    let bytes: Vec<u8> = [
-        &sc.as_bytes()[..],
-        &pw[..],
-        client_e_kp.public(),
-        &client_nonce,
-        hashed_l1.as_slice(),
-    ]
-    .concat();
+    let bytes: Vec<u8> = [&sc.as_bytes()[..], &serialize(&l1_data, 2), &pw[..]].concat();
     let reg = ClientLogin::<Default>::try_from(&bytes[..]).unwrap();
     let reg_bytes = reg.to_bytes();
     assert_eq!(reg_bytes, bytes);
diff --git a/src/tests/opaque_ke_test.rs b/src/tests/full_test.rs
similarity index 80%
rename from src/tests/opaque_ke_test.rs
rename to src/tests/full_test.rs
index d65e213..b7d9424 100644
--- a/src/tests/opaque_ke_test.rs
+++ b/src/tests/full_test.rs
@@ -55,7 +55,7 @@ pub struct TestVectorParameters {
     pub registration_upload: Vec<u8>,
     pub credential_request: Vec<u8>,
     pub credential_response: Vec<u8>,
-    pub key_exchange: Vec<u8>,
+    pub credential_finalization: Vec<u8>,
     client_registration_state: Vec<u8>,
     server_registration_state: Vec<u8>,
     client_login_state: Vec<u8>,
@@ -67,37 +67,37 @@ pub struct TestVectorParameters {
 
 static TEST_VECTOR: &str = r#"
 {
-    "client_s_pk": "64b071d0b8eccc39673b16384e86df49f258a12bad24f21f5d5f4a9a8f45561c",
-    "client_s_sk": "98a23d0757ac5c7aba8b218c5643c3a3029fb815a656de24cfc05df877925809",
-    "client_e_pk": "3a575317d7a6269bc4e76074ec906d8886b4c135fb6b590972f6fdd6c1a55676",
-    "client_e_sk": "163b91328785a5e84e21579e941be1dd7bdbc44d4c959f603d7f79417ab6b201",
-    "server_s_pk": "fc0d7d59e0fabe7c876c01c8d5408fc60dc5fdec7b89acdc5af2bc7c0de26d75",
-    "server_s_sk": "f7068582fdf2a57f7da63e6f5142abda805a7b508ce171c84f58a4e5e634a406",
-    "server_e_pk": "8e32a64f670b9d2115eba0006b00667972523bf2f8686933ef2a2819d6b5a13b",
-    "server_e_sk": "a3a06d81eeb60aeedaee96769149ae5f45184eec66e1b2bdeb04114d27019206",
+    "client_s_pk": "ba8c7e239ed64ea1e6068b52cbd5a34ba6f55d635d20a4593884dc271865ea63",
+    "client_s_sk": "edb5e886441ddcd4fdd80593d9ea7b7f82f284fcc1d565b802e99997457b960a",
+    "client_e_pk": "08973113b27d6f2291aaaeb92f34a8438d279a48b1b412bddbf2785be660e479",
+    "client_e_sk": "28fb3aadbc2716844d8de562d35e61914da734dcea0fa6ebd79603ef9eff4e01",
+    "server_s_pk": "b67d948b98eecfb516a626fab94170e490ccd90da05fce94a016c4d5d6b7136a",
+    "server_s_sk": "52350a684c353e39bbaadeb042b2de94226a493f6ae172b7fc45408bbfda6401",
+    "server_e_pk": "42103d44b03f3886fe3e419b1b96521fa5a706bb1a4e003907daa381960d664a",
+    "server_e_sk": "564e113d5b2784fcf3a55d5ca48512a2fd5c3c393753f6cfd157cb712b317c04",
     "id_u": "696455",
     "id_s": "696453",
     "password": "70617373776f7264",
-    "blinding_factor": "3497d8c6728a3dc0873e363dcd60acd95f7897d15d33cdf1d49c8b856f061900",
-    "oprf_key": "c728d1ea06fb94f577ef63d50d1645e9fea14575987971e4a64e05dda367c703",
-    "envelope_nonce": "d7d138a3a4cb796662814391f5221e05b66438a92e5f57cc9624e6227da34eab",
-    "client_nonce": "ad87ae4609f1f56aeb1de0f4760120fa75ceff39751dffc35d25ff3f2896bb60",
-    "server_nonce": "d7a8cd7cd2c4f0f7650390f6f2d4858ae99ac79f2769a9e15583d570b11c434c",
+    "blinding_factor": "a70b37951d84ed312a0a8e025b71eafdd362b4e7db872881762d506a271b6e07",
+    "oprf_key": "1eaf3cdd64da67e17e3364182f00ed0c323bfb44b20a1b7e0025a0334ab6d40d",
+    "envelope_nonce": "5dba53e994e82b6d76353341102791cdfb1c2b460e1f913c5741a9c8cf2017c9",
+    "client_nonce": "488244dbf75f86ebaa30307773d3e0eabccb91be349abef8a69c458dbf236e4c",
+    "server_nonce": "13cc5ceb7687d80293005e7f9452ce104d1bac6598dd2265f184cacd558d6b31",
     "info1": "696e666f31",
     "einfo2": "65696e666f32",
-    "registration_request": "002096738fbc9883a85a8067763abe33efadce0adb2bc64857d3cd0a6cc92e9ce325",
-    "registration_response": "0020a86baffac435f6239cfe5f61d823e7458ac211dce1d3c364375e9d1f40843c750020fc0d7d59e0fabe7c876c01c8d5408fc60dc5fdec7b89acdc5af2bc7c0de26d75",
-    "registration_upload": "01d7d138a3a4cb796662814391f5221e05b66438a92e5f57cc9624e6227da34eab002229979df679067f8baecbbe063ee97e2b5be88c2b99163d595b32a74d640138771d70002c0020fc0d7d59e0fabe7c876c01c8d5408fc60dc5fdec7b89acdc5af2bc7c0de26d750003696455000369645300400b6119d0de405d85e0e6b1748eb66194b1768ea71fd47c103f8e1fcbc837add2b282889469921798d626a02b6dd763ee9cd028f333386a19c13a3d4d4c6a3312002064b071d0b8eccc39673b16384e86df49f258a12bad24f21f5d5f4a9a8f45561c",
-    "credential_request": "002096738fbc9883a85a8067763abe33efadce0adb2bc64857d3cd0a6cc92e9ce325ad87ae4609f1f56aeb1de0f4760120fa75ceff39751dffc35d25ff3f2896bb600005696e666f313a575317d7a6269bc4e76074ec906d8886b4c135fb6b590972f6fdd6c1a55676",
-    "credential_response": "0020a86baffac435f6239cfe5f61d823e7458ac211dce1d3c364375e9d1f40843c750020fc0d7d59e0fabe7c876c01c8d5408fc60dc5fdec7b89acdc5af2bc7c0de26d7501d7d138a3a4cb796662814391f5221e05b66438a92e5f57cc9624e6227da34eab002229979df679067f8baecbbe063ee97e2b5be88c2b99163d595b32a74d640138771d70002c0020fc0d7d59e0fabe7c876c01c8d5408fc60dc5fdec7b89acdc5af2bc7c0de26d750003696455000369645300400b6119d0de405d85e0e6b1748eb66194b1768ea71fd47c103f8e1fcbc837add2b282889469921798d626a02b6dd763ee9cd028f333386a19c13a3d4d4c6a3312a3a06d81eeb60aeedaee96769149ae5f45184eec66e1b2bdeb04114d270192068e32a64f670b9d2115eba0006b00667972523bf2f8686933ef2a2819d6b5a13b00066c7c22ba8dbae814beb02d1b89659379ca61697cd3212169d387b61f5659b77f6993833a1542a692cb5ad259244a1d53a51243fb5366a5bd8e4ed7eb2f50caa9a568fc8267e9",
-    "key_exchange": "3f9d3754920514328529bf4e78d4c2d0f411dfb871a4ef83c87d3462119b836381bdc651096cb62ca96de5f4b5a9acaa87b180d2acefebd2f3feb5785adcd038",
-    "client_registration_state": "3497d8c6728a3dc0873e363dcd60acd95f7897d15d33cdf1d49c8b856f06190070617373776f7264",
-    "client_login_state": "3497d8c6728a3dc0873e363dcd60acd95f7897d15d33cdf1d49c8b856f061900163b91328785a5e84e21579e941be1dd7bdbc44d4c959f603d7f79417ab6b201ad87ae4609f1f56aeb1de0f4760120fa75ceff39751dffc35d25ff3f2896bb607962b421c96efcfdb465365eea8fe9914e6ac5c0b216e2b07c7fb3b17fa0d21ee8c37373fee8dfec6a5eae559f21c8c4e1505ed974ce8baff3529304cb4d90b570617373776f7264",
-    "server_registration_state": "c728d1ea06fb94f577ef63d50d1645e9fea14575987971e4a64e05dda367c703",
-    "server_login_state": "644fb6d36a91e0197f5d82d17f04354f9e24794988923160534d99c80970b66fe54a3d8b36d5a4d21eaf0fa7bb4717ed1741be3f7d24b96d27aeb7f4b59a72bf80d0d969a2398ea5a5232d3257c60516dcc0de65b66fc5a07b8e2f58523eb0079ac09798a23cee0e31454594d25e40f34b10b92195fea6f90e197c0fbc8658bb3435bb6179370029f03d2dbd5efa2d0f8b8f4f69ca42ef70245686f70c2d7f326d0a8bc596145099e6a668d23ce1a93f9a3f83bfdddde15c78f467a639f124dd",
-    "password_file": "c728d1ea06fb94f577ef63d50d1645e9fea14575987971e4a64e05dda367c70364b071d0b8eccc39673b16384e86df49f258a12bad24f21f5d5f4a9a8f45561c01d7d138a3a4cb796662814391f5221e05b66438a92e5f57cc9624e6227da34eab002229979df679067f8baecbbe063ee97e2b5be88c2b99163d595b32a74d640138771d70002c0020fc0d7d59e0fabe7c876c01c8d5408fc60dc5fdec7b89acdc5af2bc7c0de26d750003696455000369645300400b6119d0de405d85e0e6b1748eb66194b1768ea71fd47c103f8e1fcbc837add2b282889469921798d626a02b6dd763ee9cd028f333386a19c13a3d4d4c6a3312",
-    "export_key": "005ba69bed794d3e24ac750ed575165a0235e103d1f8d7ca1b9aa0cfc2c3d9b6",
-    "shared_secret": "3435bb6179370029f03d2dbd5efa2d0f8b8f4f69ca42ef70245686f70c2d7f326d0a8bc596145099e6a668d23ce1a93f9a3f83bfdddde15c78f467a639f124dd"
+    "registration_request": "ecf819ef83351e4c75e016ef845b62a12c14f397328960574935a42e04e0fa42",
+    "registration_response": "2e9bf80e5c101ee2d391ff2aec06af97af17f443fc93a5cc98189681cb943d700020b67d948b98eecfb516a626fab94170e490ccd90da05fce94a016c4d5d6b7136a",
+    "registration_upload": "0020ba8c7e239ed64ea1e6068b52cbd5a34ba6f55d635d20a4593884dc271865ea63015dba53e994e82b6d76353341102791cdfb1c2b460e1f913c5741a9c8cf2017c90022f11b89b207eb0735f2bdb2a9968bc9d7c1c962fd0274b4fc1fff2466c769d3710586a3a21ac8ecf13aafe2dc5460eb2f52d8f760396a17beeaf1ef3d1e4e50dd906555832fc51fc474554cb9ff063f6685f9043efc08fd6816666833d68d3b0eb6d9",
+    "credential_request": "ecf819ef83351e4c75e016ef845b62a12c14f397328960574935a42e04e0fa42488244dbf75f86ebaa30307773d3e0eabccb91be349abef8a69c458dbf236e4c0005696e666f3108973113b27d6f2291aaaeb92f34a8438d279a48b1b412bddbf2785be660e479",
+    "credential_response": "2e9bf80e5c101ee2d391ff2aec06af97af17f443fc93a5cc98189681cb943d700020b67d948b98eecfb516a626fab94170e490ccd90da05fce94a016c4d5d6b7136a015dba53e994e82b6d76353341102791cdfb1c2b460e1f913c5741a9c8cf2017c90022f11b89b207eb0735f2bdb2a9968bc9d7c1c962fd0274b4fc1fff2466c769d3710586a3a21ac8ecf13aafe2dc5460eb2f52d8f760396a17beeaf1ef3d1e4e50dd906555832fc51fc474554cb9ff063f6685f9043efc08fd6816666833d68d3b0eb6d913cc5ceb7687d80293005e7f9452ce104d1bac6598dd2265f184cacd558d6b3142103d44b03f3886fe3e419b1b96521fa5a706bb1a4e003907daa381960d664a0006a0f4de671e13c37b24e29138ff9d3df3d55c3b822fedee8dcefd643a1deab7cda5aa1c20f31a0c2a1823c8de3e793f50b257833788b75609fb6c5c4a9cad225f3a981c4ff7b2",
+    "credential_finalization": "0640114ec21fbad6da11c61dfa77a48026646a2a1bd091ef5c9dc46b45f9e53c72f5d714e026d808b6de8911947eef437e9f6a6e969e1ea13f2f4880adc7c879",
+    "client_registration_state": "a70b37951d84ed312a0a8e025b71eafdd362b4e7db872881762d506a271b6e0770617373776f7264",
+    "client_login_state": "a70b37951d84ed312a0a8e025b71eafdd362b4e7db872881762d506a271b6e0700a728fb3aadbc2716844d8de562d35e61914da734dcea0fa6ebd79603ef9eff4e01488244dbf75f86ebaa30307773d3e0eabccb91be349abef8a69c458dbf236e4cecf819ef83351e4c75e016ef845b62a12c14f397328960574935a42e04e0fa42488244dbf75f86ebaa30307773d3e0eabccb91be349abef8a69c458dbf236e4c0005696e666f3108973113b27d6f2291aaaeb92f34a8438d279a48b1b412bddbf2785be660e47970617373776f7264",
+    "server_registration_state": "1eaf3cdd64da67e17e3364182f00ed0c323bfb44b20a1b7e0025a0334ab6d40d",
+    "server_login_state": "4952acf61167e6d0e799572827724fdf8c80bda8c62ac28deb68d125661dade2cc1136e634d5fb93f1de4d170ae3e2f5885e333ac3b72d630ab56618c2247fa4d5968eefd69cd270ed14fbcb39627fea9a93cd18175e1b5cfceb06f705bbcb5ecddcd0602aacd7cb358f3273b1099895210bf8baa37d2fab5ed8c092fe9e5739bc6c50cf08ca7d4f4107d48057507480e0c9534b4f04d77cd5156cbd112565d8a2d5beaa473003dcf8dd82d4046648a701c72afeed0f627fcb782627dfaed3ca",
+    "password_file": "1eaf3cdd64da67e17e3364182f00ed0c323bfb44b20a1b7e0025a0334ab6d40dba8c7e239ed64ea1e6068b52cbd5a34ba6f55d635d20a4593884dc271865ea63015dba53e994e82b6d76353341102791cdfb1c2b460e1f913c5741a9c8cf2017c90022f11b89b207eb0735f2bdb2a9968bc9d7c1c962fd0274b4fc1fff2466c769d3710586a3a21ac8ecf13aafe2dc5460eb2f52d8f760396a17beeaf1ef3d1e4e50dd906555832fc51fc474554cb9ff063f6685f9043efc08fd6816666833d68d3b0eb6d9",
+    "export_key": "2b86667de895e4d2c15b93bc70f0aa2df67b83987fa53deac9473697e2fade045e0fd0e4af478ccff52165c1467272993da34dcb3baa45aa59cf9d817e6c6bcb",
+    "shared_secret": "bc6c50cf08ca7d4f4107d48057507480e0c9534b4f04d77cd5156cbd112565d8a2d5beaa473003dcf8dd82d4046648a701c72afeed0f627fcb782627dfaed3ca"
 }
 "#;
 
@@ -132,7 +132,7 @@ fn populate_test_vectors(values: &Value) -> TestVectorParameters {
         registration_upload: decode(&values, "registration_upload").unwrap(),
         credential_request: decode(&values, "credential_request").unwrap(),
         credential_response: decode(&values, "credential_response").unwrap(),
-        key_exchange: decode(&values, "key_exchange").unwrap(),
+        credential_finalization: decode(&values, "credential_finalization").unwrap(),
         client_registration_state: decode(&values, "client_registration_state").unwrap(),
         client_login_state: decode(&values, "client_login_state").unwrap(),
         server_registration_state: decode(&values, "server_registration_state").unwrap(),
@@ -211,7 +211,13 @@ fn stringify_test_vectors(p: &TestVectorParameters) -> String {
         )
         .as_str(),
     );
-    s.push_str(format!("\"key_exchange\": \"{}\",\n", hex::encode(&p.key_exchange)).as_str());
+    s.push_str(
+        format!(
+            "\"credential_finalization\": \"{}\",\n",
+            hex::encode(&p.credential_finalization)
+        )
+        .as_str(),
+    );
     s.push_str(
         format!(
             "\"client_registration_state\": \"{}\",\n",
@@ -349,9 +355,15 @@ fn generate_parameters<CS: CipherSuite>() -> TestVectorParameters {
     let credential_request_bytes = client_login_start_result.message.serialize().to_vec();
     let client_login_state = client_login_start_result.state.to_bytes().to_vec();
 
-    let mut server_e_sk_rng = CycleRng::new(server_e_kp.private().to_arr().to_vec());
+    let mut server_e_sk_and_nonce_rng = CycleRng::new(
+        [
+            server_e_kp.private().to_arr().to_vec(),
+            server_nonce.to_vec(),
+        ]
+        .concat(),
+    );
     let server_login_start_result = ServerLogin::<CS>::start(
-        &mut server_e_sk_rng,
+        &mut server_e_sk_and_nonce_rng,
         password_file,
         server_s_kp.private(),
         client_login_start_result.message,
@@ -372,7 +384,7 @@ fn generate_parameters<CS: CipherSuite>() -> TestVectorParameters {
             ClientLoginFinishParameters::WithIdentifiers(id_u.to_vec(), id_s.to_vec()),
         )
         .unwrap();
-    let key_exchange_bytes = client_login_finish_result.message.to_bytes().to_vec();
+    let credential_finalization_bytes = client_login_finish_result.message.to_bytes().to_vec();
 
     TestVectorParameters {
         client_s_pk: client_s_kp.public().to_arr().to_vec(),
@@ -398,7 +410,7 @@ fn generate_parameters<CS: CipherSuite>() -> TestVectorParameters {
         registration_upload: registration_upload_bytes,
         credential_request: credential_request_bytes,
         credential_response: credential_response_bytes,
-        key_exchange: key_exchange_bytes,
+        credential_finalization: credential_finalization_bytes,
         password_file: password_file_bytes,
         client_registration_state,
         server_registration_state,
@@ -529,9 +541,10 @@ fn test_credential_request() -> Result<(), ProtocolError> {
 fn test_credential_response() -> Result<(), ProtocolError> {
     let parameters = populate_test_vectors(&serde_json::from_str(TEST_VECTOR).unwrap());
 
-    let mut server_e_sk_rng = CycleRng::new(parameters.server_e_sk);
+    let mut server_e_sk_and_nonce_rng =
+        CycleRng::new([parameters.server_e_sk, parameters.server_nonce].concat());
     let server_login_start_result = ServerLogin::<RistrettoSha5123dhNoSlowHash>::start(
-        &mut server_e_sk_rng,
+        &mut server_e_sk_and_nonce_rng,
         ServerRegistration::try_from(&parameters.password_file[..]).unwrap(),
         &Key::try_from(&parameters.server_s_sk[..]).unwrap(),
         CredentialRequest::<RistrettoSha5123dhNoSlowHash>::deserialize(
@@ -560,7 +573,7 @@ fn test_credential_response() -> Result<(), ProtocolError> {
 }
 
 #[test]
-fn test_key_exchange() -> Result<(), ProtocolError> {
+fn test_credential_finalization() -> Result<(), ProtocolError> {
     let parameters = populate_test_vectors(&serde_json::from_str(TEST_VECTOR).unwrap());
 
     let client_login_finish_result =
@@ -586,7 +599,7 @@ fn test_key_exchange() -> Result<(), ProtocolError> {
         hex::encode(&client_login_finish_result.shared_secret)
     );
     assert_eq!(
-        hex::encode(&parameters.key_exchange),
+        hex::encode(&parameters.credential_finalization),
         hex::encode(client_login_finish_result.message.to_bytes())
     );
     assert_eq!(
@@ -604,7 +617,7 @@ fn test_server_login_finish() -> Result<(), ProtocolError> {
     let server_login_result =
         ServerLogin::<RistrettoSha5123dhNoSlowHash>::try_from(&parameters.server_login_state[..])?
             .finish(CredentialFinalization::try_from(
-                &parameters.key_exchange[..],
+                &parameters.credential_finalization[..],
             )?)?;
 
     assert_eq!(
diff --git a/src/tests/mod.rs b/src/tests/mod.rs
index 8c642ba..e3fceee 100644
--- a/src/tests/mod.rs
+++ b/src/tests/mod.rs
@@ -3,6 +3,7 @@
 // This source code is licensed under the MIT license found in the
 // LICENSE file in the root directory of this source tree.
 
+mod full_test;
 pub mod mock_rng;
-mod opaque_ke_test;
+mod opaque_test_vectors;
 mod voprf_test_vectors;
diff --git a/src/tests/opaque_test_vectors.rs b/src/tests/opaque_test_vectors.rs
new file mode 100644
index 0000000..c5454f0
--- /dev/null
+++ b/src/tests/opaque_test_vectors.rs
@@ -0,0 +1,430 @@
+// Copyright (c) Facebook, Inc. and its affiliates.
+//
+// This source code is licensed under the MIT license found in the
+// LICENSE file in the root directory of this source tree.
+
+use crate::{
+    ciphersuite::CipherSuite, errors::*, key_exchange::tripledh::TripleDH, keypair::Key, opaque::*,
+    slow_hash::NoOpHash, tests::mock_rng::CycleRng, *,
+};
+use curve25519_dalek::ristretto::RistrettoPoint;
+use rand_core::OsRng;
+use serde_json::Value;
+use std::convert::TryFrom;
+
+// Tests
+// =====
+
+struct Ristretto255Sha512NoSlowHash;
+impl CipherSuite for Ristretto255Sha512NoSlowHash {
+    type Group = RistrettoPoint;
+    type KeyExchange = TripleDH;
+    type Hash = sha2::Sha512;
+    type SlowHash = NoOpHash;
+}
+
+pub struct TestVectorParameters {
+    pub client_s_pk: Vec<u8>,
+    pub client_s_sk: Vec<u8>,
+    pub client_e_pk: Vec<u8>,
+    pub client_e_sk: Vec<u8>,
+    pub server_s_pk: Vec<u8>,
+    pub server_s_sk: Vec<u8>,
+    pub server_e_pk: Vec<u8>,
+    pub server_e_sk: Vec<u8>,
+    pub client_identifier: Vec<u8>,
+    pub server_identifier: Vec<u8>,
+    pub password: Vec<u8>,
+    pub blinding_factor_registration: Vec<u8>,
+    pub oprf_key: Vec<u8>,
+    pub envelope_nonce: Vec<u8>,
+    pub client_nonce: Vec<u8>,
+    pub server_nonce: Vec<u8>,
+    pub info1: Vec<u8>,
+    pub info2: Vec<u8>,
+    pub registration_request: Vec<u8>,
+    pub registration_response: Vec<u8>,
+    pub registration_upload: Vec<u8>,
+    pub credential_request: Vec<u8>,
+    pub blinding_factor_login: Vec<u8>,
+    pub credential_response: Vec<u8>,
+    pub credential_finalization: Vec<u8>,
+    pub password_file: Vec<u8>,
+    pub export_key: Vec<u8>,
+    pub shared_secret: Vec<u8>,
+}
+
+static TEST_VECTOR: &str = r#"
+{
+    "auth_key": "5da3dcc6721e638fa4eef769f7af5bd30f7132308d41fff00f0cf9745e9090235f75697a0dc58fe8692204c4702a20db4a6ee59119fe6ab4173322f06b896309",
+    "blinding_factor_login": "ed8366feb6b1d05d1f46acb727061e43aadfafe9c10e5a64e7518d63e3263503",
+    "blinding_factor_registration": "c604c785ada70d77a5256ae21767de8c3304115237d262134f5e46e512cf8e03",
+    "client_e_pk": "645a5d8c715454a2ba77918d44234d9cbbd723d03113b68c9ea076ab4e776067",
+    "client_e_sk": "9be30157b01e572d9ee296defd7bd2f98fed15fbbf3cbed847547d6759c73c08",
+    "client_identifier": "0aa0f9643e2ecaf3075201e9e762371d1b5cdfac674494b2d5ed0b0908c46e5b",
+    "client_nonce": "a9f275415d9bee6b86c2c390b7577c7684d70479c7e23bfbc01652a7464fb1cc",
+    "client_s_pk": "0aa0f9643e2ecaf3075201e9e762371d1b5cdfac674494b2d5ed0b0908c46e5b",
+    "client_s_sk": "8772da7da9af3fcdae2a23cf8a34a49954a56b48ae2f866b71113132664f0e09",
+    "credential_finalization": "b8de4c989e5caefb57f5a506fd469c4e329aa3076d17a8c8648266c2c2db6f4c33be2161c11c63b7039459de0f9f7f9c5b6a429e6002ac03516ba3979fc970f7",
+    "credential_request": "b68e0e356f8490fa9c3bed952e16cc02db21eda686b3c484f3d9d912caa41f76a9f275415d9bee6b86c2c390b7577c7684d70479c7e23bfbc01652a7464fb1cc000968656c6c6f20626f62645a5d8c715454a2ba77918d44234d9cbbd723d03113b68c9ea076ab4e776067",
+    "credential_response": "e83812f06568d57b8cdfdcc90fe91454e21bd25dd2a1c32dd1599a2e4a4b6c35002008a499182bb67b86190e2d8315ca1ae4a6cdd503db50bed3d90cc7670dcb9f2b0090f17bbbfeaf88def8713679002f4825d936a5347afdf4d020a99167843056d60022147e90c0c40a3652bdef905ab2f61386a4119a121f3411c1da6463ac5d0c633c6e6eaf0eb0d45ab8afb746ccd00a1e96cbb2eca09cdc61520772b32978470dcdb0f8aaec7016681d431f194a447bea2c0be84c7191837e362f0f69b7f29676e15d0351c529e997a215d410ef6f387a2f1e708729d2316bf27b75138fb440020c1fcdbcae720a4acd4a44602024a9d6f2d536baf230f8301c9f9ff5113e229549fd05000f3b629da91fc6405e82b16c18ae4be1ff00020b37dfa8dbd7eeea4bef1c678693b2f1e2da0370e6792f5ab860fad7a098f0d5d00d4d4205aae4d70887fb28edc16f5f1268aa021dba8810909cb2f9e6",
+    "envU": "0090f17bbbfeaf88def8713679002f4825d936a5347afdf4d020a99167843056d60022147e90c0c40a3652bdef905ab2f61386a4119a121f3411c1da6463ac5d0c633c6e6eaf0eb0d45ab8afb746ccd00a1e96cbb2eca09cdc61520772b32978470dcdb0f8aaec7016681d431f194a447bea2c0be84c7191837e362f0f69b7f29676e15d03",
+    "envelope_nonce": "90f17bbbfeaf88def8713679002f4825d936a5347afdf4d020a99167843056d6",
+    "export_key": "453f61eae9b840519eb025d2297eb31ad13c960deba40c2799d2cd4b5034161e1c13695877b460643386ed5e41d5ec9bf2bc845592469fab50abf77d4812fcf0",
+    "handshake_secret": "67b75ff7291d68e20bdbc6a15209aba0c61b7b690d69f293db3761ad62f6014476daeb8ce1235e2d29404bdba3b843dbd544250751e51af747010bb25c310fd5",
+    "info1": "68656c6c6f20626f62",
+    "info2": "6772656574696e677320616c696365",
+    "ke2": "44feeac8078ca91e22ac0133fe33890207c3d927ace293fffa66296c74e82e09ffb1ac00f76198e0ebe74eab722ac49173bdee616f32b3a7416ddee1d8075d54",
+    "km2": "0d5b3bd8e1f6b937570b501d5647cb061276bba36cfc3213205c36d2240e8fe55f2338108f52947fd589ce344ce0a677ddb178c881deb4d075874ade133b99c7",
+    "km3": "8ed3a633ad925fedcf19f9f4acca15f8bfbd147ba11e0950a2ae6c3efb6e41aef75faa20ae0f9104801bdb8a4edbd23a5cc19fe626a00c50e4718d8dedb180fd",
+    "oprf_key": "5ed895206bfc53316d307b23e46ecc6623afb3086da74189a416012be037e50b",
+    "password": "436f7272656374486f72736542617474657279537461706c65",
+    "pseudorandom_pad": "145e17b21e779ffd82223e70913999b20088ceb7747cbfee5c0f12bd6c3e05736067",
+    "registration_request": "241b621c417c0705b5ea7a8b7cdd5039fd61e6b63effe2a44418164c4d49003e",
+    "registration_response": "1867301bcc67bdf8e640b7d6edcbe2a65488446417b50d30cdba66ccb379e572002008a499182bb67b86190e2d8315ca1ae4a6cdd503db50bed3d90cc7670dcb9f2b",
+    "registration_upload": "00200aa0f9643e2ecaf3075201e9e762371d1b5cdfac674494b2d5ed0b0908c46e5b0090f17bbbfeaf88def8713679002f4825d936a5347afdf4d020a99167843056d60022147e90c0c40a3652bdef905ab2f61386a4119a121f3411c1da6463ac5d0c633c6e6eaf0eb0d45ab8afb746ccd00a1e96cbb2eca09cdc61520772b32978470dcdb0f8aaec7016681d431f194a447bea2c0be84c7191837e362f0f69b7f29676e15d03",
+    "rwdU": "17eacc90e168aafd6cf5cbf4aba71e6c08d16800e4880a3fcc5252a1b5072ab7253942f3b4da8a2132863730970ca7a737b5bec96e038a8fa2915b4eeb678180",
+    "server_e_pk": "bcae720a4acd4a44602024a9d6f2d536baf230f8301c9f9ff5113e229549fd05",
+    "server_e_sk": "c02a638f90e8a9fd25b69252ec7465b7dd33aa905ba947c013b6e6be6edf3909",
+    "server_identifier": "08a499182bb67b86190e2d8315ca1ae4a6cdd503db50bed3d90cc7670dcb9f2b",
+    "server_nonce": "51c529e997a215d410ef6f387a2f1e708729d2316bf27b75138fb440020c1fcd",
+    "server_s_pk": "08a499182bb67b86190e2d8315ca1ae4a6cdd503db50bed3d90cc7670dcb9f2b",
+    "server_s_sk": "852b5300434b5f39f7885ee812255747793e04846b910cf9ae80a5a904bd3a03",
+    "shared_secret": "d0336451e8de1df9a648772c86f97a1b9ed860297f0fafa13faa4e23bb44b9355ec196a25ba39e7db5b9102a06fa09ca778b10aee5acb11f4eb5826b1370e98c",
+    "password_file": "5ed895206bfc53316d307b23e46ecc6623afb3086da74189a416012be037e50b0aa0f9643e2ecaf3075201e9e762371d1b5cdfac674494b2d5ed0b0908c46e5b0090f17bbbfeaf88def8713679002f4825d936a5347afdf4d020a99167843056d60022147e90c0c40a3652bdef905ab2f61386a4119a121f3411c1da6463ac5d0c633c6e6eaf0eb0d45ab8afb746ccd00a1e96cbb2eca09cdc61520772b32978470dcdb0f8aaec7016681d431f194a447bea2c0be84c7191837e362f0f69b7f29676e15d03"
+}
+"#;
+
+fn decode(values: &Value, key: &str) -> Option<Vec<u8>> {
+    values[key]
+        .as_str()
+        .and_then(|s| hex::decode(&s.to_string()).ok())
+}
+
+macro_rules! parse {
+    ( $v:ident, $s:expr ) => {
+        match decode(&$v, $s) {
+            Some(x) => x,
+            None => vec![],
+        }
+    };
+}
+
+fn populate_test_vectors(values: &Value) -> TestVectorParameters {
+    TestVectorParameters {
+        client_s_pk: parse!(values, "client_s_pk"),
+        client_s_sk: parse!(values, "client_s_sk"),
+        client_e_pk: parse!(values, "client_e_pk"),
+        client_e_sk: parse!(values, "client_e_sk"),
+        server_s_pk: parse!(values, "server_s_pk"),
+        server_s_sk: parse!(values, "server_s_sk"),
+        server_e_pk: parse!(values, "server_e_pk"),
+        server_e_sk: parse!(values, "server_e_sk"),
+        client_identifier: parse!(values, "client_identifier"),
+        server_identifier: parse!(values, "server_identifier"),
+        password: parse!(values, "password"),
+        blinding_factor_registration: parse!(values, "blinding_factor_registration"),
+        oprf_key: parse!(values, "oprf_key"),
+        envelope_nonce: parse!(values, "envelope_nonce"),
+        client_nonce: parse!(values, "client_nonce"),
+        server_nonce: parse!(values, "server_nonce"),
+        info1: parse!(values, "info1"),
+        info2: parse!(values, "info2"),
+        registration_request: parse!(values, "registration_request"),
+        registration_response: parse!(values, "registration_response"),
+        registration_upload: parse!(values, "registration_upload"),
+        credential_request: parse!(values, "credential_request"),
+        credential_response: parse!(values, "credential_response"),
+        credential_finalization: parse!(values, "credential_finalization"),
+        blinding_factor_login: parse!(values, "blinding_factor_login"),
+        password_file: parse!(values, "password_file"),
+        export_key: parse!(values, "export_key"),
+        shared_secret: parse!(values, "shared_secret"),
+    }
+}
+
+#[test]
+fn test_registration_request() -> Result<(), ProtocolError> {
+    let parameters = populate_test_vectors(&serde_json::from_str(TEST_VECTOR).unwrap());
+    let mut rng = CycleRng::new(parameters.blinding_factor_registration.to_vec());
+    let client_registration_start_result =
+        ClientRegistration::<Ristretto255Sha512NoSlowHash>::start(&mut rng, &parameters.password)?;
+    assert_eq!(
+        hex::encode(&parameters.registration_request),
+        hex::encode(client_registration_start_result.message.serialize())
+    );
+    Ok(())
+}
+
+#[test]
+fn test_registration_response() -> Result<(), ProtocolError> {
+    let parameters = populate_test_vectors(&serde_json::from_str(TEST_VECTOR).unwrap());
+    let mut oprf_key_rng = CycleRng::new(parameters.oprf_key);
+    let server_registration_start_result =
+        ServerRegistration::<Ristretto255Sha512NoSlowHash>::start(
+            &mut oprf_key_rng,
+            RegistrationRequest::deserialize(&parameters.registration_request[..]).unwrap(),
+            &Key::try_from(&parameters.server_s_pk[..]).unwrap(),
+        )?;
+    assert_eq!(
+        hex::encode(parameters.registration_response),
+        hex::encode(server_registration_start_result.message.serialize())
+    );
+    Ok(())
+}
+
+#[test]
+fn test_registration_upload() -> Result<(), ProtocolError> {
+    let parameters = populate_test_vectors(&serde_json::from_str(TEST_VECTOR).unwrap());
+
+    let mut rng = CycleRng::new(parameters.blinding_factor_registration.to_vec());
+    let client_registration_start_result =
+        ClientRegistration::<Ristretto255Sha512NoSlowHash>::start(&mut rng, &parameters.password)?;
+
+    let sk_u_and_nonce: Vec<u8> = [parameters.client_s_sk, parameters.envelope_nonce].concat();
+    let mut finish_registration_rng = CycleRng::new(sk_u_and_nonce);
+    let result = client_registration_start_result.state.finish(
+        &mut finish_registration_rng,
+        RegistrationResponse::deserialize(&parameters.registration_response[..]).unwrap(),
+        ClientRegistrationFinishParameters::default(),
+    )?;
+
+    assert_eq!(
+        hex::encode(parameters.registration_upload),
+        hex::encode(result.message.serialize())
+    );
+    assert_eq!(
+        hex::encode(parameters.export_key),
+        hex::encode(result.export_key.to_vec())
+    );
+
+    Ok(())
+}
+
+#[test]
+fn test_password_file() -> Result<(), ProtocolError> {
+    let parameters = populate_test_vectors(&serde_json::from_str(TEST_VECTOR).unwrap());
+
+    let mut oprf_key_rng = CycleRng::new(parameters.oprf_key);
+    let server_registration_start_result =
+        ServerRegistration::<Ristretto255Sha512NoSlowHash>::start(
+            &mut oprf_key_rng,
+            RegistrationRequest::deserialize(&parameters.registration_request[..]).unwrap(),
+            &Key::try_from(&parameters.server_s_pk[..]).unwrap(),
+        )?;
+
+    let password_file = server_registration_start_result
+        .state
+        .finish(RegistrationUpload::deserialize(&parameters.registration_upload[..]).unwrap())?;
+    assert_eq!(
+        hex::encode(parameters.password_file),
+        hex::encode(password_file.to_bytes())
+    );
+    Ok(())
+}
+
+#[test]
+fn test_credential_request() -> Result<(), ProtocolError> {
+    let parameters = populate_test_vectors(&serde_json::from_str(TEST_VECTOR).unwrap());
+
+    let client_login_start = [
+        parameters.blinding_factor_login,
+        parameters.client_e_sk,
+        parameters.client_nonce,
+    ]
+    .concat();
+    let mut client_login_start_rng = CycleRng::new(client_login_start);
+    let client_login_start_result = ClientLogin::<Ristretto255Sha512NoSlowHash>::start(
+        &mut client_login_start_rng,
+        &parameters.password,
+        ClientLoginStartParameters::WithInfo(parameters.info1),
+    )?;
+    assert_eq!(
+        hex::encode(&parameters.credential_request),
+        hex::encode(client_login_start_result.message.serialize())
+    );
+    Ok(())
+}
+
+#[test]
+fn test_credential_response() -> Result<(), ProtocolError> {
+    let parameters = populate_test_vectors(&serde_json::from_str(TEST_VECTOR).unwrap());
+
+    let mut server_e_sk_and_nonce_rng =
+        CycleRng::new([parameters.server_e_sk, parameters.server_nonce].concat());
+    let server_login_start_result = ServerLogin::<Ristretto255Sha512NoSlowHash>::start(
+        &mut server_e_sk_and_nonce_rng,
+        ServerRegistration::try_from(&parameters.password_file[..]).unwrap(),
+        &Key::try_from(&parameters.server_s_sk[..]).unwrap(),
+        CredentialRequest::<Ristretto255Sha512NoSlowHash>::deserialize(
+            &parameters.credential_request[..],
+        )
+        .unwrap(),
+        ServerLoginStartParameters::WithInfo(parameters.info2.to_vec()),
+    )?;
+    assert_eq!(
+        hex::encode(&parameters.info1),
+        hex::encode(server_login_start_result.plain_info),
+    );
+    assert_eq!(
+        hex::encode(&parameters.credential_response),
+        hex::encode(server_login_start_result.message.serialize())
+    );
+    Ok(())
+}
+
+#[test]
+fn test_credential_finalization() -> Result<(), ProtocolError> {
+    let parameters = populate_test_vectors(&serde_json::from_str(TEST_VECTOR).unwrap());
+
+    let client_login_start = [
+        parameters.blinding_factor_login,
+        parameters.client_e_sk,
+        parameters.client_nonce,
+    ]
+    .concat();
+    let mut client_login_start_rng = CycleRng::new(client_login_start);
+    let client_login_start_result = ClientLogin::<Ristretto255Sha512NoSlowHash>::start(
+        &mut client_login_start_rng,
+        &parameters.password,
+        ClientLoginStartParameters::WithInfo(parameters.info1),
+    )?;
+
+    let client_login_finish_result = client_login_start_result.state.finish(
+        CredentialResponse::<Ristretto255Sha512NoSlowHash>::deserialize(
+            &parameters.credential_response[..],
+        )?,
+        ClientLoginFinishParameters::default(),
+    )?;
+
+    assert_eq!(
+        hex::encode(&parameters.info2),
+        hex::encode(&client_login_finish_result.confidential_info)
+    );
+    assert_eq!(
+        hex::encode(&parameters.shared_secret),
+        hex::encode(&client_login_finish_result.shared_secret)
+    );
+    assert_eq!(
+        hex::encode(&parameters.credential_finalization),
+        hex::encode(client_login_finish_result.message.to_bytes())
+    );
+    assert_eq!(
+        hex::encode(&parameters.export_key),
+        hex::encode(client_login_finish_result.export_key)
+    );
+    Ok(())
+}
+
+#[test]
+fn test_server_login_finish() -> Result<(), ProtocolError> {
+    let parameters = populate_test_vectors(&serde_json::from_str(TEST_VECTOR).unwrap());
+
+    let mut server_e_sk_and_nonce_rng =
+        CycleRng::new([parameters.server_e_sk, parameters.server_nonce].concat());
+    let server_login_start_result = ServerLogin::<Ristretto255Sha512NoSlowHash>::start(
+        &mut server_e_sk_and_nonce_rng,
+        ServerRegistration::try_from(&parameters.password_file[..]).unwrap(),
+        &Key::try_from(&parameters.server_s_sk[..]).unwrap(),
+        CredentialRequest::<Ristretto255Sha512NoSlowHash>::deserialize(
+            &parameters.credential_request[..],
+        )
+        .unwrap(),
+        ServerLoginStartParameters::WithInfo(parameters.info2.to_vec()),
+    )?;
+
+    let server_login_result =
+        server_login_start_result
+            .state
+            .finish(CredentialFinalization::try_from(
+                &parameters.credential_finalization[..],
+            )?)?;
+
+    assert_eq!(
+        hex::encode(parameters.shared_secret),
+        hex::encode(server_login_result.shared_secret)
+    );
+
+    Ok(())
+}
+
+fn test_complete_flow(
+    registration_password: &[u8],
+    login_password: &[u8],
+) -> Result<(), ProtocolError> {
+    let mut client_rng = OsRng;
+    let mut server_rng = OsRng;
+    let server_kp = Ristretto255Sha512NoSlowHash::generate_random_keypair(&mut server_rng);
+    let client_registration_start_result =
+        ClientRegistration::<Ristretto255Sha512NoSlowHash>::start(
+            &mut client_rng,
+            registration_password,
+        )?;
+    let server_registration_start_result =
+        ServerRegistration::<Ristretto255Sha512NoSlowHash>::start(
+            &mut server_rng,
+            client_registration_start_result.message,
+            server_kp.public(),
+        )?;
+    let client_registration_finish_result = client_registration_start_result.state.finish(
+        &mut client_rng,
+        server_registration_start_result.message,
+        ClientRegistrationFinishParameters::default(),
+    )?;
+    let p_file = server_registration_start_result
+        .state
+        .finish(client_registration_finish_result.message)?;
+    let client_login_start_result = ClientLogin::<Ristretto255Sha512NoSlowHash>::start(
+        &mut client_rng,
+        login_password,
+        ClientLoginStartParameters::default(),
+    )?;
+    let server_login_start_result = ServerLogin::<Ristretto255Sha512NoSlowHash>::start(
+        &mut server_rng,
+        p_file,
+        &server_kp.private(),
+        client_login_start_result.message,
+        ServerLoginStartParameters::default(),
+    )?;
+
+    let client_login_result = client_login_start_result.state.finish(
+        server_login_start_result.message,
+        ClientLoginFinishParameters::default(),
+    );
+
+    if hex::encode(registration_password) == hex::encode(login_password) {
+        let client_login_finish_result = client_login_result?;
+        let server_login_finish_result = server_login_start_result
+            .state
+            .finish(client_login_finish_result.message)?;
+
+        assert_eq!(
+            hex::encode(server_login_finish_result.shared_secret),
+            hex::encode(client_login_finish_result.shared_secret)
+        );
+        assert_eq!(
+            hex::encode(client_registration_finish_result.export_key),
+            hex::encode(client_login_finish_result.export_key)
+        );
+    } else {
+        let res = matches!(
+            client_login_result,
+            Err(ProtocolError::VerificationError(
+                PakeError::InvalidLoginError
+            ))
+        );
+        assert!(res);
+    }
+
+    Ok(())
+}
+
+#[test]
+fn test_complete_flow_success() -> Result<(), ProtocolError> {
+    test_complete_flow(b"good password", b"good password")
+}
+
+#[test]
+fn test_complete_flow_fail() -> Result<(), ProtocolError> {
+    test_complete_flow(b"good password", b"bad password")
+}