Repository: novifinancial/opaque-ke
Pull request #76 (2020-11-03 18:47:33)

Bump Crate version => 0.3.0-pre.1


Patch:
diff --git a/Cargo.lock b/Cargo.lock
index a220eba..2dab8d2 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -508,7 +508,7 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
 
 [[package]]
 name = "opaque-ke"
-version = "0.2.1"
+version = "0.3.0-pre.1"
 dependencies = [
  "anyhow",
  "base64",
diff --git a/Cargo.toml b/Cargo.toml
index c9f0e4f..3f5cdc6 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "opaque-ke"
-version = "0.2.1"
+version = "0.3.0-pre.1"
 repository = "https://github.com/novifinancial/opaque-ke"
 keywords = ["cryptography", "crypto", "opaque", "passwords", "authentication"]
 description = "An implementation of the OPAQUE password-authenticated key exchange protocol"
diff --git a/benches/oprf.rs b/benches/oprf.rs
index 899626e..13aa16f 100644
--- a/benches/oprf.rs
+++ b/benches/oprf.rs
@@ -7,11 +7,12 @@
 extern crate criterion;
 
 use criterion::Criterion;
-use curve25519_dalek::{edwards::EdwardsPoint, ristretto::RistrettoPoint};
+use curve25519_dalek::edwards::EdwardsPoint;
+use curve25519_dalek::ristretto::RistrettoPoint;
 use generic_array::arr;
 use opaque_ke::{
     group::Group,
-    oprf::{blind_shim, evaluate_shim, unblind_and_finalize_shim},
+    oprf::{generate_oprf1_shim, generate_oprf2_shim, generate_oprf3_shim, OprfClientBytes},
 };
 use rand::{prelude::ThreadRng, thread_rng};
 use sha2::Sha256;
@@ -20,9 +21,12 @@ fn oprf1(c: &mut Criterion) {
     let mut csprng: ThreadRng = thread_rng();
     let input = b"hunter2";
 
-    c.bench_function("blind with Ristretto", move |b| {
+    c.bench_function("generate_oprf1 with Ristretto", move |b| {
         b.iter(|| {
-            blind_shim::<_, RistrettoPoint>(&input[..], &mut csprng).unwrap();
+            let OprfClientBytes {
+                alpha: _alpha,
+                blinding_factor: _blinding_factor,
+            } = generate_oprf1_shim::<_, RistrettoPoint>(&input[..], None, &mut csprng).unwrap();
         })
     });
 }
@@ -31,9 +35,12 @@ fn oprf1_edwards(c: &mut Criterion) {
     let mut csprng: ThreadRng = thread_rng();
     let input = b"hunter2";
 
-    c.bench_function("blind with Edwards", move |b| {
+    c.bench_function("generate_oprf1 with Edwards", move |b| {
         b.iter(|| {
-            blind_shim::<_, EdwardsPoint>(&input[..], &mut csprng).unwrap();
+            let OprfClientBytes {
+                alpha: _alpha,
+                blinding_factor: _blinding_factor,
+            } = generate_oprf1_shim::<_, EdwardsPoint>(&input[..], None, &mut csprng).unwrap();
         })
     });
 }
@@ -42,16 +49,19 @@ fn oprf2(c: &mut Criterion) {
     let mut csprng: ThreadRng = thread_rng();
     let input = b"hunter2";
 
-    let (_, alpha) = blind_shim::<_, RistrettoPoint>(&input[..], &mut csprng).unwrap();
+    let OprfClientBytes {
+        alpha,
+        blinding_factor: _blinding_factor,
+    } = generate_oprf1_shim::<_, RistrettoPoint>(&input[..], None, &mut csprng).unwrap();
     let salt_bytes = arr![
         u8; 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
         24, 25, 26, 27, 28, 29, 30, 31, 32,
     ];
     let salt = RistrettoPoint::from_scalar_slice(&salt_bytes).unwrap();
 
-    c.bench_function("evaluate with Ristretto", move |b| {
+    c.bench_function("generate_oprf2 with Ristretto", move |b| {
         b.iter(|| {
-            let _beta = evaluate_shim::<RistrettoPoint>(alpha, &salt).unwrap();
+            let _beta = generate_oprf2_shim::<RistrettoPoint>(alpha, &salt).unwrap();
         })
     });
 }
@@ -60,16 +70,19 @@ fn oprf2_edwards(c: &mut Criterion) {
     let mut csprng: ThreadRng = thread_rng();
     let input = b"hunter2";
 
-    let (_, alpha) = blind_shim::<_, EdwardsPoint>(&input[..], &mut csprng).unwrap();
+    let OprfClientBytes {
+        alpha,
+        blinding_factor: _blinding_factor,
+    } = generate_oprf1_shim::<_, EdwardsPoint>(&input[..], None, &mut csprng).unwrap();
     let salt_bytes = arr![
         u8; 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
         24, 25, 26, 27, 28, 29, 30, 31, 32,
     ];
     let salt = RistrettoPoint::from_scalar_slice(&salt_bytes).unwrap();
 
-    c.bench_function("evaluate with Edwards", move |b| {
+    c.bench_function("generate_oprf2 with Edwards", move |b| {
         b.iter(|| {
-            let _beta = evaluate_shim::<EdwardsPoint>(alpha, &salt).unwrap();
+            let _beta = generate_oprf2_shim::<EdwardsPoint>(alpha, &salt).unwrap();
         })
     });
 }
@@ -78,17 +91,21 @@ fn oprf3(c: &mut Criterion) {
     let mut csprng: ThreadRng = thread_rng();
     let input = b"hunter2";
 
-    let (token, alpha) = blind_shim::<_, RistrettoPoint>(&input[..], &mut csprng).unwrap();
+    let OprfClientBytes {
+        alpha,
+        blinding_factor,
+    } = generate_oprf1_shim::<_, RistrettoPoint>(&input[..], None, &mut csprng).unwrap();
     let salt_bytes = arr![
         u8; 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
         24, 25, 26, 27, 28, 29, 30, 31, 32,
     ];
     let salt = RistrettoPoint::from_scalar_slice(&salt_bytes).unwrap();
-    let beta = evaluate_shim::<RistrettoPoint>(alpha, &salt).unwrap();
+    let beta = generate_oprf2_shim::<RistrettoPoint>(alpha, &salt).unwrap();
 
-    c.bench_function("unblind_and_finalize with Ristretto", move |b| {
+    c.bench_function("generate_oprf3 with Ristretto", move |b| {
         b.iter(|| {
-            let _res = unblind_and_finalize_shim::<RistrettoPoint, Sha256>(&token, beta).unwrap();
+            let _res = generate_oprf3_shim::<RistrettoPoint, Sha256>(input, beta, &blinding_factor)
+                .unwrap();
         })
     });
 }
@@ -97,17 +114,21 @@ fn oprf3_edwards(c: &mut Criterion) {
     let mut csprng: ThreadRng = thread_rng();
     let input = b"hunter2";
 
-    let (token, alpha) = blind_shim::<_, EdwardsPoint>(&input[..], &mut csprng).unwrap();
+    let OprfClientBytes {
+        alpha,
+        blinding_factor,
+    } = generate_oprf1_shim::<_, EdwardsPoint>(&input[..], None, &mut csprng).unwrap();
     let salt_bytes = arr![
         u8; 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
         24, 25, 26, 27, 28, 29, 30, 31, 32,
     ];
     let salt = RistrettoPoint::from_scalar_slice(&salt_bytes).unwrap();
-    let beta = evaluate_shim::<EdwardsPoint>(alpha, &salt).unwrap();
+    let beta = generate_oprf2_shim::<EdwardsPoint>(alpha, &salt).unwrap();
 
-    c.bench_function("unblind_and_finalize with Edwards", move |b| {
+    c.bench_function("generate_oprf3 with Edwards", move |b| {
         b.iter(|| {
-            let _res = unblind_and_finalize_shim::<EdwardsPoint, Sha256>(&token, beta).unwrap();
+            let _res =
+                generate_oprf3_shim::<EdwardsPoint, Sha256>(input, beta, &blinding_factor).unwrap();
         })
     });
 }
diff --git a/examples/simple_login.rs b/examples/simple_login.rs
index 50f0f0c..9dd7b84 100644
--- a/examples/simple_login.rs
+++ b/examples/simple_login.rs
@@ -56,8 +56,9 @@ fn account_registration(
 ) -> Vec<u8> {
     let mut client_rng = OsRng;
     let (r1, client_state) =
-        ClientRegistration::<Default>::start(password.as_bytes(), &mut client_rng).unwrap();
-    let r1_bytes = r1.serialize();
+        ClientRegistration::<Default>::start(password.as_bytes(), Some(b"pepper"), &mut client_rng)
+            .unwrap();
+    let r1_bytes = r1.to_bytes();
 
     // Client sends r1_bytes to server
 
@@ -67,7 +68,7 @@ fn account_registration(
         &mut server_rng,
     )
     .unwrap();
-    let r2_bytes = r2.serialize();
+    let r2_bytes = r2.to_bytes();
 
     // Server sends r2_bytes to client
 
@@ -78,7 +79,7 @@ fn account_registration(
             &mut client_rng,
         )
         .unwrap();
-    let r3_bytes = r3.serialize();
+    let r3_bytes = r3.to_bytes();
 
     // Client sends r3_bytes to server
 
@@ -96,8 +97,9 @@ fn account_login(
 ) -> bool {
     let mut client_rng = OsRng;
     let (l1, client_state) =
-        ClientLogin::<Default>::start(password.as_bytes(), &mut client_rng).unwrap();
-    let l1_bytes = l1.serialize();
+        ClientLogin::<Default>::start(password.as_bytes(), Some(b"pepper"), &mut client_rng)
+            .unwrap();
+    let l1_bytes = l1.to_bytes();
 
     // Client sends l1_bytes to server
 
@@ -110,7 +112,7 @@ fn account_login(
         &mut server_rng,
     )
     .unwrap();
-    let l2_bytes = l2.serialize();
+    let l2_bytes = l2.to_bytes();
 
     // Server sends l2_bytes to client
 
@@ -125,7 +127,7 @@ fn account_login(
         return false;
     }
     let (l3, client_shared_secret, _) = result.unwrap();
-    let l3_bytes = l3.serialize();
+    let l3_bytes = l3.to_bytes();
 
     // Client sends l3_bytes to server
 
diff --git a/src/elligator/field.rs b/src/elligator/field.rs
index 511d5be..1c070e7 100644
--- a/src/elligator/field.rs
+++ b/src/elligator/field.rs
@@ -7,12 +7,15 @@
 //! Field arithmetic modulo \\(p = 2\^{255} - 19\\), using \\(64\\)-bit
 //! limbs with \\(128\\)-bit products.
 
-use core::{
-    fmt::Debug,
-    ops::{Add, AddAssign, Mul, MulAssign, Neg},
-};
-
-use subtle::{Choice, ConditionallyNegatable, ConditionallySelectable, ConstantTimeEq};
+use core::fmt::Debug;
+use core::ops::Neg;
+use core::ops::{Add, AddAssign};
+use core::ops::{Mul, MulAssign};
+
+use subtle::Choice;
+use subtle::ConditionallyNegatable;
+use subtle::ConditionallySelectable;
+use subtle::ConstantTimeEq;
 
 use zeroize::Zeroize;
 
diff --git a/src/envelope.rs b/src/envelope.rs
index 02ec225..79f4252 100644
--- a/src/envelope.rs
+++ b/src/envelope.rs
@@ -3,11 +3,8 @@
 // This source code is licensed under the MIT license found in the
 // LICENSE file in the root directory of this source tree.
 
-use crate::{
-    errors::{InternalPakeError, PakeError, ProtocolError},
-    hash::Hash,
-    serialization::{serialize, tokenize, u8_to_credential_type, CredentialType},
-};
+use crate::errors::InternalPakeError;
+use crate::hash::Hash;
 use digest::Digest;
 use generic_array::{
     typenum::{Unsigned, U32},
@@ -16,7 +13,6 @@ use generic_array::{
 use hkdf::Hkdf;
 use hmac::{Hmac, Mac, NewMac};
 use rand_core::{CryptoRng, RngCore};
-use std::collections::HashMap;
 
 // Constant string used as salt for HKDF computation
 const STR_ENVU: &[u8] = b"EnvU";
@@ -40,59 +36,14 @@ const NONCE_LEN: usize = 32;
 pub(crate) struct Envelope<D: Hash> {
     nonce: Vec<u8>,
     ciphertext: Vec<u8>,
-    auth_data: Vec<u8>,
     hmac: GenericArray<u8, <D as Digest>::OutputSize>,
 }
 
-pub(crate) struct OpenedEnvelopeECF {
-    pub(crate) credentials_map: HashMap<CredentialType, Vec<u8>>,
-    pub(crate) export_key: GenericArray<u8, ExportKeySize>,
-}
-
 pub(crate) struct OpenedEnvelope {
     pub(crate) plaintext: Vec<u8>,
     pub(crate) export_key: GenericArray<u8, ExportKeySize>,
 }
 
-/// Representation for the format of the envelope
-pub struct EnvelopeCredentialsFormat {
-    pub(crate) secret_credentials: Vec<CredentialType>,
-    pub(crate) cleartext_credentials: Vec<CredentialType>,
-}
-
-impl EnvelopeCredentialsFormat {
-    /// Creates a new envelope credentials format with validity checking
-    /// An ECF is valid if:
-    /// - skU is a secret credential
-    /// - pkS is either a secret or cleartext credential
-    pub fn new(
-        secret_credentials: Vec<CredentialType>,
-        cleartext_credentials: Vec<CredentialType>,
-    ) -> Result<Self, ProtocolError> {
-        if !secret_credentials.iter().any(|&v| v == CredentialType::SkU) {
-            // No skU found in secret credentials
-            return Err(ProtocolError::ServerInvalidEnvelopeCredentialsFormatError);
-        }
-        if !secret_credentials.iter().any(|&v| v == CredentialType::PkS)
-            && !cleartext_credentials
-                .iter()
-                .any(|&v| v == CredentialType::PkS)
-        {
-            // No pkS found in either secret credentials or cleartext_credentials
-            return Err(ProtocolError::ServerInvalidEnvelopeCredentialsFormatError);
-        }
-        Ok(Self {
-            secret_credentials,
-            cleartext_credentials,
-        })
-    }
-
-    /// Uses the default setting for the envelope credentials format
-    pub fn default() -> Result<Self, ProtocolError> {
-        Self::new(vec![CredentialType::SkU], vec![CredentialType::PkS])
-    }
-}
-
 impl<D: Hash> Envelope<D> {
     /// The additional number of bytes added to the plaintext
     pub(crate) fn additional_size() -> usize {
@@ -103,6 +54,10 @@ impl<D: Hash> Envelope<D> {
         <D as Digest>::OutputSize::to_usize()
     }
 
+    fn hmac_size() -> usize {
+        <D as Digest>::OutputSize::to_usize()
+    }
+
     fn export_key_size() -> usize {
         ExportKeySize::to_usize()
     }
@@ -110,13 +65,11 @@ impl<D: Hash> Envelope<D> {
     pub(crate) fn new(
         nonce: Vec<u8>,
         ciphertext: Vec<u8>,
-        auth_data: Vec<u8>,
         hmac: GenericArray<u8, <D as Digest>::OutputSize>,
     ) -> Self {
         Self {
             nonce,
             ciphertext,
-            auth_data,
             hmac,
         }
     }
@@ -125,98 +78,23 @@ impl<D: Hash> Envelope<D> {
     /// nonce             | ciphertext       | hmac
     /// nonce_size bytes  | variable length  | hmac_size bytes
     pub(crate) fn from_bytes(bytes: &[u8]) -> Result<Self, InternalPakeError> {
-        let (result, remainder) = Self::deserialize(bytes)
-            .map_err(|_| InternalPakeError::IncompatibleEnvelopeCredentialsError)?;
-        if !remainder.is_empty() {
-            return Err(InternalPakeError::IncompatibleEnvelopeCredentialsError);
-        }
-        Ok(result)
-    }
-
-    pub(crate) fn to_bytes(&self) -> Vec<u8> {
-        self.serialize()
-    }
-
-    pub(crate) fn serialize(&self) -> Vec<u8> {
-        [
-            &self.nonce[..],
-            &serialize(&self.ciphertext, 2)[..],
-            &serialize(&self.auth_data, 2)[..],
-            &serialize(&self.hmac, 2)[..],
-        ]
-        .concat()
-    }
+        let ciphertext_start = NONCE_LEN;
+        let ciphertext_end = bytes.len() - Self::hmac_size();
 
-    pub(crate) fn deserialize(input: &[u8]) -> Result<(Self, Vec<u8>), ProtocolError> {
-        if input.len() < NONCE_LEN {
-            return Err(ProtocolError::VerificationError(
-                PakeError::SerializationError,
-            ));
-        }
-
-        let nonce = &input[..NONCE_LEN];
-        let (ciphertext, remainder) = tokenize(input[NONCE_LEN..].to_vec(), 2)?;
-        let (auth_data, remainder) = tokenize(remainder, 2)?;
-        let (hmac, remainder) = tokenize(remainder, 2)?;
-        Ok((
-            Self::new(
-                nonce.to_vec(),
-                ciphertext,
-                auth_data,
-                GenericArray::clone_from_slice(&hmac[..]),
-            ),
-            remainder,
+        Ok(Self::new(
+            bytes[..ciphertext_start].to_vec(),
+            bytes[ciphertext_start..ciphertext_end].to_vec(),
+            GenericArray::clone_from_slice(&bytes[ciphertext_end..]),
         ))
     }
 
-    fn serialize_extensions(
-        cred_format: Vec<CredentialType>,
-        credentials: &HashMap<CredentialType, Vec<u8>>,
-    ) -> Result<Vec<u8>, InternalPakeError> {
-        let mut ret = Vec::new();
-        for index_type in cred_format {
-            match &credentials.get(&index_type) {
-                Some(v) => {
-                    ret.push(index_type as u8 + 1);
-                    ret.extend(serialize(&v, 2));
-                }
-                None => return Err(InternalPakeError::IncompatibleEnvelopeCredentialsError),
-            }
-        }
-        Ok(ret)
-    }
-
-    fn deserialize_extensions(
-        bytes: &[u8],
-    ) -> Result<HashMap<CredentialType, Vec<u8>>, InternalPakeError> {
-        let mut credentials: HashMap<CredentialType, Vec<u8>> = HashMap::new();
-        let mut bytes_copy: Vec<u8> = Vec::new();
-        bytes_copy.extend_from_slice(&bytes);
-        while !bytes_copy.is_empty() {
-            let t = u8_to_credential_type(bytes_copy[0])
-                .ok_or(InternalPakeError::IncompatibleEnvelopeCredentialsError)?;
-            let (cred, remainder) = tokenize(bytes_copy[1..].to_vec(), 2)
-                .map_err(|_| InternalPakeError::IncompatibleEnvelopeCredentialsError)?;
-            bytes_copy = remainder;
-            credentials.insert(t, cred);
-        }
-        Ok(credentials)
-    }
-
-    pub(crate) fn seal<R: RngCore + CryptoRng>(
-        key: &[u8],
-        ecf: EnvelopeCredentialsFormat,
-        credentials: HashMap<CredentialType, Vec<u8>>,
-        rng: &mut R,
-    ) -> Result<(Self, GenericArray<u8, ExportKeySize>), InternalPakeError> {
-        let plaintext = Self::serialize_extensions(ecf.secret_credentials, &credentials)?;
-        let aad = Self::serialize_extensions(ecf.cleartext_credentials, &credentials)?;
-        Self::seal_raw(key, &plaintext, &aad, rng)
+    pub(crate) fn to_bytes(&self) -> Vec<u8> {
+        [&self.nonce[..], &self.ciphertext[..], &self.hmac[..]].concat()
     }
 
     /// Uses a key to convert the plaintext into an envelope, authenticated by the aad field.
     /// Note that a new nonce is sampled for each call to seal.
-    pub(crate) fn seal_raw<R: RngCore + CryptoRng>(
+    pub(crate) fn seal<R: RngCore + CryptoRng>(
         key: &[u8],
         plaintext: &[u8],
         aad: &[u8],
@@ -246,42 +124,14 @@ impl<D: Hash> Envelope<D> {
         hmac.update(&aad);
 
         Ok((
-            Self::new(
-                nonce,
-                ciphertext.to_vec(),
-                aad.to_vec(),
-                hmac.finalize().into_bytes(),
-            ),
+            Self::new(nonce, ciphertext.to_vec(), hmac.finalize().into_bytes()),
             *GenericArray::from_slice(&export_key),
         ))
     }
 
-    pub(crate) fn open(&self, key: &[u8]) -> Result<OpenedEnvelopeECF, InternalPakeError> {
-        let mut credentials_map = Self::deserialize_extensions(&self.auth_data)?;
-        let opened = self.open_raw(key, &self.auth_data)?;
-        let plaintext_map = Self::deserialize_extensions(&opened.plaintext)?;
-
-        for (i, plaintext) in plaintext_map {
-            if credentials_map.contains_key(&i) {
-                // Trying to set a credential that was already provided in the aad
-                return Err(InternalPakeError::IncompatibleEnvelopeCredentialsError);
-            }
-            credentials_map.insert(i, plaintext);
-        }
-
-        Ok(OpenedEnvelopeECF {
-            credentials_map,
-            export_key: opened.export_key,
-        })
-    }
-
     /// Attempts to decrypt the envelope using a key, which is successful only if the key and
     /// aad used to construct the envelope are the same.
-    pub(crate) fn open_raw(
-        &self,
-        key: &[u8],
-        aad: &[u8],
-    ) -> Result<OpenedEnvelope, InternalPakeError> {
+    pub(crate) fn open(&self, key: &[u8], aad: &[u8]) -> Result<OpenedEnvelope, 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()];
@@ -327,8 +177,8 @@ mod tests {
         rng.fill_bytes(&mut msg);
 
         let (envelope, export_key_1) =
-            Envelope::<sha2::Sha256>::seal_raw(&key, &msg, b"aad", &mut rng).unwrap();
-        let opened_envelope = envelope.open_raw(&key, b"aad").unwrap();
+            Envelope::<sha2::Sha256>::seal(&key, &msg, b"aad", &mut rng).unwrap();
+        let opened_envelope = envelope.open(&key, b"aad").unwrap();
         assert_eq!(&msg.to_vec(), &opened_envelope.plaintext);
         assert_eq!(&export_key_1.to_vec(), &opened_envelope.export_key.to_vec());
     }
diff --git a/src/errors.rs b/src/errors.rs
index 359ad13..6be81bf 100644
--- a/src/errors.rs
+++ b/src/errors.rs
@@ -40,9 +40,6 @@ pub enum InternalPakeError {
     /// This error occurs when the envelope seal open hmac check fails
     /// HMAC check in seal open failed.
     SealOpenHmacError,
-    /// This error occurs when the envelope cannot be constructed properly
-    /// based on the credentials that were specified to be required.
-    IncompatibleEnvelopeCredentialsError,
 }
 
 /// Represents an error in password checking
@@ -61,8 +58,6 @@ pub enum PakeError {
     KeyExchangeMacValidationError,
     /// Error in validating credentials
     InvalidLoginError,
-    /// Error with serializing / deserializing protocol messages
-    SerializationError,
 }
 
 // This is meant to express future(ly) non-trivial ways of converting the
@@ -83,9 +78,6 @@ pub enum ProtocolError {
     /// This error occurs when the server answer cannot be handled
     /// Server response cannot be handled.
     ServerError,
-    /// This error occurs when the server specifies an envelope credentials
-    /// format that is invalid
-    ServerInvalidEnvelopeCredentialsFormatError,
     /// This error occurs when the client request cannot be handled
     /// Client request cannot be handled.
     ClientError,
@@ -133,19 +125,4 @@ pub(crate) mod utils {
         }
         Ok(slice)
     }
-
-    pub fn check_slice_size_atleast<'a>(
-        slice: &'a [u8],
-        expected_len: usize,
-        arg_name: &'static str,
-    ) -> Result<&'a [u8], InternalPakeError> {
-        if slice.len() < expected_len {
-            return Err(InternalPakeError::SizeError {
-                name: arg_name,
-                len: expected_len,
-                actual_len: slice.len(),
-            });
-        }
-        Ok(slice)
-    }
 }
diff --git a/src/group.rs b/src/group.rs
index de3e4d7..7a4ed55 100644
--- a/src/group.rs
+++ b/src/group.rs
@@ -6,7 +6,8 @@
 //! Defines the Group trait to specify the underlying prime order group used in
 //! OPAQUE's OPRF
 
-use crate::{elligator, errors::InternalPakeError};
+use crate::elligator;
+use crate::errors::InternalPakeError;
 
 use curve25519_dalek::{
     edwards::{CompressedEdwardsY, EdwardsPoint},
diff --git a/src/key_exchange/tripledh.rs b/src/key_exchange/tripledh.rs
index 3ab08b5..dab106b 100644
--- a/src/key_exchange/tripledh.rs
+++ b/src/key_exchange/tripledh.rs
@@ -9,7 +9,6 @@ use crate::{
     hash::Hash,
     key_exchange::traits::{KeyExchange, ToBytes},
     keypair::{KeyPair, SizedBytes},
-    serialization::serialize,
 };
 use digest::{Digest, FixedOutput};
 use generic_array::{
@@ -28,11 +27,6 @@ pub(crate) type NonceLen = U32;
 const KE1_STATE_LEN: usize = KEY_LEN + KEY_LEN + NONCE_LEN;
 
 static STR_3DH: &[u8] = b"3DH keys";
-static STR_CLIENT_MAC: &[u8] = b"client mac";
-static STR_HANDSHAKE_SECRET: &[u8] = b"handshake secret";
-static STR_SERVER_MAC: &[u8] = b"server mac";
-static STR_SESSION_SECRET: &[u8] = b"session secret";
-static STR_OPAQUE: &[u8] = b"OPAQUE ";
 
 /// The Triple Diffie-Hellman key exchange implementation
 pub struct TripleDH;
@@ -90,7 +84,7 @@ impl<D: Hash, KeyFormat: KeyPair> KeyExchange<D, KeyFormat> for TripleDH {
             server_nonce_bytes.into()
         };
 
-        let (session_secret, km2, km3) = derive_3dh_keys::<KeyFormat, D>(
+        let (shared_secret, km2, km3) = derive_3dh_keys::<KeyFormat, D>(
             TripleDHComponents {
                 pk1: ke1_message.client_e_pk.clone(),
                 sk1: server_e_kp.private().clone(),
@@ -128,7 +122,7 @@ impl<D: Hash, KeyFormat: KeyPair> KeyExchange<D, KeyFormat> for TripleDH {
             KE2State {
                 km3,
                 hashed_transcript,
-                session_secret,
+                shared_secret,
             },
             KE2Message {
                 server_nonce,
@@ -145,7 +139,7 @@ impl<D: Hash, KeyFormat: KeyPair> KeyExchange<D, KeyFormat> for TripleDH {
         server_s_pk: KeyFormat::Repr,
         client_s_sk: KeyFormat::Repr,
     ) -> Result<(Vec<u8>, Self::KE3Message), ProtocolError> {
-        let (session_secret, km2, km3) = derive_3dh_keys::<KeyFormat, D>(
+        let (shared_secret, km2, km3) = derive_3dh_keys::<KeyFormat, D>(
             TripleDHComponents {
                 pk1: ke2_message.server_e_pk.clone(),
                 sk1: ke1_state.client_e_sk.clone(),
@@ -187,7 +181,7 @@ impl<D: Hash, KeyFormat: KeyPair> KeyExchange<D, KeyFormat> for TripleDH {
         client_mac.update(&hashed_transcript);
 
         Ok((
-            session_secret.to_vec(),
+            shared_secret.to_vec(),
             KE3Message {
                 mac: client_mac.finalize().into_bytes(),
             },
@@ -208,7 +202,7 @@ impl<D: Hash, KeyFormat: KeyPair> KeyExchange<D, KeyFormat> for TripleDH {
             ));
         }
 
-        Ok(ke2_state.session_secret.to_vec())
+        Ok(ke2_state.shared_secret.to_vec())
     }
 
     fn ke1_state_size() -> usize {
@@ -291,7 +285,7 @@ impl<KeyFormat: KeyPair> TryFrom<&[u8]> for KE1Message<KeyFormat> {
 pub struct KE2State<HashLen: ArrayLength<u8>> {
     km3: GenericArray<u8, HashLen>,
     hashed_transcript: GenericArray<u8, HashLen>,
-    session_secret: GenericArray<u8, HashLen>,
+    shared_secret: GenericArray<u8, HashLen>,
 }
 
 /// The second key exchange message
@@ -306,7 +300,7 @@ impl<HashLen: ArrayLength<u8>> ToBytes for KE2State<HashLen> {
         let output: Vec<u8> = [
             &self.km3[..],
             &self.hashed_transcript[..],
-            &self.session_secret[..],
+            &self.shared_secret[..],
         ]
         .concat();
         output
@@ -322,7 +316,7 @@ impl<HashLen: ArrayLength<u8>> TryFrom<&[u8]> for KE2State<HashLen> {
         Ok(Self {
             km3: GenericArray::clone_from_slice(&checked_bytes[..KEY_LEN]),
             hashed_transcript: GenericArray::clone_from_slice(&checked_bytes[KEY_LEN..2 * KEY_LEN]),
-            session_secret: GenericArray::clone_from_slice(&checked_bytes[2 * KEY_LEN..]),
+            shared_secret: GenericArray::clone_from_slice(&checked_bytes[2 * KEY_LEN..]),
         })
     }
 }
@@ -368,38 +362,13 @@ struct TripleDHComponents<KeyFormat: KeyPair> {
     sk3: KeyFormat::Repr,
 }
 
-// Consists of a shared secret, followed by two mac keys: (session_secret, km2, km3)
+// Consists of a shared secret, followed by two mac keys
 type TripleDHDerivationResult<D> = (
     GenericArray<u8, <D as FixedOutput>::OutputSize>,
     GenericArray<u8, <D as FixedOutput>::OutputSize>,
     GenericArray<u8, <D as FixedOutput>::OutputSize>,
 );
 
-/// The third key exchange message
-pub struct KE3Message<HashLen: ArrayLength<u8>> {
-    mac: GenericArray<u8, HashLen>,
-}
-
-impl<HashLen: ArrayLength<u8>> ToBytes for KE3Message<HashLen> {
-    fn to_bytes(&self) -> Vec<u8> {
-        self.mac.to_vec()
-    }
-}
-
-impl<HashLen: ArrayLength<u8>> TryFrom<&[u8]> for KE3Message<HashLen> {
-    type Error = InternalPakeError;
-
-    fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
-        let checked_bytes = check_slice_size(bytes, KEY_LEN, "ke3_message")?;
-
-        Ok(Self {
-            mac: GenericArray::clone_from_slice(&checked_bytes),
-        })
-    }
-}
-
-// Helper functions
-
 // 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, D: Hash>(
@@ -418,79 +387,44 @@ fn derive_3dh_keys<KeyFormat: KeyPair, D: Hash>(
 
     let info: Vec<u8> = [
         STR_3DH,
-        &serialize(&client_nonce, 2),
-        &serialize(&server_nonce, 2),
-        &serialize(&client_s_pk.to_arr(), 2),
-        &serialize(&server_s_pk.to_arr(), 2),
+        &client_nonce,
+        &server_nonce,
+        &client_s_pk.to_arr(),
+        &server_s_pk.to_arr(),
     ]
     .concat();
 
-    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,
-        b"",
-        <D as Digest>::OutputSize::to_usize(),
-    )?;
-    let km3 = hkdf_expand_label::<D>(
-        &handshake_secret,
-        &STR_CLIENT_MAC,
-        b"",
-        <D as Digest>::OutputSize::to_usize(),
-    )?;
-
+    const OUTPUT_SIZE: usize = 32;
+    let mut okm = [0u8; 3 * OUTPUT_SIZE];
+    let h = Hkdf::<D>::new(None, &ikm);
+    h.expand(&info, &mut okm)
+        .map_err(|_| InternalPakeError::HkdfError)?;
     Ok((
-        GenericArray::clone_from_slice(&session_secret),
-        GenericArray::clone_from_slice(&km2),
-        GenericArray::clone_from_slice(&km3),
+        GenericArray::clone_from_slice(&okm[..OUTPUT_SIZE]),
+        GenericArray::clone_from_slice(&okm[OUTPUT_SIZE..2 * OUTPUT_SIZE]),
+        GenericArray::clone_from_slice(&okm[2 * OUTPUT_SIZE..]),
     ))
 }
 
-fn hkdf_expand_label<D: Hash>(
-    secret: &[u8],
-    label: &[u8],
-    context: &[u8],
-    length: usize,
-) -> Result<Vec<u8>, ProtocolError> {
-    let h = Hkdf::<D>::new(None, secret);
-    hkdf_expand_label_extracted(&h, label, context, length)
+/// The third key exchange message
+pub struct KE3Message<HashLen: ArrayLength<u8>> {
+    mac: GenericArray<u8, HashLen>,
 }
 
-fn hkdf_expand_label_extracted<D: Hash>(
-    hkdf: &Hkdf<D>,
-    label: &[u8],
-    context: &[u8],
-    length: usize,
-) -> Result<Vec<u8>, ProtocolError> {
-    let mut okm = vec![0u8; length];
-
-    let mut hkdf_label: Vec<u8> = Vec::new();
-    hkdf_label.extend_from_slice(&length.to_be_bytes()[6..]);
-
-    let mut opaque_label: Vec<u8> = Vec::new();
-    opaque_label.extend_from_slice(&STR_OPAQUE);
-    opaque_label.extend_from_slice(&label);
-    hkdf_label.extend_from_slice(&serialize(&opaque_label, 1));
+impl<HashLen: ArrayLength<u8>> ToBytes for KE3Message<HashLen> {
+    fn to_bytes(&self) -> Vec<u8> {
+        self.mac.to_vec()
+    }
+}
 
-    hkdf_label.extend_from_slice(&serialize(&context, 1));
+impl<HashLen: ArrayLength<u8>> TryFrom<&[u8]> for KE3Message<HashLen> {
+    type Error = InternalPakeError;
 
-    hkdf.expand(&hkdf_label, &mut okm)
-        .map_err(|_| InternalPakeError::HkdfError)?;
-    Ok(okm)
-}
+    fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
+        let checked_bytes = check_slice_size(bytes, KEY_LEN, "ke3_message")?;
 
-fn derive_secrets<D: Hash>(
-    hkdf: &Hkdf<D>,
-    label: &[u8],
-    transcript: &[u8],
-) -> Result<Vec<u8>, ProtocolError> {
-    let hashed_transcript = D::digest(transcript);
-    hkdf_expand_label_extracted::<D>(
-        hkdf,
-        label,
-        &hashed_transcript,
-        <D as Digest>::OutputSize::to_usize(),
-    )
+        Ok(Self {
+            mac: GenericArray::clone_from_slice(&checked_bytes),
+        })
+    }
 }
diff --git a/src/lib.rs b/src/lib.rs
index 87cdba5..096304c 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -89,6 +89,7 @@
 //! let mut client_rng = OsRng;
 //! let (r1, client_state) = ClientRegistration::<Default>::start(
 //!     b"password",
+//!     Some(b"pepper"),
 //!     &mut client_rng,
 //! )?;
 //! # Ok::<(), ProtocolError>(())
@@ -118,6 +119,7 @@
 //! # let mut client_rng = OsRng;
 //! # let (r1, client_state) = ClientRegistration::<Default>::start(
 //! #     b"password",
+//! #     Some(b"pepper"),
 //! #     &mut client_rng,
 //! # )?;
 //! use opaque_ke::opaque::ServerRegistration;
@@ -151,6 +153,7 @@
 //! # let mut client_rng = OsRng;
 //! # let (r1, client_state) = ClientRegistration::<Default>::start(
 //! #     b"password",
+//! #     Some(b"pepper"),
 //! #     &mut client_rng,
 //! # )?;
 //! # let mut server_rng = OsRng;
@@ -185,6 +188,7 @@
 //! # let mut client_rng = OsRng;
 //! # let (r1, client_state) = ClientRegistration::<Default>::start(
 //! #     b"password",
+//! #     Some(b"pepper"),
 //! #     &mut client_rng,
 //! # )?;
 //! # let mut server_rng = OsRng;
@@ -228,6 +232,7 @@
 //! let mut client_rng = OsRng;
 //! let (l1, client_state) = ClientLogin::<Default>::start(
 //!   b"password",
+//!   Some(b"pepper"),
 //!   &mut client_rng,
 //! )?;
 //! # Ok::<(), ProtocolError>(())
@@ -257,6 +262,7 @@
 //! # let mut client_rng = OsRng;
 //! # let (r1, client_state) = ClientRegistration::<Default>::start(
 //! #     b"password",
+//! #     Some(b"pepper"),
 //! #     &mut client_rng,
 //! # )?;
 //! # let mut server_rng = OsRng;
@@ -266,6 +272,7 @@
 //! # let password_file_bytes = server_state.finish(r3)?.to_bytes();
 //! # let (l1, client_state) = ClientLogin::<Default>::start(
 //! #   b"password",
+//! #   Some(b"pepper"),
 //! #   &mut client_rng,
 //! # )?;
 //! use opaque_ke::opaque::ServerLogin;
@@ -301,6 +308,7 @@
 //! # let mut client_rng = OsRng;
 //! # let (r1, client_state) = ClientRegistration::<Default>::start(
 //! #     b"password",
+//! #     Some(b"pepper"),
 //! #     &mut client_rng,
 //! # )?;
 //! # let mut server_rng = OsRng;
@@ -310,6 +318,7 @@
 //! # let password_file_bytes = server_state.finish(r3)?.to_bytes();
 //! # let (l1, client_state) = ClientLogin::<Default>::start(
 //! #   b"password",
+//! #   Some(b"pepper"),
 //! #   &mut client_rng,
 //! # )?;
 //! # use std::convert::TryFrom;
@@ -356,6 +365,7 @@
 //! # let mut client_rng = OsRng;
 //! # let (r1, client_state) = ClientRegistration::<Default>::start(
 //! #     b"password",
+//! #     Some(b"pepper"),
 //! #     &mut client_rng,
 //! # )?;
 //! # let mut server_rng = OsRng;
@@ -365,6 +375,7 @@
 //! # let password_file_bytes = server_state.finish(r3)?.to_bytes();
 //! # let (l1, client_state) = ClientLogin::<Default>::start(
 //! #   b"password",
+//! #   Some(b"pepper"),
 //! #   &mut client_rng,
 //! # )?;
 //! # use std::convert::TryFrom;
@@ -421,7 +432,5 @@ mod oprf;
 
 pub mod slow_hash;
 
-mod serialization;
-
 #[cfg(test)]
 mod tests;
diff --git a/src/map_to_curve.rs b/src/map_to_curve.rs
index 64ffabe..a7667db 100644
--- a/src/map_to_curve.rs
+++ b/src/map_to_curve.rs
@@ -15,22 +15,19 @@ use sha2::{Sha256, Sha512};
 /// A subtrait of Group specifying how to hash a password into a point
 pub trait GroupWithMapToCurve: Group {
     /// transforms a password and optional pepper into a curve point
-    fn map_to_curve(password: &[u8], dst: Option<&[u8]>) -> Self;
+    fn map_to_curve(password: &[u8], pepper: Option<&[u8]>) -> Self;
 }
 
-// TODO: incorporate expand_message_xmd from https://www.ietf.org/archive/id/draft-irtf-cfrg-hash-to-curve-10.txt
-// instead of using HKDF-extract here
-
 impl GroupWithMapToCurve for RistrettoPoint {
-    fn map_to_curve(password: &[u8], dst: Option<&[u8]>) -> Self {
-        let (hashed_input, _) = Hkdf::<Sha512>::extract(dst, password);
+    fn map_to_curve(password: &[u8], pepper: Option<&[u8]>) -> Self {
+        let (hashed_input, _) = Hkdf::<Sha512>::extract(pepper, password);
         <Self as Group>::hash_to_curve(&hashed_input)
     }
 }
 
 impl GroupWithMapToCurve for EdwardsPoint {
-    fn map_to_curve(password: &[u8], dst: Option<&[u8]>) -> Self {
-        let (hashed_input, _) = Hkdf::<Sha256>::extract(dst, password);
+    fn map_to_curve(password: &[u8], pepper: Option<&[u8]>) -> Self {
+        let (hashed_input, _) = Hkdf::<Sha256>::extract(pepper, password);
         <Self as Group>::hash_to_curve(&hashed_input)
     }
 }
diff --git a/src/opaque.rs b/src/opaque.rs
index d016d1f..f97e920 100644
--- a/src/opaque.rs
+++ b/src/opaque.rs
@@ -7,24 +7,18 @@
 
 use crate::{
     ciphersuite::CipherSuite,
-    envelope::{Envelope, EnvelopeCredentialsFormat, ExportKeySize},
-    errors::{
-        utils::{check_slice_size, check_slice_size_atleast},
-        InternalPakeError, PakeError, ProtocolError,
-    },
+    envelope::{Envelope, ExportKeySize},
+    errors::{utils::check_slice_size, InternalPakeError, PakeError, ProtocolError},
     group::Group,
     hash::Hash,
     key_exchange::traits::{KeyExchange, ToBytes},
     keypair::{KeyPair, SizedBytes},
     oprf,
-    serialization::{
-        serialize, tokenize, u8_to_credential_type, CredentialType, ProtocolMessageType,
-    },
+    oprf::OprfClientBytes,
     slow_hash::SlowHash,
 };
 use generic_array::{typenum::Unsigned, GenericArray};
 use rand_core::{CryptoRng, RngCore};
-use std::collections::HashMap;
 use std::{convert::TryFrom, marker::PhantomData};
 use zeroize::Zeroize;
 
@@ -33,8 +27,6 @@ use zeroize::Zeroize;
 
 /// The message sent by the client to the server, to initiate registration
 pub struct RegisterFirstMessage<Grp> {
-    /// User identity
-    id_u: Vec<u8>,
     /// blinded password information
     alpha: Grp,
 }
@@ -42,61 +34,8 @@ pub struct RegisterFirstMessage<Grp> {
 impl<Grp: Group> TryFrom<&[u8]> for RegisterFirstMessage<Grp> {
     type Error = ProtocolError;
     fn try_from(first_message_bytes: &[u8]) -> Result<Self, Self::Error> {
-        let elem_len = Grp::ElemLen::to_usize();
-        let checked_slice =
-            check_slice_size_atleast(first_message_bytes, elem_len, "first_message_bytes")?;
-
-        let id_u = checked_slice[..checked_slice.len() - elem_len].to_vec();
-
-        // Check that the message is actually containing an element of the
-        // correct subgroup
-        let arr = GenericArray::from_slice(&checked_slice[checked_slice.len() - elem_len..]);
-        let alpha = Grp::from_element_slice(arr)?;
-        Ok(Self { id_u, alpha })
-    }
-}
-
-impl<Grp: Group> RegisterFirstMessage<Grp> {
-    /// Byte representation for the registration request
-    pub fn to_bytes(&self) -> Vec<u8> {
-        [&self.id_u[..], &self.alpha.to_arr().to_vec()[..]].concat()
-    }
-
-    /// Serialization into bytes
-    pub fn serialize(&self) -> Vec<u8> {
-        let mut registration_request: Vec<u8> = Vec::new();
-        registration_request.extend_from_slice(&serialize(&self.id_u, 2));
-        registration_request.extend_from_slice(&serialize(&self.alpha.to_arr(), 2));
-
-        let mut output: Vec<u8> = Vec::new();
-        output.push(ProtocolMessageType::from(self) as u8 + 1);
-        output.extend_from_slice(&serialize(&registration_request, 3));
-        output
-    }
-
-    /// Deserialization from bytes
-    pub fn deserialize(input: &[u8]) -> Result<Self, ProtocolError> {
-        if input.is_empty()
-            || input.is_empty()
-            || input[0] != ProtocolMessageType::RegistrationRequest as u8 + 1
-        {
-            return Err(PakeError::SerializationError.into());
-        }
-
-        let (data, remainder) = tokenize(input[1..].to_vec(), 3)?;
-        if !remainder.is_empty() {
-            return Err(PakeError::SerializationError.into());
-        }
-
-        let (id_u, remainder) = tokenize(data, 2)?;
-        let (alpha_bytes, remainder) = tokenize(remainder, 2)?;
-
-        if !remainder.is_empty() {
-            return Err(PakeError::SerializationError.into());
-        }
-
         let checked_slice = check_slice_size(
-            &alpha_bytes,
+            first_message_bytes,
             Grp::ElemLen::to_usize(),
             "first_message_bytes",
         )?;
@@ -104,7 +43,14 @@ impl<Grp: Group> RegisterFirstMessage<Grp> {
         // correct subgroup
         let arr = GenericArray::from_slice(checked_slice);
         let alpha = Grp::from_element_slice(arr)?;
-        Ok(Self { id_u, alpha })
+        Ok(Self { alpha })
+    }
+}
+
+impl<Grp: Group> RegisterFirstMessage<Grp> {
+    /// byte representation for the registration request
+    pub fn to_bytes(&self) -> GenericArray<u8, Grp::ElemLen> {
+        self.alpha.to_arr()
     }
 }
 
@@ -113,10 +59,6 @@ impl<Grp: Group> RegisterFirstMessage<Grp> {
 pub struct RegisterSecondMessage<Grp> {
     /// The server's oprf output
     beta: Grp,
-    /// Server's static public key
-    server_s_pk: Vec<u8>,
-    /// Envelope credentials format
-    ecf: EnvelopeCredentialsFormat,
 }
 
 impl<Grp> TryFrom<&[u8]> for RegisterSecondMessage<Grp>
@@ -125,25 +67,17 @@ where
 {
     type Error = ProtocolError;
 
-    fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
-        let elem_len = Grp::ElemLen::to_usize();
-        let checked_slice = check_slice_size_atleast(bytes, elem_len, "second_message_bytes")?;
-
+    fn try_from(second_message_bytes: &[u8]) -> Result<Self, Self::Error> {
+        let checked_slice = check_slice_size(
+            second_message_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[..elem_len]);
+        let arr = GenericArray::from_slice(&checked_slice);
         let beta = Grp::from_element_slice(arr)?;
-
-        let server_s_pk = checked_slice[elem_len..].to_vec();
-
-        // Note that we use a default envelope credentials format here, since it
-        // is not included in the byte representation
-        let ecf = EnvelopeCredentialsFormat::default()?;
-        Ok(Self {
-            beta,
-            server_s_pk,
-            ecf,
-        })
+        Ok(Self { beta })
     }
 }
 
@@ -151,89 +85,9 @@ impl<Grp> RegisterSecondMessage<Grp>
 where
     Grp: Group,
 {
-    /// Byte representation for the registration response message. This does not
-    /// include the envelope credentials format
+    /// byte representation for the registration response message
     pub fn to_bytes(&self) -> Vec<u8> {
-        [&self.beta.to_arr().to_vec()[..], &self.server_s_pk[..]].concat()
-    }
-
-    /// 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(&serialize(&self.server_s_pk, 2));
-
-        // Handle ecf serialization
-        let secret_credentials: Vec<u8> = self
-            .ecf
-            .secret_credentials
-            .iter()
-            .map(|&x| x as u8 + 1)
-            .collect();
-        let cleartext_credentials: Vec<u8> = self
-            .ecf
-            .cleartext_credentials
-            .iter()
-            .map(|&x| x as u8 + 1)
-            .collect();
-        let ecf_serialized = [
-            serialize(&secret_credentials, 1),
-            serialize(&cleartext_credentials, 1),
-        ]
-        .concat();
-        registration_response.extend_from_slice(&ecf_serialized);
-
-        let mut output: Vec<u8> = Vec::new();
-        output.push(ProtocolMessageType::from(self) as u8 + 1);
-        output.extend_from_slice(&serialize(&registration_response, 3));
-        output
-    }
-
-    /// Deserialization from bytes
-    pub fn deserialize(input: &[u8]) -> Result<Self, ProtocolError> {
-        if input.is_empty() || input[0] != ProtocolMessageType::RegistrationResponse as u8 + 1 {
-            return Err(PakeError::SerializationError.into());
-        }
-
-        let (data, remainder) = tokenize(input[1..].to_vec(), 3)?;
-        if !remainder.is_empty() {
-            return Err(PakeError::SerializationError.into());
-        }
-
-        let (beta_bytes, remainder) = tokenize(data, 2)?;
-        let (server_s_pk, remainder) = tokenize(remainder, 2)?;
-
-        // Handle ecf deserialization
-        let (secret_credentials, remainder) = tokenize(remainder, 1)?;
-        let (cleartext_credentials, remainder) = tokenize(remainder, 1)?;
-        let sc = secret_credentials
-            .iter()
-            .map(|x| u8_to_credential_type(*x).ok_or(PakeError::SerializationError))
-            .collect::<Result<Vec<CredentialType>, _>>()?;
-        let cc = cleartext_credentials
-            .iter()
-            .map(|x| u8_to_credential_type(*x).ok_or(PakeError::SerializationError))
-            .collect::<Result<Vec<CredentialType>, _>>()?;
-        let ecf = EnvelopeCredentialsFormat::new(sc, cc)?;
-
-        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 {
-            ecf,
-            server_s_pk,
-            beta,
-        })
+        self.beta.to_arr().to_vec()
     }
 }
 
@@ -247,6 +101,17 @@ pub struct RegisterThirdMessage<KeyFormat: KeyPair, D: Hash> {
     client_s_pk: KeyFormat::Repr,
 }
 
+impl<KeyFormat, D> RegisterThirdMessage<KeyFormat, D>
+where
+    KeyFormat: KeyPair,
+    D: Hash,
+{
+    /// byte representation for the registration upload message
+    pub fn to_bytes(&self) -> Vec<u8> {
+        [&self.envelope.to_bytes(), &self.client_s_pk.to_arr()[..]].concat()
+    }
+}
+
 impl<KeyFormat, D> TryFrom<&[u8]> for RegisterThirdMessage<KeyFormat, D>
 where
     KeyFormat: KeyPair,
@@ -272,52 +137,8 @@ where
     }
 }
 
-impl<KeyFormat, D> RegisterThirdMessage<KeyFormat, D>
-where
-    KeyFormat: KeyPair,
-    D: Hash,
-{
-    /// Serialization into bytes
-    pub fn serialize(&self) -> Vec<u8> {
-        let mut registration_upload: Vec<u8> = Vec::new();
-        registration_upload.extend_from_slice(&self.envelope.serialize());
-        registration_upload.extend_from_slice(&serialize(&self.client_s_pk.to_arr(), 2));
-
-        let mut output: Vec<u8> = Vec::new();
-        output.push(ProtocolMessageType::from(self) as u8 + 1);
-        output.extend_from_slice(&serialize(&registration_upload, 3));
-        output
-    }
-
-    /// Deserialization from bytes
-    pub fn deserialize(input: &[u8]) -> Result<Self, ProtocolError> {
-        if input.is_empty() || input[0] != ProtocolMessageType::RegistrationUpload as u8 + 1 {
-            return Err(PakeError::SerializationError.into());
-        }
-
-        let (data, remainder) = tokenize(input[1..].to_vec(), 3)?;
-        if !remainder.is_empty() {
-            return Err(PakeError::SerializationError.into());
-        }
-
-        let (envelope, remainder) = Envelope::<D>::deserialize(&data)?;
-        let (client_s_pk, remainder) = tokenize(remainder, 2)?;
-
-        if !remainder.is_empty() {
-            return Err(PakeError::SerializationError.into());
-        }
-
-        Ok(Self {
-            envelope,
-            client_s_pk: KeyFormat::check_public_key(KeyFormat::Repr::from_bytes(&client_s_pk)?)?,
-        })
-    }
-}
-
 /// The message sent by the user to the server, to initiate registration
 pub struct LoginFirstMessage<CS: CipherSuite> {
-    /// User identity
-    id_u: Vec<u8>,
     /// blinded password information
     alpha: CS::Group,
     ke1_message: <CS::KeyExchange as KeyExchange<CS::Hash, CS::KeyFormat>>::KE1Message,
@@ -326,59 +147,34 @@ pub struct LoginFirstMessage<CS: CipherSuite> {
 impl<CS: CipherSuite> TryFrom<&[u8]> for LoginFirstMessage<CS> {
     type Error = ProtocolError;
     fn try_from(first_message_bytes: &[u8]) -> Result<Self, Self::Error> {
-        Self::deserialize(first_message_bytes)
-    }
-}
-
-impl<CS: CipherSuite> LoginFirstMessage<CS> {
-    /// byte representation for the login request
-    fn to_bytes(&self) -> Vec<u8> {
-        [&self.alpha.to_arr()[..], &self.ke1_message.to_bytes()].concat()
-    }
-
-    /// Serialization into bytes
-    pub fn serialize(&self) -> Vec<u8> {
-        let mut credential_request: Vec<u8> = Vec::new();
-        credential_request.extend_from_slice(&serialize(&self.id_u, 2));
-        credential_request.extend_from_slice(&serialize(&self.alpha.to_arr(), 2));
-
-        let mut output: Vec<u8> = Vec::new();
-        output.push(ProtocolMessageType::from(self) as u8 + 1);
-        output.extend_from_slice(&serialize(&credential_request, 3));
-        output.extend_from_slice(&self.ke1_message.to_bytes());
-        output
-    }
-
-    /// Deserialization from bytes
-    pub fn deserialize(input: &[u8]) -> Result<Self, ProtocolError> {
-        if input.is_empty() || input[0] != ProtocolMessageType::CredentialRequest as u8 + 1 {
-            return Err(PakeError::SerializationError.into());
-        }
-
-        let (data, ke1m) = tokenize(input[1..].to_vec(), 3)?;
-
-        let (id_u, remainder) = tokenize(data, 2)?;
-        let (alpha_bytes, remainder) = tokenize(remainder, 2)?;
-
-        if !remainder.is_empty() {
-            return Err(PakeError::SerializationError.into());
-        }
-
+        let min_expected_len = <CS::Group as Group>::ElemLen::to_usize();
+        let checked_slice = (if first_message_bytes.len() <= min_expected_len {
+            Err(InternalPakeError::SizeError {
+                name: "first_message_bytes",
+                len: min_expected_len,
+                actual_len: first_message_bytes.len(),
+            })
+        } else {
+            Ok(first_message_bytes)
+        })?;
+        // Check that the message is actually containing an element of the
+        // correct subgroup
         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 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::KeyFormat>>::KE1Message::try_from(
-                &ke1m[..],
+                &checked_slice[elem_len..],
             )?;
+        Ok(Self { alpha, ke1_message })
+    }
+}
 
-        Ok(Self {
-            id_u,
-            alpha,
-            ke1_message,
-        })
+impl<CS: CipherSuite> LoginFirstMessage<CS> {
+    /// byte representation for the login request
+    pub fn to_bytes(&self) -> Vec<u8> {
+        [&self.alpha.to_arr()[..], &self.ke1_message.to_bytes()].concat()
     }
 }
 
@@ -393,39 +189,29 @@ pub struct LoginSecondMessage<CS: CipherSuite> {
 }
 
 impl<CS: CipherSuite> LoginSecondMessage<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.envelope.to_bytes());
-
-        let mut output: Vec<u8> = Vec::new();
-        output.push(ProtocolMessageType::from(self) as u8 + 1);
-        output.extend_from_slice(&serialize(&credential_response, 3));
-        output.extend_from_slice(&self.ke2_message.to_bytes());
-        output
-    }
-
-    /// Deserialization from bytes
-    pub fn deserialize(input: &[u8]) -> Result<Self, ProtocolError> {
-        if input.is_empty() || input[0] != ProtocolMessageType::CredentialResponse as u8 + 1 {
-            return Err(PakeError::SerializationError.into());
-        }
-
-        let (data, ke2m) = tokenize(input[1..].to_vec(), 3)?;
-        let (beta_bytes, envelope_bytes) = tokenize(data, 2)?;
-
-        let concatenated = [&beta_bytes[..], &envelope_bytes[..], &ke2m[..]].concat();
-        Self::try_from(&concatenated[..])
+    /// byte representation for the login response
+    pub fn to_bytes(&self) -> Vec<u8> {
+        [
+            &self.beta.to_arr()[..],
+            &self.envelope.to_bytes()[..],
+            &self.ke2_message.to_bytes()[..],
+        ]
+        .concat()
     }
 }
 
 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 = <<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 checked_slice =
-            check_slice_size_atleast(second_message_bytes, elem_len, "login_second_message_bytes")?;
+        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,
+            "login_second_message_bytes",
+        )?;
 
         // Check that the message is actually containing an element of the
         // correct subgroup
@@ -433,14 +219,12 @@ impl<CS: CipherSuite> TryFrom<&[u8]> for LoginSecondMessage<CS> {
         let arr = GenericArray::from_slice(beta_bytes);
         let beta = CS::Group::from_element_slice(arr)?;
 
-        let (envelope, remainder) = Envelope::<CS::Hash>::deserialize(&checked_slice[elem_len..])?;
+        let envelope =
+            Envelope::<CS::Hash>::from_bytes(&checked_slice[elem_len..elem_len + envelope_size])?;
 
-        let ke2_message_size = CS::KeyExchange::ke2_message_size();
-        let checked_remainder =
-            check_slice_size_atleast(&remainder, ke2_message_size, "login_second_message_bytes")?;
         let ke2_message =
             <CS::KeyExchange as KeyExchange<CS::Hash, CS::KeyFormat>>::KE2Message::try_from(
-                &checked_remainder,
+                &checked_slice[elem_len + envelope_size..],
             )?;
 
         Ok(Self {
@@ -468,14 +252,6 @@ impl<CS: CipherSuite> TryFrom<&[u8]> for LoginThirdMessage<CS> {
 }
 
 impl<CS: CipherSuite> LoginThirdMessage<CS> {
-    /// Serialization into bytes
-    pub fn serialize(&self) -> Vec<u8> {
-        let mut output: Vec<u8> = Vec::new();
-        output.push(ProtocolMessageType::from(self) as u8 + 1);
-        output.extend_from_slice(&self.ke3_message.to_bytes());
-        output
-    }
-
     /// byte representation for the login finalization
     pub fn to_bytes(&self) -> Vec<u8> {
         self.ke3_message.to_bytes()
@@ -487,20 +263,15 @@ impl<CS: CipherSuite> LoginThirdMessage<CS> {
 
 /// The state elements the client holds to register itself
 pub struct ClientRegistration<CS: CipherSuite> {
-    /// User identity
-    id_u: Vec<u8>,
-    /// Server identity
-    id_s: Vec<u8>,
-    /// token containing the client's password and the blinding factor
-    pub(crate) token: oprf::Token<CS::Group>,
+    /// a blinding factor
+    pub(crate) blinding_factor: <CS::Group as Group>::Scalar,
+    /// the client's password
+    password: Vec<u8>,
 }
 
 impl<CS: CipherSuite> TryFrom<&[u8]> for ClientRegistration<CS> {
     type Error = ProtocolError;
-    fn try_from(input: &[u8]) -> Result<Self, Self::Error> {
-        let (id_u, bytes) = tokenize(input.to_vec(), 2)?;
-        let (id_s, bytes) = tokenize(bytes.to_vec(), 2)?;
-
+    fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
         let min_expected_len = <CS::Group as Group>::ScalarLen::to_usize();
         let checked_slice = (if bytes.len() <= min_expected_len {
             Err(InternalPakeError::SizeError {
@@ -519,12 +290,8 @@ impl<CS: CipherSuite> TryFrom<&[u8]> for ClientRegistration<CS> {
         let blinding_factor = CS::Group::from_scalar_slice(blinding_factor_bytes)?;
         let password = checked_slice[scalar_len..].to_vec();
         Ok(Self {
-            id_u,
-            id_s,
-            token: oprf::Token {
-                data: password,
-                blind: blinding_factor,
-            },
+            blinding_factor,
+            password,
         })
     }
 }
@@ -533,10 +300,8 @@ impl<CS: CipherSuite> ClientRegistration<CS> {
     /// byte representation for the client's registration state
     pub fn to_bytes(&self) -> Vec<u8> {
         let output: Vec<u8> = [
-            &serialize(&self.id_u, 2),
-            &serialize(&self.id_s, 2),
-            &CS::Group::scalar_as_bytes(&self.token.blind)[..],
-            &self.token.data,
+            &CS::Group::scalar_as_bytes(&self.blinding_factor)[..],
+            &self.password,
         ]
         .concat();
         output
@@ -565,61 +330,24 @@ impl<CS: CipherSuite> ClientRegistration<CS> {
     ///     type SlowHash = opaque_ke::slow_hash::NoOpHash;
     /// }
     /// let mut rng = OsRng;
-    /// let (register_m1, registration_state) = ClientRegistration::<Default>::start(b"hunter2", &mut rng)?;
+    /// let (register_m1, registration_state) = ClientRegistration::<Default>::start(b"hunter2", None, &mut rng)?;
     /// # Ok::<(), ProtocolError>(())
     /// ```
     pub fn start<R: RngCore + CryptoRng>(
         password: &[u8],
+        pepper: Option<&[u8]>,
         blinding_factor_rng: &mut R,
     ) -> Result<(RegisterFirstMessage<CS::Group>, Self), ProtocolError> {
-        Self::start_with_user_and_server_name(
-            &Vec::new(),
-            &Vec::new(),
-            password,
-            blinding_factor_rng,
-        )
-    }
-
-    /// Same as ClientRegistration::start, but also accepts a username and server name as input
-    pub fn start_with_user_and_server_name<R: RngCore + CryptoRng>(
-        user_name: &[u8],
-        server_name: &[u8],
-        password: &[u8],
-        blinding_factor_rng: &mut R,
-    ) -> Result<(RegisterFirstMessage<CS::Group>, Self), ProtocolError> {
-        Self::start_with_user_and_server_name_and_postprocessing(
-            user_name,
-            server_name,
-            password,
-            blinding_factor_rng,
-            std::convert::identity,
-        )
-    }
-
-    /// Same as ClientRegistration::start, but also accepts a username and server name as input as well as
-    /// an optional postprocessing function for the blinding factor
-    pub fn start_with_user_and_server_name_and_postprocessing<R: RngCore + CryptoRng>(
-        user_name: &[u8],
-        server_name: &[u8],
-        password: &[u8],
-        blinding_factor_rng: &mut R,
-        postprocess: fn(<CS::Group as Group>::Scalar) -> <CS::Group as Group>::Scalar,
-    ) -> Result<(RegisterFirstMessage<CS::Group>, Self), ProtocolError> {
-        let (token, alpha) = oprf::blind_with_postprocessing::<R, CS::Group>(
-            &password,
-            blinding_factor_rng,
-            postprocess,
-        )?;
+        let OprfClientBytes {
+            alpha,
+            blinding_factor,
+        } = oprf::generate_oprf1::<R, CS::Group>(&password, pepper, blinding_factor_rng)?;
 
         Ok((
-            RegisterFirstMessage::<CS::Group> {
-                id_u: user_name.to_vec(),
-                alpha,
-            },
+            RegisterFirstMessage::<CS::Group> { alpha },
             Self {
-                id_u: user_name.to_vec(),
-                id_s: server_name.to_vec(),
-                token,
+                blinding_factor,
+                password: password.to_vec(),
             },
         ))
     }
@@ -656,7 +384,7 @@ impl<CS: CipherSuite> ClientRegistration<CS> {
     /// let mut client_rng = OsRng;
     /// let mut server_rng = OsRng;
     /// let server_kp = X25519KeyPair::generate_random(&mut server_rng)?;
-    /// let (register_m1, client_state) = ClientRegistration::<Default>::start(b"hunter2", &mut client_rng)?;
+    /// let (register_m1, client_state) = ClientRegistration::<Default>::start(b"hunter2", None, &mut client_rng)?;
     /// let (register_m2, server_state) =
     /// ServerRegistration::<Default>::start(register_m1, &mut server_rng)?;
     /// let mut client_rng = OsRng;
@@ -668,38 +396,21 @@ impl<CS: CipherSuite> ClientRegistration<CS> {
         r2: RegisterSecondMessage<CS::Group>,
         server_s_pk: &<CS::KeyFormat as KeyPair>::Repr,
         rng: &mut R,
-    ) -> Result<ClientRegistrationFinishResult<CS::KeyFormat, CS::Hash>, ProtocolError> {
-        let mut r2_cloned = r2;
-        r2_cloned.server_s_pk = server_s_pk.to_arr().to_vec();
-        self.finish_using_transmitted_server_public_key(r2_cloned, rng)
-    }
-
-    /// Same as finish, but without the server public key check
-    pub fn finish_using_transmitted_server_public_key<R: CryptoRng + RngCore>(
-        self,
-        r2: RegisterSecondMessage<CS::Group>,
-        rng: &mut R,
     ) -> Result<ClientRegistrationFinishResult<CS::KeyFormat, CS::Hash>, ProtocolError> {
         let client_static_keypair = CS::KeyFormat::generate_random(rng)?;
 
-        let password_derived_key =
-            get_password_derived_key::<CS::Group, CS::SlowHash, CS::Hash>(&self.token, r2.beta)?;
-
-        let mut credentials_map: HashMap<CredentialType, Vec<u8>> = HashMap::new();
-        credentials_map.insert(
-            CredentialType::SkU,
-            client_static_keypair.private().to_arr().to_vec(),
-        );
-        credentials_map.insert(
-            CredentialType::PkU,
-            client_static_keypair.public().to_arr().to_vec(),
-        );
-        credentials_map.insert(CredentialType::PkS, r2.server_s_pk);
-        credentials_map.insert(CredentialType::IdU, self.id_u.clone());
-        credentials_map.insert(CredentialType::IdS, self.id_s.clone());
-
-        let (envelope, export_key) =
-            Envelope::<CS::Hash>::seal(&password_derived_key, r2.ecf, credentials_map, rng)?;
+        let password_derived_key = get_password_derived_key::<CS::Group, CS::SlowHash, CS::Hash>(
+            self.password.clone(),
+            r2.beta,
+            &self.blinding_factor,
+        )?;
+
+        let (envelope, export_key) = Envelope::<CS::Hash>::seal(
+            &password_derived_key,
+            &client_static_keypair.private().to_arr(),
+            &server_s_pk.to_arr(),
+            rng,
+        )?;
 
         Ok((
             RegisterThirdMessage {
@@ -714,8 +425,8 @@ impl<CS: CipherSuite> ClientRegistration<CS> {
 // This can't be derived because of the use of a phantom parameter
 impl<CS: CipherSuite> Zeroize for ClientRegistration<CS> {
     fn zeroize(&mut self) {
-        self.token.data.zeroize();
-        self.token.blind.zeroize();
+        self.password.zeroize();
+        self.blinding_factor.zeroize();
     }
 }
 
@@ -728,8 +439,8 @@ impl<CS: CipherSuite> Drop for ClientRegistration<CS> {
 // This can't be derived because of the use of a phantom parameter
 impl<CS: CipherSuite> Zeroize for ClientLogin<CS> {
     fn zeroize(&mut self) {
-        self.token.data.zeroize();
-        self.token.blind.zeroize();
+        self.password.zeroize();
+        self.blinding_factor.zeroize();
     }
 }
 
@@ -756,36 +467,36 @@ where
     >: generic_array::ArrayLength<u8>,
 {
     type Error = ProtocolError;
-
-    /// The format of a serialized ServerRegistration object:
-    /// oprf_key | client_s_pk | envelope
-    fn try_from(input: &[u8]) -> Result<Self, Self::Error> {
+    fn try_from(server_registration_bytes: &[u8]) -> Result<Self, Self::Error> {
+        let key_len = <<CS::KeyFormat as KeyPair>::Repr as SizedBytes>::Len::to_usize();
         let scalar_len = <CS::Group as Group>::ScalarLen::to_usize();
-        if input.len() == scalar_len {
+        let envelope_size = key_len + Envelope::<CS::Hash>::additional_size();
+
+        if server_registration_bytes.len() == scalar_len {
             return Ok(Self {
-                oprf_key: CS::Group::from_scalar_slice(GenericArray::from_slice(input))?,
+                oprf_key: CS::Group::from_scalar_slice(GenericArray::from_slice(
+                    server_registration_bytes,
+                ))?,
                 client_s_pk: None,
                 envelope: None,
             });
         }
 
-        // Need to do this check manually because envelope is variable-size
-        let key_len = <<CS::KeyFormat as KeyPair>::Repr as SizedBytes>::Len::to_usize();
-
-        let checked_bytes =
-            check_slice_size_atleast(&input, scalar_len + key_len, "server_registration_bytes")?;
-
+        let checked_bytes = check_slice_size(
+            server_registration_bytes,
+            envelope_size + key_len + scalar_len,
+            "server_registration_bytes",
+        )?;
         let oprf_key_bytes = GenericArray::from_slice(&checked_bytes[..scalar_len]);
         let oprf_key = CS::Group::from_scalar_slice(oprf_key_bytes)?;
         let unchecked_client_s_pk = <CS::KeyFormat as KeyPair>::Repr::from_bytes(
             &checked_bytes[scalar_len..scalar_len + key_len],
         )?;
         let client_s_pk = CS::KeyFormat::check_public_key(unchecked_client_s_pk)?;
-
-        let envelope = Envelope::<CS::Hash>::from_bytes(&checked_bytes[scalar_len + key_len..])?;
-
         Ok(Self {
-            envelope: Some(envelope),
+            envelope: Some(Envelope::<CS::Hash>::from_bytes(
+                &checked_bytes[checked_bytes.len() - envelope_size..],
+            )?),
             client_s_pk: Some(client_s_pk),
             oprf_key,
         })
@@ -836,7 +547,7 @@ where
     /// }
     /// let mut client_rng = OsRng;
     /// let mut server_rng = OsRng;
-    /// let (register_m1, client_state) = ClientRegistration::<Default>::start(b"hunter2", &mut client_rng)?;
+    /// let (register_m1, client_state) = ClientRegistration::<Default>::start(b"hunter2", None, &mut client_rng)?;
     /// let (register_m2, server_state) =
     /// ServerRegistration::<Default>::start(register_m1, &mut server_rng)?;
     /// # Ok::<(), ProtocolError>(())
@@ -844,43 +555,15 @@ where
     pub fn start<R: RngCore + CryptoRng>(
         message: RegisterFirstMessage<CS::Group>,
         rng: &mut R,
-    ) -> Result<(RegisterSecondMessage<CS::Group>, Self), ProtocolError> {
-        Self::start_with_server_pk(message, &Vec::new(), rng)
-    }
-
-    /// Same as start, but with the ability to supply a server_s_pk as input
-    pub fn start_with_server_pk<R: RngCore + CryptoRng>(
-        message: RegisterFirstMessage<CS::Group>,
-        server_s_pk: &[u8],
-        rng: &mut R,
-    ) -> Result<(RegisterSecondMessage<CS::Group>, Self), ProtocolError> {
-        Self::start_with_server_pk_and_ecf(
-            message,
-            server_s_pk,
-            EnvelopeCredentialsFormat::default()?,
-            rng,
-        )
-    }
-
-    /// Same as start, but with the ability to supply a server_s_pk as input and envelope credentials format
-    pub fn start_with_server_pk_and_ecf<R: RngCore + CryptoRng>(
-        message: RegisterFirstMessage<CS::Group>,
-        server_s_pk: &[u8],
-        ecf: EnvelopeCredentialsFormat,
-        rng: &mut R,
     ) -> Result<(RegisterSecondMessage<CS::Group>, Self), ProtocolError> {
         // RFC: generate oprf_key (salt) and v_u = g^oprf_key
         let oprf_key = CS::Group::random_scalar(rng);
 
         // Compute beta = alpha^oprf_key
-        let beta = oprf::evaluate::<CS::Group>(message.alpha, &oprf_key)?;
+        let beta = oprf::generate_oprf2::<CS::Group>(message.alpha, &oprf_key)?;
 
         Ok((
-            RegisterSecondMessage {
-                beta,
-                server_s_pk: server_s_pk.to_vec(),
-                ecf,
-            },
+            RegisterSecondMessage { beta },
             Self {
                 envelope: None,
                 client_s_pk: None,
@@ -913,7 +596,7 @@ where
     /// let mut client_rng = OsRng;
     /// let mut server_rng = OsRng;
     /// let server_kp = X25519KeyPair::generate_random(&mut server_rng)?;
-    /// let (register_m1, client_state) = ClientRegistration::<Default>::start(b"hunter2", &mut client_rng)?;
+    /// let (register_m1, client_state) = ClientRegistration::<Default>::start(b"hunter2", None, &mut client_rng)?;
     /// let (register_m2, server_state) =
     /// ServerRegistration::<Default>::start(register_m1, &mut server_rng)?;
     /// let mut client_rng = OsRng;
@@ -938,21 +621,17 @@ where
 
 /// The state elements the client holds to perform a login
 pub struct ClientLogin<CS: CipherSuite> {
-    /// User identity
-    id_u: Vec<u8>,
-    /// Server identity
-    id_s: Vec<u8>,
-    /// token containing the client's password and the blinding factor
-    token: oprf::Token<CS::Group>,
+    /// 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, CS::KeyFormat>>::KE1State,
 }
 
 impl<CS: CipherSuite> TryFrom<&[u8]> for ClientLogin<CS> {
     type Error = ProtocolError;
-    fn try_from(input: &[u8]) -> Result<Self, Self::Error> {
-        let (id_u, bytes) = tokenize(input.to_vec(), 2)?;
-        let (id_s, bytes) = tokenize(bytes.to_vec(), 2)?;
-
+    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, CS::KeyFormat>>::ke1_state_size();
@@ -965,7 +644,7 @@ impl<CS: CipherSuite> TryFrom<&[u8]> for ClientLogin<CS> {
                 actual_len: bytes.len(),
             })
         } else {
-            Ok(bytes.clone())
+            Ok(bytes)
         })?;
 
         let blinding_factor_bytes = GenericArray::from_slice(&checked_slice[..scalar_len]);
@@ -976,12 +655,8 @@ impl<CS: CipherSuite> TryFrom<&[u8]> for ClientLogin<CS> {
             )?;
         let password = bytes[scalar_len + ke1_state_size..].to_vec();
         Ok(Self {
-            id_u,
-            id_s,
-            token: oprf::Token {
-                data: password,
-                blind: blinding_factor,
-            },
+            blinding_factor,
+            password,
             ke1_state,
         })
     }
@@ -991,11 +666,9 @@ impl<CS: CipherSuite> ClientLogin<CS> {
     /// byte representation for the client's login state
     pub fn to_bytes(&self) -> Vec<u8> {
         let output: Vec<u8> = [
-            &serialize(&self.id_u, 2),
-            &serialize(&self.id_s, 2),
-            &CS::Group::scalar_as_bytes(&self.token.blind)[..],
+            &CS::Group::scalar_as_bytes(&self.blinding_factor)[..],
             &self.ke1_state.to_bytes(),
-            &self.token.data,
+            &self.password,
         ]
         .concat();
         output
@@ -1030,57 +703,28 @@ impl<CS: CipherSuite> ClientLogin<CS> {
     ///     type SlowHash = opaque_ke::slow_hash::NoOpHash;
     /// }
     /// let mut client_rng = OsRng;
-    /// let (login_m1, client_login_state) = ClientLogin::<Default>::start(b"hunter2", &mut client_rng)?;
+    /// let (login_m1, client_login_state) = ClientLogin::<Default>::start(b"hunter2", None, &mut client_rng)?;
     /// # Ok::<(), ProtocolError>(())
     /// ```
     pub fn start<R: RngCore + CryptoRng>(
         password: &[u8],
+        pepper: Option<&[u8]>,
         rng: &mut R,
     ) -> Result<(LoginFirstMessage<CS>, Self), ProtocolError> {
-        Self::start_with_user_and_server_name(&Vec::new(), &Vec::new(), password, rng)
-    }
-
-    /// Same as start, but allows the user to supply a username and server name
-    pub fn start_with_user_and_server_name<R: RngCore + CryptoRng>(
-        user_name: &[u8],
-        server_name: &[u8],
-        password: &[u8],
-        rng: &mut R,
-    ) -> Result<(LoginFirstMessage<CS>, Self), ProtocolError> {
-        Self::start_with_user_and_server_name_and_postprocessing(
-            user_name,
-            server_name,
-            password,
-            rng,
-            std::convert::identity,
-        )
-    }
-
-    /// Same as start, but allows the user to supply a username and server name and postprocessing function
-    pub fn start_with_user_and_server_name_and_postprocessing<R: RngCore + CryptoRng>(
-        user_name: &[u8],
-        server_name: &[u8],
-        password: &[u8],
-        rng: &mut R,
-        postprocess: fn(<CS::Group as Group>::Scalar) -> <CS::Group as Group>::Scalar,
-    ) -> Result<(LoginFirstMessage<CS>, Self), ProtocolError> {
-        let (token, alpha) =
-            oprf::blind_with_postprocessing::<R, CS::Group>(&password, rng, postprocess)?;
+        let OprfClientBytes {
+            alpha,
+            blinding_factor,
+        } = oprf::generate_oprf1::<R, CS::Group>(&password, pepper, rng)?;
 
         let (ke1_state, ke1_message) = CS::KeyExchange::generate_ke1(alpha.to_arr().to_vec(), rng)?;
 
-        let l1 = LoginFirstMessage {
-            id_u: user_name.to_vec(),
-            alpha,
-            ke1_message,
-        };
+        let l1 = LoginFirstMessage { alpha, ke1_message };
 
         Ok((
             l1,
             Self {
-                id_u: user_name.to_vec(),
-                id_s: server_name.to_vec(),
-                token,
+                blinding_factor,
+                password: password.to_vec(),
                 ke1_state,
             },
         ))
@@ -1111,12 +755,12 @@ impl<CS: CipherSuite> ClientLogin<CS> {
     /// }
     /// let mut client_rng = OsRng;
     /// # let mut server_rng = OsRng;
-    /// # let (register_m1, client_state) = ClientRegistration::<Default>::start(b"hunter2", &mut client_rng)?;
+    /// # let (register_m1, client_state) = ClientRegistration::<Default>::start(b"hunter2", None, &mut client_rng)?;
     /// # let server_kp = X25519KeyPair::generate_random(&mut server_rng)?;
     /// # let (register_m2, server_state) = ServerRegistration::<Default>::start(register_m1, &mut server_rng)?;
     /// # let (register_m3, _export_key) = client_state.finish(register_m2, server_kp.public(), &mut client_rng)?;
     /// # let p_file = server_state.finish(register_m3)?;
-    /// let (login_m1, client_login_state) = ClientLogin::<Default>::start(b"hunter2", &mut client_rng)?;
+    /// let (login_m1, client_login_state) = ClientLogin::<Default>::start(b"hunter2", None, &mut client_rng)?;
     /// let (login_m2, server_login_state) = ServerLogin::start(p_file, &server_kp.private(), login_m1, &mut server_rng)?;
     /// let (login_m3, client_transport, _export_key) = client_login_state.finish(login_m2, &server_kp.public(), &mut client_rng)?;
     /// # Ok::<(), ProtocolError>(())
@@ -1124,17 +768,20 @@ impl<CS: CipherSuite> ClientLogin<CS> {
     pub fn finish<R: RngCore + CryptoRng>(
         self,
         l2: LoginSecondMessage<CS>,
-        _server_s_pk: &<<CS as CipherSuite>::KeyFormat as KeyPair>::Repr,
+        server_s_pk: &<<CS as CipherSuite>::KeyFormat as KeyPair>::Repr,
         _client_e_sk_rng: &mut R,
     ) -> Result<ClientLoginFinishResult<CS>, ProtocolError> {
         let l2_bytes: Vec<u8> = [&l2.beta.to_arr()[..], &l2.envelope.to_bytes()].concat();
 
-        let password_derived_key =
-            get_password_derived_key::<CS::Group, CS::SlowHash, CS::Hash>(&self.token, l2.beta)?;
+        let password_derived_key = get_password_derived_key::<CS::Group, CS::SlowHash, CS::Hash>(
+            self.password.clone(),
+            l2.beta,
+            &self.blinding_factor,
+        )?;
 
         let opened_envelope = &l2
             .envelope
-            .open(&password_derived_key)
+            .open(&password_derived_key, &server_s_pk.to_arr())
             .map_err(|e| match e {
                 InternalPakeError::SealOpenHmacError => PakeError::InvalidLoginError,
                 err => PakeError::from(err),
@@ -1144,12 +791,8 @@ impl<CS: CipherSuite> ClientLogin<CS> {
             l2_bytes,
             l2.ke2_message,
             &self.ke1_state,
-            <CS::KeyFormat as KeyPair>::Repr::from_bytes(
-                &opened_envelope.credentials_map[&CredentialType::PkS],
-            )?,
-            <CS::KeyFormat as KeyPair>::Repr::from_bytes(
-                &opened_envelope.credentials_map[&CredentialType::SkU],
-            )?,
+            server_s_pk.clone(),
+            <CS::KeyFormat as KeyPair>::Repr::from_bytes(&opened_envelope.plaintext)?,
         )?;
 
         Ok((
@@ -1213,12 +856,12 @@ impl<CS: CipherSuite> ServerLogin<CS> {
     /// let mut client_rng = OsRng;
     /// let mut server_rng = OsRng;
     /// let server_kp = X25519KeyPair::generate_random(&mut server_rng)?;
-    /// # let (register_m1, client_state) = ClientRegistration::<Default>::start(b"hunter2", &mut client_rng)?;
+    /// # let (register_m1, client_state) = ClientRegistration::<Default>::start(b"hunter2", None, &mut client_rng)?;
     /// # let (register_m2, server_state) =
     /// ServerRegistration::<Default>::start(register_m1, &mut server_rng)?;
     /// # let (register_m3, _export_key) = client_state.finish(register_m2, server_kp.public(), &mut client_rng)?;
     /// # let p_file = server_state.finish(register_m3)?;
-    /// let (login_m1, client_login_state) = ClientLogin::<Default>::start(b"hunter2", &mut client_rng)?;
+    /// let (login_m1, client_login_state) = ClientLogin::<Default>::start(b"hunter2", None, &mut client_rng)?;
     /// let (login_m2, server_login_state) = ServerLogin::start(p_file, &server_kp.private(), login_m1, &mut server_rng)?;
     /// # Ok::<(), ProtocolError>(())
     /// ```
@@ -1229,7 +872,7 @@ impl<CS: CipherSuite> ServerLogin<CS> {
         rng: &mut R,
     ) -> Result<ServerLoginStartResult<CS>, ProtocolError> {
         let l1_bytes = &l1.to_bytes();
-        let beta = oprf::evaluate(l1.alpha, &password_file.oprf_key)?;
+        let beta = oprf::generate_oprf2(l1.alpha, &password_file.oprf_key)?;
 
         let client_s_pk = password_file
             .client_s_pk
@@ -1288,12 +931,12 @@ impl<CS: CipherSuite> ServerLogin<CS> {
     /// let mut client_rng = OsRng;
     /// let mut server_rng = OsRng;
     /// let server_kp = X25519KeyPair::generate_random(&mut server_rng)?;
-    /// # let (register_m1, client_state) = ClientRegistration::<Default>::start(b"hunter2", &mut client_rng)?;
+    /// # let (register_m1, client_state) = ClientRegistration::<Default>::start(b"hunter2", None, &mut client_rng)?;
     /// # let (register_m2, server_state) =
     /// ServerRegistration::<Default>::start(register_m1, &mut server_rng)?;
     /// # let (register_m3, _export_key) = client_state.finish(register_m2, server_kp.public(), &mut client_rng)?;
     /// # let p_file = server_state.finish(register_m3)?;
-    /// let (login_m1, client_login_state) = ClientLogin::<Default>::start(b"hunter2", &mut client_rng)?;
+    /// let (login_m1, client_login_state) = ClientLogin::<Default>::start(b"hunter2", None, &mut client_rng)?;
     /// let (login_m2, server_login_state) = ServerLogin::start(p_file, &server_kp.private(), login_m1, &mut server_rng)?;
     /// let (login_m3, client_transport, _export_key) = client_login_state.finish(login_m2, &server_kp.public(), &mut client_rng)?;
     /// let mut server_transport = server_login_state.finish(login_m3)?;
@@ -1315,9 +958,10 @@ impl<CS: CipherSuite> ServerLogin<CS> {
 
 // Helper functions
 fn get_password_derived_key<G: Group, SH: SlowHash<D>, D: Hash>(
-    token: &oprf::Token<G>,
+    password: Vec<u8>,
     beta: G,
+    blinding_factor: &G::Scalar,
 ) -> Result<Vec<u8>, InternalPakeError> {
-    let oprf_output = oprf::unblind_and_finalize::<G, D>(token, beta)?;
+    let oprf_output = oprf::generate_oprf3::<G, D>(&password, beta, blinding_factor)?;
     SH::hash(oprf_output)
 }
diff --git a/src/oprf.rs b/src/oprf.rs
index d189307..c00480c 100644
--- a/src/oprf.rs
+++ b/src/oprf.rs
@@ -11,51 +11,47 @@ use generic_array::GenericArray;
 use hkdf::Hkdf;
 use rand_core::{CryptoRng, RngCore};
 
-/// Used to store the OPRF input and blinding factor
-pub struct Token<Grp: Group> {
-    pub(crate) data: Vec<u8>,
-    pub(crate) blind: Grp::Scalar,
+pub struct OprfClientBytes<Grp: Group> {
+    pub alpha: Grp,
+    pub blinding_factor: Grp::Scalar,
 }
 
-static STR_VOPRF: &[u8] = b"VOPRF05";
-
 /// Computes the first step for the multiplicative blinding version of DH-OPRF. This
 /// message is sent from the client (who holds the input) to the server (who holds the OPRF key).
 /// The client can also pass in an optional "pepper" string to be mixed in with the input through
 /// an HKDF computation.
-pub(crate) fn blind_with_postprocessing<R: RngCore + CryptoRng, G: GroupWithMapToCurve>(
+pub(crate) fn generate_oprf1<R: RngCore + CryptoRng, G: GroupWithMapToCurve>(
     input: &[u8],
+    pepper: Option<&[u8]>,
     blinding_factor_rng: &mut R,
-    postprocess: fn(G::Scalar) -> G::Scalar,
-) -> Result<(Token<G>, G), InternalPakeError> {
-    let mapped_point = G::map_to_curve(input, Some(STR_VOPRF)); // TODO: add contextString from RFC
+) -> Result<OprfClientBytes<G>, InternalPakeError> {
+    let mapped_point = G::map_to_curve(input, pepper);
     let blinding_factor = G::random_scalar(blinding_factor_rng);
-    let blind = postprocess(blinding_factor);
-    let blind_token = mapped_point * &blind;
-    Ok((
-        Token {
-            data: input.to_vec(),
-            blind,
-        },
-        blind_token,
-    ))
+    let alpha = mapped_point * &blinding_factor;
+    Ok(OprfClientBytes {
+        alpha,
+        blinding_factor,
+    })
 }
 
 /// Computes the second step for the multiplicative blinding version of DH-OPRF. This
 /// message is sent from the server (who holds the OPRF key) to the client.
-pub(crate) fn evaluate<G: Group>(point: G, oprf_key: &G::Scalar) -> Result<G, InternalPakeError> {
+pub(crate) fn generate_oprf2<G: Group>(
+    point: G,
+    oprf_key: &G::Scalar,
+) -> Result<G, InternalPakeError> {
     Ok(point * oprf_key)
 }
 
 /// Computes the third step for the multiplicative blinding version of DH-OPRF, in which
 /// the client unblinds the server's message.
-pub(crate) fn unblind_and_finalize<G: Group, H: Hash>(
-    token: &Token<G>,
+pub(crate) fn generate_oprf3<G: Group, H: Hash>(
+    input: &[u8],
     point: G,
+    blinding_factor: &G::Scalar,
 ) -> Result<GenericArray<u8, <H as Digest>::OutputSize>, InternalPakeError> {
-    let unblinded = point * &G::scalar_invert(&token.blind);
-    let ikm: Vec<u8> = [&unblinded.to_arr()[..], &token.data].concat();
-    // TODO: implement proper finalizing code here
+    let unblinded = point * &G::scalar_invert(&blinding_factor);
+    let ikm: Vec<u8> = [&unblinded.to_arr()[..], input].concat();
     let (prk, _) = Hkdf::<H>::extract(None, &ikm);
     Ok(prk)
 }
@@ -63,26 +59,31 @@ pub(crate) fn unblind_and_finalize<G: Group, H: Hash>(
 // Benchmarking shims
 #[cfg(feature = "bench")]
 #[inline]
-pub fn blind_shim<R: RngCore + CryptoRng, G: GroupWithMapToCurve>(
+pub fn generate_oprf1_shim<R: RngCore + CryptoRng, G: GroupWithMapToCurve>(
     input: &[u8],
+    pepper: Option<&[u8]>,
     blinding_factor_rng: &mut R,
-) -> Result<(Token<G>, G), InternalPakeError> {
-    blind_with_postprocessing(input, blinding_factor_rng, std::convert::identity)
+) -> Result<OprfClientBytes<G>, InternalPakeError> {
+    generate_oprf1(input, pepper, blinding_factor_rng)
 }
 
 #[cfg(feature = "bench")]
 #[inline]
-pub fn evaluate_shim<G: Group>(point: G, oprf_key: &G::Scalar) -> Result<G, InternalPakeError> {
-    evaluate(point, oprf_key)
+pub fn generate_oprf2_shim<G: Group>(
+    point: G,
+    oprf_key: &G::Scalar,
+) -> Result<G, InternalPakeError> {
+    generate_oprf2(point, oprf_key)
 }
 
 #[cfg(feature = "bench")]
 #[inline]
-pub fn unblind_and_finalize_shim<G: Group, H: Hash>(
-    token: &Token<G>,
+pub fn generate_oprf3_shim<G: Group, H: Hash>(
+    input: &[u8],
     point: G,
+    blinding_factor: &G::Scalar,
 ) -> Result<GenericArray<u8, <H as Digest>::OutputSize>, InternalPakeError> {
-    unblind_and_finalize::<G, H>(token, point)
+    generate_oprf3::<G, H>(input, point, blinding_factor)
 }
 
 // Tests
@@ -102,7 +103,7 @@ mod tests {
         input: &[u8],
         oprf_key: &[u8; 32],
     ) -> GenericArray<u8, <RistrettoPoint as Group>::ElemLen> {
-        let (hashed_input, _) = Hkdf::<Sha512>::extract(Some(STR_VOPRF), &input);
+        let (hashed_input, _) = Hkdf::<Sha512>::extract(None, &input);
         let point = RistrettoPoint::hash_to_curve(GenericArray::from_slice(&hashed_input));
         let scalar =
             RistrettoPoint::from_scalar_slice(GenericArray::from_slice(&oprf_key[..])).unwrap();
@@ -117,19 +118,18 @@ mod tests {
     fn oprf_retrieval() -> Result<(), InternalPakeError> {
         let input = b"hunter2";
         let mut rng = OsRng;
-        let (token, alpha) = blind_with_postprocessing::<_, RistrettoPoint>(
-            &input[..],
-            &mut rng,
-            std::convert::identity,
-        )?;
-        let oprf_key_bytes = arr![
+        let OprfClientBytes {
+            alpha,
+            blinding_factor,
+        } = generate_oprf1::<_, RistrettoPoint>(&input[..], None, &mut rng)?;
+        let salt_bytes = arr![
             u8; 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
             24, 25, 26, 27, 28, 29, 30, 31, 32,
         ];
-        let oprf_key = RistrettoPoint::from_scalar_slice(&oprf_key_bytes)?;
-        let beta = evaluate::<RistrettoPoint>(alpha, &oprf_key)?;
-        let res = unblind_and_finalize::<RistrettoPoint, sha2::Sha256>(&token, beta)?;
-        let res2 = prf(&input[..], &oprf_key.as_bytes());
+        let salt = RistrettoPoint::from_scalar_slice(&salt_bytes)?;
+        let beta = generate_oprf2::<RistrettoPoint>(alpha, &salt)?;
+        let res = generate_oprf3::<RistrettoPoint, sha2::Sha256>(input, beta, &blinding_factor)?;
+        let res2 = prf(&input[..], &salt.as_bytes());
         assert_eq!(res, res2);
         Ok(())
     }
@@ -139,15 +139,14 @@ mod tests {
         let mut rng = OsRng;
         let mut input = vec![0u8; 64];
         rng.fill_bytes(&mut input);
-        let (token, alpha) = blind_with_postprocessing::<_, RistrettoPoint>(
-            &input,
-            &mut rng,
-            std::convert::identity,
-        )
-        .unwrap();
-        let res = unblind_and_finalize::<RistrettoPoint, sha2::Sha256>(&token, alpha).unwrap();
-
-        let (hashed_input, _) = Hkdf::<Sha512>::extract(Some(STR_VOPRF), &input);
+        let OprfClientBytes {
+            alpha,
+            blinding_factor,
+        } = generate_oprf1::<_, RistrettoPoint>(&input, None, &mut rng).unwrap();
+        let res = generate_oprf3::<RistrettoPoint, sha2::Sha256>(&input, alpha, &blinding_factor)
+            .unwrap();
+
+        let (hashed_input, _) = Hkdf::<Sha512>::extract(None, &input);
         let mut bits = [0u8; 64];
         bits.copy_from_slice(&hashed_input);
 
diff --git a/src/serialization/mod.rs b/src/serialization/mod.rs
deleted file mode 100644
index 261e589..0000000
--- a/src/serialization/mod.rs
+++ /dev/null
@@ -1,112 +0,0 @@
-// 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::errors::PakeError;
-
-use crate::{
-    ciphersuite::CipherSuite,
-    hash::Hash,
-    keypair::KeyPair,
-    opaque::{
-        LoginFirstMessage, LoginSecondMessage, LoginThirdMessage, RegisterFirstMessage,
-        RegisterSecondMessage, RegisterThirdMessage,
-    },
-};
-
-pub enum ProtocolMessageType {
-    RegistrationRequest,
-    RegistrationResponse,
-    RegistrationUpload,
-    CredentialRequest,
-    CredentialResponse,
-    KeyExchange,
-}
-
-#[derive(Copy, Clone, Eq, Hash, PartialEq)]
-pub enum CredentialType {
-    SkU,
-    PkU,
-    PkS,
-    IdU,
-    IdS,
-}
-
-pub(crate) fn u8_to_credential_type(x: u8) -> Option<CredentialType> {
-    match x {
-        1 => Some(CredentialType::SkU),
-        2 => Some(CredentialType::PkU),
-        3 => Some(CredentialType::PkS),
-        4 => Some(CredentialType::IdU),
-        5 => Some(CredentialType::IdS),
-        _ => None,
-    }
-}
-
-impl<T> From<&RegisterFirstMessage<T>> for ProtocolMessageType {
-    fn from(_mt: &RegisterFirstMessage<T>) -> Self {
-        ProtocolMessageType::RegistrationRequest
-    }
-}
-
-impl<T> From<&RegisterSecondMessage<T>> for ProtocolMessageType {
-    fn from(_mt: &RegisterSecondMessage<T>) -> Self {
-        ProtocolMessageType::RegistrationResponse
-    }
-}
-
-impl<T: KeyPair, U: Hash> From<&RegisterThirdMessage<T, U>> for ProtocolMessageType {
-    fn from(_mt: &RegisterThirdMessage<T, U>) -> Self {
-        ProtocolMessageType::RegistrationUpload
-    }
-}
-
-impl<T: CipherSuite> From<&LoginFirstMessage<T>> for ProtocolMessageType {
-    fn from(_mt: &LoginFirstMessage<T>) -> Self {
-        ProtocolMessageType::CredentialRequest
-    }
-}
-
-impl<T: CipherSuite> From<&LoginSecondMessage<T>> for ProtocolMessageType {
-    fn from(_mt: &LoginSecondMessage<T>) -> Self {
-        ProtocolMessageType::CredentialResponse
-    }
-}
-
-impl<T: CipherSuite> From<&LoginThirdMessage<T>> for ProtocolMessageType {
-    fn from(_mt: &LoginThirdMessage<T>) -> Self {
-        ProtocolMessageType::KeyExchange
-    }
-}
-
-pub(crate) fn serialize(input: &[u8], max_bytes: usize) -> Vec<u8> {
-    let mut output: Vec<u8> = Vec::new();
-    output.extend_from_slice(&input.len().to_be_bytes()[8 - max_bytes..]);
-    output.extend_from_slice(&input[..]);
-    output
-}
-
-pub(crate) fn tokenize(input: Vec<u8>, size_bytes: usize) -> Result<(Vec<u8>, Vec<u8>), PakeError> {
-    if size_bytes > 8 || input.len() < size_bytes {
-        return Err(PakeError::SerializationError);
-    }
-
-    let mut size_array = [0u8; 8];
-    for i in 0..size_bytes {
-        size_array[8 - size_bytes + i] = input[i];
-    }
-    let size = usize::from_be_bytes(size_array);
-
-    if size_bytes + size > input.len() {
-        return Err(PakeError::SerializationError);
-    }
-
-    Ok((
-        input[size_bytes..size_bytes + size].to_vec(),
-        input[size_bytes + size..].to_vec(),
-    ))
-}
-
-#[cfg(test)]
-mod tests;
diff --git a/src/slow_hash.rs b/src/slow_hash.rs
index 5c51df8..129fafc 100644
--- a/src/slow_hash.rs
+++ b/src/slow_hash.rs
@@ -5,7 +5,8 @@
 
 //! Trait specifying a slow hashing function
 
-use crate::{errors::InternalPakeError, hash::Hash};
+use crate::errors::InternalPakeError;
+use crate::hash::Hash;
 use digest::Digest;
 use generic_array::GenericArray;
 


Test patch:
diff --git a/src/serialization/tests.rs b/src/serialization/tests.rs
deleted file mode 100644
index 66e5aad..0000000
--- a/src/serialization/tests.rs
+++ /dev/null
@@ -1,372 +0,0 @@
-// 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,
-    envelope::Envelope,
-    group::Group,
-    key_exchange::{
-        traits::{KeyExchange, ToBytes},
-        tripledh::{TripleDH, NONCE_LEN},
-    },
-    keypair::{KeyPair, SizedBytes, X25519KeyPair},
-    opaque::*,
-    serialization::{serialize, ProtocolMessageType},
-};
-
-use curve25519_dalek::ristretto::RistrettoPoint;
-use proptest::{collection::vec, prelude::*};
-use rand_core::{OsRng, RngCore};
-
-use sha2::{Digest, Sha256};
-use std::convert::TryFrom;
-
-struct Default;
-impl CipherSuite for Default {
-    type Group = RistrettoPoint;
-    type KeyFormat = crate::keypair::X25519KeyPair;
-    type KeyExchange = TripleDH;
-    type Hash = sha2::Sha256;
-    type SlowHash = crate::slow_hash::NoOpHash;
-}
-
-const MAX_ID_LENGTH: usize = 10;
-
-fn random_ristretto_point() -> RistrettoPoint {
-    let mut rng = OsRng;
-    let mut random_bits = [0u8; 64];
-    rng.fill_bytes(&mut random_bits);
-
-    // This is because RistrettoPoint is on an obsolete sha2 version
-    let mut bits = [0u8; 64];
-    let mut hasher = sha2::Sha512::new();
-    hasher.update(&random_bits[..]);
-    bits.copy_from_slice(&hasher.finalize());
-
-    RistrettoPoint::from_uniform_bytes(&bits)
-}
-
-#[test]
-fn client_registration_roundtrip() {
-    let pw = b"hunter2";
-    let mut rng = OsRng;
-    let sc = <RistrettoPoint as Group>::random_scalar(&mut rng);
-    let id_u_length: usize = rng.gen_range(0, MAX_ID_LENGTH);
-    let id_s_length: usize = rng.gen_range(0, MAX_ID_LENGTH);
-    let mut id_u = [0u8; MAX_ID_LENGTH];
-    rng.fill_bytes(&mut id_u);
-    let mut id_s = [0u8; MAX_ID_LENGTH];
-    rng.fill_bytes(&mut id_s);
-
-    // serialization order: id_u, id_s, scalar, password
-    let bytes: Vec<u8> = [
-        &serialize(&id_u[..id_u_length], 2)[..],
-        &serialize(&id_s[..id_s_length], 2)[..],
-        &sc.as_bytes()[..],
-        &pw[..],
-    ]
-    .concat();
-    let reg = ClientRegistration::<Default>::try_from(&bytes[..]).unwrap();
-    let reg_bytes = reg.to_bytes();
-    assert_eq!(reg_bytes, bytes);
-}
-
-#[test]
-fn server_registration_roundtrip() {
-    // If we don't have envelope and client_pk, the server registration just
-    // contains the prf key
-    let mut rng = OsRng;
-    let oprf_key = <RistrettoPoint as Group>::random_scalar(&mut rng);
-    let mut oprf_bytes: Vec<u8> = vec![];
-    oprf_bytes.extend_from_slice(oprf_key.as_bytes());
-    let reg = ServerRegistration::<Default>::try_from(&oprf_bytes[..]).unwrap();
-    let reg_bytes = reg.to_bytes();
-    assert_eq!(reg_bytes, oprf_bytes);
-    // If we do have envelope and client pk, the server registration contains
-    // the whole kit
-
-    // Construct a mock envelope
-    let mut mock_envelope_bytes = Vec::new();
-    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-32 hmac
-    mock_envelope_bytes.extend_from_slice(&[0, 32]);
-    mock_envelope_bytes.extend_from_slice(&[0; 32]);
-
-    let mock_client_kp = Default::generate_random_keypair(&mut rng).unwrap();
-    // serialization order: oprf_key, public key, envelope
-    let mut bytes = Vec::<u8>::new();
-    bytes.extend_from_slice(oprf_key.as_bytes());
-    bytes.extend_from_slice(&mock_client_kp.public().to_arr());
-    bytes.extend_from_slice(&mock_envelope_bytes);
-    let reg = ServerRegistration::<Default>::try_from(&bytes[..]).unwrap();
-    let reg_bytes = reg.to_bytes();
-    assert_eq!(reg_bytes, bytes);
-}
-
-#[test]
-fn register_first_message_roundtrip() {
-    let pt = random_ristretto_point();
-    let pt_bytes = pt.to_arr().to_vec();
-
-    let mut rng = OsRng;
-    let id_length: usize = rng.gen_range(0, MAX_ID_LENGTH);
-    let mut id = [0u8; MAX_ID_LENGTH];
-    rng.fill_bytes(&mut id);
-
-    let alpha_length: usize = 32;
-    let total_length: usize = alpha_length + id_length + 4;
-
-    let mut input = Vec::new();
-    input.extend_from_slice(&[ProtocolMessageType::RegistrationRequest as u8 + 1]);
-    input.extend_from_slice(&total_length.to_be_bytes()[8 - 3..]);
-    input.extend_from_slice(&id_length.to_be_bytes()[8 - 2..]);
-    input.extend_from_slice(&id[..id_length]);
-    input.extend_from_slice(&alpha_length.to_be_bytes()[8 - 2..]);
-    input.extend_from_slice(pt_bytes.as_slice());
-
-    let r1 = RegisterFirstMessage::<RistrettoPoint>::deserialize(input.as_slice()).unwrap();
-    let r1_bytes = r1.serialize();
-    assert_eq!(input, r1_bytes);
-}
-
-#[test]
-fn register_second_message_roundtrip() {
-    let pt = random_ristretto_point();
-    let beta_bytes = pt.to_arr();
-    let mut rng = OsRng;
-    let skp = Default::generate_random_keypair(&mut rng).unwrap();
-    let pubkey_bytes = skp.public().to_arr();
-    let credential_types = [1, 1, 1, 3];
-
-    let beta_length: usize = beta_bytes.len();
-    let pubkey_length: usize = pubkey_bytes.len();
-    let total_length: usize = beta_length + pubkey_length + credential_types.len() + 4;
-
-    let mut input = Vec::new();
-    input.extend_from_slice(&[ProtocolMessageType::RegistrationResponse as u8 + 1]);
-    input.extend_from_slice(&total_length.to_be_bytes()[8 - 3..]);
-    input.extend_from_slice(&beta_length.to_be_bytes()[8 - 2..]);
-    input.extend_from_slice(beta_bytes.as_slice());
-    input.extend_from_slice(&pubkey_length.to_be_bytes()[8 - 2..]);
-    input.extend_from_slice(&pubkey_bytes.as_slice());
-    input.extend_from_slice(&credential_types);
-
-    let r2 = RegisterSecondMessage::<RistrettoPoint>::deserialize(input.as_slice()).unwrap();
-    let r2_bytes = r2.serialize();
-    assert_eq!(input, r2_bytes);
-}
-
-#[test]
-fn register_third_message_roundtrip() {
-    let mut rng = OsRng;
-    let skp = Default::generate_random_keypair(&mut rng).unwrap();
-    let pubkey_bytes = skp.public().to_arr();
-
-    let mut key = [0u8; 32];
-    rng.fill_bytes(&mut key);
-
-    let mut msg = [0u8; 32];
-    rng.fill_bytes(&mut msg);
-
-    let (envelope, _) =
-        Envelope::<sha2::Sha256>::seal_raw(&key, &msg, &pubkey_bytes, &mut rng).unwrap();
-    let envelope_bytes = envelope.serialize();
-
-    let pubkey_length: usize = pubkey_bytes.len();
-    let total_length: usize = pubkey_length + envelope_bytes.len() + 2;
-
-    let mut input = Vec::new();
-    input.extend_from_slice(&[ProtocolMessageType::RegistrationUpload as u8 + 1]);
-    input.extend_from_slice(&total_length.to_be_bytes()[8 - 3..]);
-    input.extend_from_slice(&envelope_bytes);
-    input.extend_from_slice(&pubkey_length.to_be_bytes()[8 - 2..]);
-    input.extend_from_slice(&pubkey_bytes[..]);
-
-    let r3 = RegisterThirdMessage::<X25519KeyPair, sha2::Sha256>::deserialize(&input[..]).unwrap();
-    let r3_bytes = r3.serialize();
-    assert_eq!(input, r3_bytes);
-}
-
-#[test]
-fn login_first_message_roundtrip() {
-    let mut rng = OsRng;
-    let alpha = random_ristretto_point();
-    let alpha_bytes = alpha.to_arr().to_vec();
-    let id_length: usize = rng.gen_range(0, MAX_ID_LENGTH);
-    let mut id = [0u8; MAX_ID_LENGTH];
-    rng.fill_bytes(&mut id);
-
-    let client_e_kp = Default::generate_random_keypair(&mut rng).unwrap();
-    let mut client_nonce = [0u8; NONCE_LEN];
-    rng.fill_bytes(&mut client_nonce);
-
-    let ke1m: Vec<u8> = [&client_nonce[..], &client_e_kp.public()].concat();
-
-    let alpha_length = alpha_bytes.len();
-    let total_length_without_ke1m: usize = id_length + alpha_length + 4;
-
-    let mut input = Vec::new();
-    input.extend_from_slice(&[ProtocolMessageType::CredentialRequest as u8 + 1]);
-    input.extend_from_slice(&total_length_without_ke1m.to_be_bytes()[8 - 3..]);
-    input.extend_from_slice(&id_length.to_be_bytes()[8 - 2..]);
-    input.extend_from_slice(&id[..id_length]);
-    input.extend_from_slice(&alpha_length.to_be_bytes()[8 - 2..]);
-    input.extend_from_slice(&alpha_bytes);
-    input.extend_from_slice(&ke1m[..]);
-
-    let l1 = LoginFirstMessage::<Default>::deserialize(input.as_slice()).unwrap();
-    let l1_bytes = l1.serialize();
-    assert_eq!(input, l1_bytes);
-}
-
-#[test]
-fn login_second_message_roundtrip() {
-    let pt = random_ristretto_point();
-    let pt_bytes = pt.to_arr().to_vec();
-
-    let mut rng = OsRng;
-    let skp = Default::generate_random_keypair(&mut rng).unwrap();
-    let pubkey_bytes = skp.public().to_arr();
-
-    let mut key = [0u8; 32];
-    rng.fill_bytes(&mut key);
-
-    let mut msg = [0u8; 32];
-    rng.fill_bytes(&mut msg);
-
-    let (envelope, _) =
-        Envelope::<sha2::Sha256>::seal_raw(&key, &msg, &pubkey_bytes, &mut rng).unwrap();
-
-    let server_e_kp = Default::generate_random_keypair(&mut rng).unwrap();
-    let mut mac = [0u8; 32];
-    rng.fill_bytes(&mut mac);
-    let mut server_nonce = [0u8; NONCE_LEN];
-    rng.fill_bytes(&mut server_nonce);
-
-    let ke2m: Vec<u8> = [&server_nonce[..], &server_e_kp.public(), &mac[..]].concat();
-
-    let total_length_without_ke2m: usize = pt_bytes.len() + envelope.to_bytes().len() + 2;
-
-    let mut input = Vec::new();
-    input.extend_from_slice(&[ProtocolMessageType::CredentialResponse as u8 + 1]);
-    input.extend_from_slice(&total_length_without_ke2m.to_be_bytes()[8 - 3..]);
-    input.extend_from_slice(&pt_bytes.len().to_be_bytes()[8 - 2..]);
-    input.extend_from_slice(pt_bytes.as_slice());
-    input.extend_from_slice(&envelope.to_bytes());
-    input.extend_from_slice(&ke2m[..]);
-
-    let l2 = LoginSecondMessage::<Default>::deserialize(&input).unwrap();
-    let l2_bytes = l2.serialize();
-    assert_eq!(input, l2_bytes);
-}
-
-#[test]
-fn client_login_roundtrip() {
-    let pw = b"hunter2";
-    let mut rng = OsRng;
-    let id_u_length: usize = rng.gen_range(0, MAX_ID_LENGTH);
-    let id_s_length: usize = rng.gen_range(0, MAX_ID_LENGTH);
-    let mut id_u = [0u8; MAX_ID_LENGTH];
-    rng.fill_bytes(&mut id_u);
-    let mut id_s = [0u8; MAX_ID_LENGTH];
-    rng.fill_bytes(&mut id_s);
-
-    let sc = <RistrettoPoint as Group>::random_scalar(&mut rng);
-
-    let client_e_kp = Default::generate_random_keypair(&mut rng).unwrap();
-    let mut client_nonce = [0u8; NONCE_LEN];
-    rng.fill_bytes(&mut client_nonce);
-
-    let l1_data = [&sc.to_bytes()[..], &client_nonce, client_e_kp.public()].concat();
-    let mut hasher = Sha256::new();
-    hasher.update(l1_data);
-    let hashed_l1 = hasher.finalize();
-
-    // serialization order: id_u, id_s, scalar, password, ke1_state
-    let bytes: Vec<u8> = [
-        &serialize(&id_u[..id_u_length], 2)[..],
-        &serialize(&id_s[..id_s_length], 2)[..],
-        &sc.as_bytes()[..],
-        &pw[..],
-        client_e_kp.public(),
-        &client_nonce,
-        hashed_l1.as_slice(),
-    ]
-    .concat();
-    let reg = ClientLogin::<Default>::try_from(&bytes[..]).unwrap();
-    let reg_bytes = reg.to_bytes();
-    assert_eq!(reg_bytes, bytes);
-}
-
-#[test]
-fn ke1_message_roundtrip() {
-    let mut rng = OsRng;
-
-    let client_e_kp = Default::generate_random_keypair(&mut rng).unwrap();
-    let mut client_nonce = [0u8; NONCE_LEN];
-    rng.fill_bytes(&mut client_nonce);
-
-    let ke1m: Vec<u8> = [&client_nonce[..], &client_e_kp.public()].concat();
-    let reg =
-        <TripleDH as KeyExchange<sha2::Sha256, crate::keypair::X25519KeyPair>>::KE1Message::try_from(&ke1m[..]).unwrap();
-    let reg_bytes = reg.to_bytes();
-    assert_eq!(reg_bytes, ke1m);
-}
-
-proptest! {
-
-#[test]
-fn test_nocrash_register_first_message(bytes in vec(any::<u8>(), 0..200)) {
-    RegisterFirstMessage::<RistrettoPoint>::try_from(&bytes[..]).map_or(true, |_| true);
-}
-
-#[test]
-fn test_nocrash_register_second_message(bytes in vec(any::<u8>(), 0..200)) {
-    RegisterSecondMessage::<RistrettoPoint>::try_from(&bytes[..]).map_or(true, |_| true);
-}
-
-#[test]
-fn test_nocrash_register_third_message(bytes in vec(any::<u8>(), 0..200)) {
-    RegisterThirdMessage::<crate::keypair::X25519KeyPair, sha2::Sha512>::try_from(&bytes[..]).map_or(true, |_| true);
-}
-
-#[test]
-fn test_nocrash_login_first_message(bytes in vec(any::<u8>(), 0..500)) {
-    LoginFirstMessage::<Default>::try_from(&bytes[..]).map_or(true, |_| true);
-}
-
-#[test]
-fn test_nocrash_login_second_message(bytes in vec(any::<u8>(), 0..500)) {
-    LoginSecondMessage::<Default>::try_from(&bytes[..]).map_or(true, |_| true);
-}
-
-#[test]
-fn test_nocrash_login_third_message(bytes in vec(any::<u8>(), 0..500)) {
-    LoginThirdMessage::<Default>::try_from(&bytes[..]).map_or(true, |_| true);
-}
-
-#[test]
-fn test_nocrash_client_registration(bytes in vec(any::<u8>(), 0..700)) {
-    ClientRegistration::<Default>::try_from(&bytes[..]).map_or(true, |_| true);
-}
-
-#[test]
-fn test_nocrash_server_registration(bytes in vec(any::<u8>(), 0..700)) {
-    ServerRegistration::<Default>::try_from(&bytes[..]).map_or(true, |_| true);
-}
-
-#[test]
-fn test_nocrash_client_login(bytes in vec(any::<u8>(), 0..700)) {
-    ClientLogin::<Default>::try_from(&bytes[..]).map_or(true, |_| true);
-}
-
-#[test]
-fn test_nocrash_server_login(bytes in vec(any::<u8>(), 0..700)) {
-    ServerLogin::<Default>::try_from(&bytes[..]).map_or(true, |_| true);
-}
-
-}
diff --git a/src/tests/mod.rs b/src/tests/mod.rs
index 1449398..09ac24f 100644
--- a/src/tests/mod.rs
+++ b/src/tests/mod.rs
@@ -5,3 +5,4 @@
 
 pub mod mock_rng;
 mod opaque_ke_test;
+mod serialization;
diff --git a/src/tests/opaque_ke_test.rs b/src/tests/opaque_ke_test.rs
index 848afb0..748cf1b 100644
--- a/src/tests/opaque_ke_test.rs
+++ b/src/tests/opaque_ke_test.rs
@@ -14,7 +14,6 @@ use crate::{
     tests::mock_rng::CycleRng,
 };
 use curve25519_dalek::edwards::EdwardsPoint;
-use generic_array::GenericArray;
 use rand_core::{OsRng, RngCore};
 use serde_json::Value;
 use std::convert::TryFrom;
@@ -40,10 +39,10 @@ pub struct TestVectorParameters {
     pub server_s_sk: Vec<u8>,
     pub server_e_pk: Vec<u8>,
     pub server_e_sk: Vec<u8>,
-    pub id_u: Vec<u8>,
-    pub id_s: Vec<u8>,
     pub password: Vec<u8>,
+    pub blinding_factor_raw: Vec<u8>,
     pub blinding_factor: Vec<u8>,
+    pub pepper: Vec<u8>,
     pub oprf_key: Vec<u8>,
     pub envelope_nonce: Vec<u8>,
     pub client_nonce: Vec<u8>,
@@ -65,35 +64,35 @@ pub struct TestVectorParameters {
 
 static TEST_VECTOR: &str = r#"
 {
-    "client_s_pk": "7489b55c78b380db87d664178e5a020eb2f9bbeac0a44f6fb034ccba8de4a934",
-    "client_s_sk": "f0499a6c8bac723debd497b672c2d89ed2d96fd190fce247e0dd3019dce8ec59",
-    "client_e_pk": "c87afc8a9dc82c93dc6fa9d27654c6b909de929e542e94a87ffb7b3256190a46",
-    "client_e_sk": "107078f8e2ddd88c3d37e611ae932d798403e475f52a6695639999f963063576",
-    "server_s_pk": "764f186883a88353586c2427bfbe0ff3e5a0f56af414b0c42a5a300fc426ba4d",
-    "server_s_sk": "c089cb11e78ea8923cc25857ba51fd5da820079a9a2b377bc87dcd496b563e5c",
-    "server_e_pk": "05d99649994c006a508b996d11a94f52ae68cca44087bdd69602dfceb92d950c",
-    "server_e_sk": "70c4df069c1a7b70c16cf6409157674c3f8adfd0919f9dd67a254cf167c7e87f",
-    "id_u": "696455",
-    "id_s": "696453",
+    "client_s_pk": "b2341df425f90244c72d8e19b249ca0d6d1a3a3dfe6ee1773e1b782a81efef29",
+    "client_s_sk": "701e8cd1263abd2f2a22d4dc94b1d5fe3c9cb14030e7e7c154745825b059fd7f",
+    "client_e_pk": "97cb1eb93a69542597517b110ccca457d5ce8d8bfcbfb2a9258bb7b4bd7f716e",
+    "client_e_sk": "80616968ed8daae02c02d3ba41a70104ed0deecd2276e058994d601a1351b359",
+    "server_s_pk": "e12d737e520eaf8504fbf302c2945011bff360bdf02ee102f2ebd6a883c80e02",
+    "server_s_sk": "9075d3d3c5b6bc2f6218e7672c0532c619ce09dddf196006c5ffdaf628a3d760",
+    "server_e_pk": "f73d27d7ca78ded52209bc3bae000f9d95b147360edac1e97c148a3a7396a279",
+    "server_e_sk": "a0e59a07908fc793c590fd83343003a54330e24af908ed31c921e6e6504c3248",
     "password": "70617373776f7264",
-    "blinding_factor": "c5629094a160136e99012cf9c8eb19d9d62f87cadf846636bd175064a78b2d00",
-    "oprf_key": "f431dcb851f3c8202b9dd1a06d8d32434bbab88de4fdd079452faf2359a8d408",
-    "envelope_nonce": "be38985f7e04dab53e0bddf32cc9eeb64d7f072e089650b681ba4bb04bcfaeb2",
-    "client_nonce": "0c51879d4ae4cbd047fbf1ba9c7512c25c8d809486f5e6018dff8c525d9f41f1",
-    "server_nonce": "c896afa11787f8374bbeb3876151bcf4b75c9511a70be3dddce7606a353f3bc3",
-    "r1": "01000027000369645500201d540787a850896d3c7407e5a2c17729772170dae61640872aeca109d64d4581",
-    "r2": "02000028002033f9c4bdfe3d2597cbf0c86db2b0b3e81a4400ad4c9618372f6e24d89229d9a4000001010103",
-    "r3": "030000aebe38985f7e04dab53e0bddf32cc9eeb64d7f072e089650b681ba4bb04bcfaeb20023441a15c5ccbbf863e0db5e03c6edc63696b05d83a66e4aa3e10aa1320936fe8357bc250023030020764f186883a88353586c2427bfbe0ff3e5a0f56af414b0c42a5a300fc426ba4d00208229fa7e73d11f6935de9d5aae17ab5ec77d6cff8d8456437a8098bb54aa9b9300207489b55c78b380db87d664178e5a020eb2f9bbeac0a44f6fb034ccba8de4a934",
-    "l1": "04000027000369645500201d540787a850896d3c7407e5a2c17729772170dae61640872aeca109d64d45810c51879d4ae4cbd047fbf1ba9c7512c25c8d809486f5e6018dff8c525d9f41f1c87afc8a9dc82c93dc6fa9d27654c6b909de929e542e94a87ffb7b3256190a46",
-    "l2": "050000ae002033f9c4bdfe3d2597cbf0c86db2b0b3e81a4400ad4c9618372f6e24d89229d9a4be38985f7e04dab53e0bddf32cc9eeb64d7f072e089650b681ba4bb04bcfaeb20023441a15c5ccbbf863e0db5e03c6edc63696b05d83a66e4aa3e10aa1320936fe8357bc250023030020764f186883a88353586c2427bfbe0ff3e5a0f56af414b0c42a5a300fc426ba4d00208229fa7e73d11f6935de9d5aae17ab5ec77d6cff8d8456437a8098bb54aa9b9370c4df069c1a7b70c16cf6409157674c3f8adfd0919f9dd67a254cf167c7e87f05d99649994c006a508b996d11a94f52ae68cca44087bdd69602dfceb92d950cbd9f8a529e49110d4bfe863d449663b0cda71ba29e0aa46f63bbc7b6b054ad8c",
-    "l3": "6ba92c16abdd010bc8e9a5175d639512f8b270767d4b7198d03a985935e7da6d",
-    "client_registration_state": "00036964550003696453c5629094a160136e99012cf9c8eb19d9d62f87cadf846636bd175064a78b2d0070617373776f7264",
-    "client_login_state": "00036964550003696453c5629094a160136e99012cf9c8eb19d9d62f87cadf846636bd175064a78b2d00107078f8e2ddd88c3d37e611ae932d798403e475f52a6695639999f9630635760c51879d4ae4cbd047fbf1ba9c7512c25c8d809486f5e6018dff8c525d9f41f123c1c83fbf2a84c442b079fcacff55b13a4aebf9ba326e992c83b550afbb0c8770617373776f7264",
-    "server_registration_state": "f431dcb851f3c8202b9dd1a06d8d32434bbab88de4fdd079452faf2359a8d408",
-    "server_login_state": "72486032f6ff6f079144a891fdcb5ca63ede147f327313437c6bf2fd79d08b1faf03840b6c031f7afb66e2740ae064fc140c9aec2ac42295a6d1201d6ad5cdc641d81a7e3805c996ff9fb15fbcd4eddb528a3622f0f4488bca04bace6d740ee3",
-    "password_file": "f431dcb851f3c8202b9dd1a06d8d32434bbab88de4fdd079452faf2359a8d4087489b55c78b380db87d664178e5a020eb2f9bbeac0a44f6fb034ccba8de4a934be38985f7e04dab53e0bddf32cc9eeb64d7f072e089650b681ba4bb04bcfaeb20023441a15c5ccbbf863e0db5e03c6edc63696b05d83a66e4aa3e10aa1320936fe8357bc250023030020764f186883a88353586c2427bfbe0ff3e5a0f56af414b0c42a5a300fc426ba4d00208229fa7e73d11f6935de9d5aae17ab5ec77d6cff8d8456437a8098bb54aa9b93",
-    "export_key": "c2bc61bafeb9ab541fa362dc154c7a07dab8479e486da2daf9408438d9dc562f",
-    "shared_secret": "41d81a7e3805c996ff9fb15fbcd4eddb528a3622f0f4488bca04bace6d740ee3"
+    "blinding_factor_raw": "ca2d8ae51794579bd0f46044d7daccf222b4590053536b48575bc169f7478fd0a0b580fb0aae948c26ba403a2e7b98f563e434a0aad93f4105419c474453c34e",
+    "blinding_factor": "5a9a073b1a1efedebdb404bc073ae74b316920d68ab628bed0c500cae95d6e02",
+    "pepper": "706570706572",
+    "oprf_key": "203fabe2af9c8dc668b81db1ece9c2412c94c276495f33202479886de1b12907",
+    "envelope_nonce": "b0076712e01fecdb12301d5d7da92236e47f20494e68defb32084f1ab6c3d4f8",
+    "client_nonce": "b9f09e9b0606fa88c4194011d5c204861b73c43cbf1ea0d08c03ec2fd6d05572",
+    "server_nonce": "a213c02274e7f20fc3b571d25e98854c5dae2cfde6c9bf228a66bf3eff3e2a97",
+    "r1": "7e2c67a156ab27490f20008fcae9e9f722d8a9f4eeac373a711259981ca05dd5",
+    "r2": "710fdd19883e869e784c84f2864fa0bfc227662404b77cc8a54d79ae7fb931ea",
+    "r3": "b0076712e01fecdb12301d5d7da92236e47f20494e68defb32084f1ab6c3d4f8923b1d26cac4e3d91cec445b3322f4cc69a727f184353cb4dfe6d55a4c7d2bb4b77fbd41eacb8434f102c8c29cd4831e708046d38615df566675421ae8eb4933b2341df425f90244c72d8e19b249ca0d6d1a3a3dfe6ee1773e1b782a81efef29",
+    "l1": "7e2c67a156ab27490f20008fcae9e9f722d8a9f4eeac373a711259981ca05dd5b9f09e9b0606fa88c4194011d5c204861b73c43cbf1ea0d08c03ec2fd6d0557297cb1eb93a69542597517b110ccca457d5ce8d8bfcbfb2a9258bb7b4bd7f716e",
+    "l2": "710fdd19883e869e784c84f2864fa0bfc227662404b77cc8a54d79ae7fb931eab0076712e01fecdb12301d5d7da92236e47f20494e68defb32084f1ab6c3d4f8923b1d26cac4e3d91cec445b3322f4cc69a727f184353cb4dfe6d55a4c7d2bb4b77fbd41eacb8434f102c8c29cd4831e708046d38615df566675421ae8eb4933a0e59a07908fc793c590fd83343003a54330e24af908ed31c921e6e6504c3248f73d27d7ca78ded52209bc3bae000f9d95b147360edac1e97c148a3a7396a27939ccf2a17a5b281068665b4865e6c6331533461a8e10a4ceffc4c6a6609c326a",
+    "l3": "127144e6469e001d56237a58c8c869a8173e042bf2ff19d8331441d36ada9c3f",
+    "client_registration_state": "5a9a073b1a1efedebdb404bc073ae74b316920d68ab628bed0c500cae95d6e0270617373776f7264",
+    "client_login_state": "5a9a073b1a1efedebdb404bc073ae74b316920d68ab628bed0c500cae95d6e0280616968ed8daae02c02d3ba41a70104ed0deecd2276e058994d601a1351b359b9f09e9b0606fa88c4194011d5c204861b73c43cbf1ea0d08c03ec2fd6d05572f258311568d792d6ebecee225c0fde4512139e29a435e9f9a0b82dc3809a83ab70617373776f7264",
+    "server_registration_state": "203fabe2af9c8dc668b81db1ece9c2412c94c276495f33202479886de1b12907",
+    "server_login_state": "ebc0953924d55ad66aa801a7c85f47f35889b90002451a04fb7134b8a2a5a33cd69098c0a81ce06f58cbe4fd6ba23c9c1404ad6f639ba64d5f0f7bf0a041fc5872b17f13bd41cbfbdfa8d74bc94ec1abcc77b9a3da8fbad918ca0a5f84a81443",
+    "password_file": "203fabe2af9c8dc668b81db1ece9c2412c94c276495f33202479886de1b12907b2341df425f90244c72d8e19b249ca0d6d1a3a3dfe6ee1773e1b782a81efef29b0076712e01fecdb12301d5d7da92236e47f20494e68defb32084f1ab6c3d4f8923b1d26cac4e3d91cec445b3322f4cc69a727f184353cb4dfe6d55a4c7d2bb4b77fbd41eacb8434f102c8c29cd4831e708046d38615df566675421ae8eb4933",
+    "export_key": "da3a52148a58168c9f804df5e216e3d3f16e935d4d70a5eb249433d88e02ae4c",
+    "shared_secret": "72b17f13bd41cbfbdfa8d74bc94ec1abcc77b9a3da8fbad918ca0a5f84a81443"
 }
 "#;
 
@@ -113,10 +112,10 @@ fn populate_test_vectors(values: &Value) -> TestVectorParameters {
         server_s_sk: decode(&values, "server_s_sk").unwrap(),
         server_e_pk: decode(&values, "server_e_pk").unwrap(),
         server_e_sk: decode(&values, "server_e_sk").unwrap(),
-        id_u: decode(&values, "id_u").unwrap(),
-        id_s: decode(&values, "id_s").unwrap(),
         password: decode(&values, "password").unwrap(),
+        blinding_factor_raw: decode(&values, "blinding_factor_raw").unwrap(),
         blinding_factor: decode(&values, "blinding_factor").unwrap(),
+        pepper: decode(&values, "pepper").unwrap(),
         oprf_key: decode(&values, "oprf_key").unwrap(),
         envelope_nonce: decode(&values, "envelope_nonce").unwrap(),
         client_nonce: decode(&values, "client_nonce").unwrap(),
@@ -148,9 +147,14 @@ fn stringify_test_vectors(p: &TestVectorParameters) -> String {
     s.push_str(format!("\"server_s_sk\": \"{}\",\n", hex::encode(&p.server_s_sk)).as_str());
     s.push_str(format!("\"server_e_pk\": \"{}\",\n", hex::encode(&p.server_e_pk)).as_str());
     s.push_str(format!("\"server_e_sk\": \"{}\",\n", hex::encode(&p.server_e_sk)).as_str());
-    s.push_str(format!("\"id_u\": \"{}\",\n", hex::encode(&p.id_u)).as_str());
-    s.push_str(format!("\"id_s\": \"{}\",\n", hex::encode(&p.id_s)).as_str());
     s.push_str(format!("\"password\": \"{}\",\n", hex::encode(&p.password)).as_str());
+    s.push_str(
+        format!(
+            "\"blinding_factor_raw\": \"{}\",\n",
+            hex::encode(&p.blinding_factor_raw)
+        )
+        .as_str(),
+    );
     s.push_str(
         format!(
             "\"blinding_factor\": \"{}\",\n",
@@ -158,6 +162,7 @@ fn stringify_test_vectors(p: &TestVectorParameters) -> String {
         )
         .as_str(),
     );
+    s.push_str(format!("\"pepper\": \"{}\",\n", hex::encode(&p.pepper)).as_str());
     s.push_str(format!("\"oprf_key\": \"{}\",\n", hex::encode(&p.oprf_key)).as_str());
     s.push_str(
         format!(
@@ -234,9 +239,8 @@ where
     let server_e_kp = CS::generate_random_keypair(&mut rng).unwrap();
     let client_s_kp = CS::generate_random_keypair(&mut rng).unwrap();
     let client_e_kp = CS::generate_random_keypair(&mut rng).unwrap();
-    let id_u = b"idU";
-    let id_s = b"idS";
     let password = b"password";
+    let pepper = b"pepper";
     let mut blinding_factor_raw = [0u8; 64];
     rng.fill_bytes(&mut blinding_factor_raw);
     let mut oprf_key_raw = [0u8; 32];
@@ -249,21 +253,20 @@ where
     rng.fill_bytes(&mut server_nonce);
 
     let mut blinding_factor_registration_rng = CycleRng::new(blinding_factor_raw.to_vec());
-    let (r1, client_registration) = ClientRegistration::<CS>::start_with_user_and_server_name(
-        id_u,
-        id_s,
+    let (r1, client_registration) = ClientRegistration::<CS>::start(
         password,
+        Some(pepper),
         &mut blinding_factor_registration_rng,
     )
     .unwrap();
-    let r1_bytes = r1.serialize().to_vec();
+    let r1_bytes = r1.to_bytes().to_vec();
     let blinding_factor_bytes =
-        CS::Group::scalar_as_bytes(&client_registration.token.blind).clone();
+        CS::Group::scalar_as_bytes(&client_registration.blinding_factor).clone();
     let client_registration_state = client_registration.to_bytes().to_vec();
 
     let mut oprf_key_rng = CycleRng::new(oprf_key_raw.to_vec());
     let (r2, server_registration) = ServerRegistration::<CS>::start(r1, &mut oprf_key_rng).unwrap();
-    let r2_bytes = r2.serialize().to_vec();
+    let r2_bytes = r2.to_bytes().to_vec();
     let oprf_key_bytes = CS::Group::scalar_as_bytes(&server_registration.oprf_key).clone();
     let server_registration_state = server_registration.to_bytes().to_vec();
 
@@ -275,7 +278,7 @@ where
     let (r3, export_key_registration) = client_registration
         .finish(r2, server_s_kp.public(), &mut finish_registration_rng)
         .unwrap();
-    let r3_bytes = r3.serialize().to_vec();
+    let r3_bytes = r3.to_bytes().to_vec();
 
     let password_file = server_registration.finish(r3).unwrap();
     let password_file_bytes = password_file.to_bytes();
@@ -286,14 +289,9 @@ where
     client_login_start.extend_from_slice(&client_nonce);
 
     let mut client_login_start_rng = CycleRng::new(client_login_start);
-    let (l1, client_login) = ClientLogin::<CS>::start_with_user_and_server_name(
-        id_u,
-        id_s,
-        password,
-        &mut client_login_start_rng,
-    )
-    .unwrap();
-    let l1_bytes = l1.serialize().to_vec();
+    let (l1, client_login) =
+        ClientLogin::<CS>::start(password, Some(pepper), &mut client_login_start_rng).unwrap();
+    let l1_bytes = l1.to_bytes().to_vec();
     let client_login_state = client_login.to_bytes().to_vec();
 
     let mut server_e_sk_rng = CycleRng::new(server_e_kp.private().to_arr().to_vec());
@@ -304,7 +302,7 @@ where
         &mut server_e_sk_rng,
     )
     .unwrap();
-    let l2_bytes = l2.serialize().to_vec();
+    let l2_bytes = l2.to_bytes().to_vec();
     let server_login_state = server_login.to_bytes().to_vec();
 
     let mut client_e_sk_rng = CycleRng::new(client_e_kp.private().to_arr().to_vec());
@@ -322,10 +320,10 @@ where
         server_s_sk: server_s_kp.private().to_arr().to_vec(),
         server_e_pk: server_e_kp.public().to_arr().to_vec(),
         server_e_sk: server_e_kp.private().to_arr().to_vec(),
-        id_u: id_u.to_vec(),
-        id_s: id_s.to_vec(),
         password: password.to_vec(),
+        blinding_factor_raw: blinding_factor_raw.to_vec(),
         blinding_factor: blinding_factor_bytes.to_vec(),
+        pepper: pepper.to_vec(),
         oprf_key: oprf_key_bytes.to_vec(),
         envelope_nonce: envelope_nonce.to_vec(),
         client_nonce: client_nonce.to_vec(),
@@ -352,25 +350,17 @@ fn generate_test_vectors() {
     println!("{}", stringify_test_vectors(&parameters));
 }
 
-// For fixing the blinding factor
-fn postprocess_blinding_factor<G: Group>(_: G::Scalar) -> G::Scalar {
-    let parameters = populate_test_vectors(&serde_json::from_str(TEST_VECTOR).unwrap());
-    G::from_scalar_slice(GenericArray::from_slice(&parameters.blinding_factor[..])).unwrap()
-}
-
 #[test]
 fn test_r1() -> Result<(), PakeError> {
     let parameters = populate_test_vectors(&serde_json::from_str(TEST_VECTOR).unwrap());
-    let mut rng = OsRng;
-    let (r1, client_registration) = ClientRegistration::<X255193dhNoSlowHash>::start_with_user_and_server_name_and_postprocessing(
-        &parameters.id_u,
-        &parameters.id_s,
+    let mut blinding_factor_rng = CycleRng::new(parameters.blinding_factor_raw);
+    let (r1, client_registration) = ClientRegistration::<X255193dhNoSlowHash>::start(
         &parameters.password,
-        &mut rng,
-        postprocess_blinding_factor::<<X255193dhNoSlowHash as CipherSuite>::Group>,
+        Some(&parameters.pepper),
+        &mut blinding_factor_rng,
     )
     .unwrap();
-    assert_eq!(hex::encode(&parameters.r1), hex::encode(r1.serialize()));
+    assert_eq!(hex::encode(&parameters.r1), hex::encode(r1.to_bytes()));
     assert_eq!(
         hex::encode(&parameters.client_registration_state),
         hex::encode(client_registration.to_bytes())
@@ -383,11 +373,11 @@ fn test_r2() -> Result<(), PakeError> {
     let parameters = populate_test_vectors(&serde_json::from_str(TEST_VECTOR).unwrap());
     let mut oprf_key_rng = CycleRng::new(parameters.oprf_key);
     let (r2, server_registration) = ServerRegistration::<X255193dhNoSlowHash>::start(
-        RegisterFirstMessage::deserialize(&parameters.r1[..]).unwrap(),
+        RegisterFirstMessage::try_from(&parameters.r1[..]).unwrap(),
         &mut oprf_key_rng,
     )
     .unwrap();
-    assert_eq!(hex::encode(parameters.r2), hex::encode(r2.serialize()));
+    assert_eq!(hex::encode(parameters.r2), hex::encode(r2.to_bytes()));
     assert_eq!(
         hex::encode(&parameters.server_registration_state),
         hex::encode(server_registration.to_bytes())
@@ -407,13 +397,13 @@ fn test_r3() -> Result<(), PakeError> {
     )
     .unwrap()
     .finish(
-        RegisterSecondMessage::deserialize(&parameters.r2[..]).unwrap(),
+        RegisterSecondMessage::try_from(&parameters.r2[..]).unwrap(),
         &Key::try_from(&parameters.server_s_pk[..]).unwrap(),
         &mut finish_registration_rng,
     )
     .unwrap();
 
-    assert_eq!(hex::encode(parameters.r3), hex::encode(r3.serialize()));
+    assert_eq!(hex::encode(parameters.r3), hex::encode(r3.to_bytes()));
     assert_eq!(
         hex::encode(parameters.export_key),
         hex::encode(export_key_registration.to_vec())
@@ -431,7 +421,7 @@ fn test_password_file() -> Result<(), PakeError> {
     )
     .unwrap();
     let password_file = server_registration
-        .finish(RegisterThirdMessage::deserialize(&parameters.r3[..]).unwrap())
+        .finish(RegisterThirdMessage::try_from(&parameters.r3[..]).unwrap())
         .unwrap();
 
     assert_eq!(
@@ -446,22 +436,19 @@ fn test_l1() -> Result<(), PakeError> {
     let parameters = populate_test_vectors(&serde_json::from_str(TEST_VECTOR).unwrap());
 
     let client_login_start = [
-        vec![0u8; 64], // FIXME: don't hardcode this
+        parameters.blinding_factor_raw,
         parameters.client_e_sk,
         parameters.client_nonce,
     ]
     .concat();
     let mut client_login_start_rng = CycleRng::new(client_login_start);
-    let (l1, client_login) =
-        ClientLogin::<X255193dhNoSlowHash>::start_with_user_and_server_name_and_postprocessing(
-            &parameters.id_u,
-            &parameters.id_s,
-            &parameters.password,
-            &mut client_login_start_rng,
-            postprocess_blinding_factor::<<X255193dhNoSlowHash as CipherSuite>::Group>,
-        )
-        .unwrap();
-    assert_eq!(hex::encode(&parameters.l1), hex::encode(l1.serialize()));
+    let (l1, client_login) = ClientLogin::<X255193dhNoSlowHash>::start(
+        &parameters.password,
+        Some(&parameters.pepper),
+        &mut client_login_start_rng,
+    )
+    .unwrap();
+    assert_eq!(hex::encode(&parameters.l1), hex::encode(l1.to_bytes()));
     assert_eq!(
         hex::encode(&parameters.client_login_state),
         hex::encode(client_login.to_bytes())
@@ -477,12 +464,12 @@ fn test_l2() -> Result<(), PakeError> {
     let (l2, server_login) = ServerLogin::<X255193dhNoSlowHash>::start(
         ServerRegistration::try_from(&parameters.password_file[..]).unwrap(),
         &Key::try_from(&parameters.server_s_sk[..]).unwrap(),
-        LoginFirstMessage::<X255193dhNoSlowHash>::deserialize(&parameters.l1[..]).unwrap(),
+        LoginFirstMessage::<X255193dhNoSlowHash>::try_from(&parameters.l1[..]).unwrap(),
         &mut server_e_sk_rng,
     )
     .unwrap();
 
-    assert_eq!(hex::encode(&parameters.l2), hex::encode(l2.serialize()));
+    assert_eq!(hex::encode(&parameters.l2), hex::encode(l2.to_bytes()));
     assert_eq!(
         hex::encode(&parameters.server_login_state),
         hex::encode(server_login.to_bytes())
@@ -499,7 +486,7 @@ fn test_l3() -> Result<(), PakeError> {
         ClientLogin::<X255193dhNoSlowHash>::try_from(&parameters.client_login_state[..])
             .unwrap()
             .finish(
-                LoginSecondMessage::<X255193dhNoSlowHash>::deserialize(&parameters.l2[..]).unwrap(),
+                LoginSecondMessage::<X255193dhNoSlowHash>::try_from(&parameters.l2[..]).unwrap(),
                 &Key::try_from(&parameters.server_s_pk[..])?,
                 &mut client_e_sk_rng,
             )
@@ -543,15 +530,18 @@ fn test_complete_flow(
     let mut client_rng = OsRng;
     let mut server_rng = OsRng;
     let server_kp = X255193dhNoSlowHash::generate_random_keypair(&mut server_rng)?;
-    let (register_m1, client_state) =
-        ClientRegistration::<X255193dhNoSlowHash>::start(registration_password, &mut client_rng)?;
+    let (register_m1, client_state) = ClientRegistration::<X255193dhNoSlowHash>::start(
+        registration_password,
+        None,
+        &mut client_rng,
+    )?;
     let (register_m2, server_state) =
         ServerRegistration::<X255193dhNoSlowHash>::start(register_m1, &mut server_rng)?;
     let (register_m3, registration_export_key) =
         client_state.finish(register_m2, server_kp.public(), &mut client_rng)?;
     let p_file = server_state.finish(register_m3)?;
     let (login_m1, client_login_state) =
-        ClientLogin::<X255193dhNoSlowHash>::start(login_password, &mut client_rng)?;
+        ClientLogin::<X255193dhNoSlowHash>::start(login_password, None, &mut client_rng)?;
     let (login_m2, server_login_state) = ServerLogin::<X255193dhNoSlowHash>::start(
         p_file,
         &server_kp.private(),
diff --git a/src/tests/serialization.rs b/src/tests/serialization.rs
new file mode 100644
index 0000000..c5a5660
--- /dev/null
+++ b/src/tests/serialization.rs
@@ -0,0 +1,230 @@
+// 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,
+    envelope::Envelope,
+    group::Group,
+    key_exchange::{
+        traits::{KeyExchange, ToBytes},
+        tripledh::{TripleDH, NONCE_LEN},
+    },
+    keypair::{KeyPair, SizedBytes, X25519KeyPair},
+    opaque::*,
+};
+
+use curve25519_dalek::ristretto::RistrettoPoint;
+use generic_array::typenum::Unsigned;
+use proptest::{collection::vec, prelude::*};
+use rand_core::{OsRng, RngCore};
+
+use sha2::{Digest, Sha256};
+use std::convert::TryFrom;
+
+struct Default;
+impl CipherSuite for Default {
+    type Group = RistrettoPoint;
+    type KeyFormat = crate::keypair::X25519KeyPair;
+    type KeyExchange = TripleDH;
+    type Hash = sha2::Sha256;
+    type SlowHash = crate::slow_hash::NoOpHash;
+}
+
+fn random_ristretto_point() -> RistrettoPoint {
+    let mut rng = OsRng;
+    let mut random_bits = [0u8; 64];
+    rng.fill_bytes(&mut random_bits);
+
+    // This is because RistrettoPoint is on an obsolete sha2 version
+    let mut bits = [0u8; 64];
+    let mut hasher = sha2::Sha512::new();
+    hasher.update(&random_bits[..]);
+    bits.copy_from_slice(&hasher.finalize());
+
+    RistrettoPoint::from_uniform_bytes(&bits)
+}
+
+#[test]
+fn client_registration_roundtrip() {
+    let pw = b"hunter2";
+    let mut rng = OsRng;
+    let sc = <RistrettoPoint as Group>::random_scalar(&mut rng);
+    // serialization order: scalar, password
+    let bytes: Vec<u8> = [&sc.as_bytes()[..], &pw[..]].concat();
+    let reg = ClientRegistration::<Default>::try_from(&bytes[..]).unwrap();
+    let reg_bytes = reg.to_bytes();
+    assert_eq!(reg_bytes, bytes);
+}
+
+#[test]
+fn server_registration_roundtrip() {
+    // If we don't have envelope and client_pk, the server registration just
+    // contains the prf key
+    let mut rng = OsRng;
+    let sc = <RistrettoPoint as Group>::random_scalar(&mut rng);
+    let mut oprf_bytes: Vec<u8> = vec![];
+    oprf_bytes.extend_from_slice(sc.as_bytes());
+    let reg = ServerRegistration::<Default>::try_from(&oprf_bytes[..]).unwrap();
+    let reg_bytes = reg.to_bytes();
+    assert_eq!(reg_bytes, oprf_bytes);
+    // If we do have envelope and client pk, the server registration contains
+    // the whole kit
+    let key_len =
+        <<<Default as CipherSuite>::KeyFormat as KeyPair>::Repr as SizedBytes>::Len::to_usize();
+    let envelope_size = key_len + Envelope::<sha2::Sha256>::additional_size();
+    let mut mock_envelope_bytes = vec![0u8; envelope_size];
+    rng.fill_bytes(&mut mock_envelope_bytes);
+    println!("{}", mock_envelope_bytes.len());
+    let mock_client_kp = Default::generate_random_keypair(&mut rng).unwrap();
+    // serialization order: scalar, public key, envelope
+    let mut bytes = Vec::<u8>::new();
+    bytes.extend_from_slice(sc.as_bytes());
+    bytes.extend_from_slice(&mock_client_kp.public().to_arr());
+    bytes.extend_from_slice(&mock_envelope_bytes);
+    let reg = ServerRegistration::<Default>::try_from(&bytes[..]).unwrap();
+    let reg_bytes = reg.to_bytes();
+    assert_eq!(reg_bytes, bytes);
+}
+
+#[test]
+fn register_first_message_roundtrip() {
+    let pt = random_ristretto_point();
+    let pt_bytes = pt.to_arr();
+    let r1 = RegisterFirstMessage::<RistrettoPoint>::try_from(pt_bytes.as_slice()).unwrap();
+    let r1_bytes = r1.to_bytes();
+    assert_eq!(pt_bytes, r1_bytes);
+}
+
+#[test]
+fn register_second_message_roundtrip() {
+    let pt = random_ristretto_point();
+    let pt_bytes = pt.to_arr();
+
+    let message = pt_bytes.to_vec();
+    let r2 = RegisterSecondMessage::<RistrettoPoint>::try_from(&message[..]).unwrap();
+    let r2_bytes = r2.to_bytes();
+    assert_eq!(message, r2_bytes);
+}
+
+#[test]
+fn register_third_message_roundtrip() {
+    let mut rng = OsRng;
+    let skp = Default::generate_random_keypair(&mut rng).unwrap();
+    let pubkey_bytes = skp.public().to_arr();
+
+    let mut key = [0u8; 32];
+    rng.fill_bytes(&mut key);
+
+    let mut msg = [0u8; 32];
+    rng.fill_bytes(&mut msg);
+
+    let (ciphertext, _) =
+        Envelope::<sha2::Sha256>::seal(&key, &msg, &pubkey_bytes, &mut rng).unwrap();
+
+    let message: Vec<u8> = [&ciphertext.to_bytes(), &pubkey_bytes[..]].concat();
+    let r3 = RegisterThirdMessage::<X25519KeyPair, sha2::Sha256>::try_from(&message[..]).unwrap();
+    let r3_bytes = r3.to_bytes();
+    assert_eq!(message, r3_bytes);
+}
+
+#[test]
+fn client_login_roundtrip() {
+    let pw = b"hunter2";
+    let mut rng = OsRng;
+    let sc = <RistrettoPoint as Group>::random_scalar(&mut rng);
+
+    let client_e_kp = Default::generate_random_keypair(&mut rng).unwrap();
+    let mut client_nonce = [0u8; NONCE_LEN];
+    rng.fill_bytes(&mut client_nonce);
+
+    let l1_data = [&sc.to_bytes()[..], &client_nonce, client_e_kp.public()].concat();
+    let mut hasher = Sha256::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 reg = ClientLogin::<Default>::try_from(&bytes[..]).unwrap();
+    let reg_bytes = reg.to_bytes();
+    assert_eq!(reg_bytes, bytes);
+}
+
+#[test]
+fn login_first_message_roundtrip() {
+    let mut rng = OsRng;
+
+    let client_e_kp = Default::generate_random_keypair(&mut rng).unwrap();
+    let mut client_nonce = [0u8; NONCE_LEN];
+    rng.fill_bytes(&mut client_nonce);
+
+    let ke1m: Vec<u8> = [&client_nonce[..], &client_e_kp.public()].concat();
+    let reg = <TripleDH as KeyExchange<sha2::Sha256, crate::keypair::X25519KeyPair>>::KE1Message::try_from(
+        &ke1m[..],
+    )
+    .unwrap();
+    let reg_bytes = reg.to_bytes();
+    assert_eq!(reg_bytes, ke1m);
+}
+
+proptest! {
+
+    #[test]
+    fn test_nocrash_register_first_message(bytes in vec(any::<u8>(), 0..200)) {
+        RegisterFirstMessage::<RistrettoPoint>::try_from(&bytes[..]).map_or(true, |_| true);
+    }
+
+    #[test]
+    fn test_nocrash_register_second_message(bytes in vec(any::<u8>(), 0..200)) {
+        RegisterSecondMessage::<RistrettoPoint>::try_from(&bytes[..]).map_or(true, |_| true);
+    }
+
+    #[test]
+    fn test_nocrash_register_third_message(bytes in vec(any::<u8>(), 0..200)) {
+        RegisterThirdMessage::<crate::keypair::X25519KeyPair, sha2::Sha512>::try_from(&bytes[..]).map_or(true, |_| true);
+    }
+
+    #[test]
+    fn test_nocrash_login_first_message(bytes in vec(any::<u8>(), 0..500)) {
+        LoginFirstMessage::<Default>::try_from(&bytes[..]).map_or(true, |_| true);
+    }
+
+    #[test]
+    fn test_nocrash_login_second_message(bytes in vec(any::<u8>(), 0..500)) {
+        LoginSecondMessage::<Default>::try_from(&bytes[..]).map_or(true, |_| true);
+    }
+
+    #[test]
+    fn test_nocrash_login_third_message(bytes in vec(any::<u8>(), 0..500)) {
+        LoginThirdMessage::<Default>::try_from(&bytes[..]).map_or(true, |_| true);
+    }
+
+    #[test]
+    fn test_nocrash_client_registration(bytes in vec(any::<u8>(), 0..700)) {
+        ClientRegistration::<Default>::try_from(&bytes[..]).map_or(true, |_| true);
+    }
+
+    #[test]
+    fn test_nocrash_server_registration(bytes in vec(any::<u8>(), 0..700)) {
+        ServerRegistration::<Default>::try_from(&bytes[..]).map_or(true, |_| true);
+    }
+
+    #[test]
+    fn test_nocrash_client_login(bytes in vec(any::<u8>(), 0..700)) {
+        ClientLogin::<Default>::try_from(&bytes[..]).map_or(true, |_| true);
+    }
+
+    #[test]
+    fn test_nocrash_server_login(bytes in vec(any::<u8>(), 0..700)) {
+        ServerLogin::<Default>::try_from(&bytes[..]).map_or(true, |_| true);
+    }
+
+}