Repository: novifinancial/opaque-ke
Pull request #75 (2020-11-02 15:36:42)

Rebase TLS on master
It's one way to synchronize current work :)

This may require a manual merge, submitting the PR for review rather than merge-button-pressing.

kevinlewi: Thank you!

Patch:
diff --git a/benches/oprf.rs b/benches/oprf.rs
index 13aa16f..899626e 100644
--- a/benches/oprf.rs
+++ b/benches/oprf.rs
@@ -7,12 +7,11 @@
 extern crate criterion;
 
 use criterion::Criterion;
-use curve25519_dalek::edwards::EdwardsPoint;
-use curve25519_dalek::ristretto::RistrettoPoint;
+use curve25519_dalek::{edwards::EdwardsPoint, ristretto::RistrettoPoint};
 use generic_array::arr;
 use opaque_ke::{
     group::Group,
-    oprf::{generate_oprf1_shim, generate_oprf2_shim, generate_oprf3_shim, OprfClientBytes},
+    oprf::{blind_shim, evaluate_shim, unblind_and_finalize_shim},
 };
 use rand::{prelude::ThreadRng, thread_rng};
 use sha2::Sha256;
@@ -21,12 +20,9 @@ fn oprf1(c: &mut Criterion) {
     let mut csprng: ThreadRng = thread_rng();
     let input = b"hunter2";
 
-    c.bench_function("generate_oprf1 with Ristretto", move |b| {
+    c.bench_function("blind with Ristretto", move |b| {
         b.iter(|| {
-            let OprfClientBytes {
-                alpha: _alpha,
-                blinding_factor: _blinding_factor,
-            } = generate_oprf1_shim::<_, RistrettoPoint>(&input[..], None, &mut csprng).unwrap();
+            blind_shim::<_, RistrettoPoint>(&input[..], &mut csprng).unwrap();
         })
     });
 }
@@ -35,12 +31,9 @@ fn oprf1_edwards(c: &mut Criterion) {
     let mut csprng: ThreadRng = thread_rng();
     let input = b"hunter2";
 
-    c.bench_function("generate_oprf1 with Edwards", move |b| {
+    c.bench_function("blind with Edwards", move |b| {
         b.iter(|| {
-            let OprfClientBytes {
-                alpha: _alpha,
-                blinding_factor: _blinding_factor,
-            } = generate_oprf1_shim::<_, EdwardsPoint>(&input[..], None, &mut csprng).unwrap();
+            blind_shim::<_, EdwardsPoint>(&input[..], &mut csprng).unwrap();
         })
     });
 }
@@ -49,19 +42,16 @@ fn oprf2(c: &mut Criterion) {
     let mut csprng: ThreadRng = thread_rng();
     let input = b"hunter2";
 
-    let OprfClientBytes {
-        alpha,
-        blinding_factor: _blinding_factor,
-    } = generate_oprf1_shim::<_, RistrettoPoint>(&input[..], None, &mut csprng).unwrap();
+    let (_, alpha) = blind_shim::<_, RistrettoPoint>(&input[..], &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("generate_oprf2 with Ristretto", move |b| {
+    c.bench_function("evaluate with Ristretto", move |b| {
         b.iter(|| {
-            let _beta = generate_oprf2_shim::<RistrettoPoint>(alpha, &salt).unwrap();
+            let _beta = evaluate_shim::<RistrettoPoint>(alpha, &salt).unwrap();
         })
     });
 }
@@ -70,19 +60,16 @@ fn oprf2_edwards(c: &mut Criterion) {
     let mut csprng: ThreadRng = thread_rng();
     let input = b"hunter2";
 
-    let OprfClientBytes {
-        alpha,
-        blinding_factor: _blinding_factor,
-    } = generate_oprf1_shim::<_, EdwardsPoint>(&input[..], None, &mut csprng).unwrap();
+    let (_, alpha) = blind_shim::<_, EdwardsPoint>(&input[..], &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("generate_oprf2 with Edwards", move |b| {
+    c.bench_function("evaluate with Edwards", move |b| {
         b.iter(|| {
-            let _beta = generate_oprf2_shim::<EdwardsPoint>(alpha, &salt).unwrap();
+            let _beta = evaluate_shim::<EdwardsPoint>(alpha, &salt).unwrap();
         })
     });
 }
@@ -91,21 +78,17 @@ fn oprf3(c: &mut Criterion) {
     let mut csprng: ThreadRng = thread_rng();
     let input = b"hunter2";
 
-    let OprfClientBytes {
-        alpha,
-        blinding_factor,
-    } = generate_oprf1_shim::<_, RistrettoPoint>(&input[..], None, &mut csprng).unwrap();
+    let (token, alpha) = blind_shim::<_, RistrettoPoint>(&input[..], &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 = generate_oprf2_shim::<RistrettoPoint>(alpha, &salt).unwrap();
+    let beta = evaluate_shim::<RistrettoPoint>(alpha, &salt).unwrap();
 
-    c.bench_function("generate_oprf3 with Ristretto", move |b| {
+    c.bench_function("unblind_and_finalize with Ristretto", move |b| {
         b.iter(|| {
-            let _res = generate_oprf3_shim::<RistrettoPoint, Sha256>(input, beta, &blinding_factor)
-                .unwrap();
+            let _res = unblind_and_finalize_shim::<RistrettoPoint, Sha256>(&token, beta).unwrap();
         })
     });
 }
@@ -114,21 +97,17 @@ fn oprf3_edwards(c: &mut Criterion) {
     let mut csprng: ThreadRng = thread_rng();
     let input = b"hunter2";
 
-    let OprfClientBytes {
-        alpha,
-        blinding_factor,
-    } = generate_oprf1_shim::<_, EdwardsPoint>(&input[..], None, &mut csprng).unwrap();
+    let (token, alpha) = blind_shim::<_, EdwardsPoint>(&input[..], &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 = generate_oprf2_shim::<EdwardsPoint>(alpha, &salt).unwrap();
+    let beta = evaluate_shim::<EdwardsPoint>(alpha, &salt).unwrap();
 
-    c.bench_function("generate_oprf3 with Edwards", move |b| {
+    c.bench_function("unblind_and_finalize with Edwards", move |b| {
         b.iter(|| {
-            let _res =
-                generate_oprf3_shim::<EdwardsPoint, Sha256>(input, beta, &blinding_factor).unwrap();
+            let _res = unblind_and_finalize_shim::<EdwardsPoint, Sha256>(&token, beta).unwrap();
         })
     });
 }
diff --git a/examples/simple_login.rs b/examples/simple_login.rs
index 9dd7b84..50f0f0c 100644
--- a/examples/simple_login.rs
+++ b/examples/simple_login.rs
@@ -56,9 +56,8 @@ fn account_registration(
 ) -> Vec<u8> {
     let mut client_rng = OsRng;
     let (r1, client_state) =
-        ClientRegistration::<Default>::start(password.as_bytes(), Some(b"pepper"), &mut client_rng)
-            .unwrap();
-    let r1_bytes = r1.to_bytes();
+        ClientRegistration::<Default>::start(password.as_bytes(), &mut client_rng).unwrap();
+    let r1_bytes = r1.serialize();
 
     // Client sends r1_bytes to server
 
@@ -68,7 +67,7 @@ fn account_registration(
         &mut server_rng,
     )
     .unwrap();
-    let r2_bytes = r2.to_bytes();
+    let r2_bytes = r2.serialize();
 
     // Server sends r2_bytes to client
 
@@ -79,7 +78,7 @@ fn account_registration(
             &mut client_rng,
         )
         .unwrap();
-    let r3_bytes = r3.to_bytes();
+    let r3_bytes = r3.serialize();
 
     // Client sends r3_bytes to server
 
@@ -97,9 +96,8 @@ fn account_login(
 ) -> bool {
     let mut client_rng = OsRng;
     let (l1, client_state) =
-        ClientLogin::<Default>::start(password.as_bytes(), Some(b"pepper"), &mut client_rng)
-            .unwrap();
-    let l1_bytes = l1.to_bytes();
+        ClientLogin::<Default>::start(password.as_bytes(), &mut client_rng).unwrap();
+    let l1_bytes = l1.serialize();
 
     // Client sends l1_bytes to server
 
@@ -112,7 +110,7 @@ fn account_login(
         &mut server_rng,
     )
     .unwrap();
-    let l2_bytes = l2.to_bytes();
+    let l2_bytes = l2.serialize();
 
     // Server sends l2_bytes to client
 
@@ -127,7 +125,7 @@ fn account_login(
         return false;
     }
     let (l3, client_shared_secret, _) = result.unwrap();
-    let l3_bytes = l3.to_bytes();
+    let l3_bytes = l3.serialize();
 
     // Client sends l3_bytes to server
 
diff --git a/src/elligator/field.rs b/src/elligator/field.rs
index 1c070e7..511d5be 100644
--- a/src/elligator/field.rs
+++ b/src/elligator/field.rs
@@ -7,15 +7,12 @@
 //! Field arithmetic modulo \\(p = 2\^{255} - 19\\), using \\(64\\)-bit
 //! limbs with \\(128\\)-bit products.
 
-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 core::{
+    fmt::Debug,
+    ops::{Add, AddAssign, Mul, MulAssign, Neg},
+};
+
+use subtle::{Choice, ConditionallyNegatable, ConditionallySelectable, ConstantTimeEq};
 
 use zeroize::Zeroize;
 
diff --git a/src/envelope.rs b/src/envelope.rs
index 79f4252..02ec225 100644
--- a/src/envelope.rs
+++ b/src/envelope.rs
@@ -3,8 +3,11 @@
 // 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;
-use crate::hash::Hash;
+use crate::{
+    errors::{InternalPakeError, PakeError, ProtocolError},
+    hash::Hash,
+    serialization::{serialize, tokenize, u8_to_credential_type, CredentialType},
+};
 use digest::Digest;
 use generic_array::{
     typenum::{Unsigned, U32},
@@ -13,6 +16,7 @@ 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";
@@ -36,14 +40,59 @@ 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 {
@@ -54,10 +103,6 @@ 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()
     }
@@ -65,11 +110,13 @@ 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,
         }
     }
@@ -78,23 +125,98 @@ 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 ciphertext_start = NONCE_LEN;
-        let ciphertext_end = bytes.len() - Self::hmac_size();
+        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()
+    }
 
-        Ok(Self::new(
-            bytes[..ciphertext_start].to_vec(),
-            bytes[ciphertext_start..ciphertext_end].to_vec(),
-            GenericArray::clone_from_slice(&bytes[ciphertext_end..]),
+    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,
         ))
     }
 
-    pub(crate) fn to_bytes(&self) -> Vec<u8> {
-        [&self.nonce[..], &self.ciphertext[..], &self.hmac[..]].concat()
+    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)
     }
 
     /// 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<R: RngCore + CryptoRng>(
+    pub(crate) fn seal_raw<R: RngCore + CryptoRng>(
         key: &[u8],
         plaintext: &[u8],
         aad: &[u8],
@@ -124,14 +246,42 @@ impl<D: Hash> Envelope<D> {
         hmac.update(&aad);
 
         Ok((
-            Self::new(nonce, ciphertext.to_vec(), hmac.finalize().into_bytes()),
+            Self::new(
+                nonce,
+                ciphertext.to_vec(),
+                aad.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(&self, key: &[u8], aad: &[u8]) -> Result<OpenedEnvelope, InternalPakeError> {
+    pub(crate) fn open_raw(
+        &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()];
@@ -177,8 +327,8 @@ mod tests {
         rng.fill_bytes(&mut msg);
 
         let (envelope, export_key_1) =
-            Envelope::<sha2::Sha256>::seal(&key, &msg, b"aad", &mut rng).unwrap();
-        let opened_envelope = envelope.open(&key, b"aad").unwrap();
+            Envelope::<sha2::Sha256>::seal_raw(&key, &msg, b"aad", &mut rng).unwrap();
+        let opened_envelope = envelope.open_raw(&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 6be81bf..359ad13 100644
--- a/src/errors.rs
+++ b/src/errors.rs
@@ -40,6 +40,9 @@ 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
@@ -58,6 +61,8 @@ 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
@@ -78,6 +83,9 @@ 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,
@@ -125,4 +133,19 @@ 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 7a4ed55..de3e4d7 100644
--- a/src/group.rs
+++ b/src/group.rs
@@ -6,8 +6,7 @@
 //! Defines the Group trait to specify the underlying prime order group used in
 //! OPAQUE's OPRF
 
-use crate::elligator;
-use crate::errors::InternalPakeError;
+use crate::{elligator, errors::InternalPakeError};
 
 use curve25519_dalek::{
     edwards::{CompressedEdwardsY, EdwardsPoint},
diff --git a/src/key_exchange/tripledh.rs b/src/key_exchange/tripledh.rs
index dab106b..3ab08b5 100644
--- a/src/key_exchange/tripledh.rs
+++ b/src/key_exchange/tripledh.rs
@@ -9,6 +9,7 @@ use crate::{
     hash::Hash,
     key_exchange::traits::{KeyExchange, ToBytes},
     keypair::{KeyPair, SizedBytes},
+    serialization::serialize,
 };
 use digest::{Digest, FixedOutput};
 use generic_array::{
@@ -27,6 +28,11 @@ 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;
@@ -84,7 +90,7 @@ impl<D: Hash, KeyFormat: KeyPair> KeyExchange<D, KeyFormat> for TripleDH {
             server_nonce_bytes.into()
         };
 
-        let (shared_secret, km2, km3) = derive_3dh_keys::<KeyFormat, D>(
+        let (session_secret, km2, km3) = derive_3dh_keys::<KeyFormat, D>(
             TripleDHComponents {
                 pk1: ke1_message.client_e_pk.clone(),
                 sk1: server_e_kp.private().clone(),
@@ -122,7 +128,7 @@ impl<D: Hash, KeyFormat: KeyPair> KeyExchange<D, KeyFormat> for TripleDH {
             KE2State {
                 km3,
                 hashed_transcript,
-                shared_secret,
+                session_secret,
             },
             KE2Message {
                 server_nonce,
@@ -139,7 +145,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 (shared_secret, km2, km3) = derive_3dh_keys::<KeyFormat, D>(
+        let (session_secret, km2, km3) = derive_3dh_keys::<KeyFormat, D>(
             TripleDHComponents {
                 pk1: ke2_message.server_e_pk.clone(),
                 sk1: ke1_state.client_e_sk.clone(),
@@ -181,7 +187,7 @@ impl<D: Hash, KeyFormat: KeyPair> KeyExchange<D, KeyFormat> for TripleDH {
         client_mac.update(&hashed_transcript);
 
         Ok((
-            shared_secret.to_vec(),
+            session_secret.to_vec(),
             KE3Message {
                 mac: client_mac.finalize().into_bytes(),
             },
@@ -202,7 +208,7 @@ impl<D: Hash, KeyFormat: KeyPair> KeyExchange<D, KeyFormat> for TripleDH {
             ));
         }
 
-        Ok(ke2_state.shared_secret.to_vec())
+        Ok(ke2_state.session_secret.to_vec())
     }
 
     fn ke1_state_size() -> usize {
@@ -285,7 +291,7 @@ impl<KeyFormat: KeyPair> TryFrom<&[u8]> for KE1Message<KeyFormat> {
 pub struct KE2State<HashLen: ArrayLength<u8>> {
     km3: GenericArray<u8, HashLen>,
     hashed_transcript: GenericArray<u8, HashLen>,
-    shared_secret: GenericArray<u8, HashLen>,
+    session_secret: GenericArray<u8, HashLen>,
 }
 
 /// The second key exchange message
@@ -300,7 +306,7 @@ impl<HashLen: ArrayLength<u8>> ToBytes for KE2State<HashLen> {
         let output: Vec<u8> = [
             &self.km3[..],
             &self.hashed_transcript[..],
-            &self.shared_secret[..],
+            &self.session_secret[..],
         ]
         .concat();
         output
@@ -316,7 +322,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]),
-            shared_secret: GenericArray::clone_from_slice(&checked_bytes[2 * KEY_LEN..]),
+            session_secret: GenericArray::clone_from_slice(&checked_bytes[2 * KEY_LEN..]),
         })
     }
 }
@@ -362,13 +368,38 @@ struct TripleDHComponents<KeyFormat: KeyPair> {
     sk3: KeyFormat::Repr,
 }
 
-// Consists of a shared secret, followed by two mac keys
+// Consists of a shared secret, followed by two mac keys: (session_secret, km2, km3)
 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>(
@@ -387,44 +418,79 @@ fn derive_3dh_keys<KeyFormat: KeyPair, D: Hash>(
 
     let info: Vec<u8> = [
         STR_3DH,
-        &client_nonce,
-        &server_nonce,
-        &client_s_pk.to_arr(),
-        &server_s_pk.to_arr(),
+        &serialize(&client_nonce, 2),
+        &serialize(&server_nonce, 2),
+        &serialize(&client_s_pk.to_arr(), 2),
+        &serialize(&server_s_pk.to_arr(), 2),
     ]
     .concat();
 
-    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)?;
+    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(),
+    )?;
+
     Ok((
-        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..]),
+        GenericArray::clone_from_slice(&session_secret),
+        GenericArray::clone_from_slice(&km2),
+        GenericArray::clone_from_slice(&km3),
     ))
 }
 
-/// The third key exchange message
-pub struct KE3Message<HashLen: ArrayLength<u8>> {
-    mac: GenericArray<u8, HashLen>,
+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)
 }
 
-impl<HashLen: ArrayLength<u8>> ToBytes for KE3Message<HashLen> {
-    fn to_bytes(&self) -> Vec<u8> {
-        self.mac.to_vec()
-    }
-}
+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];
 
-impl<HashLen: ArrayLength<u8>> TryFrom<&[u8]> for KE3Message<HashLen> {
-    type Error = InternalPakeError;
+    let mut hkdf_label: Vec<u8> = Vec::new();
+    hkdf_label.extend_from_slice(&length.to_be_bytes()[6..]);
 
-    fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
-        let checked_bytes = check_slice_size(bytes, KEY_LEN, "ke3_message")?;
+    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));
 
-        Ok(Self {
-            mac: GenericArray::clone_from_slice(&checked_bytes),
-        })
-    }
+    hkdf_label.extend_from_slice(&serialize(&context, 1));
+
+    hkdf.expand(&hkdf_label, &mut okm)
+        .map_err(|_| InternalPakeError::HkdfError)?;
+    Ok(okm)
+}
+
+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(),
+    )
 }
diff --git a/src/lib.rs b/src/lib.rs
index 096304c..87cdba5 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -89,7 +89,6 @@
 //! let mut client_rng = OsRng;
 //! let (r1, client_state) = ClientRegistration::<Default>::start(
 //!     b"password",
-//!     Some(b"pepper"),
 //!     &mut client_rng,
 //! )?;
 //! # Ok::<(), ProtocolError>(())
@@ -119,7 +118,6 @@
 //! # 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;
@@ -153,7 +151,6 @@
 //! # 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;
@@ -188,7 +185,6 @@
 //! # 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;
@@ -232,7 +228,6 @@
 //! let mut client_rng = OsRng;
 //! let (l1, client_state) = ClientLogin::<Default>::start(
 //!   b"password",
-//!   Some(b"pepper"),
 //!   &mut client_rng,
 //! )?;
 //! # Ok::<(), ProtocolError>(())
@@ -262,7 +257,6 @@
 //! # 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;
@@ -272,7 +266,6 @@
 //! # 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;
@@ -308,7 +301,6 @@
 //! # 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;
@@ -318,7 +310,6 @@
 //! # 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;
@@ -365,7 +356,6 @@
 //! # 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;
@@ -375,7 +365,6 @@
 //! # 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;
@@ -432,5 +421,7 @@ 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 a7667db..64ffabe 100644
--- a/src/map_to_curve.rs
+++ b/src/map_to_curve.rs
@@ -15,19 +15,22 @@ 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], pepper: Option<&[u8]>) -> Self;
+    fn map_to_curve(password: &[u8], dst: 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], pepper: Option<&[u8]>) -> Self {
-        let (hashed_input, _) = Hkdf::<Sha512>::extract(pepper, password);
+    fn map_to_curve(password: &[u8], dst: Option<&[u8]>) -> Self {
+        let (hashed_input, _) = Hkdf::<Sha512>::extract(dst, password);
         <Self as Group>::hash_to_curve(&hashed_input)
     }
 }
 
 impl GroupWithMapToCurve for EdwardsPoint {
-    fn map_to_curve(password: &[u8], pepper: Option<&[u8]>) -> Self {
-        let (hashed_input, _) = Hkdf::<Sha256>::extract(pepper, password);
+    fn map_to_curve(password: &[u8], dst: Option<&[u8]>) -> Self {
+        let (hashed_input, _) = Hkdf::<Sha256>::extract(dst, password);
         <Self as Group>::hash_to_curve(&hashed_input)
     }
 }
diff --git a/src/opaque.rs b/src/opaque.rs
index f97e920..d016d1f 100644
--- a/src/opaque.rs
+++ b/src/opaque.rs
@@ -7,18 +7,24 @@
 
 use crate::{
     ciphersuite::CipherSuite,
-    envelope::{Envelope, ExportKeySize},
-    errors::{utils::check_slice_size, InternalPakeError, PakeError, ProtocolError},
+    envelope::{Envelope, EnvelopeCredentialsFormat, ExportKeySize},
+    errors::{
+        utils::{check_slice_size, check_slice_size_atleast},
+        InternalPakeError, PakeError, ProtocolError,
+    },
     group::Group,
     hash::Hash,
     key_exchange::traits::{KeyExchange, ToBytes},
     keypair::{KeyPair, SizedBytes},
     oprf,
-    oprf::OprfClientBytes,
+    serialization::{
+        serialize, tokenize, u8_to_credential_type, CredentialType, ProtocolMessageType,
+    },
     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;
 
@@ -27,6 +33,8 @@ 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,
 }
@@ -34,8 +42,61 @@ 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(
-            first_message_bytes,
+            &alpha_bytes,
             Grp::ElemLen::to_usize(),
             "first_message_bytes",
         )?;
@@ -43,14 +104,7 @@ impl<Grp: Group> TryFrom<&[u8]> for RegisterFirstMessage<Grp> {
         // correct subgroup
         let arr = GenericArray::from_slice(checked_slice);
         let alpha = Grp::from_element_slice(arr)?;
-        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()
+        Ok(Self { id_u, alpha })
     }
 }
 
@@ -59,6 +113,10 @@ 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>
@@ -67,17 +125,25 @@ where
 {
     type Error = ProtocolError;
 
-    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",
-        )?;
+    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")?;
+
         // Check that the message is actually containing an element of the
         // correct subgroup
-        let arr = GenericArray::from_slice(&checked_slice);
+        let arr = GenericArray::from_slice(&checked_slice[..elem_len]);
         let beta = Grp::from_element_slice(arr)?;
-        Ok(Self { beta })
+
+        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,
+        })
     }
 }
 
@@ -85,9 +151,89 @@ impl<Grp> RegisterSecondMessage<Grp>
 where
     Grp: Group,
 {
-    /// byte representation for the registration response message
+    /// Byte representation for the registration response message. This does not
+    /// include the envelope credentials format
     pub fn to_bytes(&self) -> Vec<u8> {
-        self.beta.to_arr().to_vec()
+        [&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,
+        })
     }
 }
 
@@ -101,17 +247,6 @@ 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,
@@ -137,8 +272,52 @@ 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,
@@ -147,34 +326,59 @@ 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> {
-        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
+        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 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::from_element_slice(arr)?;
+        let alpha = <CS::Group as Group>::from_element_slice(arr)?;
 
         let ke1_message =
             <CS::KeyExchange as KeyExchange<CS::Hash, CS::KeyFormat>>::KE1Message::try_from(
-                &checked_slice[elem_len..],
+                &ke1m[..],
             )?;
-        Ok(Self { 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()
+        Ok(Self {
+            id_u,
+            alpha,
+            ke1_message,
+        })
     }
 }
 
@@ -189,29 +393,39 @@ pub struct LoginSecondMessage<CS: CipherSuite> {
 }
 
 impl<CS: CipherSuite> LoginSecondMessage<CS> {
-    /// 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()
+    /// 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[..])
     }
 }
 
 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 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",
-        )?;
+        let checked_slice =
+            check_slice_size_atleast(second_message_bytes, elem_len, "login_second_message_bytes")?;
 
         // Check that the message is actually containing an element of the
         // correct subgroup
@@ -219,12 +433,14 @@ 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 =
-            Envelope::<CS::Hash>::from_bytes(&checked_slice[elem_len..elem_len + envelope_size])?;
+        let (envelope, remainder) = Envelope::<CS::Hash>::deserialize(&checked_slice[elem_len..])?;
 
+        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_slice[elem_len + envelope_size..],
+                &checked_remainder,
             )?;
 
         Ok(Self {
@@ -252,6 +468,14 @@ 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()
@@ -263,15 +487,20 @@ impl<CS: CipherSuite> LoginThirdMessage<CS> {
 
 /// The state elements the client holds to register itself
 pub struct ClientRegistration<CS: CipherSuite> {
-    /// a blinding factor
-    pub(crate) blinding_factor: <CS::Group as Group>::Scalar,
-    /// the client's password
-    password: Vec<u8>,
+    /// 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>,
 }
 
 impl<CS: CipherSuite> TryFrom<&[u8]> for ClientRegistration<CS> {
     type Error = ProtocolError;
-    fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
+    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)?;
+
         let min_expected_len = <CS::Group as Group>::ScalarLen::to_usize();
         let checked_slice = (if bytes.len() <= min_expected_len {
             Err(InternalPakeError::SizeError {
@@ -290,8 +519,12 @@ 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 {
-            blinding_factor,
-            password,
+            id_u,
+            id_s,
+            token: oprf::Token {
+                data: password,
+                blind: blinding_factor,
+            },
         })
     }
 }
@@ -300,8 +533,10 @@ impl<CS: CipherSuite> ClientRegistration<CS> {
     /// byte representation for the client's registration state
     pub fn to_bytes(&self) -> Vec<u8> {
         let output: Vec<u8> = [
-            &CS::Group::scalar_as_bytes(&self.blinding_factor)[..],
-            &self.password,
+            &serialize(&self.id_u, 2),
+            &serialize(&self.id_s, 2),
+            &CS::Group::scalar_as_bytes(&self.token.blind)[..],
+            &self.token.data,
         ]
         .concat();
         output
@@ -330,24 +565,61 @@ 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", None, &mut rng)?;
+    /// let (register_m1, registration_state) = ClientRegistration::<Default>::start(b"hunter2", &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> {
-        let OprfClientBytes {
-            alpha,
-            blinding_factor,
-        } = oprf::generate_oprf1::<R, CS::Group>(&password, pepper, blinding_factor_rng)?;
+        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,
+        )?;
 
         Ok((
-            RegisterFirstMessage::<CS::Group> { alpha },
+            RegisterFirstMessage::<CS::Group> {
+                id_u: user_name.to_vec(),
+                alpha,
+            },
             Self {
-                blinding_factor,
-                password: password.to_vec(),
+                id_u: user_name.to_vec(),
+                id_s: server_name.to_vec(),
+                token,
             },
         ))
     }
@@ -384,7 +656,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", None, &mut client_rng)?;
+    /// let (register_m1, client_state) = ClientRegistration::<Default>::start(b"hunter2", &mut client_rng)?;
     /// let (register_m2, server_state) =
     /// ServerRegistration::<Default>::start(register_m1, &mut server_rng)?;
     /// let mut client_rng = OsRng;
@@ -397,20 +669,37 @@ impl<CS: CipherSuite> ClientRegistration<CS> {
         server_s_pk: &<CS::KeyFormat as KeyPair>::Repr,
         rng: &mut R,
     ) -> Result<ClientRegistrationFinishResult<CS::KeyFormat, CS::Hash>, ProtocolError> {
-        let client_static_keypair = CS::KeyFormat::generate_random(rng)?;
+        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)
+    }
 
-        let password_derived_key = get_password_derived_key::<CS::Group, CS::SlowHash, CS::Hash>(
-            self.password.clone(),
-            r2.beta,
-            &self.blinding_factor,
-        )?;
+    /// 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 (envelope, export_key) = Envelope::<CS::Hash>::seal(
-            &password_derived_key,
-            &client_static_keypair.private().to_arr(),
-            &server_s_pk.to_arr(),
-            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)?;
 
         Ok((
             RegisterThirdMessage {
@@ -425,8 +714,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.password.zeroize();
-        self.blinding_factor.zeroize();
+        self.token.data.zeroize();
+        self.token.blind.zeroize();
     }
 }
 
@@ -439,8 +728,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.password.zeroize();
-        self.blinding_factor.zeroize();
+        self.token.data.zeroize();
+        self.token.blind.zeroize();
     }
 }
 
@@ -467,36 +756,36 @@ where
     >: generic_array::ArrayLength<u8>,
 {
     type Error = ProtocolError;
-    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();
-        let envelope_size = key_len + Envelope::<CS::Hash>::additional_size();
 
-        if server_registration_bytes.len() == scalar_len {
+    /// The format of a serialized ServerRegistration object:
+    /// oprf_key | client_s_pk | envelope
+    fn try_from(input: &[u8]) -> Result<Self, Self::Error> {
+        let scalar_len = <CS::Group as Group>::ScalarLen::to_usize();
+        if input.len() == scalar_len {
             return Ok(Self {
-                oprf_key: CS::Group::from_scalar_slice(GenericArray::from_slice(
-                    server_registration_bytes,
-                ))?,
+                oprf_key: CS::Group::from_scalar_slice(GenericArray::from_slice(input))?,
                 client_s_pk: None,
                 envelope: None,
             });
         }
 
-        let checked_bytes = check_slice_size(
-            server_registration_bytes,
-            envelope_size + key_len + scalar_len,
-            "server_registration_bytes",
-        )?;
+        // 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 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::<CS::Hash>::from_bytes(
-                &checked_bytes[checked_bytes.len() - envelope_size..],
-            )?),
+            envelope: Some(envelope),
             client_s_pk: Some(client_s_pk),
             oprf_key,
         })
@@ -547,7 +836,7 @@ where
     /// }
     /// let mut client_rng = OsRng;
     /// let mut server_rng = OsRng;
-    /// let (register_m1, client_state) = ClientRegistration::<Default>::start(b"hunter2", None, &mut client_rng)?;
+    /// let (register_m1, client_state) = ClientRegistration::<Default>::start(b"hunter2", &mut client_rng)?;
     /// let (register_m2, server_state) =
     /// ServerRegistration::<Default>::start(register_m1, &mut server_rng)?;
     /// # Ok::<(), ProtocolError>(())
@@ -555,15 +844,43 @@ 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::generate_oprf2::<CS::Group>(message.alpha, &oprf_key)?;
+        let beta = oprf::evaluate::<CS::Group>(message.alpha, &oprf_key)?;
 
         Ok((
-            RegisterSecondMessage { beta },
+            RegisterSecondMessage {
+                beta,
+                server_s_pk: server_s_pk.to_vec(),
+                ecf,
+            },
             Self {
                 envelope: None,
                 client_s_pk: None,
@@ -596,7 +913,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", None, &mut client_rng)?;
+    /// let (register_m1, client_state) = ClientRegistration::<Default>::start(b"hunter2", &mut client_rng)?;
     /// let (register_m2, server_state) =
     /// ServerRegistration::<Default>::start(register_m1, &mut server_rng)?;
     /// let mut client_rng = OsRng;
@@ -621,17 +938,21 @@ where
 
 /// The state elements the client holds to perform a login
 pub struct ClientLogin<CS: CipherSuite> {
-    /// 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>,
+    /// 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>,
     ke1_state: <CS::KeyExchange as KeyExchange<CS::Hash, CS::KeyFormat>>::KE1State,
 }
 
 impl<CS: CipherSuite> TryFrom<&[u8]> for ClientLogin<CS> {
     type Error = ProtocolError;
-    fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
+    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)?;
+
         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();
@@ -644,7 +965,7 @@ impl<CS: CipherSuite> TryFrom<&[u8]> for ClientLogin<CS> {
                 actual_len: bytes.len(),
             })
         } else {
-            Ok(bytes)
+            Ok(bytes.clone())
         })?;
 
         let blinding_factor_bytes = GenericArray::from_slice(&checked_slice[..scalar_len]);
@@ -655,8 +976,12 @@ impl<CS: CipherSuite> TryFrom<&[u8]> for ClientLogin<CS> {
             )?;
         let password = bytes[scalar_len + ke1_state_size..].to_vec();
         Ok(Self {
-            blinding_factor,
-            password,
+            id_u,
+            id_s,
+            token: oprf::Token {
+                data: password,
+                blind: blinding_factor,
+            },
             ke1_state,
         })
     }
@@ -666,9 +991,11 @@ impl<CS: CipherSuite> ClientLogin<CS> {
     /// byte representation for the client's login state
     pub fn to_bytes(&self) -> Vec<u8> {
         let output: Vec<u8> = [
-            &CS::Group::scalar_as_bytes(&self.blinding_factor)[..],
+            &serialize(&self.id_u, 2),
+            &serialize(&self.id_s, 2),
+            &CS::Group::scalar_as_bytes(&self.token.blind)[..],
             &self.ke1_state.to_bytes(),
-            &self.password,
+            &self.token.data,
         ]
         .concat();
         output
@@ -703,28 +1030,57 @@ 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", None, &mut client_rng)?;
+    /// let (login_m1, client_login_state) = ClientLogin::<Default>::start(b"hunter2", &mut client_rng)?;
     /// # Ok::<(), ProtocolError>(())
     /// ```
     pub fn start<R: RngCore + CryptoRng>(
         password: &[u8],
-        pepper: Option<&[u8]>,
         rng: &mut R,
     ) -> Result<(LoginFirstMessage<CS>, Self), ProtocolError> {
-        let OprfClientBytes {
-            alpha,
-            blinding_factor,
-        } = oprf::generate_oprf1::<R, CS::Group>(&password, pepper, rng)?;
+        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 (ke1_state, ke1_message) = CS::KeyExchange::generate_ke1(alpha.to_arr().to_vec(), rng)?;
 
-        let l1 = LoginFirstMessage { alpha, ke1_message };
+        let l1 = LoginFirstMessage {
+            id_u: user_name.to_vec(),
+            alpha,
+            ke1_message,
+        };
 
         Ok((
             l1,
             Self {
-                blinding_factor,
-                password: password.to_vec(),
+                id_u: user_name.to_vec(),
+                id_s: server_name.to_vec(),
+                token,
                 ke1_state,
             },
         ))
@@ -755,12 +1111,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", None, &mut client_rng)?;
+    /// # let (register_m1, client_state) = ClientRegistration::<Default>::start(b"hunter2", &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", None, &mut client_rng)?;
+    /// let (login_m1, client_login_state) = ClientLogin::<Default>::start(b"hunter2", &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>(())
@@ -768,20 +1124,17 @@ 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.password.clone(),
-            l2.beta,
-            &self.blinding_factor,
-        )?;
+        let password_derived_key =
+            get_password_derived_key::<CS::Group, CS::SlowHash, CS::Hash>(&self.token, l2.beta)?;
 
         let opened_envelope = &l2
             .envelope
-            .open(&password_derived_key, &server_s_pk.to_arr())
+            .open(&password_derived_key)
             .map_err(|e| match e {
                 InternalPakeError::SealOpenHmacError => PakeError::InvalidLoginError,
                 err => PakeError::from(err),
@@ -791,8 +1144,12 @@ impl<CS: CipherSuite> ClientLogin<CS> {
             l2_bytes,
             l2.ke2_message,
             &self.ke1_state,
-            server_s_pk.clone(),
-            <CS::KeyFormat as KeyPair>::Repr::from_bytes(&opened_envelope.plaintext)?,
+            <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],
+            )?,
         )?;
 
         Ok((
@@ -856,12 +1213,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", None, &mut client_rng)?;
+    /// # let (register_m1, client_state) = ClientRegistration::<Default>::start(b"hunter2", &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", None, &mut client_rng)?;
+    /// let (login_m1, client_login_state) = ClientLogin::<Default>::start(b"hunter2", &mut client_rng)?;
     /// let (login_m2, server_login_state) = ServerLogin::start(p_file, &server_kp.private(), login_m1, &mut server_rng)?;
     /// # Ok::<(), ProtocolError>(())
     /// ```
@@ -872,7 +1229,7 @@ impl<CS: CipherSuite> ServerLogin<CS> {
         rng: &mut R,
     ) -> Result<ServerLoginStartResult<CS>, ProtocolError> {
         let l1_bytes = &l1.to_bytes();
-        let beta = oprf::generate_oprf2(l1.alpha, &password_file.oprf_key)?;
+        let beta = oprf::evaluate(l1.alpha, &password_file.oprf_key)?;
 
         let client_s_pk = password_file
             .client_s_pk
@@ -931,12 +1288,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", None, &mut client_rng)?;
+    /// # let (register_m1, client_state) = ClientRegistration::<Default>::start(b"hunter2", &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", None, &mut client_rng)?;
+    /// let (login_m1, client_login_state) = ClientLogin::<Default>::start(b"hunter2", &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)?;
@@ -958,10 +1315,9 @@ impl<CS: CipherSuite> ServerLogin<CS> {
 
 // Helper functions
 fn get_password_derived_key<G: Group, SH: SlowHash<D>, D: Hash>(
-    password: Vec<u8>,
+    token: &oprf::Token<G>,
     beta: G,
-    blinding_factor: &G::Scalar,
 ) -> Result<Vec<u8>, InternalPakeError> {
-    let oprf_output = oprf::generate_oprf3::<G, D>(&password, beta, blinding_factor)?;
+    let oprf_output = oprf::unblind_and_finalize::<G, D>(token, beta)?;
     SH::hash(oprf_output)
 }
diff --git a/src/oprf.rs b/src/oprf.rs
index c00480c..d189307 100644
--- a/src/oprf.rs
+++ b/src/oprf.rs
@@ -11,47 +11,51 @@ use generic_array::GenericArray;
 use hkdf::Hkdf;
 use rand_core::{CryptoRng, RngCore};
 
-pub struct OprfClientBytes<Grp: Group> {
-    pub alpha: Grp,
-    pub blinding_factor: Grp::Scalar,
+/// Used to store the OPRF input and blinding factor
+pub struct Token<Grp: Group> {
+    pub(crate) data: Vec<u8>,
+    pub(crate) blind: 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 generate_oprf1<R: RngCore + CryptoRng, G: GroupWithMapToCurve>(
+pub(crate) fn blind_with_postprocessing<R: RngCore + CryptoRng, G: GroupWithMapToCurve>(
     input: &[u8],
-    pepper: Option<&[u8]>,
     blinding_factor_rng: &mut R,
-) -> Result<OprfClientBytes<G>, InternalPakeError> {
-    let mapped_point = G::map_to_curve(input, pepper);
+    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
     let blinding_factor = G::random_scalar(blinding_factor_rng);
-    let alpha = mapped_point * &blinding_factor;
-    Ok(OprfClientBytes {
-        alpha,
-        blinding_factor,
-    })
+    let blind = postprocess(blinding_factor);
+    let blind_token = mapped_point * &blind;
+    Ok((
+        Token {
+            data: input.to_vec(),
+            blind,
+        },
+        blind_token,
+    ))
 }
 
 /// 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 generate_oprf2<G: Group>(
-    point: G,
-    oprf_key: &G::Scalar,
-) -> Result<G, InternalPakeError> {
+pub(crate) fn evaluate<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 generate_oprf3<G: Group, H: Hash>(
-    input: &[u8],
+pub(crate) fn unblind_and_finalize<G: Group, H: Hash>(
+    token: &Token<G>,
     point: G,
-    blinding_factor: &G::Scalar,
 ) -> Result<GenericArray<u8, <H as Digest>::OutputSize>, InternalPakeError> {
-    let unblinded = point * &G::scalar_invert(&blinding_factor);
-    let ikm: Vec<u8> = [&unblinded.to_arr()[..], input].concat();
+    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 (prk, _) = Hkdf::<H>::extract(None, &ikm);
     Ok(prk)
 }
@@ -59,31 +63,26 @@ pub(crate) fn generate_oprf3<G: Group, H: Hash>(
 // Benchmarking shims
 #[cfg(feature = "bench")]
 #[inline]
-pub fn generate_oprf1_shim<R: RngCore + CryptoRng, G: GroupWithMapToCurve>(
+pub fn blind_shim<R: RngCore + CryptoRng, G: GroupWithMapToCurve>(
     input: &[u8],
-    pepper: Option<&[u8]>,
     blinding_factor_rng: &mut R,
-) -> Result<OprfClientBytes<G>, InternalPakeError> {
-    generate_oprf1(input, pepper, blinding_factor_rng)
+) -> Result<(Token<G>, G), InternalPakeError> {
+    blind_with_postprocessing(input, blinding_factor_rng, std::convert::identity)
 }
 
 #[cfg(feature = "bench")]
 #[inline]
-pub fn generate_oprf2_shim<G: Group>(
-    point: G,
-    oprf_key: &G::Scalar,
-) -> Result<G, InternalPakeError> {
-    generate_oprf2(point, oprf_key)
+pub fn evaluate_shim<G: Group>(point: G, oprf_key: &G::Scalar) -> Result<G, InternalPakeError> {
+    evaluate(point, oprf_key)
 }
 
 #[cfg(feature = "bench")]
 #[inline]
-pub fn generate_oprf3_shim<G: Group, H: Hash>(
-    input: &[u8],
+pub fn unblind_and_finalize_shim<G: Group, H: Hash>(
+    token: &Token<G>,
     point: G,
-    blinding_factor: &G::Scalar,
 ) -> Result<GenericArray<u8, <H as Digest>::OutputSize>, InternalPakeError> {
-    generate_oprf3::<G, H>(input, point, blinding_factor)
+    unblind_and_finalize::<G, H>(token, point)
 }
 
 // Tests
@@ -103,7 +102,7 @@ mod tests {
         input: &[u8],
         oprf_key: &[u8; 32],
     ) -> GenericArray<u8, <RistrettoPoint as Group>::ElemLen> {
-        let (hashed_input, _) = Hkdf::<Sha512>::extract(None, &input);
+        let (hashed_input, _) = Hkdf::<Sha512>::extract(Some(STR_VOPRF), &input);
         let point = RistrettoPoint::hash_to_curve(GenericArray::from_slice(&hashed_input));
         let scalar =
             RistrettoPoint::from_scalar_slice(GenericArray::from_slice(&oprf_key[..])).unwrap();
@@ -118,18 +117,19 @@ mod tests {
     fn oprf_retrieval() -> Result<(), InternalPakeError> {
         let input = b"hunter2";
         let mut rng = OsRng;
-        let OprfClientBytes {
-            alpha,
-            blinding_factor,
-        } = generate_oprf1::<_, RistrettoPoint>(&input[..], None, &mut rng)?;
-        let salt_bytes = arr![
+        let (token, alpha) = blind_with_postprocessing::<_, RistrettoPoint>(
+            &input[..],
+            &mut rng,
+            std::convert::identity,
+        )?;
+        let oprf_key_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)?;
-        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());
+        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());
         assert_eq!(res, res2);
         Ok(())
     }
@@ -139,14 +139,15 @@ mod tests {
         let mut rng = OsRng;
         let mut input = vec![0u8; 64];
         rng.fill_bytes(&mut 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 (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 mut bits = [0u8; 64];
         bits.copy_from_slice(&hashed_input);
 
diff --git a/src/serialization/mod.rs b/src/serialization/mod.rs
new file mode 100644
index 0000000..261e589
--- /dev/null
+++ b/src/serialization/mod.rs
@@ -0,0 +1,112 @@
+// 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 129fafc..5c51df8 100644
--- a/src/slow_hash.rs
+++ b/src/slow_hash.rs
@@ -5,8 +5,7 @@
 
 //! Trait specifying a slow hashing function
 
-use crate::errors::InternalPakeError;
-use crate::hash::Hash;
+use crate::{errors::InternalPakeError, hash::Hash};
 use digest::Digest;
 use generic_array::GenericArray;
 


Test patch:
diff --git a/src/serialization/tests.rs b/src/serialization/tests.rs
new file mode 100644
index 0000000..66e5aad
--- /dev/null
+++ b/src/serialization/tests.rs
@@ -0,0 +1,372 @@
+// 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 09ac24f..1449398 100644
--- a/src/tests/mod.rs
+++ b/src/tests/mod.rs
@@ -5,4 +5,3 @@
 
 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 748cf1b..848afb0 100644
--- a/src/tests/opaque_ke_test.rs
+++ b/src/tests/opaque_ke_test.rs
@@ -14,6 +14,7 @@ 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;
@@ -39,10 +40,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>,
@@ -64,35 +65,35 @@ pub struct TestVectorParameters {
 
 static TEST_VECTOR: &str = r#"
 {
-    "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",
+    "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",
     "password": "70617373776f7264",
-    "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"
+    "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"
 }
 "#;
 
@@ -112,10 +113,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(),
@@ -147,14 +148,9 @@ 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",
@@ -162,7 +158,6 @@ 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!(
@@ -239,8 +234,9 @@ 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];
@@ -253,20 +249,21 @@ 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(
+    let (r1, client_registration) = ClientRegistration::<CS>::start_with_user_and_server_name(
+        id_u,
+        id_s,
         password,
-        Some(pepper),
         &mut blinding_factor_registration_rng,
     )
     .unwrap();
-    let r1_bytes = r1.to_bytes().to_vec();
+    let r1_bytes = r1.serialize().to_vec();
     let blinding_factor_bytes =
-        CS::Group::scalar_as_bytes(&client_registration.blinding_factor).clone();
+        CS::Group::scalar_as_bytes(&client_registration.token.blind).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.to_bytes().to_vec();
+    let r2_bytes = r2.serialize().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();
 
@@ -278,7 +275,7 @@ where
     let (r3, export_key_registration) = client_registration
         .finish(r2, server_s_kp.public(), &mut finish_registration_rng)
         .unwrap();
-    let r3_bytes = r3.to_bytes().to_vec();
+    let r3_bytes = r3.serialize().to_vec();
 
     let password_file = server_registration.finish(r3).unwrap();
     let password_file_bytes = password_file.to_bytes();
@@ -289,9 +286,14 @@ 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(password, Some(pepper), &mut client_login_start_rng).unwrap();
-    let l1_bytes = l1.to_bytes().to_vec();
+    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 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());
@@ -302,7 +304,7 @@ where
         &mut server_e_sk_rng,
     )
     .unwrap();
-    let l2_bytes = l2.to_bytes().to_vec();
+    let l2_bytes = l2.serialize().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());
@@ -320,10 +322,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(),
@@ -350,17 +352,25 @@ 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 blinding_factor_rng = CycleRng::new(parameters.blinding_factor_raw);
-    let (r1, client_registration) = ClientRegistration::<X255193dhNoSlowHash>::start(
+    let mut rng = OsRng;
+    let (r1, client_registration) = ClientRegistration::<X255193dhNoSlowHash>::start_with_user_and_server_name_and_postprocessing(
+        &parameters.id_u,
+        &parameters.id_s,
         &parameters.password,
-        Some(&parameters.pepper),
-        &mut blinding_factor_rng,
+        &mut rng,
+        postprocess_blinding_factor::<<X255193dhNoSlowHash as CipherSuite>::Group>,
     )
     .unwrap();
-    assert_eq!(hex::encode(&parameters.r1), hex::encode(r1.to_bytes()));
+    assert_eq!(hex::encode(&parameters.r1), hex::encode(r1.serialize()));
     assert_eq!(
         hex::encode(&parameters.client_registration_state),
         hex::encode(client_registration.to_bytes())
@@ -373,11 +383,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::try_from(&parameters.r1[..]).unwrap(),
+        RegisterFirstMessage::deserialize(&parameters.r1[..]).unwrap(),
         &mut oprf_key_rng,
     )
     .unwrap();
-    assert_eq!(hex::encode(parameters.r2), hex::encode(r2.to_bytes()));
+    assert_eq!(hex::encode(parameters.r2), hex::encode(r2.serialize()));
     assert_eq!(
         hex::encode(&parameters.server_registration_state),
         hex::encode(server_registration.to_bytes())
@@ -397,13 +407,13 @@ fn test_r3() -> Result<(), PakeError> {
     )
     .unwrap()
     .finish(
-        RegisterSecondMessage::try_from(&parameters.r2[..]).unwrap(),
+        RegisterSecondMessage::deserialize(&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.to_bytes()));
+    assert_eq!(hex::encode(parameters.r3), hex::encode(r3.serialize()));
     assert_eq!(
         hex::encode(parameters.export_key),
         hex::encode(export_key_registration.to_vec())
@@ -421,7 +431,7 @@ fn test_password_file() -> Result<(), PakeError> {
     )
     .unwrap();
     let password_file = server_registration
-        .finish(RegisterThirdMessage::try_from(&parameters.r3[..]).unwrap())
+        .finish(RegisterThirdMessage::deserialize(&parameters.r3[..]).unwrap())
         .unwrap();
 
     assert_eq!(
@@ -436,19 +446,22 @@ fn test_l1() -> Result<(), PakeError> {
     let parameters = populate_test_vectors(&serde_json::from_str(TEST_VECTOR).unwrap());
 
     let client_login_start = [
-        parameters.blinding_factor_raw,
+        vec![0u8; 64], // FIXME: don't hardcode this
         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(
-        &parameters.password,
-        Some(&parameters.pepper),
-        &mut client_login_start_rng,
-    )
-    .unwrap();
-    assert_eq!(hex::encode(&parameters.l1), hex::encode(l1.to_bytes()));
+    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()));
     assert_eq!(
         hex::encode(&parameters.client_login_state),
         hex::encode(client_login.to_bytes())
@@ -464,12 +477,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>::try_from(&parameters.l1[..]).unwrap(),
+        LoginFirstMessage::<X255193dhNoSlowHash>::deserialize(&parameters.l1[..]).unwrap(),
         &mut server_e_sk_rng,
     )
     .unwrap();
 
-    assert_eq!(hex::encode(&parameters.l2), hex::encode(l2.to_bytes()));
+    assert_eq!(hex::encode(&parameters.l2), hex::encode(l2.serialize()));
     assert_eq!(
         hex::encode(&parameters.server_login_state),
         hex::encode(server_login.to_bytes())
@@ -486,7 +499,7 @@ fn test_l3() -> Result<(), PakeError> {
         ClientLogin::<X255193dhNoSlowHash>::try_from(&parameters.client_login_state[..])
             .unwrap()
             .finish(
-                LoginSecondMessage::<X255193dhNoSlowHash>::try_from(&parameters.l2[..]).unwrap(),
+                LoginSecondMessage::<X255193dhNoSlowHash>::deserialize(&parameters.l2[..]).unwrap(),
                 &Key::try_from(&parameters.server_s_pk[..])?,
                 &mut client_e_sk_rng,
             )
@@ -530,18 +543,15 @@ 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,
-        None,
-        &mut client_rng,
-    )?;
+    let (register_m1, client_state) =
+        ClientRegistration::<X255193dhNoSlowHash>::start(registration_password, &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, None, &mut client_rng)?;
+        ClientLogin::<X255193dhNoSlowHash>::start(login_password, &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
deleted file mode 100644
index c5a5660..0000000
--- a/src/tests/serialization.rs
+++ /dev/null
@@ -1,230 +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::*,
-};
-
-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);
-    }
-
-}