Repository: novifinancial/opaque-ke
Pull request #178 (2021-06-16 19:39:29)

Rebasing v0.6 changes onto master
Includes support for client enumeration mitigations (introducing credential_identifier, server_setup), as well as support for fake credential response and the new "internal" mode for the envelope.


Patch:
diff --git a/Cargo.lock b/Cargo.lock
index 10b95e9..529e9db 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -567,7 +567,7 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
 
 [[package]]
 name = "opaque-ke"
-version = "0.5.1-pre.1"
+version = "0.6.0-pre.1"
 dependencies = [
  "anyhow",
  "base64",
diff --git a/Cargo.toml b/Cargo.toml
index 5a5b17a..7749533 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "opaque-ke"
-version = "0.5.1-pre.1"
+version = "0.6.0-pre.1"
 repository = "https://github.com/novifinancial/opaque-ke"
 keywords = ["cryptography", "crypto", "opaque", "passwords", "authentication"]
 description = "An implementation of the OPAQUE password-authenticated key exchange protocol"
diff --git a/examples/digital_locker.rs b/examples/digital_locker.rs
index 8107238..045f1f6 100644
--- a/examples/digital_locker.rs
+++ b/examples/digital_locker.rs
@@ -33,10 +33,10 @@ use std::process::exit;
 use opaque_ke::{
     ciphersuite::CipherSuite,
     rand::{rngs::OsRng, RngCore},
-    ClientLogin, ClientLoginFinishParameters, ClientLoginStartParameters, ClientRegistration,
+    ClientLogin, ClientLoginFinishParameters, ClientRegistration,
     ClientRegistrationFinishParameters, CredentialFinalization, CredentialRequest,
     CredentialResponse, RegistrationRequest, RegistrationResponse, RegistrationUpload, ServerLogin,
-    ServerLoginStartParameters, ServerRegistration,
+    ServerLoginStartParameters, ServerRegistration, ServerSetup,
 };
 
 // The ciphersuite trait allows to specify the underlying primitives
@@ -81,7 +81,8 @@ fn decrypt(key: &[u8], ciphertext: &[u8]) -> Vec<u8> {
 
 // Password-based registration and encryption of client secret message between a client and server
 fn register_locker(
-    server_kp: &opaque_ke::keypair::KeyPair<curve25519_dalek::ristretto::RistrettoPoint>,
+    server_setup: &ServerSetup<Default>,
+    locker_id: usize,
     password: String,
     secret_message: String,
 ) -> Locker {
@@ -91,12 +92,10 @@ fn register_locker(
     let registration_request_bytes = client_registration_start_result.message.serialize();
 
     // Client sends registration_request_bytes to server
-
-    let mut server_rng = OsRng;
     let server_registration_start_result = ServerRegistration::<Default>::start(
-        &mut server_rng,
+        &server_setup,
         RegistrationRequest::deserialize(&registration_request_bytes[..]).unwrap(),
-        server_kp.public(),
+        &locker_id.to_be_bytes(),
     )
     .unwrap();
     let registration_response_bytes = server_registration_start_result.message.serialize();
@@ -121,10 +120,9 @@ fn register_locker(
 
     // Client sends message_bytes to server
 
-    let password_file = server_registration_start_result
-        .state
-        .finish(RegistrationUpload::deserialize(&message_bytes[..]).unwrap())
-        .unwrap();
+    let password_file = ServerRegistration::finish(
+        RegistrationUpload::<Default>::deserialize(&message_bytes[..]).unwrap(),
+    );
 
     Locker {
         contents: ciphertext,
@@ -134,17 +132,14 @@ fn register_locker(
 
 // Open the contents of a locker with a password between a client and server
 fn open_locker(
-    server_kp: &opaque_ke::keypair::KeyPair<curve25519_dalek::ristretto::RistrettoPoint>,
+    server_setup: &ServerSetup<Default>,
+    locker_id: usize,
     password: String,
     locker: &Locker,
 ) -> Result<String, String> {
     let mut client_rng = OsRng;
-    let client_login_start_result = ClientLogin::<Default>::start(
-        &mut client_rng,
-        password.as_bytes(),
-        ClientLoginStartParameters::default(),
-    )
-    .unwrap();
+    let client_login_start_result =
+        ClientLogin::<Default>::start(&mut client_rng, password.as_bytes()).unwrap();
     let credential_request_bytes = client_login_start_result.message.serialize();
 
     // Client sends credential_request_bytes to server
@@ -154,9 +149,10 @@ fn open_locker(
     let mut server_rng = OsRng;
     let server_login_start_result = ServerLogin::start(
         &mut server_rng,
-        password_file,
-        &server_kp.private(),
+        &server_setup,
+        Some(password_file),
         CredentialRequest::deserialize(&credential_request_bytes[..]).unwrap(),
+        &locker_id.to_be_bytes(),
         ServerLoginStartParameters::default(),
     )
     .unwrap();
@@ -200,7 +196,7 @@ fn open_locker(
 
 fn main() {
     let mut rng = OsRng;
-    let server_kp = Default::generate_random_keypair(&mut rng);
+    let server_setup = ServerSetup::<Default>::new(&mut rng);
 
     let mut rl = Editor::<()>::new();
     let mut registered_lockers: Vec<Locker> = vec![];
@@ -225,8 +221,10 @@ fn main() {
                             &mut rl,
                             None,
                         );
+                        let locker_id = registered_lockers.len();
                         registered_lockers.push(register_locker(
-                            &server_kp,
+                            &server_setup,
+                            locker_id,
                             password,
                             secret_message,
                         ));
@@ -252,7 +250,12 @@ fn main() {
                             continue;
                         }
 
-                        match open_locker(&server_kp, password, &registered_lockers[locker_index]) {
+                        match open_locker(
+                            &server_setup,
+                            locker_index,
+                            password,
+                            &registered_lockers[locker_index],
+                        ) {
                             Ok(contents) => {
                                 println!("\n\nSuccess! Contents: {}\n\n", contents);
                             }
diff --git a/examples/simple_login.rs b/examples/simple_login.rs
index 44f9572..f8f42e0 100644
--- a/examples/simple_login.rs
+++ b/examples/simple_login.rs
@@ -27,10 +27,9 @@ use std::process::exit;
 
 use opaque_ke::{
     ciphersuite::CipherSuite, rand::rngs::OsRng, ClientLogin, ClientLoginFinishParameters,
-    ClientLoginStartParameters, ClientRegistration, ClientRegistrationFinishParameters,
-    CredentialFinalization, CredentialRequest, CredentialResponse, RegistrationRequest,
-    RegistrationResponse, RegistrationUpload, ServerLogin, ServerLoginStartParameters,
-    ServerRegistration,
+    ClientRegistration, ClientRegistrationFinishParameters, CredentialFinalization,
+    CredentialRequest, CredentialResponse, RegistrationRequest, RegistrationResponse,
+    RegistrationUpload, ServerLogin, ServerLoginStartParameters, ServerRegistration, ServerSetup,
 };
 
 // The ciphersuite trait allows to specify the underlying primitives
@@ -46,7 +45,8 @@ impl CipherSuite for Default {
 
 // Password-based registration between a client and server
 fn account_registration(
-    server_kp: &opaque_ke::keypair::KeyPair<curve25519_dalek::ristretto::RistrettoPoint>,
+    server_setup: &ServerSetup<Default>,
+    username: String,
     password: String,
 ) -> Vec<u8> {
     let mut client_rng = OsRng;
@@ -56,11 +56,10 @@ fn account_registration(
 
     // Client sends registration_request_bytes to server
 
-    let mut server_rng = OsRng;
     let server_registration_start_result = ServerRegistration::<Default>::start(
-        &mut server_rng,
+        &server_setup,
         RegistrationRequest::deserialize(&registration_request_bytes[..]).unwrap(),
-        server_kp.public(),
+        username.as_bytes(),
     )
     .unwrap();
     let registration_response_bytes = server_registration_start_result.message.serialize();
@@ -79,26 +78,22 @@ fn account_registration(
 
     // Client sends message_bytes to server
 
-    let password_file = server_registration_start_result
-        .state
-        .finish(RegistrationUpload::deserialize(&message_bytes[..]).unwrap())
-        .unwrap();
+    let password_file = ServerRegistration::finish(
+        RegistrationUpload::<Default>::deserialize(&message_bytes[..]).unwrap(),
+    );
     password_file.serialize()
 }
 
 // Password-based login between a client and server
 fn account_login(
-    server_kp: &opaque_ke::keypair::KeyPair<curve25519_dalek::ristretto::RistrettoPoint>,
+    server_setup: &ServerSetup<Default>,
+    username: String,
     password: String,
     password_file_bytes: &[u8],
 ) -> bool {
     let mut client_rng = OsRng;
-    let client_login_start_result = ClientLogin::<Default>::start(
-        &mut client_rng,
-        password.as_bytes(),
-        ClientLoginStartParameters::default(),
-    )
-    .unwrap();
+    let client_login_start_result =
+        ClientLogin::<Default>::start(&mut client_rng, password.as_bytes()).unwrap();
     let credential_request_bytes = client_login_start_result.message.serialize();
 
     // Client sends credential_request_bytes to server
@@ -107,9 +102,10 @@ fn account_login(
     let mut server_rng = OsRng;
     let server_login_start_result = ServerLogin::start(
         &mut server_rng,
-        password_file,
-        &server_kp.private(),
+        &server_setup,
+        Some(password_file),
         CredentialRequest::deserialize(&credential_request_bytes[..]).unwrap(),
+        username.as_bytes(),
         ServerLoginStartParameters::default(),
     )
     .unwrap();
@@ -141,7 +137,7 @@ fn account_login(
 
 fn main() {
     let mut rng = OsRng;
-    let server_kp = Default::generate_random_keypair(&mut rng);
+    let server_setup = ServerSetup::<Default>::new(&mut rng);
 
     let mut rl = Editor::<()>::new();
     let mut registered_users = HashMap::<String, Vec<u8>>::new();
@@ -164,13 +160,16 @@ fn main() {
                 let (username, password) = get_two_strings("Username", "Password", &mut rl, None);
                 match line.as_ref() {
                     "1" => {
-                        registered_users
-                            .insert(username, account_registration(&server_kp, password));
+                        registered_users.insert(
+                            username.clone(),
+                            account_registration(&server_setup, username, password),
+                        );
                         continue;
                     }
                     "2" => match registered_users.get(&username) {
                         Some(password_file_bytes) => {
-                            if account_login(&server_kp, password, password_file_bytes) {
+                            if account_login(&server_setup, username, password, password_file_bytes)
+                            {
                                 println!("\nLogin success!");
                             } else {
                                 // Note that at this point, the client knows whether or not the login
diff --git a/src/ciphersuite.rs b/src/ciphersuite.rs
index f1b9b24..9c90601 100644
--- a/src/ciphersuite.rs
+++ b/src/ciphersuite.rs
@@ -6,13 +6,11 @@
 //! Defines the CipherSuite trait to specify the underlying primitives for OPAQUE
 
 use crate::{
-    hash::Hash, key_exchange::traits::KeyExchange, keypair::KeyPair,
-    map_to_curve::GroupWithMapToCurve, slow_hash::SlowHash,
+    hash::Hash, key_exchange::traits::KeyExchange, map_to_curve::GroupWithMapToCurve,
+    slow_hash::SlowHash,
 };
 use digest::Digest;
 
-use rand::{CryptoRng, RngCore};
-
 /// Configures the underlying primitives used in OPAQUE
 /// * `Group`: a finite cyclic group along with a point representation, along
 ///   with an extension trait PasswordToCurve that allows some customization on
@@ -33,9 +31,4 @@ pub trait CipherSuite {
     type Hash: Hash;
     /// A slow hashing function, typically used for password hashing
     type SlowHash: SlowHash<Self::Hash>;
-
-    /// Generating a random key pair given a cryptographic rng
-    fn generate_random_keypair<R: RngCore + CryptoRng>(rng: &mut R) -> KeyPair<Self::Group> {
-        KeyPair::<Self::Group>::generate_random(rng)
-    }
 }
diff --git a/src/envelope.rs b/src/envelope.rs
index bfed3d9..f59d653 100644
--- a/src/envelope.rs
+++ b/src/envelope.rs
@@ -4,10 +4,13 @@
 // LICENSE file in the root directory of this source tree.
 
 use crate::{
-    errors::{utils::check_slice_size_atleast, InternalPakeError, PakeError, ProtocolError},
+    ciphersuite::CipherSuite,
+    errors::{utils::check_slice_size, InternalPakeError, PakeError, ProtocolError},
+    group::Group,
     hash::Hash,
-    keypair::PublicKey,
-    serialization::serialize,
+    keypair::{KeyPair, PrivateKey, PublicKey},
+    map_to_curve::GroupWithMapToCurve,
+    opaque::{bytestrings_from_identifiers, Identifiers},
 };
 use digest::Digest;
 use generic_array::{typenum::Unsigned, GenericArray};
@@ -19,80 +22,62 @@ use std::convert::TryFrom;
 use zeroize::Zeroize;
 
 // Constant string used as salt for HKDF computation
-const STR_PAD: &[u8] = b"Pad";
 const STR_AUTH_KEY: &[u8] = b"AuthKey";
 const STR_EXPORT_KEY: &[u8] = b"ExportKey";
+const STR_PRIVATE_KEY: &[u8] = b"PrivateKey";
+const STR_OPAQUE_HASH_TO_SCALAR: &[u8] = b"OPAQUE-HashToScalar";
 
 const NONCE_LEN: usize = 32;
 
+fn build_inner_envelope_internal<CS: CipherSuite>(
+    random_pwd: &[u8],
+    nonce: &[u8],
+) -> Result<PublicKey, InternalPakeError> {
+    let h = Hkdf::<CS::Hash>::new(None, random_pwd);
+    let mut keypair_seed = vec![0u8; <PrivateKey as SizedBytes>::Len::to_usize()];
+    h.expand(&[nonce, STR_PRIVATE_KEY].concat(), &mut keypair_seed)
+        .map_err(|_| InternalPakeError::HkdfError)?;
+    let client_static_keypair =
+        KeyPair::<CS::Group>::from_private_key_slice(CS::Group::scalar_as_bytes(
+            &CS::Group::hash_to_scalar::<CS::Hash>(&keypair_seed[..], STR_OPAQUE_HASH_TO_SCALAR)?,
+        ))?;
+
+    Ok(client_static_keypair.public().clone())
+}
+
+fn recover_keys_internal<CS: CipherSuite>(
+    random_pwd: &[u8],
+    nonce: &[u8],
+) -> Result<KeyPair<CS::Group>, InternalPakeError> {
+    let h = Hkdf::<CS::Hash>::new(None, random_pwd);
+    let mut keypair_seed = vec![0u8; <PrivateKey as SizedBytes>::Len::to_usize()];
+    h.expand(&[nonce, STR_PRIVATE_KEY].concat(), &mut keypair_seed)
+        .map_err(|_| InternalPakeError::HkdfError)?;
+    let client_static_keypair =
+        KeyPair::<CS::Group>::from_private_key_slice(CS::Group::scalar_as_bytes(
+            &CS::Group::hash_to_scalar::<CS::Hash>(&keypair_seed[..], STR_OPAQUE_HASH_TO_SCALAR)?,
+        ))?;
+
+    Ok(client_static_keypair)
+}
+
 #[derive(Clone, Copy, PartialEq, Zeroize)]
 #[zeroize(drop)]
 pub(crate) enum InnerEnvelopeMode {
-    Base = 1,
-    CustomIdentifier = 2,
+    Zero = 0,
+    Internal = 1,
 }
 
 impl TryFrom<u8> for InnerEnvelopeMode {
     type Error = PakeError;
     fn try_from(x: u8) -> Result<Self, Self::Error> {
         match x {
-            1 => Ok(InnerEnvelopeMode::Base),
-            2 => Ok(InnerEnvelopeMode::CustomIdentifier),
+            1 => Ok(InnerEnvelopeMode::Internal),
             _ => Err(PakeError::SerializationError),
         }
     }
 }
 
-#[derive(Clone, Zeroize)]
-#[zeroize(drop)]
-pub(crate) struct InnerEnvelope {
-    mode: InnerEnvelopeMode,
-    nonce: Vec<u8>,
-    ciphertext: Vec<u8>,
-}
-
-impl InnerEnvelope {
-    pub(crate) fn serialize(&self) -> Vec<u8> {
-        [&[self.mode as u8], &self.nonce[..], &self.ciphertext[..]].concat()
-    }
-
-    pub(crate) fn deserialize(input: &[u8]) -> Result<(Self, Vec<u8>), ProtocolError> {
-        if input.is_empty() {
-            return Err(ProtocolError::VerificationError(
-                PakeError::SerializationError,
-            ));
-        }
-        let mode = InnerEnvelopeMode::try_from(input[0])?;
-
-        let key_len = <PublicKey as SizedBytes>::Len::to_usize();
-
-        let bytes = &input[1..];
-        if bytes.len() < NONCE_LEN + key_len {
-            return Err(ProtocolError::VerificationError(
-                PakeError::SerializationError,
-            ));
-        }
-
-        Ok((
-            Self {
-                mode,
-                nonce: bytes[..NONCE_LEN].to_vec(),
-                ciphertext: bytes[NONCE_LEN..NONCE_LEN + key_len].to_vec(),
-            },
-            bytes[NONCE_LEN + key_len..].to_vec(),
-        ))
-    }
-
-    #[cfg(test)]
-    pub fn as_byte_ptrs(&self) -> Vec<(*const u8, usize)> {
-        vec![
-            /* Cannot easily get raw pointer of enum value, otherwise would do self.mode.as_ptr() */
-            (self.nonce.as_ptr(), self.nonce.len()),
-            (self.ciphertext.as_ptr(), self.ciphertext.len()),
-        ]
-    }
-}
-
 /// This struct is an instantiation of the envelope as described in
 /// https://tools.ietf.org/html/draft-krawczyk-cfrg-opaque-06#section-4
 ///
@@ -103,131 +88,151 @@ impl InnerEnvelope {
 /// The specification update has simplified this assumption by taking
 /// an XOR-based approach without compromising on security, and to avoid
 /// the confusion around the implementation of an RKR-secure encryption.
-#[derive(Clone)]
-pub(crate) struct Envelope<D: Hash> {
-    inner_envelope: InnerEnvelope,
-    hmac: GenericArray<u8, <D as Digest>::OutputSize>,
+pub(crate) struct Envelope<CS: CipherSuite> {
+    mode: InnerEnvelopeMode,
+    nonce: Vec<u8>,
+    hmac: GenericArray<u8, <CS::Hash as Digest>::OutputSize>,
+}
+
+// Cannot be derived because it would require for CS to be Clone.
+impl<CS: CipherSuite> Clone for Envelope<CS> {
+    fn clone(&self) -> Self {
+        Self {
+            mode: self.mode,
+            nonce: self.nonce.clone(),
+            hmac: self.hmac.clone(),
+        }
+    }
 }
 
 // Note that this struct represents an envelope that has been "opened" with the asssociated
 // key. This key is also used to derive the export_key parameter, which is technically
 // unrelated to the envelope's encrypted and authenticated contents.
-pub(crate) struct OpenedEnvelope<D: Hash> {
-    pub(crate) client_s_sk: Vec<u8>,
-    pub(crate) export_key: GenericArray<u8, <D as Digest>::OutputSize>,
+pub(crate) struct OpenedEnvelope<CS: CipherSuite> {
+    pub(crate) client_static_keypair: KeyPair<CS::Group>,
+    pub(crate) export_key: GenericArray<u8, <CS::Hash as Digest>::OutputSize>,
+    pub(crate) id_u: Vec<u8>,
+    pub(crate) id_s: Vec<u8>,
 }
 
 pub(crate) struct OpenedInnerEnvelope<D: Hash> {
-    pub(crate) plaintext: Vec<u8>,
     pub(crate) export_key: GenericArray<u8, <D as Digest>::OutputSize>,
 }
 
-impl<D: Hash> Envelope<D> {
+impl<CS: CipherSuite> Envelope<CS> {
     fn hmac_key_size() -> usize {
-        <D as Digest>::OutputSize::to_usize()
+        <CS::Hash as Digest>::OutputSize::to_usize()
     }
 
     fn export_key_size() -> usize {
-        <D as Digest>::OutputSize::to_usize()
+        <CS::Hash as Digest>::OutputSize::to_usize()
     }
 
-    pub(crate) fn get_mode(&self) -> InnerEnvelopeMode {
-        self.inner_envelope.mode
-    }
-
-    /// The format of the output is:
-    /// mode | nonce             | ciphertext       | hmac
-    /// u8   | nonce_size bytes  | variable length  | hmac_size bytes
-    pub(crate) fn from_bytes(bytes: &[u8]) -> Result<Self, InternalPakeError> {
-        let (result, remainder) = Self::deserialize(bytes)
-            .map_err(|_| InternalPakeError::InvalidEnvelopeStructureError)?;
-        if !remainder.is_empty() {
-            return Err(InternalPakeError::InvalidEnvelopeStructureError);
-        }
-        Ok(result)
-    }
-
-    pub(crate) fn to_bytes(&self) -> Vec<u8> {
-        self.serialize()
+    pub(crate) fn len() -> usize {
+        <CS::Hash as Digest>::OutputSize::to_usize() + NONCE_LEN
     }
 
     pub(crate) fn serialize(&self) -> Vec<u8> {
-        [&self.inner_envelope.serialize(), &self.hmac[..]].concat()
+        [&self.nonce[..], &self.hmac[..]].concat()
     }
+    pub(crate) fn deserialize(bytes: &[u8]) -> Result<Self, ProtocolError> {
+        let mode = InnerEnvelopeMode::Internal; // Better way to hard-code this?
+
+        if bytes.len() < NONCE_LEN {
+            return Err(ProtocolError::VerificationError(
+                PakeError::SerializationError,
+            ));
+        }
+        let nonce = bytes[..NONCE_LEN].to_vec();
 
-    pub(crate) fn deserialize(input: &[u8]) -> Result<(Self, Vec<u8>), ProtocolError> {
-        let (inner_envelope, remainder) = InnerEnvelope::deserialize(input)?;
+        let remainder = match mode {
+            InnerEnvelopeMode::Zero => {
+                return Err(InternalPakeError::IncompatibleEnvelopeModeError.into())
+            }
+            InnerEnvelopeMode::Internal => bytes[NONCE_LEN..].to_vec(),
+        };
 
         let hmac_key_size = Self::hmac_key_size();
-        let hmac_and_remainder =
-            check_slice_size_atleast(&remainder, hmac_key_size, "hmac_key_size")?;
+        let hmac = check_slice_size(&remainder, hmac_key_size, "hmac_key_size")?;
 
-        Ok((
-            Self {
-                inner_envelope,
-                hmac: GenericArray::clone_from_slice(&hmac_and_remainder[..hmac_key_size]),
-            },
-            hmac_and_remainder[hmac_key_size..].to_vec(),
-        ))
+        Ok(Self {
+            mode,
+            nonce,
+            hmac: GenericArray::clone_from_slice(hmac),
+        })
+    }
+
+    // Creates a dummy envelope object that serializes to the all-zeros byte string
+    pub(crate) fn dummy() -> Self {
+        Self {
+            mode: InnerEnvelopeMode::Zero,
+            nonce: vec![0u8; NONCE_LEN],
+            hmac: GenericArray::clone_from_slice(&vec![
+                0u8;
+                <CS::Hash as Digest>::OutputSize::to_usize()
+            ]),
+        }
     }
 
+    #[allow(clippy::type_complexity)]
     pub(crate) fn seal<R: RngCore + CryptoRng>(
         rng: &mut R,
         key: &[u8],
-        client_s_sk: &[u8],
         server_s_pk: &[u8],
-        optional_ids: Option<(Vec<u8>, Vec<u8>)>,
-    ) -> Result<(Self, GenericArray<u8, <D as Digest>::OutputSize>), InternalPakeError> {
-        let aad = construct_aad(server_s_pk, &optional_ids);
-        Self::seal_raw(rng, key, client_s_sk, &aad, mode_from_ids(&optional_ids))
+        optional_ids: Option<Identifiers>,
+    ) -> Result<
+        (
+            Self,
+            PublicKey,
+            GenericArray<u8, <CS::Hash as Digest>::OutputSize>,
+        ),
+        InternalPakeError,
+    > {
+        let mut nonce = vec![0u8; NONCE_LEN];
+        rng.fill_bytes(&mut nonce);
+
+        let (mode, client_s_pk) = (
+            InnerEnvelopeMode::Internal,
+            build_inner_envelope_internal::<CS>(key, &nonce)?,
+        );
+
+        let (id_u, id_s) =
+            bytestrings_from_identifiers(&optional_ids, &client_s_pk.to_arr(), server_s_pk);
+        let aad = construct_aad(&id_u, &id_s, server_s_pk);
+
+        let (envelope, export_key) = Self::seal_raw(key, &nonce, &aad, mode)?;
+        Ok((envelope, client_s_pk, export_key))
     }
 
     /// Uses a key to convert the plaintext into an envelope, authenticated by the aad field.
     /// Note that a new nonce is sampled for each call to seal.
-    pub(crate) fn seal_raw<R: RngCore + CryptoRng>(
-        rng: &mut R,
+    #[allow(clippy::type_complexity)]
+    pub(crate) fn seal_raw(
         key: &[u8],
-        plaintext: &[u8],
+        nonce: &[u8],
         aad: &[u8],
         mode: InnerEnvelopeMode,
-    ) -> Result<(Self, GenericArray<u8, <D as Digest>::OutputSize>), InternalPakeError> {
-        let mut nonce = vec![0u8; NONCE_LEN];
-        rng.fill_bytes(&mut nonce);
-
-        let h = Hkdf::<D>::new(Some(&nonce), key);
-        let mut xor_key = vec![0u8; plaintext.len()];
+    ) -> Result<(Self, GenericArray<u8, <CS::Hash as Digest>::OutputSize>), InternalPakeError> {
+        let h = Hkdf::<CS::Hash>::new(None, key);
         let mut hmac_key = vec![0u8; Self::hmac_key_size()];
         let mut export_key = vec![0u8; Self::export_key_size()];
 
-        h.expand(STR_PAD, &mut xor_key)
+        h.expand(&[nonce, STR_AUTH_KEY].concat(), &mut hmac_key)
             .map_err(|_| InternalPakeError::HkdfError)?;
-        h.expand(STR_AUTH_KEY, &mut hmac_key)
+        h.expand(&[nonce, STR_EXPORT_KEY].concat(), &mut export_key)
             .map_err(|_| InternalPakeError::HkdfError)?;
-        h.expand(STR_EXPORT_KEY, &mut export_key)
-            .map_err(|_| InternalPakeError::HkdfError)?;
-
-        let ciphertext: Vec<u8> = xor_key
-            .iter()
-            .zip(plaintext.iter())
-            .map(|(&x1, &x2)| x1 ^ x2)
-            .collect();
-
-        let inner_envelope = InnerEnvelope {
-            mode,
-            nonce,
-            ciphertext,
-        };
 
         let mut hmac =
-            Hmac::<D>::new_varkey(&hmac_key).map_err(|_| InternalPakeError::HmacError)?;
-        hmac.update(&inner_envelope.serialize());
+            Hmac::<CS::Hash>::new_varkey(&hmac_key).map_err(|_| InternalPakeError::HmacError)?;
+        hmac.update(nonce);
         hmac.update(aad);
 
         let hmac_bytes = hmac.finalize().into_bytes();
 
         Ok((
             Self {
-                inner_envelope,
+                mode,
+                nonce: nonce.to_vec(),
                 hmac: hmac_bytes,
             },
             GenericArray::clone_from_slice(&export_key),
@@ -238,24 +243,29 @@ impl<D: Hash> Envelope<D> {
         &self,
         key: &[u8],
         server_s_pk: &[u8],
-        optional_ids: &Option<(Vec<u8>, Vec<u8>)>,
-    ) -> Result<OpenedEnvelope<D>, InternalPakeError> {
-        // First, check that mode matches
-        if self.inner_envelope.mode != mode_from_ids(optional_ids) {
-            return Err(InternalPakeError::IncompatibleEnvelopeModeError);
-        }
+        optional_ids: &Option<Identifiers>,
+    ) -> Result<OpenedEnvelope<CS>, InternalPakeError> {
+        let client_static_keypair = match self.mode {
+            InnerEnvelopeMode::Zero => {
+                return Err(InternalPakeError::IncompatibleEnvelopeModeError)
+            }
+            InnerEnvelopeMode::Internal => recover_keys_internal::<CS>(key, &self.nonce)?,
+        };
 
-        let aad = construct_aad(server_s_pk, optional_ids);
-        let opened = self.open_raw(key, &aad)?;
+        let (id_u, id_s) = bytestrings_from_identifiers(
+            optional_ids,
+            &client_static_keypair.public().to_arr(),
+            server_s_pk,
+        );
+        let aad = construct_aad(&id_u, &id_s, server_s_pk);
 
-        if opened.plaintext.len() != <PublicKey as SizedBytes>::Len::to_usize() {
-            // Plaintext should consist of a single key
-            return Err(InternalPakeError::UnexpectedEnvelopeContentsError);
-        }
+        let opened = self.open_raw(key, &aad)?;
 
         Ok(OpenedEnvelope {
-            client_s_sk: opened.plaintext,
+            client_static_keypair,
             export_key: opened.export_key,
+            id_u,
+            id_s,
         })
     }
 
@@ -265,35 +275,26 @@ impl<D: Hash> Envelope<D> {
         &self,
         key: &[u8],
         aad: &[u8],
-    ) -> Result<OpenedInnerEnvelope<D>, InternalPakeError> {
-        let h = Hkdf::<D>::new(Some(&self.inner_envelope.nonce), key);
-        let mut xor_key = vec![0u8; self.inner_envelope.ciphertext.len()];
+    ) -> Result<OpenedInnerEnvelope<CS::Hash>, InternalPakeError> {
+        let h = Hkdf::<CS::Hash>::new(None, key);
         let mut hmac_key = vec![0u8; Self::hmac_key_size()];
         let mut export_key = vec![0u8; Self::export_key_size()];
 
-        h.expand(STR_PAD, &mut xor_key)
-            .map_err(|_| InternalPakeError::HkdfError)?;
-        h.expand(STR_AUTH_KEY, &mut hmac_key)
+        h.expand(&[&self.nonce, STR_AUTH_KEY].concat(), &mut hmac_key)
             .map_err(|_| InternalPakeError::HkdfError)?;
-        h.expand(STR_EXPORT_KEY, &mut export_key)
+        h.expand(&[&self.nonce, STR_EXPORT_KEY].concat(), &mut export_key)
             .map_err(|_| InternalPakeError::HkdfError)?;
 
         let mut hmac =
-            Hmac::<D>::new_varkey(&hmac_key).map_err(|_| InternalPakeError::HmacError)?;
-        hmac.update(&self.inner_envelope.serialize());
+            Hmac::<CS::Hash>::new_varkey(&hmac_key).map_err(|_| InternalPakeError::HmacError)?;
+        hmac.update(&self.nonce);
         hmac.update(aad);
         if hmac.verify(&self.hmac).is_err() {
             return Err(InternalPakeError::SealOpenHmacError);
         }
 
-        let plaintext: Vec<u8> = xor_key
-            .iter()
-            .zip(self.inner_envelope.ciphertext.iter())
-            .map(|(&x1, &x2)| x1 ^ x2)
-            .collect();
         Ok(OpenedInnerEnvelope {
-            plaintext,
-            export_key: GenericArray::<u8, <D as Digest>::OutputSize>::clone_from_slice(
+            export_key: GenericArray::<u8, <CS::Hash as Digest>::OutputSize>::clone_from_slice(
                 &export_key,
             ),
         })
@@ -301,23 +302,20 @@ impl<D: Hash> Envelope<D> {
 
     #[cfg(test)]
     pub fn as_byte_ptrs(&self) -> Vec<(*const u8, usize)> {
-        [
-            self.inner_envelope.as_byte_ptrs(),
-            vec![(self.hmac.as_ptr(), self.hmac.len())],
-        ]
-        .concat()
+        vec![(self.hmac.as_ptr(), self.hmac.len())]
     }
 }
 
 // This can't be derived because of the use of a phantom parameter
-impl<D: Hash> Zeroize for Envelope<D> {
+impl<CS: CipherSuite> Zeroize for Envelope<CS> {
     fn zeroize(&mut self) {
-        self.inner_envelope.zeroize();
+        self.mode.zeroize();
+        self.nonce.zeroize();
         self.hmac.zeroize();
     }
 }
 
-impl<D: Hash> Drop for Envelope<D> {
+impl<CS: CipherSuite> Drop for Envelope<CS> {
     fn drop(&mut self) {
         self.zeroize();
     }
@@ -325,45 +323,6 @@ impl<D: Hash> Drop for Envelope<D> {
 
 // Helper functions
 
-fn construct_aad(server_s_pk: &[u8], optional_ids: &Option<(Vec<u8>, Vec<u8>)>) -> Vec<u8> {
-    let ids = optional_ids
-        .iter()
-        .flat_map(|(l, r)| [serialize(l, 2), serialize(r, 2)].concat())
-        .collect();
-    [server_s_pk.to_vec(), ids].concat()
-}
-
-pub(crate) fn mode_from_ids(optional_ids: &Option<(Vec<u8>, Vec<u8>)>) -> InnerEnvelopeMode {
-    match optional_ids {
-        Some(_) => InnerEnvelopeMode::CustomIdentifier,
-        None => InnerEnvelopeMode::Base,
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-    use rand::rngs::OsRng;
-
-    #[test]
-    fn seal_and_open() {
-        let mut rng = OsRng;
-        let mut key = [0u8; 32];
-        rng.fill_bytes(&mut key);
-
-        let mut msg = [0u8; 100];
-        rng.fill_bytes(&mut msg);
-
-        let (envelope, export_key_1) = Envelope::<sha2::Sha256>::seal_raw(
-            &mut rng,
-            &key,
-            &msg,
-            b"aad",
-            InnerEnvelopeMode::Base,
-        )
-        .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());
-    }
+fn construct_aad(id_u: &[u8], id_s: &[u8], server_s_pk: &[u8]) -> Vec<u8> {
+    [server_s_pk, id_s, id_u].concat()
 }
diff --git a/src/errors.rs b/src/errors.rs
index e422250..8b6ce9b 100644
--- a/src/errors.rs
+++ b/src/errors.rs
@@ -92,6 +92,8 @@ pub enum ProtocolError {
     ///
     /// Internal error during password verification: {0}
     VerificationError(PakeError),
+    /// This error occurs when the inner envelope is malformed
+    InvalidInnerEnvelopeError,
     /// This error occurs when the server answer cannot be handled
     /// Server response cannot be handled.
     ServerError,
diff --git a/src/key_exchange/traits.rs b/src/key_exchange/traits.rs
index bb68fc3..e8a62c1 100644
--- a/src/key_exchange/traits.rs
+++ b/src/key_exchange/traits.rs
@@ -21,7 +21,6 @@ pub trait KeyExchange<D: Hash, G: Group> {
     type KE3Message: FromBytes + ToBytes + Clone;
 
     fn generate_ke1<R: RngCore + CryptoRng>(
-        info: Vec<u8>,
         rng: &mut R,
     ) -> Result<(Self::KE1State, Self::KE1Message), ProtocolError>;
 
@@ -35,8 +34,8 @@ pub trait KeyExchange<D: Hash, G: Group> {
         server_s_sk: PrivateKey,
         id_u: Vec<u8>,
         id_s: Vec<u8>,
-        e_info: Vec<u8>,
-    ) -> Result<(Vec<u8>, Self::KE2State, Self::KE2Message), ProtocolError>;
+        context: Vec<u8>,
+    ) -> Result<(Self::KE2State, Self::KE2Message), ProtocolError>;
 
     #[allow(clippy::too_many_arguments, clippy::type_complexity)]
     fn generate_ke3(
@@ -48,7 +47,8 @@ pub trait KeyExchange<D: Hash, G: Group> {
         client_s_sk: PrivateKey,
         id_u: Vec<u8>,
         id_s: Vec<u8>,
-    ) -> Result<(Vec<u8>, Vec<u8>, Self::KE3Message), ProtocolError>;
+        context: Vec<u8>,
+    ) -> Result<(Vec<u8>, Self::KE3Message), ProtocolError>;
 
     #[allow(clippy::type_complexity)]
     fn finish_ke(
diff --git a/src/key_exchange/tripledh.rs b/src/key_exchange/tripledh.rs
index 0ddd1a8..17c24ec 100644
--- a/src/key_exchange/tripledh.rs
+++ b/src/key_exchange/tripledh.rs
@@ -14,7 +14,7 @@ use crate::{
     hash::Hash,
     key_exchange::traits::{FromBytes, KeyExchange, ToBytes, ToBytesWithPointers},
     keypair::{KeyPair, PrivateKey, PublicKey, SizedBytesExt},
-    serialization::{serialize, tokenize},
+    serialization::serialize,
 };
 use digest::{Digest, FixedOutput};
 use generic_array::{
@@ -30,14 +30,12 @@ use zeroize::Zeroize;
 const KEY_LEN: usize = 32;
 pub(crate) type NonceLen = U32;
 
-static STR_3DH: &[u8] = b"3DH";
-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_HANDSHAKE_ENC: &[u8] = b"handshake enc";
-static STR_ENCRYPTION_PAD: &[u8] = b"encryption pad";
-static STR_SESSION_SECRET: &[u8] = b"session secret";
-static STR_OPAQUE: &[u8] = b"OPAQUE ";
+static STR_RFC: &[u8] = b"RFCXXXX";
+static STR_CLIENT_MAC: &[u8] = b"ClientMAC";
+static STR_HANDSHAKE_SECRET: &[u8] = b"HandshakeSecret";
+static STR_SERVER_MAC: &[u8] = b"ServerMAC";
+static STR_SESSION_KEY: &[u8] = b"SessionKey";
+static STR_OPAQUE: &[u8] = b"OPAQUE-";
 
 #[allow(clippy::upper_case_acronyms)]
 /// The Triple Diffie-Hellman key exchange implementation
@@ -51,7 +49,6 @@ impl<D: Hash, G: Group> KeyExchange<D, G> for TripleDH {
     type KE3Message = Ke3Message<<D as FixedOutput>::OutputSize>;
 
     fn generate_ke1<R: RngCore + CryptoRng>(
-        info: Vec<u8>,
         rng: &mut R,
     ) -> Result<(Self::KE1State, Self::KE1Message), ProtocolError> {
         let client_e_kp = KeyPair::<G>::generate_random(rng);
@@ -59,7 +56,6 @@ impl<D: Hash, G: Group> KeyExchange<D, G> for TripleDH {
 
         let ke1_message = Ke1Message {
             client_nonce,
-            info,
             client_e_pk: client_e_kp.public().clone(),
         };
 
@@ -82,21 +78,22 @@ impl<D: Hash, G: Group> KeyExchange<D, G> for TripleDH {
         server_s_sk: PrivateKey,
         id_u: Vec<u8>,
         id_s: Vec<u8>,
-        e_info: Vec<u8>,
-    ) -> Result<(Vec<u8>, Self::KE2State, Self::KE2Message), ProtocolError> {
+        context: Vec<u8>,
+    ) -> Result<(Self::KE2State, Self::KE2Message), ProtocolError> {
         let server_e_kp = KeyPair::<G>::generate_random(rng);
         let server_nonce = generate_nonce::<R>(rng);
 
         let mut transcript_hasher = D::new()
-            .chain(STR_3DH)
-            .chain(&serialize(&id_u, 2))
+            .chain(STR_RFC)
+            .chain(&serialize(&context, 2))
+            .chain(&id_u)
             .chain(&serialized_credential_request[..])
-            .chain(&serialize(&id_s, 2))
+            .chain(&id_s)
             .chain(&l2_bytes[..])
             .chain(&server_nonce[..])
             .chain(&server_e_kp.public().to_arr());
 
-        let (session_key, km2, ke2, km3) = derive_3dh_keys::<D, G>(
+        let (session_key, km2, km3) = derive_3dh_keys::<D, G>(
             TripleDHComponents {
                 pk1: ke1_message.client_e_pk.clone(),
                 sk1: server_e_kp.private().clone(),
@@ -108,19 +105,6 @@ impl<D: Hash, G: Group> KeyExchange<D, G> for TripleDH {
             &transcript_hasher.clone().finalize(),
         )?;
 
-        // Compute encryption of e_info
-        let h = Hkdf::<D>::from_prk(&ke2).map_err(|_| InternalPakeError::HkdfError)?;
-        let mut encryption_pad = vec![0u8; e_info.len()];
-        h.expand(STR_ENCRYPTION_PAD, &mut encryption_pad)
-            .map_err(|_| InternalPakeError::HkdfError)?;
-        let ciphertext: Vec<u8> = encryption_pad
-            .iter()
-            .zip(e_info.iter())
-            .map(|(&x1, &x2)| x1 ^ x2)
-            .collect();
-
-        transcript_hasher.update(&serialize(&ciphertext, 2));
-
         let mut mac_hasher =
             Hmac::<D>::new_varkey(&km2).map_err(|_| InternalPakeError::HmacError)?;
         mac_hasher.update(&transcript_hasher.clone().finalize());
@@ -129,7 +113,6 @@ impl<D: Hash, G: Group> KeyExchange<D, G> for TripleDH {
         transcript_hasher.update(&mac);
 
         Ok((
-            ke1_message.info,
             Ke2State {
                 km3,
                 hashed_transcript: transcript_hasher.finalize(),
@@ -138,7 +121,6 @@ impl<D: Hash, G: Group> KeyExchange<D, G> for TripleDH {
             Ke2Message {
                 server_nonce,
                 server_e_pk: server_e_kp.public().clone(),
-                e_info: ciphertext,
                 mac,
             },
         ))
@@ -154,16 +136,18 @@ impl<D: Hash, G: Group> KeyExchange<D, G> for TripleDH {
         client_s_sk: PrivateKey,
         id_u: Vec<u8>,
         id_s: Vec<u8>,
-    ) -> Result<(Vec<u8>, Vec<u8>, Self::KE3Message), ProtocolError> {
+        context: Vec<u8>,
+    ) -> Result<(Vec<u8>, Self::KE3Message), ProtocolError> {
         let mut transcript_hasher = D::new()
-            .chain(STR_3DH)
-            .chain(&serialize(&id_u, 2))
+            .chain(STR_RFC)
+            .chain(&serialize(&context, 2))
+            .chain(&id_u)
             .chain(&serialized_credential_request)
-            .chain(&serialize(&id_s, 2))
+            .chain(&id_s)
             .chain(&l2_component[..])
             .chain(&ke2_message.to_bytes_without_info_or_mac());
 
-        let (session_key, km2, ke2, km3) = derive_3dh_keys::<D, G>(
+        let (session_key, km2, km3) = derive_3dh_keys::<D, G>(
             TripleDHComponents {
                 pk1: ke2_message.server_e_pk.clone(),
                 sk1: ke1_state.client_e_sk.clone(),
@@ -175,8 +159,6 @@ impl<D: Hash, G: Group> KeyExchange<D, G> for TripleDH {
             &transcript_hasher.clone().finalize(),
         )?;
 
-        transcript_hasher.update(&serialize(&ke2_message.e_info[..], 2));
-
         let mut server_mac =
             Hmac::<D>::new_varkey(&km2).map_err(|_| InternalPakeError::HmacError)?;
         server_mac.update(&transcript_hasher.clone().finalize());
@@ -193,19 +175,7 @@ impl<D: Hash, G: Group> KeyExchange<D, G> for TripleDH {
             Hmac::<D>::new_varkey(&km3).map_err(|_| InternalPakeError::HmacError)?;
         client_mac.update(&transcript_hasher.finalize());
 
-        // Compute decryption of e_info
-        let h = Hkdf::<D>::from_prk(&ke2).map_err(|_| InternalPakeError::HkdfError)?;
-        let mut encryption_pad = vec![0u8; ke2_message.e_info.len()];
-        h.expand(STR_ENCRYPTION_PAD, &mut encryption_pad)
-            .map_err(|_| InternalPakeError::HkdfError)?;
-        let plaintext: Vec<u8> = encryption_pad
-            .iter()
-            .zip(ke2_message.e_info.iter())
-            .map(|(&x1, &x2)| x1 ^ x2)
-            .collect();
-
         Ok((
-            plaintext,
             session_key.to_vec(),
             Ke3Message {
                 mac: client_mac.finalize().into_bytes(),
@@ -248,7 +218,6 @@ pub struct Ke1State {
 #[derive(PartialEq, Eq, Clone)]
 pub struct Ke1Message {
     pub(crate) client_nonce: GenericArray<u8, NonceLen>,
-    pub(crate) info: Vec<u8>,
     pub(crate) client_e_pk: PublicKey,
 }
 
@@ -286,12 +255,7 @@ impl ToBytesWithPointers for Ke1State {
 
 impl ToBytes for Ke1Message {
     fn to_bytes(&self) -> Vec<u8> {
-        [
-            &self.client_nonce[..],
-            &serialize(&self.info, 2),
-            &self.client_e_pk.to_arr(),
-        ]
-        .concat()
+        [&self.client_nonce[..], &self.client_e_pk.to_arr()].concat()
     }
 }
 
@@ -299,20 +263,11 @@ impl FromBytes for Ke1Message {
     fn from_bytes<CS: CipherSuite>(ke1_message_bytes: &[u8]) -> Result<Self, PakeError> {
         let nonce_len = NonceLen::to_usize();
         let checked_nonce =
-            check_slice_size_atleast(ke1_message_bytes, nonce_len, "ke1_message nonce")?;
-
-        let (info, remainder) = tokenize(&checked_nonce[nonce_len..], 2)?;
-
-        // Check the public key bytes
-        let unchecked_client_e_pk =
-            check_slice_size(&remainder, KEY_LEN, "ke1_message client_e_pk")?;
-        let client_e_pk =
-            KeyPair::<CS::Group>::check_public_key(PublicKey::from_bytes(unchecked_client_e_pk)?)?;
+            check_slice_size(ke1_message_bytes, nonce_len + KEY_LEN, "ke1_message nonce")?;
 
         Ok(Self {
             client_nonce: GenericArray::clone_from_slice(&checked_nonce[..nonce_len]),
-            info,
-            client_e_pk,
+            client_e_pk: PublicKey::from_bytes(&checked_nonce[nonce_len..])?,
         })
     }
 }
@@ -364,7 +319,6 @@ impl<HashLen: ArrayLength<u8>> ToBytesWithPointers for Ke2State<HashLen> {
 pub struct Ke2Message<HashLen: ArrayLength<u8>> {
     server_nonce: GenericArray<u8, NonceLen>,
     server_e_pk: PublicKey,
-    e_info: Vec<u8>,
     mac: GenericArray<u8, HashLen>,
 }
 
@@ -385,12 +339,7 @@ impl<HashLen: ArrayLength<u8>> FromBytes for Ke2State<HashLen> {
 
 impl<HashLen: ArrayLength<u8>> ToBytes for Ke2Message<HashLen> {
     fn to_bytes(&self) -> Vec<u8> {
-        [
-            &self.to_bytes_without_info_or_mac(),
-            &serialize(&self.e_info, 2),
-            &self.mac[..],
-        ]
-        .concat()
+        [&self.to_bytes_without_info_or_mac(), &self.mac[..]].concat()
     }
 }
 
@@ -410,8 +359,11 @@ impl<HashLen: ArrayLength<u8>> FromBytes for Ke2Message<HashLen> {
             KEY_LEN,
             "ke2_message server_e_pk",
         )?;
-        let (e_info, remainder) = tokenize(&unchecked_server_e_pk[KEY_LEN..], 2)?;
-        let checked_mac = check_slice_size(&remainder, HashLen::to_usize(), "ke1_message mac")?;
+        let checked_mac = check_slice_size(
+            &unchecked_server_e_pk[KEY_LEN..],
+            HashLen::to_usize(),
+            "ke1_message mac",
+        )?;
 
         // Check the public key bytes
         let server_e_pk = KeyPair::<CS::Group>::check_public_key(PublicKey::from_bytes(
@@ -420,8 +372,7 @@ impl<HashLen: ArrayLength<u8>> FromBytes for Ke2Message<HashLen> {
 
         Ok(Self {
             server_nonce: GenericArray::clone_from_slice(&checked_nonce[..nonce_len]),
-            server_e_pk,
-            e_info,
+            server_e_pk: PublicKey::from_bytes(&server_e_pk)?,
             mac: GenericArray::clone_from_slice(checked_mac),
         })
     }
@@ -439,12 +390,11 @@ struct TripleDHComponents {
 }
 
 #[allow(clippy::upper_case_acronyms)]
-// Consists of a session key, followed by two mac keys and an encryption key: (session_key, km2, ke2, km3)
+// Consists of a session key, followed by two mac keys: (session_key, km2, km3)
 type TripleDHDerivationResult<D> = (
     GenericArray<u8, <D as FixedOutput>::OutputSize>,
     GenericArray<u8, <D as FixedOutput>::OutputSize>,
     GenericArray<u8, <D as FixedOutput>::OutputSize>,
-    GenericArray<u8, <D as FixedOutput>::OutputSize>,
 );
 
 /// The third key exchange message
@@ -492,7 +442,7 @@ fn derive_3dh_keys<D: Hash, G: Group>(
     )?;
     let session_key = derive_secrets::<D>(
         &extracted_ikm,
-        STR_SESSION_SECRET,
+        STR_SESSION_KEY,
         hashed_derivation_transcript,
     )?;
 
@@ -502,12 +452,6 @@ fn derive_3dh_keys<D: Hash, G: Group>(
         b"",
         <D as Digest>::OutputSize::to_usize(),
     )?;
-    let ke2 = hkdf_expand_label::<D>(
-        &handshake_secret,
-        STR_HANDSHAKE_ENC,
-        b"",
-        <D as Digest>::OutputSize::to_usize(),
-    )?;
     let km3 = hkdf_expand_label::<D>(
         &handshake_secret,
         STR_CLIENT_MAC,
@@ -518,7 +462,6 @@ fn derive_3dh_keys<D: Hash, G: Group>(
     Ok((
         GenericArray::clone_from_slice(&session_key),
         GenericArray::clone_from_slice(&km2),
-        GenericArray::clone_from_slice(&ke2),
         GenericArray::clone_from_slice(&km3),
     ))
 }
diff --git a/src/lib.rs b/src/lib.rs
index 0d162a1..3cacd08 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -19,7 +19,7 @@
 //!
 //! We will use the following choices in this example:
 //! ```
-//! use opaque_ke::ciphersuite::CipherSuite;
+//! use opaque_ke::CipherSuite;
 //! struct Default;
 //! impl CipherSuite for Default {
 //!     type Group = curve25519_dalek::ristretto::RistrettoPoint;
@@ -36,10 +36,11 @@
 //! which can be enabled through the `slow-hash` feature. See more details in the [features](#features) section.
 //!
 //! ## Setup
-//! To set up the protocol, the server begins by generating a static keypair:
+//! To set up the protocol, the server begins by creating a `ServerSetup` object:
 //! ```
 //! # use opaque_ke::errors::ProtocolError;
-//! # use opaque_ke::ciphersuite::CipherSuite;
+//! # use opaque_ke::CipherSuite;
+//! # use opaque_ke::ServerSetup;
 //! # struct Default;
 //! # impl CipherSuite for Default {
 //! #     type Group = curve25519_dalek::ristretto::RistrettoPoint;
@@ -49,17 +50,17 @@
 //! # }
 //! use rand::{rngs::OsRng, RngCore};
 //! let mut rng = OsRng;
-//! let server_kp = Default::generate_random_keypair(&mut rng);
+//! let server_setup = ServerSetup::<Default>::new(&mut rng);
 //! # Ok::<(), ProtocolError>(())
 //! ```
-//! The server must persist this keypair for the registration and login steps, where the public component will be
-//! used by the client during both registration and login, and the private component will be used by the server during login.
+//! The server must persist an instance of [ServerSetup] for the registration and login steps.
 //!
 //! ## Registration
 //! The registration protocol between the client and server consists of four steps along with three messages:
 //! [RegistrationRequest], [RegistrationResponse], and [RegistrationUpload]. A successful execution of the registration protocol results in the
-//! server producing a password file corresponding to the password provided by
-//! the client. This password file is typically stored server-side, and retrieved upon future login attempts made by the client.
+//! server producing a password file corresponding to a server-side identifier for the client, along with the password provided by
+//! the client. This password file is typically stored in a key-value database, where the keys consist of these server-side identifiers for each client,
+//! and the values consist of their corresponding password files, to be retrieved upon future login attempts made by the client.
 //!
 //! ### Client Registration Start
 //! In the first step of registration, the client chooses as input a registration password. The client runs [ClientRegistration::start]
@@ -71,7 +72,7 @@
 //! #   ServerRegistration,
 //! #   slow_hash::NoOpHash,
 //! # };
-//! # use opaque_ke::ciphersuite::CipherSuite;
+//! # use opaque_ke::CipherSuite;
 //! # struct Default;
 //! # impl CipherSuite for Default {
 //! #     type Group = curve25519_dalek::ristretto::RistrettoPoint;
@@ -90,18 +91,18 @@
 //! ```
 //!
 //! ### Server Registration Start
-//! In the second step of registration, the server takes as input the instance of [RegistrationRequest] from the client, and
-//! the server's public key `server_kp.public()`.
-//! The server runs [ServerRegistration::start] to produce an a [ServerRegistrationStartResult], which consists of
-//! a [RegistrationResponse] to be returned to the client and
-//! a [ServerRegistration] which must be persisted on the server for the final step of server registration.
+//! In the second step of registration, the server takes as input a persisted instance of [ServerSetup], a [RegistrationRequest] from the client, and
+//! a server-side identifier for the client.
+//! The server runs [ServerRegistration::start] to produce a [ServerRegistrationStartResult], which consists of
+//! a [RegistrationResponse] to be returned to the client.
 //! ```
 //! # use opaque_ke::{
 //! #   errors::ProtocolError,
 //! #   ClientRegistration,
+//! #   ServerSetup,
 //! #   slow_hash::NoOpHash,
 //! # };
-//! # use opaque_ke::ciphersuite::CipherSuite;
+//! # use opaque_ke::CipherSuite;
 //! # struct Default;
 //! # impl CipherSuite for Default {
 //! #     type Group = curve25519_dalek::ristretto::RistrettoPoint;
@@ -116,12 +117,12 @@
 //! #     b"password",
 //! # )?;
 //! use opaque_ke::ServerRegistration;
-//! let mut server_rng = OsRng;
-//! let server_kp = Default::generate_random_keypair(&mut server_rng);
+//! # let mut server_rng = OsRng;
+//! # let server_setup = ServerSetup::<Default>::new(&mut server_rng);
 //! let server_registration_start_result = ServerRegistration::<Default>::start(
-//!     &mut server_rng,
+//!     &server_setup,
 //!     client_registration_start_result.message,
-//!     server_kp.public(),
+//!     b"alice@example.com",
 //! )?;
 //! # Ok::<(), ProtocolError>(())
 //! ```
@@ -135,10 +136,10 @@
 //! ```
 //! # use opaque_ke::{
 //! #   errors::ProtocolError,
-//! #   ClientRegistration, ClientRegistrationFinishParameters, ServerRegistration,
+//! #   ClientRegistration, ClientRegistrationFinishParameters, ServerRegistration, ServerSetup,
 //! #   slow_hash::NoOpHash,
 //! # };
-//! # use opaque_ke::ciphersuite::CipherSuite;
+//! # use opaque_ke::CipherSuite;
 //! # struct Default;
 //! # impl CipherSuite for Default {
 //! #     type Group = curve25519_dalek::ristretto::RistrettoPoint;
@@ -153,8 +154,8 @@
 //! #     b"password",
 //! # )?;
 //! # let mut server_rng = OsRng;
-//! # let server_kp = Default::generate_random_keypair(&mut server_rng);
-//! # let server_registration_start_result = ServerRegistration::<Default>::start(&mut server_rng, client_registration_start_result.message, server_kp.public())?;
+//! # let server_setup = ServerSetup::<Default>::new(&mut server_rng);
+//! # let server_registration_start_result = ServerRegistration::<Default>::start(&server_setup, client_registration_start_result.message, b"alice@example.com")?;
 //! let client_registration_finish_result = client_registration_start_result.state.finish(
 //!     &mut client_rng,
 //!     server_registration_start_result.message,
@@ -173,10 +174,10 @@
 //! ```
 //! # use opaque_ke::{
 //! #   errors::ProtocolError,
-//! #   ClientRegistration, ClientRegistrationFinishParameters, ServerRegistration,
+//! #   ClientRegistration, ClientRegistrationFinishParameters, ServerRegistration, ServerSetup,
 //! #   slow_hash::NoOpHash,
 //! # };
-//! # use opaque_ke::ciphersuite::CipherSuite;
+//! # use opaque_ke::CipherSuite;
 //! # struct Default;
 //! # impl CipherSuite for Default {
 //! #     type Group = curve25519_dalek::ristretto::RistrettoPoint;
@@ -191,19 +192,20 @@
 //! #     b"password",
 //! # )?;
 //! # let mut server_rng = OsRng;
-//! # let server_kp = Default::generate_random_keypair(&mut server_rng);
-//! # let server_registration_start_result = ServerRegistration::<Default>::start(&mut server_rng, client_registration_start_result.message, server_kp.public())?;
+//! # let server_setup = ServerSetup::<Default>::new(&mut server_rng);
+//! # let server_registration_start_result = ServerRegistration::<Default>::start(&server_setup, client_registration_start_result.message, b"alice@example.com")?;
 //! # let client_registration_finish_result = client_registration_start_result.state.finish(&mut client_rng, server_registration_start_result.message, ClientRegistrationFinishParameters::default())?;
-//! let password_file = server_registration_start_result.state.finish(
+//! let password_file = ServerRegistration::<Default>::finish(
 //!     client_registration_finish_result.message,
-//! )?;
+//! );
 //! # Ok::<(), ProtocolError>(())
 //! ```
 //!
 //! ## Login
 //! The login protocol between a client and server also consists of four steps along with three messages:
 //! [CredentialRequest], [CredentialResponse], [CredentialFinalization]. The server is expected to have access to the password file
-//! corresponding to an output of the registration phase. The login protocol will execute successfully only if the same password
+//! corresponding to an output of the registration phase (see [Dummy Server Login](#dummy-server-login) for handling the scenario where
+//! no password file is available). The login protocol will execute successfully only if the same password
 //! was used in the registration phase that produced the password file that the server is testing against.
 //!
 //! ### Client Login Start
@@ -217,7 +219,7 @@
 //! #   ClientRegistration, ServerRegistration, ServerLogin, CredentialFinalization,
 //! #   slow_hash::NoOpHash,
 //! # };
-//! # use opaque_ke::ciphersuite::CipherSuite;
+//! # use opaque_ke::CipherSuite;
 //! # struct Default;
 //! # impl CipherSuite for Default {
 //! #     type Group = curve25519_dalek::ristretto::RistrettoPoint;
@@ -226,31 +228,31 @@
 //! #     type SlowHash = opaque_ke::slow_hash::NoOpHash;
 //! # }
 //! # use rand::{rngs::OsRng, RngCore};
-//! use opaque_ke::{ClientLogin, ClientLoginStartParameters};
+//! use opaque_ke::ClientLogin;
 //! let mut client_rng = OsRng;
 //! let client_login_start_result = ClientLogin::<Default>::start(
 //!   &mut client_rng,
 //!   b"password",
-//!   ClientLoginStartParameters::default(),
 //! )?;
 //! # Ok::<(), ProtocolError>(())
 //! ```
 //!
 //! ### Server Login Start
 //! In the second step of login, the server takes as input
-//! a [CredentialRequest] from the client,
-//! the server's private key `server_kp.private()`, and
-//! the password file output from registration.
+//! a persisted instance of [ServerSetup],
+//! the password file output from registration,
+//! a [CredentialRequest] from the client, and
+//! a server-side identifier for the client.
 //! The server runs [ServerLogin::start] to produce an output consisting of
 //! a [CredentialResponse] which is returned to the client, and
 //! a [ServerLogin] which must be persisted on the server for the final step of login.
 //! ```
 //! # use opaque_ke::{
 //! #   errors::ProtocolError,
-//! #   ClientRegistration, ClientRegistrationFinishParameters, ServerRegistration, ClientLogin, ClientLoginStartParameters, CredentialFinalization,
+//! #   ClientRegistration, ClientRegistrationFinishParameters, ServerRegistration, ClientLogin, CredentialFinalization, ServerSetup,
 //! #   slow_hash::NoOpHash,
 //! # };
-//! # use opaque_ke::ciphersuite::CipherSuite;
+//! # use opaque_ke::CipherSuite;
 //! # struct Default;
 //! # impl CipherSuite for Default {
 //! #     type Group = curve25519_dalek::ristretto::RistrettoPoint;
@@ -265,27 +267,32 @@
 //! #     b"password",
 //! # )?;
 //! # let mut server_rng = OsRng;
-//! # let server_kp = Default::generate_random_keypair(&mut server_rng);
-//! # let server_registration_start_result = ServerRegistration::<Default>::start(&mut server_rng, client_registration_start_result.message, server_kp.public())?;
+//! # let server_setup = ServerSetup::<Default>::new(&mut server_rng);
+//! # let server_registration_start_result = ServerRegistration::<Default>::start(&server_setup, client_registration_start_result.message, b"alice@example.com")?;
 //! # let client_registration_finish_result = client_registration_start_result.state.finish(&mut client_rng, server_registration_start_result.message, ClientRegistrationFinishParameters::default())?;
-//! # let password_file_bytes = server_registration_start_result.state.finish(client_registration_finish_result.message)?.serialize();
+//! # let password_file_bytes = ServerRegistration::<Default>::finish(client_registration_finish_result.message).serialize();
 //! # let client_login_start_result = ClientLogin::<Default>::start(
 //! #   &mut client_rng,
 //! #   b"password",
-//! #   ClientLoginStartParameters::default(),
 //! # )?;
 //! use opaque_ke::{ServerLogin, ServerLoginStartParameters};
 //! let password_file = ServerRegistration::<Default>::deserialize(&password_file_bytes[..])?;
 //! let mut server_rng = OsRng;
 //! let server_login_start_result = ServerLogin::start(
 //!     &mut server_rng,
-//!     password_file,
-//!     &server_kp.private(),
+//!     &server_setup,
+//!     Some(password_file),
 //!     client_login_start_result.message,
+//!     b"alice@example.com",
 //!     ServerLoginStartParameters::default(),
 //! )?;
 //! # Ok::<(), ProtocolError>(())
 //! ```
+//! Note that if there is no corresponding password file found for the user,
+//! the server can use `None` in place of `Some(password_file)` in order to generate
+//! a [CredentialResponse] that is indistinguishable from a valid [CredentialResponse]
+//! returned for a registered client. This allows the server to prevent leaking information
+//! about whether or not a client has previously registered with the server.
 //!
 //! ### Client Login Finish
 //! In the third step of login, the client takes as input a [CredentialResponse] from the server.
@@ -295,10 +302,10 @@
 //! ```
 //! # use opaque_ke::{
 //! #   errors::ProtocolError,
-//! #   ClientRegistration, ClientRegistrationFinishParameters, ServerRegistration, ClientLogin, ClientLoginStartParameters, ClientLoginFinishParameters, ServerLogin, ServerLoginStartParameters, CredentialFinalization,
+//! #   ClientRegistration, ClientRegistrationFinishParameters, ServerRegistration, ClientLogin, ClientLoginFinishParameters, ServerLogin, ServerLoginStartParameters, CredentialFinalization, ServerSetup,
 //! #   slow_hash::NoOpHash,
 //! # };
-//! # use opaque_ke::ciphersuite::CipherSuite;
+//! # use opaque_ke::CipherSuite;
 //! # struct Default;
 //! # impl CipherSuite for Default {
 //! #     type Group = curve25519_dalek::ristretto::RistrettoPoint;
@@ -313,21 +320,20 @@
 //! #     b"password",
 //! # )?;
 //! # let mut server_rng = OsRng;
-//! # let server_kp = Default::generate_random_keypair(&mut server_rng);
-//! # let server_registration_start_result = ServerRegistration::<Default>::start(&mut server_rng, client_registration_start_result.message, server_kp.public())?;
+//! # let server_setup = ServerSetup::<Default>::new(&mut server_rng);
+//! # let server_registration_start_result = ServerRegistration::<Default>::start(&server_setup, client_registration_start_result.message, b"alice@example.com")?;
 //! # let client_registration_finish_result = client_registration_start_result.state.finish(&mut client_rng, server_registration_start_result.message, ClientRegistrationFinishParameters::default())?;
-//! # let password_file_bytes = server_registration_start_result.state.finish(client_registration_finish_result.message)?.serialize();
+//! # let password_file_bytes = ServerRegistration::<Default>::finish(client_registration_finish_result.message).serialize();
 //! # let client_login_start_result = ClientLogin::<Default>::start(
 //! #     &mut client_rng,
 //! #     b"password",
-//! #     ClientLoginStartParameters::default(),
 //! # )?;
 //! # let password_file =
 //! #   ServerRegistration::<Default>::deserialize(
 //! #     &password_file_bytes[..],
 //! #   )?;
 //! # let server_login_start_result =
-//! #     ServerLogin::start(&mut server_rng, password_file, &server_kp.private(), client_login_start_result.message, ServerLoginStartParameters::default())?;
+//! #     ServerLogin::start(&mut server_rng, &server_setup, Some(password_file), client_login_start_result.message, b"alice@example.com", ServerLoginStartParameters::default())?;
 //! let client_login_finish_result = client_login_start_result.state.finish(
 //!     server_login_start_result.message,
 //!     ClientLoginFinishParameters::default(),
@@ -341,10 +347,10 @@
 //! ```
 //! # use opaque_ke::{
 //! #   errors::ProtocolError,
-//! #   ClientRegistration, ClientRegistrationFinishParameters, ServerRegistration, ClientLogin, ClientLoginStartParameters, ClientLoginFinishParameters, ServerLogin, ServerLoginStartParameters, CredentialFinalization,
+//! #   ClientRegistration, ClientRegistrationFinishParameters, ServerRegistration, ClientLogin, ClientLoginFinishParameters, ServerLogin, ServerLoginStartParameters, CredentialFinalization, ServerSetup,
 //! #   slow_hash::NoOpHash,
 //! # };
-//! # use opaque_ke::ciphersuite::CipherSuite;
+//! # use opaque_ke::CipherSuite;
 //! # struct Default;
 //! # impl CipherSuite for Default {
 //! #     type Group = curve25519_dalek::ristretto::RistrettoPoint;
@@ -359,21 +365,20 @@
 //! #     b"password",
 //! # )?;
 //! # let mut server_rng = OsRng;
-//! # let server_kp = Default::generate_random_keypair(&mut server_rng);
-//! # let server_registration_start_result = ServerRegistration::<Default>::start(&mut server_rng, client_registration_start_result.message, server_kp.public())?;
+//! # let server_setup = ServerSetup::<Default>::new(&mut server_rng);
+//! # let server_registration_start_result = ServerRegistration::<Default>::start(&server_setup, client_registration_start_result.message, b"alice@example.com")?;
 //! # let client_registration_finish_result = client_registration_start_result.state.finish(&mut client_rng, server_registration_start_result.message, ClientRegistrationFinishParameters::default())?;
-//! # let password_file_bytes = server_registration_start_result.state.finish(client_registration_finish_result.message)?.serialize();
+//! # let password_file_bytes = ServerRegistration::<Default>::finish(client_registration_finish_result.message).serialize();
 //! # let client_login_start_result = ClientLogin::<Default>::start(
 //! #   &mut client_rng,
 //! #   b"password",
-//! #   ClientLoginStartParameters::default(),
 //! # )?;
 //! # let password_file =
 //! #   ServerRegistration::<Default>::deserialize(
 //! #     &password_file_bytes[..],
 //! #   )?;
 //! # let server_login_start_result =
-//! #     ServerLogin::start(&mut server_rng, password_file, &server_kp.private(), client_login_start_result.message, ServerLoginStartParameters::default())?;
+//! #     ServerLogin::start(&mut server_rng, &server_setup, Some(password_file), client_login_start_result.message, b"alice@example.com", ServerLoginStartParameters::default())?;
 //! # let client_login_finish_result = client_login_start_result.state.finish(
 //! #   server_login_start_result.message,
 //! #   ClientLoginFinishParameters::default(),
@@ -416,10 +421,10 @@
 //! ```
 //! # use opaque_ke::{
 //! #   errors::ProtocolError,
-//! #   ClientRegistration, ClientRegistrationFinishParameters, ServerRegistration, ClientLogin, ClientLoginStartParameters, ClientLoginFinishParameters, ServerLogin, ServerLoginStartParameters, CredentialFinalization,
+//! #   ClientRegistration, ClientRegistrationFinishParameters, ServerRegistration, ClientLogin, ClientLoginFinishParameters, ServerLogin, ServerLoginStartParameters, CredentialFinalization, ServerSetup,
 //! #   slow_hash::NoOpHash,
 //! # };
-//! # use opaque_ke::ciphersuite::CipherSuite;
+//! # use opaque_ke::CipherSuite;
 //! # struct Default;
 //! # impl CipherSuite for Default {
 //! #     type Group = curve25519_dalek::ristretto::RistrettoPoint;
@@ -435,24 +440,23 @@
 //! # )?;
 //! # let mut server_rng = OsRng;
 //! // During setup, server generates its static keypair
-//! let server_kp = Default::generate_random_keypair(&mut server_rng);
-//! # let server_registration_start_result = ServerRegistration::<Default>::start(&mut server_rng, client_registration_start_result.message, server_kp.public())?;
+//! let server_setup = ServerSetup::<Default>::new(&mut server_rng);
+//! # let server_registration_start_result = ServerRegistration::<Default>::start(&server_setup, client_registration_start_result.message, b"alice@example.com")?;
 //!
 //! // During setup or registration, the server transmits its static public key to the client
-//! let server_s_pk = server_kp.public(); // obtained from the server
+//! let server_s_pk = server_setup.keypair().public(); // obtained from the server
 //! # let client_registration_finish_result = client_registration_start_result.state.finish(&mut client_rng, server_registration_start_result.message, ClientRegistrationFinishParameters::default())?;
-//! # let password_file_bytes = server_registration_start_result.state.finish(client_registration_finish_result.message)?.serialize();
+//! # let password_file_bytes = ServerRegistration::<Default>::finish(client_registration_finish_result.message).serialize();
 //! # let client_login_start_result = ClientLogin::<Default>::start(
 //! #     &mut client_rng,
 //! #     b"password",
-//! #     ClientLoginStartParameters::default(),
 //! # )?;
 //! # let password_file =
 //! #   ServerRegistration::<Default>::deserialize(
 //! #     &password_file_bytes[..],
 //! #   )?;
 //! # let server_login_start_result =
-//! #     ServerLogin::start(&mut server_rng, password_file, &server_kp.private(), client_login_start_result.message, ServerLoginStartParameters::default())?;
+//! #     ServerLogin::start(&mut server_rng, &server_setup, Some(password_file), client_login_start_result.message, b"alice@example.com", ServerLoginStartParameters::default())?;
 //!
 //! // And then later, during login...
 //! let client_login_finish_result = client_login_start_result.state.finish(
@@ -492,10 +496,10 @@
 //! ```
 //! # use opaque_ke::{
 //! #   errors::ProtocolError,
-//! #   ClientRegistration, ClientRegistrationFinishParameters, ServerRegistration, ClientLogin, ClientLoginStartParameters, ClientLoginFinishParameters, ServerLogin, ServerLoginStartParameters, CredentialFinalization,
+//! #   ClientRegistration, ClientRegistrationFinishParameters, ServerRegistration, ClientLogin, ClientLoginFinishParameters, ServerLogin, ServerLoginStartParameters, CredentialFinalization, ServerSetup,
 //! #   slow_hash::NoOpHash,
 //! # };
-//! # use opaque_ke::ciphersuite::CipherSuite;
+//! # use opaque_ke::CipherSuite;
 //! # struct Default;
 //! # impl CipherSuite for Default {
 //! #     type Group = curve25519_dalek::ristretto::RistrettoPoint;
@@ -510,26 +514,25 @@
 //! #     b"password",
 //! # )?;
 //! # let mut server_rng = OsRng;
-//! # let server_kp = Default::generate_random_keypair(&mut server_rng);
-//! # let server_registration_start_result = ServerRegistration::<Default>::start(&mut server_rng, client_registration_start_result.message, server_kp.public())?;
+//! # let server_setup = ServerSetup::<Default>::new(&mut server_rng);
+//! # let server_registration_start_result = ServerRegistration::<Default>::start(&server_setup, client_registration_start_result.message, b"alice@example.com")?;
 //! // During registration...
 //! let client_registration_finish_result = client_registration_start_result.state.finish(
 //!     &mut client_rng,
 //!     server_registration_start_result.message,
 //!     ClientRegistrationFinishParameters::default()
 //! )?;
-//! # let password_file_bytes = server_registration_start_result.state.finish(client_registration_finish_result.message)?.serialize();
+//! # let password_file_bytes = ServerRegistration::<Default>::finish(client_registration_finish_result.message).serialize();
 //! # let client_login_start_result = ClientLogin::<Default>::start(
 //! #     &mut client_rng,
 //! #     b"password",
-//! #     ClientLoginStartParameters::default(),
 //! # )?;
 //! # let password_file =
 //! #   ServerRegistration::<Default>::deserialize(
 //! #     &password_file_bytes[..],
 //! #   )?;
 //! # let server_login_start_result =
-//! #     ServerLogin::start(&mut server_rng, password_file, &server_kp.private(), client_login_start_result.message, ServerLoginStartParameters::default())?;
+//! #     ServerLogin::start(&mut server_rng, &server_setup, Some(password_file), client_login_start_result.message, b"alice@example.com", ServerLoginStartParameters::default())?;
 //!
 //! // And then later, during login...
 //! let client_login_finish_result = client_login_start_result.state.finish(
@@ -546,8 +549,10 @@
 //!
 //! ## Custom Identifiers
 //!
-//! Typically when applications use OPAQUE to authenticate a client to a server, the client has a registered "username" which is sent to the server to
-//! identify the corresponding password file established during registration. The server may also have an identifier corresponding to an entity (e.g. facebook.com).
+//! Typically when applications use OPAQUE to authenticate a client to a server, the client has a registered username which is sent to the server to
+//! identify the corresponding password file established during registration. This username may or may not coincide with the server-side identifier;
+//! however, this username must be known to both the client and the server (whereas the server-side identifier does not need to be exposed to the client).
+//! The server may also have an identifier corresponding to an entity (e.g. Facebook).
 //! By default, neither of these public identifiers need to be supplied to the OPAQUE protocol.
 //!
 //! But, for applications that wish to cryptographically bind these identities to
@@ -556,10 +561,10 @@
 //! ```
 //! # use opaque_ke::{
 //! #   errors::ProtocolError,
-//! #   ClientRegistration, ClientRegistrationFinishParameters, ServerRegistration,
+//! #   ClientRegistration, ClientRegistrationFinishParameters, Identifiers, ServerRegistration, ServerSetup,
 //! #   slow_hash::NoOpHash,
 //! # };
-//! # use opaque_ke::ciphersuite::CipherSuite;
+//! # use opaque_ke::CipherSuite;
 //! # struct Default;
 //! # impl CipherSuite for Default {
 //! #     type Group = curve25519_dalek::ristretto::RistrettoPoint;
@@ -574,14 +579,16 @@
 //! #     b"password",
 //! # )?;
 //! # let mut server_rng = OsRng;
-//! # let server_kp = Default::generate_random_keypair(&mut server_rng);
-//! # let server_registration_start_result = ServerRegistration::<Default>::start(&mut server_rng, client_registration_start_result.message, server_kp.public())?;
+//! # let server_setup = ServerSetup::<Default>::new(&mut server_rng);
+//! # let server_registration_start_result = ServerRegistration::<Default>::start(&server_setup, client_registration_start_result.message, b"alice@example.com")?;
 //! let client_registration_finish_result = client_registration_start_result.state.finish(
 //!     &mut client_rng,
 //!     server_registration_start_result.message,
 //!     ClientRegistrationFinishParameters::WithIdentifiers(
-//!         b"username".to_vec(),
-//!         b"facebook.com".to_vec(),
+//!         Identifiers::ClientAndServerIdentifiers(
+//!             b"Alice_the_Cryptographer".to_vec(),
+//!             b"Facebook".to_vec(),
+//!         ),
 //!     ),
 //! )?;
 //! # Ok::<(), ProtocolError>(())
@@ -591,10 +598,10 @@
 //! ```
 //! # use opaque_ke::{
 //! #   errors::ProtocolError,
-//! #   ClientRegistration, ClientRegistrationFinishParameters, ServerRegistration, ClientLogin, ClientLoginStartParameters, CredentialFinalization,
+//! #   ClientRegistration, ClientRegistrationFinishParameters, ServerRegistration, ClientLogin, CredentialFinalization, Identifiers, ServerSetup,
 //! #   slow_hash::NoOpHash,
 //! # };
-//! # use opaque_ke::ciphersuite::CipherSuite;
+//! # use opaque_ke::CipherSuite;
 //! # struct Default;
 //! # impl CipherSuite for Default {
 //! #     type Group = curve25519_dalek::ristretto::RistrettoPoint;
@@ -609,26 +616,28 @@
 //! #     b"password",
 //! # )?;
 //! # let mut server_rng = OsRng;
-//! # let server_kp = Default::generate_random_keypair(&mut server_rng);
-//! # let server_registration_start_result = ServerRegistration::<Default>::start(&mut server_rng, client_registration_start_result.message, server_kp.public())?;
-//! # let client_registration_finish_result = client_registration_start_result.state.finish(&mut client_rng, server_registration_start_result.message, ClientRegistrationFinishParameters::WithIdentifiers(b"username".to_vec(), b"facebook.com".to_vec()))?;
-//! # let password_file_bytes = server_registration_start_result.state.finish(client_registration_finish_result.message)?.serialize();
+//! # let server_setup = ServerSetup::<Default>::new(&mut server_rng);
+//! # let server_registration_start_result = ServerRegistration::<Default>::start(&server_setup, client_registration_start_result.message, b"alice@example.com")?;
+//! # let client_registration_finish_result = client_registration_start_result.state.finish(&mut client_rng, server_registration_start_result.message, ClientRegistrationFinishParameters::WithIdentifiers(Identifiers::ClientAndServerIdentifiers(b"Alice_the_Cryptographer".to_vec(), b"Facebook".to_vec())))?;
+//! # let password_file_bytes = ServerRegistration::<Default>::finish(client_registration_finish_result.message).serialize();
 //! # let client_login_start_result = ClientLogin::<Default>::start(
 //! #   &mut client_rng,
 //! #   b"password",
-//! #   ClientLoginStartParameters::default(),
 //! # )?;
 //! # use opaque_ke::{ServerLogin, ServerLoginStartParameters};
 //! # let password_file = ServerRegistration::<Default>::deserialize(&password_file_bytes[..])?;
 //! # let mut server_rng = OsRng;
 //! let server_login_start_result = ServerLogin::start(
 //!     &mut server_rng,
-//!     password_file,
-//!     &server_kp.private(),
+//!     &server_setup,
+//!     Some(password_file),
 //!     client_login_start_result.message,
+//!     b"alice@example.com",
 //!     ServerLoginStartParameters::WithIdentifiers(
-//!         b"username".to_vec(),
-//!         b"facebook.com".to_vec(),
+//!         Identifiers::ClientAndServerIdentifiers(
+//!             b"Alice_the_Cryptographer".to_vec(),
+//!             b"Facebook".to_vec(),
+//!         ),
 //!     ),
 //! )?;
 //! # Ok::<(), ProtocolError>(())
@@ -638,10 +647,10 @@
 //! ```
 //! # use opaque_ke::{
 //! #   errors::ProtocolError,
-//! #   ClientRegistration, ClientRegistrationFinishParameters, ServerRegistration, ClientLogin, ClientLoginStartParameters, ClientLoginFinishParameters, ServerLogin, ServerLoginStartParameters, CredentialFinalization,
+//! #   ClientRegistration, ClientRegistrationFinishParameters, ServerRegistration, ClientLogin, ClientLoginFinishParameters, Identifiers, ServerLogin, ServerLoginStartParameters, CredentialFinalization, ServerSetup,
 //! #   slow_hash::NoOpHash,
 //! # };
-//! # use opaque_ke::ciphersuite::CipherSuite;
+//! # use opaque_ke::CipherSuite;
 //! # struct Default;
 //! # impl CipherSuite for Default {
 //! #     type Group = curve25519_dalek::ristretto::RistrettoPoint;
@@ -656,47 +665,56 @@
 //! #     b"password",
 //! # )?;
 //! # let mut server_rng = OsRng;
-//! # let server_kp = Default::generate_random_keypair(&mut server_rng);
-//! # let server_registration_start_result = ServerRegistration::<Default>::start(&mut server_rng, client_registration_start_result.message, server_kp.public())?;
-//! # let client_registration_finish_result = client_registration_start_result.state.finish(&mut client_rng, server_registration_start_result.message, ClientRegistrationFinishParameters::WithIdentifiers(b"username".to_vec(), b"facebook.com".to_vec()))?;
-//! # let password_file_bytes = server_registration_start_result.state.finish(client_registration_finish_result.message)?.serialize();
+//! # let server_setup = ServerSetup::<Default>::new(&mut server_rng);
+//! # let server_registration_start_result = ServerRegistration::<Default>::start(&server_setup, client_registration_start_result.message, b"alice@example.com")?;
+//! # let client_registration_finish_result = client_registration_start_result.state.finish(&mut client_rng, server_registration_start_result.message, ClientRegistrationFinishParameters::WithIdentifiers(Identifiers::ClientAndServerIdentifiers(b"Alice_the_Cryptographer".to_vec(), b"Facebook".to_vec())))?;
+//! # let password_file_bytes = ServerRegistration::<Default>::finish(client_registration_finish_result.message).serialize();
 //! # let client_login_start_result = ClientLogin::<Default>::start(
 //! #     &mut client_rng,
 //! #     b"password",
-//! #     ClientLoginStartParameters::default(),
 //! # )?;
 //! # let password_file =
 //! #   ServerRegistration::<Default>::deserialize(
 //! #     &password_file_bytes[..],
 //! #   )?;
 //! # let server_login_start_result =
-//! #     ServerLogin::start(&mut server_rng, password_file, &server_kp.private(), client_login_start_result.message, ServerLoginStartParameters::WithIdentifiers(b"username".to_vec(), b"facebook.com".to_vec()))?;
+//! #     ServerLogin::start(&mut server_rng, &server_setup, Some(password_file), client_login_start_result.message, b"alice@example.com", ServerLoginStartParameters::WithIdentifiers(Identifiers::ClientAndServerIdentifiers(b"Alice_the_Cryptographer".to_vec(), b"Facebook".to_vec())))?;
 //! let client_login_finish_result = client_login_start_result.state.finish(
 //!     server_login_start_result.message,
 //!     ClientLoginFinishParameters::WithIdentifiers(
-//!         b"username".to_vec(),
-//!         b"facebook.com".to_vec(),
+//!         Identifiers::ClientAndServerIdentifiers(
+//!             b"Alice_the_Cryptographer".to_vec(),
+//!             b"Facebook".to_vec(),
+//!         ),
 //!     ),
 //! )?;
+//!
 //! # Ok::<(), ProtocolError>(())
 //! ```
 //! Failing to supply the same pair of custom identifiers in any of the three steps above will result in an error in attempting to complete
 //! the protocol!
 //!
-//! ## Key Exchange Additional Data
+//! Note that if only one of the client and server identifiers are present, then [Identifiers::ClientIdentifier] and [Identifiers::ServerIdentifier] can be
+//! used to specify them individually.
 //!
-//! A key exchange protocol typically supports the passing of data between the two parties before the exchange is complete, so as to bind the integrity
-//! and/or confidentiality of application-specific data to the security of the key exchange. During the login phase, the client and server can pass
-//! additional data alongside the first two messages of the protocol, with confidential data being supported for the second message.
+//! ## Key Exchange Context
 //!
-//! The following three messages support passing of additional data:
-//! - The first login message, where the client can populate [ClientLoginStartParameters::WithInfo] with plaintext additional data, and
-//! the server can retrieve using the `plain_info` field of [ServerLoginStartResult].
-//! - The second login message, where the server can populate [ServerLoginStartParameters::WithInfo] with confidential additional data,
-//! and the client can retrieve using the `confidential_info` field of [ClientLoginFinishResult].
+//! A key exchange protocol typically allows for the specifying of shared "context" information between the two parties before the exchange is complete,
+//! so as to bind the integrity of application-specific data or configuration parameters to the security of the key exchange.
+//! During the login phase, the client and server can specify this context using:
+//! - The second login message, where the server can populate [ServerLoginStartParameters::WithContext], and
+//! - The third login message, where the client can populate [ClientLoginFinishParameters::WithContext].
 //!
-//! For the second login message, the `WithInfoAndIdentifiers` variant can be used to specify these fields in addition to
-//! [custom identifiers](#custom-identifiers), with the ordering of the fields as `WithInfoAndIdentifiers(confidential_info, username, server_name)`.
+//! For both of these messages, the `WithContextAndIdentifiers` variant can be used to specify these fields in addition to
+//! [custom identifiers](#custom-identifiers), with the ordering of the fields as
+//! `WithContextAndIdentifiers(context, Identifiers::ClientAndServerIdentifiers(username, server_name))`.
+//!
+//! ## Dummy Server Login
+//!
+//! For applications in which the server does not wish to reveal to the client whether an existing password file has been
+//! registered, the server can return a "dummy" credential response message to the client for an unregistered client,
+//! which is indistinguishable from the normal credential response message that the server would return for a registered client.
+//! The dummy message is created by passing a `None` to the password_file parameter for [ServerLogin::start].
 //!
 //! # Features
 //!
@@ -762,17 +780,20 @@ mod tests;
 
 pub use rand;
 
+pub use ciphersuite::CipherSuite;
+
 pub use crate::messages::{
     CredentialFinalization, CredentialRequest, CredentialResponse, RegistrationRequest,
     RegistrationResponse, RegistrationUpload,
 };
-pub use crate::opaque::{ClientLogin, ClientRegistration, ServerLogin, ServerRegistration};
 pub use crate::opaque::{
-    ClientLoginFinishParameters, ClientLoginStartParameters, ClientRegistrationFinishParameters,
-    ServerLoginStartParameters,
+    ClientLogin, ClientRegistration, ServerLogin, ServerRegistration, ServerSetup,
+};
+pub use crate::opaque::{
+    ClientLoginFinishParameters, ClientRegistrationFinishParameters, ServerLoginStartParameters,
 };
 pub use crate::opaque::{
     ClientLoginFinishResult, ClientLoginStartResult, ClientRegistrationFinishResult,
-    ClientRegistrationStartResult, ServerLoginFinishResult, ServerLoginStartResult,
+    ClientRegistrationStartResult, Identifiers, ServerLoginFinishResult, ServerLoginStartResult,
     ServerRegistrationStartResult,
 };
diff --git a/src/map_to_curve.rs b/src/map_to_curve.rs
index 3875b88..24b0525 100644
--- a/src/map_to_curve.rs
+++ b/src/map_to_curve.rs
@@ -24,6 +24,10 @@ pub trait GroupWithMapToCurve: Group {
     /// transforms a password and domain separation tag (DST) into a curve point
     fn map_to_curve<H: Hash>(msg: &[u8], dst: &[u8]) -> Result<Self, InternalPakeError>;
 
+    /// Hashes a slice of pseudo-random bytes to a scalar
+    fn hash_to_scalar<H: Hash>(input: &[u8], dst: &[u8])
+        -> Result<Self::Scalar, InternalPakeError>;
+
     /// Generates the contextString parameter as defined in
     /// <https://www.ietf.org/archive/id/draft-irtf-cfrg-voprf-05.txt>
     fn get_context_string(mode: u8) -> Vec<u8> {
@@ -43,6 +47,18 @@ impl GroupWithMapToCurve for RistrettoPoint {
             &GenericArray::clone_from_slice(&uniform_bytes[..]),
         ))
     }
+
+    fn hash_to_scalar<H: Hash>(
+        input: &[u8],
+        dst: &[u8],
+    ) -> Result<Self::Scalar, InternalPakeError> {
+        const LEN_IN_BYTES: usize = 64;
+        let uniform_bytes = expand_message_xmd::<H>(input, dst, LEN_IN_BYTES)?;
+        let mut bits = [0u8; LEN_IN_BYTES];
+        bits.copy_from_slice(&uniform_bytes[..]);
+
+        Ok(Self::Scalar::from_bytes_mod_order_wide(&bits))
+    }
 }
 
 // Computes ceil(x / y)
diff --git a/src/messages.rs b/src/messages.rs
index 32c4956..2cbdb7d 100644
--- a/src/messages.rs
+++ b/src/messages.rs
@@ -15,9 +15,12 @@ use crate::{
     group::Group,
     key_exchange::traits::{FromBytes, KeyExchange, ToBytes},
     keypair::{KeyPair, PublicKey, SizedBytesExt},
+    opaque::ServerSetup,
 };
+use digest::Digest;
 use generic_array::{typenum::Unsigned, GenericArray};
 use generic_bytes::SizedBytes;
+use rand::{CryptoRng, RngCore};
 
 // Messages
 // =========
@@ -126,7 +129,9 @@ impl_serialize_and_deserialize_for!(RegistrationResponse);
 pub struct RegistrationUpload<CS: CipherSuite> {
     /// The "envelope" generated by the user, containing sealed
     /// cryptographic identifiers
-    pub(crate) envelope: Envelope<CS::Hash>,
+    pub(crate) envelope: Envelope<CS>,
+    /// The masking key used to mask the envelope
+    pub(crate) masking_key: GenericArray<u8, <CS::Hash as Digest>::OutputSize>,
     /// The user's public key
     pub(crate) client_s_pk: PublicKey,
 }
@@ -136,6 +141,7 @@ impl<CS: CipherSuite> Clone for RegistrationUpload<CS> {
     fn clone(&self) -> Self {
         Self {
             envelope: self.envelope.clone(),
+            masking_key: self.masking_key.clone(),
             client_s_pk: self.client_s_pk.clone(),
         }
     }
@@ -146,6 +152,7 @@ impl<CS: CipherSuite> RegistrationUpload<CS> {
     pub fn serialize(&self) -> Vec<u8> {
         [
             self.client_s_pk.to_arr().to_vec(),
+            self.masking_key.to_vec(),
             self.envelope.serialize(),
         ]
         .concat()
@@ -154,22 +161,35 @@ impl<CS: CipherSuite> RegistrationUpload<CS> {
     /// Deserialization from bytes
     pub fn deserialize(input: &[u8]) -> Result<Self, ProtocolError> {
         let key_len = <PublicKey as SizedBytes>::Len::to_usize();
-
-        let checked_slice = check_slice_size_atleast(input, key_len, "registration_upload_bytes")?;
-
-        let (envelope, remainder) = Envelope::<CS::Hash>::deserialize(&checked_slice[key_len..])?;
-
-        if !remainder.is_empty() {
-            return Err(PakeError::SerializationError.into());
-        }
-
+        let hash_len = <CS::Hash as Digest>::OutputSize::to_usize();
+        let checked_slice =
+            check_slice_size_atleast(input, key_len + hash_len, "registration_upload_bytes")?;
+        let envelope = Envelope::<CS>::deserialize(&checked_slice[key_len + hash_len..])?;
         Ok(Self {
             envelope,
+            masking_key: GenericArray::clone_from_slice(
+                &checked_slice[key_len..key_len + hash_len],
+            ),
             client_s_pk: KeyPair::<CS::Group>::check_public_key(PublicKey::from_bytes(
                 &checked_slice[..key_len],
             )?)?,
         })
     }
+
+    // Creates a dummy instance used for faking a [CredentialResponse]
+    pub(crate) fn dummy<R: RngCore + CryptoRng>(
+        rng: &mut R,
+        server_setup: &ServerSetup<CS>,
+    ) -> Self {
+        let mut masking_key = vec![0u8; <CS::Hash as Digest>::OutputSize::to_usize()];
+        rng.fill_bytes(&mut masking_key);
+
+        Self {
+            envelope: Envelope::<CS>::dummy(),
+            masking_key: GenericArray::clone_from_slice(&masking_key),
+            client_s_pk: server_setup.fake_keypair.public().clone(),
+        }
+    }
 }
 
 impl_serialize_and_deserialize_for!(RegistrationUpload);
@@ -229,9 +249,8 @@ impl_serialize_and_deserialize_for!(CredentialRequest);
 pub struct CredentialResponse<CS: CipherSuite> {
     /// the server's oprf output
     pub(crate) beta: CS::Group,
-    pub(crate) server_s_pk: PublicKey,
-    /// the user's sealed information,
-    pub(crate) envelope: Envelope<CS::Hash>,
+    pub(crate) masking_nonce: Vec<u8>,
+    pub(crate) masked_response: Vec<u8>,
     pub(crate) ke2_message: <CS::KeyExchange as KeyExchange<CS::Hash, CS::Group>>::KE2Message,
 }
 
@@ -240,8 +259,8 @@ impl<CS: CipherSuite> Clone for CredentialResponse<CS> {
     fn clone(&self) -> Self {
         Self {
             beta: self.beta,
-            server_s_pk: self.server_s_pk.clone(),
-            envelope: self.envelope.clone(),
+            masking_nonce: self.masking_nonce.clone(),
+            masked_response: self.masked_response.clone(),
             ke2_message: self.ke2_message.clone(),
         }
     }
@@ -251,7 +270,7 @@ impl<CS: CipherSuite> CredentialResponse<CS> {
     /// Serialization into bytes
     pub fn serialize(&self) -> Vec<u8> {
         [
-            Self::serialize_without_ke(&self.beta, &self.server_s_pk, &self.envelope),
+            Self::serialize_without_ke(&self.beta, &self.masking_nonce, &self.masked_response),
             self.ke2_message.to_bytes(),
         ]
         .concat()
@@ -259,23 +278,26 @@ impl<CS: CipherSuite> CredentialResponse<CS> {
 
     pub(crate) fn serialize_without_ke(
         beta: &CS::Group,
-        server_s_pk: &PublicKey,
-        envelope: &Envelope<CS::Hash>,
+        masking_nonce: &[u8],
+        masked_response: &[u8],
     ) -> Vec<u8> {
-        [
-            &beta.to_arr(),
-            &server_s_pk.to_arr()[..],
-            &envelope.to_bytes(),
-        ]
-        .concat()
+        [&beta.to_arr(), masking_nonce, masked_response].concat()
     }
 
     /// Deserialization from bytes
     pub fn deserialize(input: &[u8]) -> Result<Self, ProtocolError> {
         let elem_len = <CS::Group as Group>::ElemLen::to_usize();
         let key_len = <PublicKey as SizedBytes>::Len::to_usize();
-        let checked_slice =
-            check_slice_size_atleast(input, elem_len + key_len, "login_second_message_bytes")?;
+        let nonce_len: usize = 32;
+        let envelope_len = Envelope::<CS>::len();
+        let masked_response_len = key_len + envelope_len;
+        let ke2_message_len = CS::KeyExchange::ke2_message_size();
+
+        let checked_slice = check_slice_size_atleast(
+            input,
+            elem_len + nonce_len + masked_response_len + ke2_message_len,
+            "credential_response_bytes",
+        )?;
 
         // Check that the message is actually containing an element of the
         // correct subgroup
@@ -288,25 +310,19 @@ impl<CS: CipherSuite> CredentialResponse<CS> {
             return Err(PakeError::IdentityGroupElementError.into());
         }
 
-        let unchecked_server_s_pk =
-            PublicKey::from_bytes(&checked_slice[elem_len..elem_len + key_len])?;
-        let server_s_pk = KeyPair::<CS::Group>::check_public_key(unchecked_server_s_pk)?;
-
-        let (envelope, remainder) =
-            Envelope::<CS::Hash>::deserialize(&checked_slice[elem_len + key_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 masking_nonce = checked_slice[elem_len..elem_len + nonce_len].to_vec();
+        let masked_response = checked_slice
+            [elem_len + nonce_len..elem_len + nonce_len + masked_response_len]
+            .to_vec();
         let ke2_message =
             <CS::KeyExchange as KeyExchange<CS::Hash, CS::Group>>::KE2Message::from_bytes::<CS>(
-                checked_remainder,
+                &checked_slice[elem_len + nonce_len + masked_response_len..],
             )?;
 
         Ok(Self {
             beta,
-            server_s_pk,
-            envelope,
+            masking_nonce,
+            masked_response,
             ke2_message,
         })
     }
diff --git a/src/opaque.rs b/src/opaque.rs
index 32930af..fbce901 100644
--- a/src/opaque.rs
+++ b/src/opaque.rs
@@ -7,12 +7,12 @@
 
 use crate::{
     ciphersuite::CipherSuite,
-    envelope::{mode_from_ids, Envelope},
-    errors::{utils::check_slice_size_atleast, InternalPakeError, PakeError, ProtocolError},
+    envelope::Envelope,
+    errors::{utils::check_slice_size, InternalPakeError, PakeError, ProtocolError},
     group::Group,
     hash::Hash,
     key_exchange::traits::{FromBytes, KeyExchange, ToBytesWithPointers},
-    keypair::{KeyPair, PrivateKey, PublicKey, SizedBytesExt},
+    keypair::{KeyPair, PrivateKey, PublicKey},
     map_to_curve::GroupWithMapToCurve,
     oprf,
     serialization::{serialize, tokenize},
@@ -23,10 +23,67 @@ use crate::{
 use digest::Digest;
 use generic_array::{typenum::Unsigned, GenericArray};
 use generic_bytes::SizedBytes;
+use hkdf::Hkdf;
 use rand::{CryptoRng, RngCore};
 use std::marker::PhantomData;
 use zeroize::Zeroize;
 
+const STR_CREDENTIAL_RESPONSE_PAD: &[u8] = b"CredentialResponsePad";
+const STR_MASKING_KEY: &[u8] = b"MaskingKey";
+const STR_OPRF_KEY: &[u8] = b"OprfKey";
+
+// Server Setup
+// ============
+
+/// The state elements the server holds upon setup
+pub struct ServerSetup<CS: CipherSuite> {
+    oprf_seed: GenericArray<u8, <CS::Hash as Digest>::OutputSize>,
+    keypair: KeyPair<CS::Group>,
+    pub(crate) fake_keypair: KeyPair<CS::Group>,
+}
+
+impl<CS: CipherSuite> ServerSetup<CS> {
+    /// Generate a new instance of server setup
+    pub fn new<R: CryptoRng + RngCore>(rng: &mut R) -> Self {
+        let mut seed = vec![0u8; <CS::Hash as Digest>::OutputSize::to_usize()];
+        rng.fill_bytes(&mut seed);
+
+        Self {
+            oprf_seed: GenericArray::clone_from_slice(&seed[..]),
+            keypair: KeyPair::<CS::Group>::generate_random(rng),
+            fake_keypair: KeyPair::<CS::Group>::generate_random(rng),
+        }
+    }
+
+    /// Serialization into bytes
+    pub fn serialize(&self) -> Vec<u8> {
+        [
+            self.oprf_seed.to_vec(),
+            self.keypair.private().to_arr().to_vec(),
+            self.fake_keypair.private().to_arr().to_vec(),
+        ]
+        .concat()
+    }
+
+    /// Deserialization from bytes
+    pub fn deserialize(input: &[u8]) -> Result<Self, ProtocolError> {
+        let seed_len = <CS::Hash as Digest>::OutputSize::to_usize();
+        let key_len = <PrivateKey as SizedBytes>::Len::to_usize();
+        let checked_slice = check_slice_size(input, seed_len + key_len + key_len, "server_setup")?;
+
+        Ok(Self {
+            oprf_seed: GenericArray::clone_from_slice(&checked_slice[..seed_len]),
+            keypair: KeyPair::from_private_key_slice(&checked_slice[seed_len..seed_len + key_len])?,
+            fake_keypair: KeyPair::from_private_key_slice(&checked_slice[seed_len + key_len..])?,
+        })
+    }
+
+    /// Returns the keypair
+    pub fn keypair(&self) -> &KeyPair<CS::Group> {
+        &self.keypair
+    }
+}
+
 // Registration
 // ============
 
@@ -48,12 +105,11 @@ impl<CS: CipherSuite> Clone for ClientRegistration<CS> {
 impl<CS: CipherSuite> ClientRegistration<CS> {
     /// Serialization into bytes
     pub fn serialize(&self) -> Vec<u8> {
-        let output: Vec<u8> = [
+        [
             &CS::Group::scalar_as_bytes(&self.token.blind)[..],
             &self.token.data,
         ]
-        .concat();
-        output
+        .concat()
     }
 
     /// Deserialization from bytes
@@ -94,12 +150,40 @@ impl<CS: CipherSuite> ClientRegistration<CS> {
 
 impl_serialize_and_deserialize_for!(ClientRegistration);
 
+/// Options for specifying custom identifiers
+#[derive(Clone)]
+pub enum Identifiers {
+    /// Supply only a client identifier
+    ClientIdentifier(Vec<u8>),
+    /// Supply only a server identifier
+    ServerIdentifier(Vec<u8>),
+    /// Supply a client and server identifier
+    ClientAndServerIdentifiers(Vec<u8>, Vec<u8>),
+}
+
+pub(crate) fn bytestrings_from_identifiers(
+    ids: &Option<Identifiers>,
+    client_s_pk: &[u8],
+    server_s_pk: &[u8],
+) -> (Vec<u8>, Vec<u8>) {
+    let (client_identity, server_identity): (Vec<u8>, Vec<u8>) = match ids {
+        None => (client_s_pk.to_vec(), server_s_pk.to_vec()),
+        Some(Identifiers::ClientIdentifier(id_u)) => (id_u.clone(), server_s_pk.to_vec()),
+        Some(Identifiers::ServerIdentifier(id_s)) => (client_s_pk.to_vec(), id_s.clone()),
+        Some(Identifiers::ClientAndServerIdentifiers(id_u, id_s)) => (id_u.clone(), id_s.clone()),
+    };
+    (
+        serialize(&client_identity, 2),
+        serialize(&server_identity, 2),
+    )
+}
+
 /// Optional parameters for client registration finish
 #[derive(Clone)]
 pub enum ClientRegistrationFinishParameters {
-    /// Specifying the identifiers idU and idS (corresponding to custom identifier mode)
-    WithIdentifiers(Vec<u8>, Vec<u8>),
-    /// No identifiers specified (corresponding to base mode)
+    /// Specifying the identifiers idU and idS
+    WithIdentifiers(Identifiers),
+    /// No identifiers or private key specified
     Default,
 }
 
@@ -129,28 +213,6 @@ impl<CS: CipherSuite> Clone for ClientRegistrationStartResult<CS> {
 
 impl<CS: CipherSuite> ClientRegistration<CS> {
     /// Returns an initial "blinded" request to send to the server, as well as a ClientRegistration
-    ///
-    /// # Arguments
-    /// * `password` - A user password
-    ///
-    /// # Example
-    ///
-    /// ```
-    /// use opaque_ke::ClientRegistration;
-    /// # use opaque_ke::errors::ProtocolError;
-    /// use rand::{rngs::OsRng, RngCore};
-    /// use opaque_ke::ciphersuite::CipherSuite;
-    /// struct Default;
-    /// impl CipherSuite for Default {
-    ///     type Group = curve25519_dalek::ristretto::RistrettoPoint;
-    ///     type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDH;
-    ///     type Hash = sha2::Sha512;
-    ///     type SlowHash = opaque_ke::slow_hash::NoOpHash;
-    /// }
-    /// let mut client_rng = OsRng;
-    /// let client_registration_start_result = ClientRegistration::<Default>::start(&mut client_rng, b"hunter2")?;
-    /// # Ok::<(), ProtocolError>(())
-    /// ```
     pub fn start<R: RngCore + CryptoRng>(
         blinding_factor_rng: &mut R,
         password: &[u8],
@@ -190,35 +252,6 @@ impl<CS: CipherSuite> Clone for ClientRegistrationFinishResult<CS> {
 impl<CS: CipherSuite> ClientRegistration<CS> {
     /// "Unblinds" the server's answer and returns a final message containing
     /// cryptographic identifiers, to be sent to the server on setup finalization
-    ///
-    /// # Arguments
-    /// * `message` - the server's answer to the initial registration attempt
-    ///
-    /// # Example
-    ///
-    /// ```
-    /// use opaque_ke::{ClientRegistration, ClientRegistrationFinishParameters, ServerRegistration};
-    /// # use opaque_ke::errors::ProtocolError;
-    /// # use opaque_ke::keypair::KeyPair;
-    /// use rand::{rngs::OsRng, RngCore};
-    /// use opaque_ke::ciphersuite::CipherSuite;
-    /// struct Default;
-    /// impl CipherSuite for Default {
-    ///     type Group = curve25519_dalek::ristretto::RistrettoPoint;
-    ///     type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDH;
-    ///     type Hash = sha2::Sha512;
-    ///     type SlowHash = opaque_ke::slow_hash::NoOpHash;
-    /// }
-    /// let mut client_rng = OsRng;
-    /// let mut server_rng = OsRng;
-    /// let server_kp = Default::generate_random_keypair(&mut server_rng);
-    /// let client_registration_start_result = ClientRegistration::<Default>::start(&mut client_rng, b"hunter2")?;
-    /// let server_registration_start_result =
-    /// ServerRegistration::<Default>::start(&mut server_rng, client_registration_start_result.message, server_kp.public())?;
-    /// let mut client_rng = OsRng;
-    /// let client_registration_finish_result = client_registration_start_result.state.finish(&mut client_rng, server_registration_start_result.message, ClientRegistrationFinishParameters::default())?;
-    /// # Ok::<(), ProtocolError>(())
-    /// ```
     pub fn finish<R: CryptoRng + RngCore>(
         self,
         rng: &mut R,
@@ -226,26 +259,26 @@ impl<CS: CipherSuite> ClientRegistration<CS> {
         params: ClientRegistrationFinishParameters,
     ) -> Result<ClientRegistrationFinishResult<CS>, ProtocolError> {
         let optional_ids = match params {
-            ClientRegistrationFinishParameters::WithIdentifiers(id_u, id_s) => Some((id_u, id_s)),
+            ClientRegistrationFinishParameters::WithIdentifiers(ids) => Some(ids),
             ClientRegistrationFinishParameters::Default => None,
         };
-        let client_static_keypair = CS::generate_random_keypair(rng);
 
         let password_derived_key =
             get_password_derived_key::<CS::Group, CS::SlowHash, CS::Hash>(&self.token, r2.beta)?;
 
-        let (envelope, export_key) = Envelope::<CS::Hash>::seal(
-            rng,
-            &password_derived_key,
-            &client_static_keypair.private().to_arr().to_vec(),
-            &r2.server_s_pk,
-            optional_ids,
-        )?;
+        let h = Hkdf::<CS::Hash>::new(None, &password_derived_key);
+        let mut masking_key = vec![0u8; <CS::Hash as Digest>::OutputSize::to_usize()];
+        h.expand(STR_MASKING_KEY, &mut masking_key)
+            .map_err(|_| InternalPakeError::HkdfError)?;
+
+        let (envelope, client_s_pk, export_key) =
+            Envelope::<CS>::seal(rng, &password_derived_key, &r2.server_s_pk, optional_ids)?;
 
         Ok(ClientRegistrationFinishResult {
             message: RegistrationUpload {
                 envelope,
-                client_s_pk: client_static_keypair.public().clone(),
+                masking_key: GenericArray::clone_from_slice(&masking_key[..]),
+                client_s_pk,
             },
             export_key,
             #[cfg(test)]
@@ -254,12 +287,11 @@ impl<CS: CipherSuite> ClientRegistration<CS> {
     }
 }
 
-/// Contains the fields that are returned by a server registration start
+/// Contains the fields that are returned by a server registration start.
+/// Note that there is no state output in this step
 pub struct ServerRegistrationStartResult<CS: CipherSuite> {
     /// The registration resposne message to send to the client
     pub message: RegistrationResponse<CS>,
-    /// The state that the server must keep in order to complete registration
-    pub state: ServerRegistration<CS>,
 }
 
 // Cannot be derived because it would require for CS to be Clone.
@@ -267,123 +299,51 @@ impl<CS: CipherSuite> Clone for ServerRegistrationStartResult<CS> {
     fn clone(&self) -> Self {
         Self {
             message: self.message.clone(),
-            state: self.state.clone(),
         }
     }
 }
 
 /// The state elements the server holds to record a registration
-pub struct ServerRegistration<CS: CipherSuite> {
-    envelope: Option<Envelope<CS::Hash>>,
-    client_s_pk: Option<PublicKey>,
-    pub(crate) oprf_key: <CS::Group as Group>::Scalar,
-}
+pub struct ServerRegistration<CS: CipherSuite>(RegistrationUpload<CS>);
 
 // Cannot be derived because it would require for CS to be Clone.
 impl<CS: CipherSuite> Clone for ServerRegistration<CS> {
     fn clone(&self) -> Self {
-        Self {
-            envelope: self.envelope.clone(),
-            client_s_pk: self.client_s_pk.clone(),
-            oprf_key: self.oprf_key.clone(),
-        }
+        Self(self.0.clone())
     }
 }
 
 impl<CS: CipherSuite> ServerRegistration<CS> {
     /// Serialization into bytes
     pub fn serialize(&self) -> Vec<u8> {
-        let mut output: Vec<u8> = CS::Group::scalar_as_bytes(&self.oprf_key).to_vec();
-        self.client_s_pk
-            .iter()
-            .for_each(|v| output.extend_from_slice(&v.to_arr()));
-        self.envelope
-            .iter()
-            .for_each(|v| output.extend_from_slice(&v.to_bytes()));
-        output
+        self.0.serialize()
     }
 
     /// Deserialization from bytes
     pub fn deserialize(input: &[u8]) -> Result<Self, ProtocolError> {
-        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(input))?,
-                client_s_pk: None,
-                envelope: None,
-            });
-        }
-
-        // Need to do this check manually because envelope is variable-size
-        let key_len = <PublicKey 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 =
-            PublicKey::from_bytes(&checked_bytes[scalar_len..scalar_len + key_len])?;
-        let client_s_pk = KeyPair::<CS::Group>::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),
-            client_s_pk: Some(client_s_pk),
-            oprf_key,
-        })
+        Ok(Self(RegistrationUpload::deserialize(input)?))
     }
 
     #[cfg(test)]
     pub fn as_byte_ptrs(&self) -> Vec<(*const u8, usize)> {
         [
-            match &self.envelope {
-                Some(env) => env.as_byte_ptrs(),
-                None => vec![],
-            },
-            match &self.client_s_pk {
-                Some(pk) => vec![(pk.as_ptr(), pk.len())],
-                None => vec![],
-            },
+            self.0.envelope.as_byte_ptrs(),
+            vec![(self.0.client_s_pk.as_ptr(), self.0.client_s_pk.len())],
             /* cannot provide raw pointer to self.oprf_key until this is exposed in curve25519_dalek::scalar::Scalar */
         ].concat()
     }
 
     /// From the client's "blinded" password, returns a response to be
     /// sent back to the client, as well as a ServerRegistration
-    ///
-    /// # Arguments
-    /// * `message`   - the initial registration message
-    ///
-    /// # Example
-    ///
-    /// ```
-    /// use opaque_ke::*;
-    /// # use opaque_ke::errors::ProtocolError;
-    /// use rand::{rngs::OsRng, RngCore};
-    /// use opaque_ke::ciphersuite::CipherSuite;
-    /// struct Default;
-    /// impl CipherSuite for Default {
-    ///     type Group = curve25519_dalek::ristretto::RistrettoPoint;
-    ///     type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDH;
-    ///     type Hash = sha2::Sha512;
-    ///     type SlowHash = opaque_ke::slow_hash::NoOpHash;
-    /// }
-    /// let mut client_rng = OsRng;
-    /// let mut server_rng = OsRng;
-    /// let server_kp = Default::generate_random_keypair(&mut server_rng);
-    /// let client_registration_start_result = ClientRegistration::<Default>::start(&mut client_rng, b"hunter2")?;
-    /// let server_registration_start_result = ServerRegistration::<Default>::start(&mut server_rng, client_registration_start_result.message, server_kp.public())?;
-    /// # Ok::<(), ProtocolError>(())
-    /// ```
-    pub fn start<R: RngCore + CryptoRng>(
-        rng: &mut R,
+    pub fn start(
+        server_setup: &ServerSetup<CS>,
         message: RegistrationRequest<CS>,
-        server_s_pk: &PublicKey,
+        credential_identifier: &[u8],
     ) -> Result<ServerRegistrationStartResult<CS>, ProtocolError> {
-        // RFC: generate oprf_key (salt) and v_u = g^oprf_key
-        let oprf_key = CS::Group::random_nonzero_scalar(rng);
+        let oprf_key = oprf_key_from_seed::<CS::Group, CS::Hash>(
+            &server_setup.oprf_seed,
+            credential_identifier,
+        )?;
 
         // Compute beta = alpha^oprf_key
         let beta = oprf::evaluate::<CS::Group>(message.alpha, &oprf_key);
@@ -391,52 +351,23 @@ impl<CS: CipherSuite> ServerRegistration<CS> {
         Ok(ServerRegistrationStartResult {
             message: RegistrationResponse {
                 beta,
-                server_s_pk: server_s_pk.clone(),
-            },
-            state: Self {
-                envelope: None,
-                client_s_pk: None,
-                oprf_key,
+                server_s_pk: server_setup.keypair.public().clone(),
             },
         })
     }
 
     /// From the client's cryptographic identifiers, fully populates and
     /// returns a ServerRegistration
-    ///
-    /// # Arguments
-    /// * `message` - the final client message
-    ///
-    /// # Example
-    ///
-    /// ```
-    /// use opaque_ke::{*, keypair::KeyPair};
-    /// # use opaque_ke::errors::ProtocolError;
-    /// use rand::{rngs::OsRng, RngCore};
-    /// use opaque_ke::ciphersuite::CipherSuite;
-    /// struct Default;
-    /// impl CipherSuite for Default {
-    ///     type Group = curve25519_dalek::ristretto::RistrettoPoint;
-    ///     type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDH;
-    ///     type Hash = sha2::Sha512;
-    ///     type SlowHash = opaque_ke::slow_hash::NoOpHash;
-    /// }
-    /// let mut client_rng = OsRng;
-    /// let mut server_rng = OsRng;
-    /// let server_kp = Default::generate_random_keypair(&mut server_rng);
-    /// let client_registration_start_result = ClientRegistration::<Default>::start(&mut client_rng, b"hunter2")?;
-    /// let server_registration_start_result = ServerRegistration::<Default>::start(&mut server_rng, client_registration_start_result.message, server_kp.public())?;
-    /// let mut client_rng = OsRng;
-    /// let client_registration_finish_result = client_registration_start_result.state.finish(&mut client_rng, server_registration_start_result.message, ClientRegistrationFinishParameters::default())?;
-    /// let client_record = server_registration_start_result.state.finish(client_registration_finish_result.message)?;
-    /// # Ok::<(), ProtocolError>(())
-    /// ```
-    pub fn finish(self, message: RegistrationUpload<CS>) -> Result<Self, ProtocolError> {
-        Ok(Self {
-            envelope: Some(message.envelope),
-            client_s_pk: Some(message.client_s_pk),
-            oprf_key: self.oprf_key.clone(),
-        })
+    pub fn finish(message: RegistrationUpload<CS>) -> Self {
+        Self(message)
+    }
+
+    // Creates a dummy instance used for faking a [CredentialResponse]
+    pub(crate) fn dummy<R: RngCore + CryptoRng>(
+        rng: &mut R,
+        server_setup: &ServerSetup<CS>,
+    ) -> Self {
+        Self(RegistrationUpload::dummy(rng, server_setup))
     }
 }
 
@@ -523,21 +454,6 @@ impl<CS: CipherSuite> ClientLogin<CS> {
     }
 }
 
-impl_serialize_and_deserialize_for!(ClientLogin);
-
-/// Optional parameters for client login start
-#[derive(Clone)]
-pub enum ClientLoginStartParameters {
-    /// Specifying a plaintext info field that will be sent to the server
-    WithInfo(Vec<u8>),
-}
-
-impl Default for ClientLoginStartParameters {
-    fn default() -> Self {
-        Self::WithInfo(Vec::new())
-    }
-}
-
 /// Contains the fields that are returned by a client login start
 pub struct ClientLoginStartResult<CS: CipherSuite> {
     /// The message to send to the server to begin the login protocol
@@ -559,9 +475,14 @@ impl<CS: CipherSuite> Clone for ClientLoginStartResult<CS> {
 /// Optional parameters for client login finish
 #[derive(Clone)]
 pub enum ClientLoginFinishParameters {
-    /// Specifying a user identifier and server identifier that will be matched against the client
-    WithIdentifiers(Vec<u8>, Vec<u8>),
-    /// No info and no custom identifiers
+    /// Specifying a context field that the server must agree on
+    WithContext(Vec<u8>),
+    /// Specifying a user identifier and server identifier that will be matched against the server
+    WithIdentifiers(Identifiers),
+    /// Specifying a context field that the server must agree on,
+    /// along with a user identifier and server identifier and context that will be matched against the server
+    WithContextAndIdentifiers(Vec<u8>, Identifiers),
+    /// No custom identifiers and no context
     Default,
 }
 
@@ -581,8 +502,6 @@ pub struct ClientLoginFinishResult<CS: CipherSuite> {
     pub export_key: GenericArray<u8, <CS::Hash as Digest>::OutputSize>,
     /// The server's static public key
     pub server_s_pk: PublicKey,
-    /// The confidential info sent by the client
-    pub confidential_info: Vec<u8>,
     /// Instance of the ClientLogin, only used in tests for checking zeroize
     #[cfg(test)]
     pub state: ClientLogin<CS>,
@@ -596,7 +515,6 @@ impl<CS: CipherSuite> Clone for ClientLoginFinishResult<CS> {
             session_key: self.session_key.clone(),
             export_key: self.export_key.clone(),
             server_s_pk: self.server_s_pk.clone(),
-            confidential_info: self.confidential_info.clone(),
             #[cfg(test)]
             state: self.state.clone(),
         }
@@ -605,38 +523,13 @@ impl<CS: CipherSuite> Clone for ClientLoginFinishResult<CS> {
 
 impl<CS: CipherSuite> ClientLogin<CS> {
     /// Returns an initial "blinded" password request to send to the server, as well as a ClientLogin
-    ///
-    /// # Arguments
-    /// * `password` - A user password
-    ///
-    /// # Example
-    ///
-    /// ```
-    /// use opaque_ke::{ClientLogin, ClientLoginStartParameters};
-    /// # use opaque_ke::errors::ProtocolError;
-    /// use rand::{rngs::OsRng, RngCore};
-    /// use opaque_ke::ciphersuite::CipherSuite;
-    /// struct Default;
-    /// impl CipherSuite for Default {
-    ///     type Group = curve25519_dalek::ristretto::RistrettoPoint;
-    ///     type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDH;
-    ///     type Hash = sha2::Sha512;
-    ///     type SlowHash = opaque_ke::slow_hash::NoOpHash;
-    /// }
-    /// let mut client_rng = OsRng;
-    /// let client_login_start_result = ClientLogin::<Default>::start(&mut client_rng, b"hunter2", ClientLoginStartParameters::default())?;
-    /// # Ok::<(), ProtocolError>(())
-    /// ```
     pub fn start<R: RngCore + CryptoRng>(
         rng: &mut R,
         password: &[u8],
-        params: ClientLoginStartParameters,
     ) -> Result<ClientLoginStartResult<CS>, ProtocolError> {
-        let ClientLoginStartParameters::WithInfo(info) = params;
-
         let (token, alpha) = oprf::blind::<R, CS::Group, CS::Hash>(password, rng)?;
 
-        let (ke1_state, ke1_message) = CS::KeyExchange::generate_ke1(info, rng)?;
+        let (ke1_state, ke1_message) = CS::KeyExchange::generate_ke1(rng)?;
 
         let credential_request = CredentialRequest { alpha, ke1_message };
         let serialized_credential_request = credential_request.serialize();
@@ -653,92 +546,75 @@ impl<CS: CipherSuite> ClientLogin<CS> {
 
     /// "Unblinds" the server's answer and returns the opened assets from
     /// the server
-    ///
-    /// # Arguments
-    /// * `message` - the server's answer to the initial login attempt
-    ///
-    /// # Example
-    ///
-    /// ```
-    /// use opaque_ke::{ClientLogin, ClientLoginStartParameters, ClientLoginFinishParameters, ServerLogin, ServerLoginStartParameters};
-    /// # use opaque_ke::{ClientRegistration, ClientRegistrationFinishParameters, ServerRegistration};
-    /// # use opaque_ke::errors::ProtocolError;
-    /// # use opaque_ke::keypair::KeyPair;
-    /// use rand::{rngs::OsRng, RngCore};
-    /// use opaque_ke::ciphersuite::CipherSuite;
-    /// struct Default;
-    /// impl CipherSuite for Default {
-    ///     type Group = curve25519_dalek::ristretto::RistrettoPoint;
-    ///     type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDH;
-    ///     type Hash = sha2::Sha512;
-    ///     type SlowHash = opaque_ke::slow_hash::NoOpHash;
-    /// }
-    /// let mut client_rng = OsRng;
-    /// # let mut server_rng = OsRng;
-    /// # let client_registration_start_result = ClientRegistration::<Default>::start(&mut client_rng, b"hunter2")?;
-    /// # let server_kp = Default::generate_random_keypair(&mut server_rng);
-    /// # let server_registration_start_result = ServerRegistration::<Default>::start(&mut server_rng, client_registration_start_result.message, server_kp.public())?;
-    /// # let client_registration_finish_result = client_registration_start_result.state.finish(&mut client_rng, server_registration_start_result.message, ClientRegistrationFinishParameters::default())?;
-    /// # let p_file = server_registration_start_result.state.finish(client_registration_finish_result.message)?;
-    /// let client_login_start_result = ClientLogin::<Default>::start(&mut client_rng, b"hunter2", ClientLoginStartParameters::default())?;
-    /// let server_login_start_result = ServerLogin::start(&mut server_rng, p_file, &server_kp.private(), client_login_start_result.message, ServerLoginStartParameters::default())?;
-    /// let client_login_finish_result = client_login_start_result.state.finish(server_login_start_result.message, ClientLoginFinishParameters::default())?;
-    /// # Ok::<(), ProtocolError>(())
-    /// ```
     pub fn finish(
         self,
-        l2: CredentialResponse<CS>,
+        credential_response: CredentialResponse<CS>,
         params: ClientLoginFinishParameters,
     ) -> Result<ClientLoginFinishResult<CS>, ProtocolError> {
-        let optional_ids = match params {
-            ClientLoginFinishParameters::Default => None,
-            ClientLoginFinishParameters::WithIdentifiers(id_u, id_s) => Some((id_u, id_s)),
+        let (context, optional_ids) = match params {
+            ClientLoginFinishParameters::Default => (vec![], None),
+            ClientLoginFinishParameters::WithContext(context) => (context, None),
+            ClientLoginFinishParameters::WithIdentifiers(ids) => (vec![], Some(ids)),
+            // add context
+            ClientLoginFinishParameters::WithContextAndIdentifiers(context, ids) => {
+                (context, Some(ids))
+            }
         };
 
-        let server_s_pk_bytes = l2.server_s_pk.to_arr().to_vec();
+        let password_derived_key = get_password_derived_key::<CS::Group, CS::SlowHash, CS::Hash>(
+            &self.token,
+            credential_response.beta,
+        )?;
 
-        let password_derived_key =
-            get_password_derived_key::<CS::Group, CS::SlowHash, CS::Hash>(&self.token, l2.beta)?;
-        let opened_envelope = &l2
-            .envelope
+        let h = Hkdf::<CS::Hash>::new(None, &password_derived_key);
+        let mut masking_key = vec![0u8; <CS::Hash as Digest>::OutputSize::to_usize()];
+        h.expand(STR_MASKING_KEY, &mut masking_key)
+            .map_err(|_| InternalPakeError::HkdfError)?;
+
+        let (server_s_pk, envelope) = unmask_response::<CS>(
+            &masking_key,
+            &credential_response.masking_nonce,
+            &credential_response.masked_response,
+        )
+        .map_err(|e| match e {
+            ProtocolError::InvalidInnerEnvelopeError => PakeError::InvalidLoginError.into(),
+            ProtocolError::VerificationError(PakeError::SerializationError) => {
+                PakeError::InvalidLoginError.into()
+            }
+            err => err,
+        })?;
+        let server_s_pk_bytes = server_s_pk.to_arr().to_vec();
+
+        let opened_envelope = &envelope
             .open(&password_derived_key, &server_s_pk_bytes, &optional_ids)
             .map_err(|e| match e {
                 InternalPakeError::SealOpenHmacError => PakeError::InvalidLoginError,
                 err => PakeError::from(err),
             })?;
 
-        let client_s_sk = PrivateKey::from_bytes(&opened_envelope.client_s_sk)?;
-
-        let (id_u, id_s) = match optional_ids {
-            None => (
-                KeyPair::<CS::Group>::public_from_private(&client_s_sk)
-                    .to_arr()
-                    .to_vec(),
-                server_s_pk_bytes,
-            ),
-            Some((id_u, id_s)) => (id_u, id_s),
-        };
+        let credential_response_component = CredentialResponse::<CS>::serialize_without_ke(
+            &credential_response.beta,
+            &credential_response.masking_nonce,
+            &credential_response.masked_response,
+        );
 
-        let credential_response_component =
-            CredentialResponse::<CS>::serialize_without_ke(&l2.beta, &l2.server_s_pk, &l2.envelope);
-
-        let (confidential_info, session_key, ke3_message) = CS::KeyExchange::generate_ke3(
+        let (session_key, ke3_message) = CS::KeyExchange::generate_ke3(
             credential_response_component,
-            l2.ke2_message,
+            credential_response.ke2_message,
             &self.ke1_state,
             &self.serialized_credential_request,
-            l2.server_s_pk.clone(),
-            client_s_sk,
-            id_u,
-            id_s,
+            server_s_pk.clone(),
+            opened_envelope.client_static_keypair.private().clone(),
+            opened_envelope.id_u.clone(),
+            opened_envelope.id_s.clone(),
+            context,
         )?;
 
         Ok(ClientLoginFinishResult {
-            confidential_info,
             message: CredentialFinalization { ke3_message },
             session_key,
             export_key: opened_envelope.export_key.clone(),
-            server_s_pk: l2.server_s_pk,
+            server_s_pk,
             #[cfg(test)]
             state: self,
         })
@@ -764,19 +640,19 @@ impl<CS: CipherSuite> Clone for ServerLogin<CS> {
 /// Optional parameters for server login start
 #[derive(Clone)]
 pub enum ServerLoginStartParameters {
-    /// Specifying a confidential info field that will be sent to the client
-    WithInfo(Vec<u8>),
+    /// Specifying a context field that the client must agree on
+    WithContext(Vec<u8>),
     /// Specifying a user identifier and server identifier that will be matched against the client
-    WithIdentifiers(Vec<u8>, Vec<u8>),
-    /// Specifying a confidential info field that will be sent to the client,
+    WithIdentifiers(Identifiers),
+    /// Specifying a context field that the client must agree on,
     /// along with a user identifier and and server identifier that will be matched against the client
     /// (in that order)
-    WithInfoAndIdentifiers(Vec<u8>, Vec<u8>, Vec<u8>),
+    WithContextAndIdentifiers(Vec<u8>, Identifiers),
 }
 
 impl Default for ServerLoginStartParameters {
     fn default() -> Self {
-        Self::WithInfo(Vec::new())
+        Self::WithContext(Vec::new())
     }
 }
 
@@ -786,8 +662,6 @@ pub struct ServerLoginStartResult<CS: CipherSuite> {
     pub message: CredentialResponse<CS>,
     /// The state that the server must keep in order to finish the protocl
     pub state: ServerLogin<CS>,
-    /// The plaintext info sent by the client
-    pub plain_info: Vec<u8>,
 }
 
 // Cannot be derived because it would require for CS to be Clone.
@@ -796,7 +670,6 @@ impl<CS: CipherSuite> Clone for ServerLoginStartResult<CS> {
         Self {
             message: self.message.clone(),
             state: self.state.clone(),
-            plain_info: self.plain_info.clone(),
         }
     }
 }
@@ -839,87 +712,62 @@ impl<CS: CipherSuite> ServerLogin<CS> {
         })
     }
 
-    /// From the client's "blinded"" password, returns a challenge to be
+    /// From the client's "blinded" password, returns a challenge to be
     /// sent back to the client, as well as a ServerLogin
-    ///
-    /// # Arguments
-    /// * `message`   - the initial registration message
-    ///
-    /// # Example
-    ///
-    /// ```
-    /// use opaque_ke::{ClientLogin, ClientLoginStartParameters, ServerLogin, ServerLoginStartParameters};
-    /// # use opaque_ke::{ClientRegistration, ClientRegistrationFinishParameters, ServerRegistration};
-    /// # use opaque_ke::errors::ProtocolError;
-    /// # use opaque_ke::keypair::KeyPair;
-    /// use rand::{rngs::OsRng, RngCore};
-    /// use opaque_ke::ciphersuite::CipherSuite;
-    /// struct Default;
-    /// impl CipherSuite for Default {
-    ///     type Group = curve25519_dalek::ristretto::RistrettoPoint;
-    ///     type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDH;
-    ///     type Hash = sha2::Sha512;
-    ///     type SlowHash = opaque_ke::slow_hash::NoOpHash;
-    /// }
-    /// let mut client_rng = OsRng;
-    /// let mut server_rng = OsRng;
-    /// let server_kp = Default::generate_random_keypair(&mut server_rng);
-    /// # let client_registration_start_result = ClientRegistration::<Default>::start(&mut client_rng, b"hunter2")?;
-    /// # let server_registration_start_result = ServerRegistration::<Default>::start(&mut server_rng, client_registration_start_result.message, server_kp.public())?;
-    /// # let client_registration_finish_result = client_registration_start_result.state.finish(&mut client_rng, server_registration_start_result.message, ClientRegistrationFinishParameters::default())?;
-    /// # let p_file = server_registration_start_result.state.finish(client_registration_finish_result.message)?;
-    /// let client_login_start_result = ClientLogin::<Default>::start(&mut client_rng, b"hunter2", ClientLoginStartParameters::default())?;
-    /// let server_login_start_result = ServerLogin::start(&mut server_rng, p_file, &server_kp.private(), client_login_start_result.message, ServerLoginStartParameters::default())?;
-    /// # Ok::<(), ProtocolError>(())
-    /// ```
     pub fn start<R: RngCore + CryptoRng>(
         rng: &mut R,
-        password_file: ServerRegistration<CS>,
-        server_s_sk: &PrivateKey,
+        server_setup: &ServerSetup<CS>,
+        password_file: Option<ServerRegistration<CS>>,
         l1: CredentialRequest<CS>,
+        credential_identifier: &[u8],
         params: ServerLoginStartParameters,
     ) -> Result<ServerLoginStartResult<CS>, ProtocolError> {
-        let client_s_pk = password_file
-            .client_s_pk
-            .clone()
-            .ok_or(InternalPakeError::SealError)?;
-
-        let (e_info, optional_ids) = match params {
-            ServerLoginStartParameters::WithInfo(e_info) => (e_info, None),
-            ServerLoginStartParameters::WithIdentifiers(id_u, id_s) => {
-                (Vec::new(), Some((id_u, id_s)))
-            }
-            ServerLoginStartParameters::WithInfoAndIdentifiers(e_info, id_u, id_s) => {
-                (e_info, Some((id_u, id_s)))
-            }
+        let record = match password_file {
+            Some(x) => x,
+            None => ServerRegistration::dummy(rng, server_setup),
         };
 
-        let envelope = password_file
-            .envelope
-            .clone()
-            .ok_or(InternalPakeError::SealError)?;
-        if envelope.get_mode() != mode_from_ids(&optional_ids) {
-            return Err(InternalPakeError::IncompatibleEnvelopeModeError.into());
-        }
+        let client_s_pk = record.0.client_s_pk.clone();
 
-        let (id_u, id_s) = match optional_ids {
-            None => (
-                client_s_pk.to_arr().to_vec(),
-                KeyPair::<CS::Group>::public_from_private(server_s_sk)
-                    .to_arr()
-                    .to_vec(),
-            ),
-            Some((id_u, id_s)) => (id_u, id_s),
+        let (context, optional_ids) = match params {
+            ServerLoginStartParameters::WithContext(context) => (context, None),
+            ServerLoginStartParameters::WithIdentifiers(ids) => (Vec::new(), Some(ids)),
+            ServerLoginStartParameters::WithContextAndIdentifiers(context, ids) => {
+                (context, Some(ids))
+            }
         };
 
-        let l1_bytes = &l1.serialize();
-        let beta = oprf::evaluate(l1.alpha, &password_file.oprf_key);
+        let server_s_sk = server_setup.keypair.private();
         let server_s_pk = KeyPair::<CS::Group>::public_from_private(server_s_sk);
 
+        let mut masking_nonce = vec![0u8; 32];
+        rng.fill_bytes(&mut masking_nonce);
+
+        let masked_response = mask_response(
+            &record.0.masking_key,
+            &masking_nonce,
+            &server_s_pk,
+            &record.0.envelope,
+        )?;
+
+        let (id_u, id_s) = bytestrings_from_identifiers(
+            &optional_ids,
+            &client_s_pk.to_arr(),
+            &server_s_pk.to_arr(),
+        );
+
+        let l1_bytes = &l1.serialize();
+
+        let oprf_key = oprf_key_from_seed::<CS::Group, CS::Hash>(
+            &server_setup.oprf_seed,
+            credential_identifier,
+        )?;
+        let beta = oprf::evaluate(l1.alpha, &oprf_key);
+
         let credential_response_component =
-            CredentialResponse::<CS>::serialize_without_ke(&beta, &server_s_pk, &envelope);
+            CredentialResponse::<CS>::serialize_without_ke(&beta, &masking_nonce, &masked_response);
 
-        let (plain_info, ke2_state, ke2_message) = CS::KeyExchange::generate_ke2(
+        let (ke2_state, ke2_message) = CS::KeyExchange::generate_ke2(
             rng,
             l1_bytes.to_vec(),
             credential_response_component,
@@ -928,18 +776,17 @@ impl<CS: CipherSuite> ServerLogin<CS> {
             server_s_sk.clone(),
             id_u,
             id_s,
-            e_info,
+            context,
         )?;
 
         let credential_response = CredentialResponse {
             beta,
-            server_s_pk,
-            envelope,
+            masking_nonce,
+            masked_response,
             ke2_message,
         };
 
         Ok(ServerLoginStartResult {
-            plain_info,
             message: credential_response,
             state: Self {
                 _cs: PhantomData,
@@ -950,39 +797,6 @@ impl<CS: CipherSuite> ServerLogin<CS> {
 
     /// From the client's second and final message, check the client's
     /// authentication and produce a message transport
-    ///
-    /// # Arguments
-    /// * `message` - the client's second login message
-    ///
-    /// # Example
-    ///
-    /// ```
-    /// use opaque_ke::{ClientLogin, ClientLoginFinishParameters, ClientLoginStartParameters, ServerLogin, ServerLoginStartParameters};
-    /// # use opaque_ke::{ClientRegistration, ClientRegistrationFinishParameters, ServerRegistration};
-    /// # use opaque_ke::errors::ProtocolError;
-    /// # use opaque_ke::keypair::KeyPair;
-    /// use rand::{rngs::OsRng, RngCore};
-    /// use opaque_ke::ciphersuite::CipherSuite;
-    /// struct Default;
-    /// impl CipherSuite for Default {
-    ///     type Group = curve25519_dalek::ristretto::RistrettoPoint;
-    ///     type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDH;
-    ///     type Hash = sha2::Sha512;
-    ///     type SlowHash = opaque_ke::slow_hash::NoOpHash;
-    /// }
-    /// let mut client_rng = OsRng;
-    /// let mut server_rng = OsRng;
-    /// let server_kp = Default::generate_random_keypair(&mut server_rng);
-    /// # let client_registration_start_result = ClientRegistration::<Default>::start(&mut client_rng, b"hunter2")?;
-    /// # let server_registration_start_result = ServerRegistration::<Default>::start(&mut server_rng, client_registration_start_result.message, server_kp.public())?;
-    /// # let client_registration_finish_result = client_registration_start_result.state.finish(&mut client_rng, server_registration_start_result.message, ClientRegistrationFinishParameters::default())?;
-    /// # let p_file = server_registration_start_result.state.finish(client_registration_finish_result.message)?;
-    /// let client_login_start_result = ClientLogin::<Default>::start(&mut client_rng, b"hunter2", ClientLoginStartParameters::default())?;
-    /// let server_login_start_result = ServerLogin::start(&mut server_rng, p_file, &server_kp.private(), client_login_start_result.message, ServerLoginStartParameters::default())?;
-    /// let client_login_finish_result = client_login_start_result.state.finish(server_login_start_result.message, ClientLoginFinishParameters::default())?;
-    /// let mut server_transport = server_login_start_result.state.finish(client_login_finish_result.message)?;
-    /// # Ok::<(), ProtocolError>(())
-    /// ```
     pub fn finish(
         self,
         message: CredentialFinalization<CS>,
@@ -1033,9 +847,9 @@ 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 ServerRegistration<CS> {
     fn zeroize(&mut self) {
-        self.envelope.zeroize();
-        self.client_s_pk.zeroize();
-        self.oprf_key.zeroize();
+        self.0.envelope.zeroize();
+        self.0.masking_key.zeroize();
+        self.0.client_s_pk.zeroize();
     }
 }
 
@@ -1083,3 +897,72 @@ fn get_password_derived_key<G: GroupWithMapToCurve, SH: SlowHash<D>, D: Hash>(
     let oprf_output = oprf::finalize::<G, D>(&token.data, &token.blind, beta);
     SH::hash(oprf_output)
 }
+
+fn oprf_key_from_seed<G: GroupWithMapToCurve, D: Hash>(
+    oprf_seed: &GenericArray<u8, D::OutputSize>,
+    credential_identifier: &[u8],
+) -> Result<G::Scalar, InternalPakeError> {
+    let mut oprf_key_bytes = vec![0u8; <PrivateKey as SizedBytes>::Len::to_usize()];
+    Hkdf::<D>::from_prk(oprf_seed)
+        .map_err(|_| InternalPakeError::HkdfError)?
+        .expand(
+            &[credential_identifier, STR_OPRF_KEY].concat(),
+            &mut oprf_key_bytes,
+        )
+        .map_err(|_| InternalPakeError::HkdfError)?;
+    G::hash_to_scalar::<D>(&oprf_key_bytes[..], b"")
+}
+
+fn mask_response<CS: CipherSuite>(
+    masking_key: &[u8],
+    masking_nonce: &[u8],
+    server_s_pk: &PublicKey,
+    envelope: &Envelope<CS>,
+) -> Result<Vec<u8>, ProtocolError> {
+    let mut xor_pad = vec![0u8; <PublicKey as SizedBytes>::Len::to_usize() + Envelope::<CS>::len()];
+    Hkdf::<CS::Hash>::from_prk(masking_key)
+        .map_err(|_| InternalPakeError::HkdfError)?
+        .expand(
+            &[masking_nonce, STR_CREDENTIAL_RESPONSE_PAD].concat(),
+            &mut xor_pad,
+        )
+        .map_err(|_| InternalPakeError::HkdfError)?;
+
+    let plaintext = [&server_s_pk.to_arr()[..], &envelope.serialize()].concat();
+
+    Ok(xor_pad
+        .iter()
+        .zip(plaintext.iter())
+        .map(|(&x1, &x2)| x1 ^ x2)
+        .collect())
+}
+
+fn unmask_response<CS: CipherSuite>(
+    masking_key: &[u8],
+    masking_nonce: &[u8],
+    masked_response: &[u8],
+) -> Result<(PublicKey, Envelope<CS>), ProtocolError> {
+    let mut xor_pad = vec![0u8; <PublicKey as SizedBytes>::Len::to_usize() + Envelope::<CS>::len()];
+    Hkdf::<CS::Hash>::from_prk(masking_key)
+        .map_err(|_| InternalPakeError::HkdfError)?
+        .expand(
+            &[masking_nonce, STR_CREDENTIAL_RESPONSE_PAD].concat(),
+            &mut xor_pad,
+        )
+        .map_err(|_| InternalPakeError::HkdfError)?;
+    let plaintext: Vec<u8> = xor_pad
+        .iter()
+        .zip(masked_response.iter())
+        .map(|(&x1, &x2)| x1 ^ x2)
+        .collect();
+    let key_len = <PublicKey as SizedBytes>::Len::to_usize();
+    let unchecked_server_s_pk =
+        PublicKey::from_arr(&GenericArray::clone_from_slice(&plaintext[..key_len]))?;
+    let envelope = Envelope::deserialize(&plaintext[key_len..])?;
+
+    // Ensure that public key is valid
+    let server_s_pk = KeyPair::<CS::Group>::check_public_key(unchecked_server_s_pk)
+        .map_err(|_| ProtocolError::VerificationError(PakeError::SerializationError))?;
+
+    Ok((server_s_pk, envelope))
+}


Test patch:
diff --git a/src/serialization/tests.rs b/src/serialization/tests.rs
index 78052f4..c9b27ff 100644
--- a/src/serialization/tests.rs
+++ b/src/serialization/tests.rs
@@ -12,7 +12,7 @@ use crate::{
         traits::{FromBytes, KeyExchange, ToBytes},
         tripledh::{NonceLen, TripleDH},
     },
-    opaque::*,
+    keypair::{KeyPair, PublicKey},
     serialization::{i2osp, os2ip, serialize},
     *,
 };
@@ -33,7 +33,7 @@ impl CipherSuite for Default {
     type SlowHash = crate::slow_hash::NoOpHash;
 }
 
-const MAX_INFO_LENGTH: usize = 10;
+const HASH_SIZE: usize = 64; // Because of SHA512
 const MAC_SIZE: usize = 64; // Because of SHA512
 
 fn random_ristretto_point() -> RistrettoPoint {
@@ -68,28 +68,20 @@ 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_nonzero_scalar(&mut rng);
-    let mut oprf_bytes: Vec<u8> = vec![];
-    oprf_bytes.extend_from_slice(oprf_key.as_bytes());
-    let reg = ServerRegistration::<Default>::deserialize(&oprf_bytes[..]).unwrap();
-    let reg_bytes = reg.serialize();
-    assert_eq!(reg_bytes, oprf_bytes);
-
-    let mut ciphertext = [0u8; 32];
-    rng.fill_bytes(&mut ciphertext);
+    let mut masking_key = [0u8; HASH_SIZE];
+    rng.fill_bytes(&mut masking_key);
 
     // Construct a mock envelope
     let mut mock_envelope_bytes = Vec::new();
-    mock_envelope_bytes.extend_from_slice(&[1; 1]); // mode = 1
     mock_envelope_bytes.extend_from_slice(&vec![0; NonceLen::to_usize()]); // empty nonce
-    mock_envelope_bytes.extend_from_slice(&ciphertext); // ciphertext which is an encrypted private key
+                                                                           // mock_envelope_bytes.extend_from_slice(&ciphertext); // ciphertext which is an encrypted private key
     mock_envelope_bytes.extend_from_slice(&[0; MAC_SIZE]); // length-MAC_SIZE hmac
 
-    let mock_client_kp = Default::generate_random_keypair(&mut rng);
+    let mock_client_kp = KeyPair::<<Default as CipherSuite>::Group>::generate_random(&mut rng);
     // 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(&masking_key);
     bytes.extend_from_slice(&mock_envelope_bytes);
     let reg = ServerRegistration::<Default>::deserialize(&bytes[..]).unwrap();
     let reg_bytes = reg.serialize();
@@ -125,7 +117,7 @@ fn registration_response_roundtrip() {
     let pt = random_ristretto_point();
     let beta_bytes = pt.to_arr();
     let mut rng = OsRng;
-    let skp = Default::generate_random_keypair(&mut rng);
+    let skp = KeyPair::<<Default as CipherSuite>::Group>::generate_random(&mut rng);
     let pubkey_bytes = skp.public().to_arr();
 
     let mut input = Vec::new();
@@ -151,27 +143,25 @@ fn registration_response_roundtrip() {
 #[test]
 fn registration_upload_roundtrip() {
     let mut rng = OsRng;
-    let skp = Default::generate_random_keypair(&mut rng);
+    let skp = KeyPair::<<Default as CipherSuite>::Group>::generate_random(&mut rng);
     let pubkey_bytes = skp.public().to_arr();
 
     let mut key = [0u8; 32];
     rng.fill_bytes(&mut key);
+    let mut nonce = [0u8; 32];
+    rng.fill_bytes(&mut nonce);
 
-    let mut msg = [0u8; 32];
-    rng.fill_bytes(&mut msg);
+    let mut masking_key = vec![0u8; <sha2::Sha512 as Digest>::OutputSize::to_usize()];
+    rng.fill_bytes(&mut masking_key);
 
-    let (envelope, _) = Envelope::<sha2::Sha512>::seal_raw(
-        &mut rng,
-        &key,
-        &msg,
-        &pubkey_bytes,
-        InnerEnvelopeMode::Base,
-    )
-    .unwrap();
+    let (envelope, _) =
+        Envelope::<Default>::seal_raw(&key, &nonce, &pubkey_bytes, InnerEnvelopeMode::Internal)
+            .unwrap();
     let envelope_bytes = envelope.serialize();
 
     let mut input = Vec::new();
     input.extend_from_slice(&pubkey_bytes[..]);
+    input.extend_from_slice(&masking_key[..]);
     input.extend_from_slice(&envelope_bytes);
 
     let r3 = RegistrationUpload::<Default>::deserialize(&input[..]).unwrap();
@@ -185,19 +175,11 @@ fn credential_request_roundtrip() {
     let alpha = random_ristretto_point();
     let alpha_bytes = alpha.to_arr().to_vec();
 
-    let client_e_kp = Default::generate_random_keypair(&mut rng);
+    let client_e_kp = KeyPair::<<Default as CipherSuite>::Group>::generate_random(&mut rng);
     let mut client_nonce = vec![0u8; NonceLen::to_usize()];
     rng.fill_bytes(&mut client_nonce);
 
-    let mut info = [0u8; MAX_INFO_LENGTH];
-    rng.fill_bytes(&mut info);
-
-    let ke1m: Vec<u8> = [
-        &client_nonce[..],
-        &serialize(&info.to_vec(), 2),
-        &client_e_kp.public(),
-    ]
-    .concat();
+    let ke1m: Vec<u8> = [&client_nonce[..], &client_e_kp.public()].concat();
 
     let mut input = Vec::new();
     input.extend_from_slice(&alpha_bytes);
@@ -225,47 +207,26 @@ fn credential_response_roundtrip() {
     let pt_bytes = pt.to_arr().to_vec();
 
     let mut rng = OsRng;
-    let skp = Default::generate_random_keypair(&mut rng);
-    let pubkey_bytes = skp.public().to_arr();
 
-    let mut key = [0u8; 32];
-    rng.fill_bytes(&mut key);
+    let mut masking_nonce = vec![0u8; 32];
+    rng.fill_bytes(&mut masking_nonce);
 
-    let mut msg = [0u8; 32];
-    rng.fill_bytes(&mut msg);
-
-    let (envelope, _) = Envelope::<sha2::Sha512>::seal_raw(
-        &mut rng,
-        &key,
-        &msg,
-        &pubkey_bytes,
-        InnerEnvelopeMode::Base,
-    )
-    .unwrap();
+    let mut masked_response =
+        vec![0u8; <PublicKey as SizedBytes>::Len::to_usize() + Envelope::<Default>::len()];
+    rng.fill_bytes(&mut masked_response);
 
-    let server_e_kp = Default::generate_random_keypair(&mut rng);
+    let server_e_kp = KeyPair::<<Default as CipherSuite>::Group>::generate_random(&mut rng);
     let mut mac = [0u8; MAC_SIZE];
     rng.fill_bytes(&mut mac);
     let mut server_nonce = vec![0u8; NonceLen::to_usize()];
     rng.fill_bytes(&mut server_nonce);
 
-    let mut e_info = [0u8; MAX_INFO_LENGTH];
-    rng.fill_bytes(&mut e_info);
-
-    let ke2m: Vec<u8> = [
-        &server_nonce[..],
-        &server_e_kp.public(),
-        &serialize(&e_info.to_vec(), 2),
-        &mac[..],
-    ]
-    .concat();
-
-    let serialized_envelope = envelope.serialize();
+    let ke2m: Vec<u8> = [&server_nonce[..], &server_e_kp.public(), &mac[..]].concat();
 
     let mut input = Vec::new();
     input.extend_from_slice(pt_bytes.as_slice());
-    input.extend_from_slice(&pubkey_bytes.as_slice());
-    input.extend_from_slice(&serialized_envelope);
+    input.extend_from_slice(&masking_nonce);
+    input.extend_from_slice(&masked_response);
     input.extend_from_slice(&ke2m[..]);
 
     let l2 = CredentialResponse::<Default>::deserialize(&input).unwrap();
@@ -279,8 +240,8 @@ fn credential_response_roundtrip() {
     assert!(match CredentialRequest::<Default>::deserialize(
         &[
             identity_bytes,
-            pubkey_bytes.to_vec(),
-            serialized_envelope,
+            masking_nonce.to_vec(),
+            masked_response,
             ke2m.to_vec()
         ]
         .concat()
@@ -309,7 +270,7 @@ fn client_login_roundtrip() {
     let mut rng = OsRng;
     let sc = <RistrettoPoint as Group>::random_nonzero_scalar(&mut rng);
 
-    let client_e_kp = Default::generate_random_keypair(&mut rng);
+    let client_e_kp = KeyPair::<<Default as CipherSuite>::Group>::generate_random(&mut rng);
     let mut client_nonce = vec![0u8; NonceLen::to_usize()];
     rng.fill_bytes(&mut client_nonce);
 
@@ -333,19 +294,11 @@ fn client_login_roundtrip() {
 fn ke1_message_roundtrip() {
     let mut rng = OsRng;
 
-    let client_e_kp = Default::generate_random_keypair(&mut rng);
+    let client_e_kp = KeyPair::<<Default as CipherSuite>::Group>::generate_random(&mut rng);
     let mut client_nonce = vec![0u8; NonceLen::to_usize()];
     rng.fill_bytes(&mut client_nonce);
 
-    let mut info = [0u8; MAX_INFO_LENGTH];
-    rng.fill_bytes(&mut info);
-
-    let ke1m: Vec<u8> = [
-        &client_nonce[..],
-        &serialize(&info.to_vec(), 2),
-        &client_e_kp.public(),
-    ]
-    .concat();
+    let ke1m: Vec<u8> = [&client_nonce[..], &client_e_kp.public()].concat();
     let reg = <TripleDH as KeyExchange<sha2::Sha512, RistrettoPoint>>::KE1Message::from_bytes::<
         Default,
     >(&ke1m[..])
@@ -358,21 +311,13 @@ fn ke1_message_roundtrip() {
 fn ke2_message_roundtrip() {
     let mut rng = OsRng;
 
-    let server_e_kp = Default::generate_random_keypair(&mut rng);
+    let server_e_kp = KeyPair::<<Default as CipherSuite>::Group>::generate_random(&mut rng);
     let mut mac = [0u8; MAC_SIZE];
     rng.fill_bytes(&mut mac);
     let mut server_nonce = vec![0u8; NonceLen::to_usize()];
     rng.fill_bytes(&mut server_nonce);
-    let mut e_info = [0u8; MAX_INFO_LENGTH];
-    rng.fill_bytes(&mut e_info);
-
-    let ke2m: Vec<u8> = [
-        &server_nonce[..],
-        &server_e_kp.public(),
-        &serialize(&e_info.to_vec(), 2),
-        &mac[..],
-    ]
-    .concat();
+
+    let ke2m: Vec<u8> = [&server_nonce[..], &server_e_kp.public(), &mac[..]].concat();
 
     let reg = <TripleDH as KeyExchange<sha2::Sha512, RistrettoPoint>>::KE2Message::from_bytes::<
         Default,
diff --git a/src/tests/full_test.rs b/src/tests/full_test.rs
index 3c9142e..4f86c2b 100644
--- a/src/tests/full_test.rs
+++ b/src/tests/full_test.rs
@@ -10,7 +10,7 @@ use crate::{
     errors::*,
     group::Group,
     key_exchange::tripledh::{NonceLen, TripleDH},
-    keypair::{PrivateKey, PublicKey, SizedBytesExt},
+    keypair::KeyPair,
     opaque::*,
     slow_hash::NoOpHash,
     tests::mock_rng::CycleRng,
@@ -44,16 +44,18 @@ pub struct TestVectorParameters {
     pub server_s_sk: Vec<u8>,
     pub server_e_pk: Vec<u8>,
     pub server_e_sk: Vec<u8>,
+    pub fake_sk: Vec<u8>,
+    pub credential_identifier: Vec<u8>,
     pub id_u: Vec<u8>,
     pub id_s: Vec<u8>,
     pub password: Vec<u8>,
     pub blinding_factor: Vec<u8>,
-    pub oprf_key: Vec<u8>,
+    pub oprf_seed: Vec<u8>,
+    pub masking_nonce: Vec<u8>,
     pub envelope_nonce: Vec<u8>,
     pub client_nonce: Vec<u8>,
     pub server_nonce: Vec<u8>,
-    pub info1: Vec<u8>,
-    pub einfo2: Vec<u8>,
+    pub context: Vec<u8>,
     pub registration_request: Vec<u8>,
     pub registration_response: Vec<u8>,
     pub registration_upload: Vec<u8>,
@@ -61,7 +63,6 @@ pub struct TestVectorParameters {
     pub credential_response: Vec<u8>,
     pub credential_finalization: Vec<u8>,
     client_registration_state: Vec<u8>,
-    server_registration_state: Vec<u8>,
     client_login_state: Vec<u8>,
     server_login_state: Vec<u8>,
     pub password_file: Vec<u8>,
@@ -70,40 +71,42 @@ pub struct TestVectorParameters {
 }
 
 static STR_PASSWORD: &str = "password";
+static STR_CREDENTIAL_IDENTIFIER: &str = "credential_identifier";
 
 static TEST_VECTOR: &str = r#"
 {
-    "client_s_pk": "6e0a6082dd29936c44b47ecb8a5fe72e4b321a0ac314b0080ca4c48afdabd215",
-    "client_s_sk": "3000848b34d9073885d427e766b7093cc13bdce992ef31cd00ba2c77ff074504",
-    "client_e_pk": "5260ad6eb47ac1db44babcce9327327d50e1a0133c0425acca3efcf41b119718",
-    "client_e_sk": "1526e0bed9af28830da956589d65768ed2a20d9689e82c90b89e4e33904e4009",
-    "server_s_pk": "c21a38653eb19437669bfa066a446b6eea7c1f253ac7adf4798d6c68e171b273",
-    "server_s_sk": "2a38b385e9fb7e0a89aa0f005c729b0c4e22eeedea8c105cf136d9c5c334880f",
-    "server_e_pk": "125f4a2dd9353c8c37a08527a323908835d3fbc374260d32e829d5c3fa81f325",
-    "server_e_sk": "7ef6ada36f3983c0f24705a29d3a453e3e17c28d347f226b24c0aa5ab12a700d",
+    "client_s_pk": "b47c69b4ea5e87139649349516c2842145993a2a00cc6e63d27c57f170475260",
+    "client_s_sk": "60a33dd8e1970aa3d2ed09c03ad0380e0cf628a669d3b7d030d3fea0dd7f5c06",
+    "client_e_pk": "5a513aecfa17dab422221a980819c680aea9a49947c7c0caca94fc61dcb4632c",
+    "client_e_sk": "9f42ca864614d4175e1540e4c56fe18362cb56b778dccf6b0a9446a23735dc03",
+    "server_s_pk": "8ed3fd51aa5e6931559fa6ae9be9829e609e441efbabb0846933fd5e30a3a268",
+    "server_s_sk": "a514a8842cd760449887fb2f943440b17073b5073691ceeaa0552210e693ea01",
+    "server_e_pk": "e8249649f7614f6268df01e54eb992043d49df04c98f8c8cea27c263d95dec4b",
+    "server_e_sk": "a4b66443250a0cc39ad9baae6ada72c243ddee53b712eb48933993230c13500f",
+    "fake_sk": "60a33dd8e1970aa3d2ed09c03ad0380e0cf628a669d3b7d030d3fea0dd7f5c06",
+    "credential_identifier": "637265644964656e746966696572",
     "id_u": "696455",
     "id_s": "696453",
     "password": "70617373776f7264",
-    "blinding_factor": "a32862d66eb57246321fb6b229e83786745c3afdf8957ebe38b01c17571ba105",
-    "oprf_key": "0851f5ec43e0b702bfcb9d8ec208085b51c0fc5200234901407c176327aa0b02",
-    "envelope_nonce": "78b006042d011bdca5d0058a978d2103a6d546de311a2e8cd025fbc67303a468",
-    "client_nonce": "43497a6f86ba31a6a7f399271fb3b4b4f82c4af086bc431ebaeab7d768ff8a64",
-    "server_nonce": "680cd27da18bc56d4317e7db61de1726a70ebe4e49aee38a3bdb6787cf85466e",
-    "info1": "696e666f31",
-    "einfo2": "65696e666f32",
-    "registration_request": "14ba86e53018ce5507d2bfb2d98ad3f60e302d826bff3410a5ec669c8e1ef17d",
-    "registration_response": "aee5c937a85acfbdbd71faf1c5519bfe9e44b0b7489dcc663df9f1ca5b520b6ac21a38653eb19437669bfa066a446b6eea7c1f253ac7adf4798d6c68e171b273",
-    "registration_upload": "6e0a6082dd29936c44b47ecb8a5fe72e4b321a0ac314b0080ca4c48afdabd2150278b006042d011bdca5d0058a978d2103a6d546de311a2e8cd025fbc67303a4687618b32fe2ec2a5c2b6efecec1e6e535106de80af68733673daf0b644965966fa3f279e532d7ecef363f8d55ff6df4c473cfb1049a73f632972bfcc6744185d13a671dd6678d49fb1629a6fbfbe266937378fd9e772c2dd72692d1a35c020010",
-    "credential_request": "14ba86e53018ce5507d2bfb2d98ad3f60e302d826bff3410a5ec669c8e1ef17d43497a6f86ba31a6a7f399271fb3b4b4f82c4af086bc431ebaeab7d768ff8a640005696e666f315260ad6eb47ac1db44babcce9327327d50e1a0133c0425acca3efcf41b119718",
-    "credential_response": "aee5c937a85acfbdbd71faf1c5519bfe9e44b0b7489dcc663df9f1ca5b520b6ac21a38653eb19437669bfa066a446b6eea7c1f253ac7adf4798d6c68e171b2730278b006042d011bdca5d0058a978d2103a6d546de311a2e8cd025fbc67303a4687618b32fe2ec2a5c2b6efecec1e6e535106de80af68733673daf0b644965966fa3f279e532d7ecef363f8d55ff6df4c473cfb1049a73f632972bfcc6744185d13a671dd6678d49fb1629a6fbfbe266937378fd9e772c2dd72692d1a35c020010680cd27da18bc56d4317e7db61de1726a70ebe4e49aee38a3bdb6787cf85466e125f4a2dd9353c8c37a08527a323908835d3fbc374260d32e829d5c3fa81f32500068a4c321f3c375613862ba83f7e5abb8f1d26dbd8035d39f192eb1324c2214457098054a2cbdf5d4ab2894eaf152af8c4be61d701c5a1ab1ec3e1cee5810898140b81771db0be",
-    "credential_finalization": "330aa8ac01bbc9d9642fe1c286187379efe12da14aceab86b22449d21f242d89adabc0295751b4e007beabc413ae9cbf9979e324749953705fd85b87c9c1b1a2",
-    "client_registration_state": "a32862d66eb57246321fb6b229e83786745c3afdf8957ebe38b01c17571ba10570617373776f7264",
-    "client_login_state": "a32862d66eb57246321fb6b229e83786745c3afdf8957ebe38b01c17571ba105006714ba86e53018ce5507d2bfb2d98ad3f60e302d826bff3410a5ec669c8e1ef17d43497a6f86ba31a6a7f399271fb3b4b4f82c4af086bc431ebaeab7d768ff8a640005696e666f315260ad6eb47ac1db44babcce9327327d50e1a0133c0425acca3efcf41b11971800401526e0bed9af28830da956589d65768ed2a20d9689e82c90b89e4e33904e400943497a6f86ba31a6a7f399271fb3b4b4f82c4af086bc431ebaeab7d768ff8a6470617373776f7264",
-    "server_registration_state": "0851f5ec43e0b702bfcb9d8ec208085b51c0fc5200234901407c176327aa0b02",
-    "server_login_state": "89cd93dce8f59cf7b187736c50cbc3ca2e0bbbad0a0be1ddee180a2d95db60695b384be2dd673434ef94bcdbb1b457f63f41cd79ed2422c021c34ca1433b70576920ebb950ad1c40c8c015ec6832e12427e755ba21d005f0b6d5d66d2368ab5ec6f32151fced5a3aa25472c425912242de3638ef57f28b0dd02a956064e5bb9b8fcd73add52d233a454d6b20125e9506a95aae8772ebbfac4d70efe1fb10078fd40f93d84aa7db53853ca74436c917c427cd5c2e408860937e6f7ab80816ca47",
-    "password_file": "0851f5ec43e0b702bfcb9d8ec208085b51c0fc5200234901407c176327aa0b026e0a6082dd29936c44b47ecb8a5fe72e4b321a0ac314b0080ca4c48afdabd2150278b006042d011bdca5d0058a978d2103a6d546de311a2e8cd025fbc67303a4687618b32fe2ec2a5c2b6efecec1e6e535106de80af68733673daf0b644965966fa3f279e532d7ecef363f8d55ff6df4c473cfb1049a73f632972bfcc6744185d13a671dd6678d49fb1629a6fbfbe266937378fd9e772c2dd72692d1a35c020010",
-    "export_key": "8197f91f0d4de1ab126d8dfd06abd0d5df420ce40a135ef376e4ffe515930f413632390e7dc3dcfd19afff62b9113e10eb6c359fc327df6e9ad4d0f06c242322",
-    "session_key": "8fcd73add52d233a454d6b20125e9506a95aae8772ebbfac4d70efe1fb10078fd40f93d84aa7db53853ca74436c917c427cd5c2e408860937e6f7ab80816ca47"
+    "blinding_factor": "08f845725404c823f477eb1e8f79dab63fdbbb2110a6c360fc98a4d2720e9d0a",
+    "oprf_seed": "1e7fddf167679cb1e83a179d4275034c09d2d745a1fec311a5e59ed30d0b80e2100ee8e6bbc996dc298f7f9e7dcc03c052853a02e4273d33c2973c7a6128affd",
+    "masking_nonce": "2b49d01802a69aacdad4979c503b96d08f44e4c67eaf82bbf6e71c6bb5473aa819359428f408bda29976beb0243c8a91fbeb2ee57840b90c62f4d87f88344db0",
+    "envelope_nonce": "f4351a2d4f1efb09877fbef82d44bff3a963b08cc727874aa75c5d57d604aa2e",
+    "client_nonce": "e56a024c1d89f05ff245b98ba097cdfdcc5c181b3d5e9d52100d421d3160f80a",
+    "server_nonce": "9b78591d87600abf26789c0691dd5f760d5620aa58e34181cb24503bf04c936a",
+    "context": "636f6e74657874",
+    "registration_request": "0cdc7df1cca989b56917c95127e59ec8f05bda7c606cb45e714bfa582b429832",
+    "registration_response": "e88418f5a9145287062e50b060e6f6790583ec8646430af1bff0a2729bf20d1b8ed3fd51aa5e6931559fa6ae9be9829e609e441efbabb0846933fd5e30a3a268",
+    "registration_upload": "d6f1486284e595707ae341a4d083d454477933b1bcf770bfc4087127c0a8e844e833f76e997aef5b46d2108811667183d08f0cc0a8465dac277287591cac1e42933ed23a2c9476cfa939854a40fc746c21606535b19f0a48cf8cc565f7c3e6df60a33dd8e1970aa3d2ed09c03ad0380e0cf628a669d3b7d030d3fea0dd7f5c0654e4188e55b7fe2eed8a7aee79ae6cfefabab86e7b7822f05bc422ac7e7a9acb968001b3dc5ead255a2d7599a7be60aa97ebed89808db20faa445e912f7df2da",
+    "credential_request": "0cdc7df1cca989b56917c95127e59ec8f05bda7c606cb45e714bfa582b429832e56a024c1d89f05ff245b98ba097cdfdcc5c181b3d5e9d52100d421d3160f80a5a513aecfa17dab422221a980819c680aea9a49947c7c0caca94fc61dcb4632c",
+    "credential_response": "e88418f5a9145287062e50b060e6f6790583ec8646430af1bff0a2729bf20d1b2b49d01802a69aacdad4979c503b96d08f44e4c67eaf82bbf6e71c6bb5473aa8718337df372fbb0de1beb29e2f4e6a2419858326ffe3f2a24172cca25e6344edd7db031cac3e206218eda4555d816f341c428317a4d37ed63441a278f78185b202b675b620e6f35056964d400c311cad23a1e6b0d9a91837d9d0021280bf0facf422961c96cffea530a24eb2486d4fa91adadaf7ac9a17d35b329b2add32e368a4b66443250a0cc39ad9baae6ada72c243ddee53b712eb48933993230c13500f2896e6f69e8610ced17584f34c09d872300bac6c99b8157392517ab9e9ed1f4aa163f8040d899cc77cf1f0ca2c4be6aef1616288cd3a6ac21989bdfc07bc4e94a284cf4c588583b2361195feab1ddcd390defde6282db2edc3eb535ede66404b",
+    "credential_finalization": "2f8c71675d7db1b32ed3daaa7f15fc353f6af536ab1199e41e43ece9871d8b69336b8c84c4906810bb87c1a0407bd5f5d780c7d10a1c94016103639e507cf6d0",
+    "client_registration_state": "08f845725404c823f477eb1e8f79dab63fdbbb2110a6c360fc98a4d2720e9d0a70617373776f7264",
+    "client_login_state": "08f845725404c823f477eb1e8f79dab63fdbbb2110a6c360fc98a4d2720e9d0a00600cdc7df1cca989b56917c95127e59ec8f05bda7c606cb45e714bfa582b429832e56a024c1d89f05ff245b98ba097cdfdcc5c181b3d5e9d52100d421d3160f80a5a513aecfa17dab422221a980819c680aea9a49947c7c0caca94fc61dcb4632c00409f42ca864614d4175e1540e4c56fe18362cb56b778dccf6b0a9446a23735dc03e56a024c1d89f05ff245b98ba097cdfdcc5c181b3d5e9d52100d421d3160f80a70617373776f7264",
+    "server_login_state": "a62f305635e341c151f5e51b89307940031337a0ad8f1369ddec9b672dc31f35d59be00eb66d77bda0079d6eda94809c863da359fef3a636704ae3fa1c9b9b2d18eb9b193528fbb392a5eab5da8068b7c276c8fe00814213ddd70d02157902bebfce850b403aaa4c99f8dbd5ff50d4ad3e703fb564a3fc474861e3f69d7c9a90037d3dbf36f215082644d5c5bc91e138f9665e7bc538f4bc70f97c91dfcd029b1c027b03dc99137478b3570d9da27922b88a8784f1c2f07cd04a0db04246531a",
+    "password_file": "d6f1486284e595707ae341a4d083d454477933b1bcf770bfc4087127c0a8e844e833f76e997aef5b46d2108811667183d08f0cc0a8465dac277287591cac1e42933ed23a2c9476cfa939854a40fc746c21606535b19f0a48cf8cc565f7c3e6df60a33dd8e1970aa3d2ed09c03ad0380e0cf628a669d3b7d030d3fea0dd7f5c0654e4188e55b7fe2eed8a7aee79ae6cfefabab86e7b7822f05bc422ac7e7a9acb968001b3dc5ead255a2d7599a7be60aa97ebed89808db20faa445e912f7df2da",
+    "export_key": "f1abeb7ab0a43ff1924b59d744053b271d999f341eedc740f1f62d785d19bec939479e5e39f2ec25f5ef712ecd10a085653ad1ed9049092cb2a3d44d6cc205ba",
+    "session_key": "037d3dbf36f215082644d5c5bc91e138f9665e7bc538f4bc70f97c91dfcd029b1c027b03dc99137478b3570d9da27922b88a8784f1c2f07cd04a0db04246531a"
 }
 "#;
 
@@ -123,16 +126,18 @@ 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(),
+        fake_sk: decode(&values, "fake_sk").unwrap(),
+        credential_identifier: decode(&values, "credential_identifier").unwrap(),
         id_u: decode(&values, "id_u").unwrap(),
         id_s: decode(&values, "id_s").unwrap(),
         password: decode(&values, "password").unwrap(),
         blinding_factor: decode(&values, "blinding_factor").unwrap(),
-        oprf_key: decode(&values, "oprf_key").unwrap(),
+        oprf_seed: decode(&values, "oprf_seed").unwrap(),
+        masking_nonce: decode(&values, "masking_nonce").unwrap(),
         envelope_nonce: decode(&values, "envelope_nonce").unwrap(),
         client_nonce: decode(&values, "client_nonce").unwrap(),
         server_nonce: decode(&values, "server_nonce").unwrap(),
-        info1: decode(&values, "info1").unwrap(),
-        einfo2: decode(&values, "einfo2").unwrap(),
+        context: decode(&values, "context").unwrap(),
         registration_request: decode(&values, "registration_request").unwrap(),
         registration_response: decode(&values, "registration_response").unwrap(),
         registration_upload: decode(&values, "registration_upload").unwrap(),
@@ -141,7 +146,6 @@ fn populate_test_vectors(values: &Value) -> TestVectorParameters {
         credential_finalization: decode(&values, "credential_finalization").unwrap(),
         client_registration_state: decode(&values, "client_registration_state").unwrap(),
         client_login_state: decode(&values, "client_login_state").unwrap(),
-        server_registration_state: decode(&values, "server_registration_state").unwrap(),
         server_login_state: decode(&values, "server_login_state").unwrap(),
         password_file: decode(&values, "password_file").unwrap(),
         export_key: decode(&values, "export_key").unwrap(),
@@ -160,6 +164,13 @@ 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!(
+            "\"credential_identifier\": \"{}\",\n",
+            hex::encode(&p.credential_identifier)
+        )
+        .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());
@@ -170,7 +181,14 @@ fn stringify_test_vectors(p: &TestVectorParameters) -> String {
         )
         .as_str(),
     );
-    s.push_str(format!("\"oprf_key\": \"{}\",\n", hex::encode(&p.oprf_key)).as_str());
+    s.push_str(format!("\"oprf_seed\": \"{}\",\n", hex::encode(&p.oprf_seed)).as_str());
+    s.push_str(
+        format!(
+            "\"masking_nonce\": \"{}\",\n",
+            hex::encode(&p.masking_nonce)
+        )
+        .as_str(),
+    );
     s.push_str(
         format!(
             "\"envelope_nonce\": \"{}\",\n",
@@ -180,8 +198,7 @@ fn stringify_test_vectors(p: &TestVectorParameters) -> String {
     );
     s.push_str(format!("\"client_nonce\": \"{}\",\n", hex::encode(&p.client_nonce)).as_str());
     s.push_str(format!("\"server_nonce\": \"{}\",\n", hex::encode(&p.server_nonce)).as_str());
-    s.push_str(format!("\"info1\": \"{}\",\n", hex::encode(&p.info1)).as_str());
-    s.push_str(format!("\"einfo2\": \"{}\",\n", hex::encode(&p.einfo2)).as_str());
+    s.push_str(format!("\"context\": \"{}\",\n", hex::encode(&p.context)).as_str());
     s.push_str(
         format!(
             "\"registration_request\": \"{}\",\n",
@@ -238,13 +255,6 @@ fn stringify_test_vectors(p: &TestVectorParameters) -> String {
         )
         .as_str(),
     );
-    s.push_str(
-        format!(
-            "\"server_registration_state\": \"{}\",\n",
-            hex::encode(&p.server_registration_state)
-        )
-        .as_str(),
-    );
     s.push_str(
         format!(
             "\"server_login_state\": \"{}\",\n",
@@ -269,15 +279,20 @@ fn generate_parameters<CS: CipherSuite>() -> TestVectorParameters {
     let mut rng = OsRng;
 
     // Inputs
-    let server_s_kp = CS::generate_random_keypair(&mut rng);
-    let server_e_kp = CS::generate_random_keypair(&mut rng);
-    let client_s_kp = CS::generate_random_keypair(&mut rng);
-    let client_e_kp = CS::generate_random_keypair(&mut rng);
+    let server_s_kp = KeyPair::<CS::Group>::generate_random(&mut rng);
+    let server_e_kp = KeyPair::<CS::Group>::generate_random(&mut rng);
+    let client_s_kp = KeyPair::<CS::Group>::generate_random(&mut rng);
+    let client_e_kp = KeyPair::<CS::Group>::generate_random(&mut rng);
+    let fake_kp = KeyPair::<CS::Group>::generate_random(&mut rng);
+    let credential_identifier = b"credIdentifier";
     let id_u = b"idU";
     let id_s = b"idS";
     let password = b"password";
-    let mut oprf_key_raw = [0u8; 32];
-    rng.fill_bytes(&mut oprf_key_raw);
+    let context = b"context";
+    let mut oprf_seed = [0u8; 64];
+    rng.fill_bytes(&mut oprf_seed);
+    let mut masking_nonce = [0u8; 64];
+    rng.fill_bytes(&mut masking_nonce);
     let mut envelope_nonce = [0u8; 32];
     rng.fill_bytes(&mut envelope_nonce);
     let mut client_nonce = vec![0u8; NonceLen::to_usize()];
@@ -285,12 +300,15 @@ fn generate_parameters<CS: CipherSuite>() -> TestVectorParameters {
     let mut server_nonce = vec![0u8; NonceLen::to_usize()];
     rng.fill_bytes(&mut server_nonce);
 
+    let fake_sk: Vec<u8> = fake_kp.private().to_vec();
+    let server_setup = ServerSetup::<CS>::deserialize(
+        &[&oprf_seed, &server_s_kp.private().to_arr()[..], &fake_sk].concat(),
+    )
+    .unwrap();
+
     let blinding_factor = CS::Group::random_nonzero_scalar(&mut rng);
     let blinding_factor_bytes = CS::Group::scalar_as_bytes(&blinding_factor).clone();
 
-    let info1 = b"info1";
-    let einfo2 = b"einfo2";
-
     let mut blinding_factor_registration_rng = CycleRng::new(blinding_factor_bytes.to_vec());
     let client_registration_start_result =
         ClientRegistration::<CS>::start(&mut blinding_factor_registration_rng, password).unwrap();
@@ -307,20 +325,16 @@ fn generate_parameters<CS: CipherSuite>() -> TestVectorParameters {
         .to_vec();
     let client_registration_state = client_registration_start_result.state.serialize().to_vec();
 
-    let mut oprf_key_rng = CycleRng::new(oprf_key_raw.to_vec());
     let server_registration_start_result = ServerRegistration::<CS>::start(
-        &mut oprf_key_rng,
+        &server_setup,
         client_registration_start_result.message,
-        server_s_kp.public(),
+        &credential_identifier[..],
     )
     .unwrap();
     let registration_response_bytes = server_registration_start_result
         .message
         .serialize()
         .to_vec();
-    let oprf_key_bytes =
-        CS::Group::scalar_as_bytes(&server_registration_start_result.state.oprf_key).clone();
-    let server_registration_state = server_registration_start_result.state.serialize().to_vec();
 
     let mut client_s_sk_and_nonce: Vec<u8> = Vec::new();
     client_s_sk_and_nonce.extend_from_slice(&client_s_kp.private().to_arr());
@@ -332,7 +346,9 @@ fn generate_parameters<CS: CipherSuite>() -> TestVectorParameters {
         .finish(
             &mut finish_registration_rng,
             server_registration_start_result.message,
-            ClientRegistrationFinishParameters::WithIdentifiers(id_u.to_vec(), id_s.to_vec()),
+            ClientRegistrationFinishParameters::WithIdentifiers(
+                Identifiers::ClientAndServerIdentifiers(id_u.to_vec(), id_s.to_vec()),
+            ),
         )
         .unwrap();
     let registration_upload_bytes = client_registration_finish_result
@@ -340,10 +356,7 @@ fn generate_parameters<CS: CipherSuite>() -> TestVectorParameters {
         .serialize()
         .to_vec();
 
-    let password_file = server_registration_start_result
-        .state
-        .finish(client_registration_finish_result.message)
-        .unwrap();
+    let password_file = ServerRegistration::finish(client_registration_finish_result.message);
     let password_file_bytes = password_file.serialize();
 
     let mut client_login_start: Vec<u8> = Vec::new();
@@ -352,17 +365,14 @@ fn generate_parameters<CS: CipherSuite>() -> TestVectorParameters {
     client_login_start.extend_from_slice(&client_nonce);
 
     let mut client_login_start_rng = CycleRng::new(client_login_start);
-    let client_login_start_result = ClientLogin::<CS>::start(
-        &mut client_login_start_rng,
-        password,
-        ClientLoginStartParameters::WithInfo(info1.to_vec()),
-    )
-    .unwrap();
+    let client_login_start_result =
+        ClientLogin::<CS>::start(&mut client_login_start_rng, password).unwrap();
     let credential_request_bytes = client_login_start_result.message.serialize().to_vec();
     let client_login_state = client_login_start_result.state.serialize().to_vec();
 
     let mut server_e_sk_and_nonce_rng = CycleRng::new(
         [
+            masking_nonce.to_vec(),
             server_e_kp.private().to_arr().to_vec(),
             server_nonce.to_vec(),
         ]
@@ -370,13 +380,13 @@ fn generate_parameters<CS: CipherSuite>() -> TestVectorParameters {
     );
     let server_login_start_result = ServerLogin::<CS>::start(
         &mut server_e_sk_and_nonce_rng,
-        password_file,
-        server_s_kp.private(),
+        &server_setup,
+        Some(password_file),
         client_login_start_result.message,
-        ServerLoginStartParameters::WithInfoAndIdentifiers(
-            einfo2.to_vec(),
-            id_u.to_vec(),
-            id_s.to_vec(),
+        credential_identifier,
+        ServerLoginStartParameters::WithContextAndIdentifiers(
+            context.to_vec(),
+            Identifiers::ClientAndServerIdentifiers(id_u.to_vec(), id_s.to_vec()),
         ),
     )
     .unwrap();
@@ -387,7 +397,10 @@ fn generate_parameters<CS: CipherSuite>() -> TestVectorParameters {
         .state
         .finish(
             server_login_start_result.message,
-            ClientLoginFinishParameters::WithIdentifiers(id_u.to_vec(), id_s.to_vec()),
+            ClientLoginFinishParameters::WithContextAndIdentifiers(
+                context.to_vec(),
+                Identifiers::ClientAndServerIdentifiers(id_u.to_vec(), id_s.to_vec()),
+            ),
         )
         .unwrap();
     let credential_finalization_bytes = client_login_finish_result.message.serialize();
@@ -401,16 +414,18 @@ fn generate_parameters<CS: CipherSuite>() -> TestVectorParameters {
         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(),
+        fake_sk,
+        credential_identifier: credential_identifier.to_vec(),
         id_u: id_u.to_vec(),
         id_s: id_s.to_vec(),
         password: password.to_vec(),
         blinding_factor: blinding_factor_bytes.to_vec(),
-        oprf_key: oprf_key_bytes.to_vec(),
+        oprf_seed: oprf_seed.to_vec(),
+        masking_nonce: masking_nonce.to_vec(),
         envelope_nonce: envelope_nonce.to_vec(),
         client_nonce: client_nonce.to_vec(),
         server_nonce: server_nonce.to_vec(),
-        info1: info1.to_vec(),
-        einfo2: einfo2.to_vec(),
+        context: context.to_vec(),
         registration_request: registration_request_bytes,
         registration_response: registration_response_bytes,
         registration_upload: registration_upload_bytes,
@@ -419,7 +434,6 @@ fn generate_parameters<CS: CipherSuite>() -> TestVectorParameters {
         credential_finalization: credential_finalization_bytes,
         password_file: password_file_bytes,
         client_registration_state,
-        server_registration_state,
         client_login_state,
         server_login_state,
         session_key: client_login_finish_result.session_key,
@@ -463,7 +477,7 @@ fn test_serialization() -> Result<(), ProtocolError> {
             serde_json::to_string(&client_registration_start_result.message).unwrap();
         assert_eq!(
             registration_request_json,
-            r#""FLqG5TAYzlUH0r+y2YrT9g4wLYJr/zQQpexmnI4e8X0=""#
+            r#""DNx98cypibVpF8lRJ+WeyPBb2nxgbLRecUv6WCtCmDI=""#
         );
         let registration_request: RegistrationRequest<RistrettoSha5123dhNoSlowHash> =
             serde_json::from_str(&registration_request_json).unwrap();
@@ -489,21 +503,26 @@ fn test_serialization() -> Result<(), ProtocolError> {
 #[test]
 fn test_registration_response() -> Result<(), ProtocolError> {
     let parameters = populate_test_vectors(&serde_json::from_str(TEST_VECTOR).unwrap());
-    let mut oprf_key_rng = CycleRng::new(parameters.oprf_key);
+
+    let server_setup = ServerSetup::<RistrettoSha5123dhNoSlowHash>::deserialize(
+        &[
+            &parameters.oprf_seed[..],
+            &parameters.server_s_sk[..],
+            &parameters.fake_sk[..],
+        ]
+        .concat(),
+    )?;
+
     let server_registration_start_result =
         ServerRegistration::<RistrettoSha5123dhNoSlowHash>::start(
-            &mut oprf_key_rng,
+            &server_setup,
             RegistrationRequest::deserialize(&parameters.registration_request[..])?,
-            &PublicKey::from_bytes(&parameters.server_s_pk[..])?,
+            &parameters.credential_identifier,
         )?;
     assert_eq!(
         hex::encode(parameters.registration_response),
         hex::encode(server_registration_start_result.message.serialize())
     );
-    assert_eq!(
-        hex::encode(&parameters.server_registration_state),
-        hex::encode(server_registration_start_result.state.serialize())
-    );
     Ok(())
 }
 
@@ -520,7 +539,9 @@ fn test_registration_upload() -> Result<(), ProtocolError> {
     .finish(
         &mut finish_registration_rng,
         RegistrationResponse::deserialize(&parameters.registration_response[..])?,
-        ClientRegistrationFinishParameters::WithIdentifiers(parameters.id_u, parameters.id_s),
+        ClientRegistrationFinishParameters::WithIdentifiers(
+            Identifiers::ClientAndServerIdentifiers(parameters.id_u, parameters.id_s),
+        ),
     )?;
 
     assert_eq!(
@@ -539,12 +560,11 @@ fn test_registration_upload() -> Result<(), ProtocolError> {
 fn test_password_file() -> Result<(), ProtocolError> {
     let parameters = populate_test_vectors(&serde_json::from_str(TEST_VECTOR).unwrap());
 
-    let server_registration = ServerRegistration::<RistrettoSha5123dhNoSlowHash>::deserialize(
-        &parameters.server_registration_state[..],
-    )?;
-    let password_file = server_registration.finish(RegistrationUpload::deserialize(
-        &parameters.registration_upload[..],
-    )?)?;
+    let password_file = ServerRegistration::finish(RegistrationUpload::<
+        RistrettoSha5123dhNoSlowHash,
+    >::deserialize(
+        &parameters.registration_upload[..]
+    )?);
 
     assert_eq!(
         hex::encode(parameters.password_file),
@@ -567,7 +587,6 @@ fn test_credential_request() -> Result<(), ProtocolError> {
     let client_login_start_result = ClientLogin::<RistrettoSha5123dhNoSlowHash>::start(
         &mut client_login_start_rng,
         &parameters.password,
-        ClientLoginStartParameters::WithInfo(parameters.info1),
     )?;
     assert_eq!(
         hex::encode(&parameters.credential_request),
@@ -584,25 +603,38 @@ fn test_credential_request() -> Result<(), ProtocolError> {
 fn test_credential_response() -> Result<(), ProtocolError> {
     let parameters = populate_test_vectors(&serde_json::from_str(TEST_VECTOR).unwrap());
 
-    let mut server_e_sk_and_nonce_rng =
-        CycleRng::new([parameters.server_e_sk, parameters.server_nonce].concat());
+    let server_setup = ServerSetup::<RistrettoSha5123dhNoSlowHash>::deserialize(
+        &[
+            &parameters.oprf_seed[..],
+            &parameters.server_s_sk[..],
+            &parameters.fake_sk[..],
+        ]
+        .concat(),
+    )?;
+
+    let mut server_e_sk_and_nonce_rng = CycleRng::new(
+        [
+            parameters.masking_nonce,
+            parameters.server_e_sk,
+            parameters.server_nonce,
+        ]
+        .concat(),
+    );
     let server_login_start_result = ServerLogin::<RistrettoSha5123dhNoSlowHash>::start(
         &mut server_e_sk_and_nonce_rng,
-        ServerRegistration::deserialize(&parameters.password_file[..])?,
-        &PrivateKey::from_bytes(&parameters.server_s_sk[..])?,
+        &server_setup,
+        Some(ServerRegistration::deserialize(
+            &parameters.password_file[..],
+        )?),
         CredentialRequest::<RistrettoSha5123dhNoSlowHash>::deserialize(
             &parameters.credential_request[..],
         )?,
-        ServerLoginStartParameters::WithInfoAndIdentifiers(
-            parameters.einfo2.to_vec(),
-            parameters.id_u,
-            parameters.id_s,
+        &parameters.credential_identifier,
+        ServerLoginStartParameters::WithContextAndIdentifiers(
+            parameters.context,
+            Identifiers::ClientAndServerIdentifiers(parameters.id_u, parameters.id_s),
         ),
     )?;
-    assert_eq!(
-        hex::encode(&parameters.info1),
-        hex::encode(server_login_start_result.plain_info),
-    );
     assert_eq!(
         hex::encode(&parameters.credential_response),
         hex::encode(server_login_start_result.message.serialize())
@@ -625,13 +657,12 @@ fn test_credential_finalization() -> Result<(), ProtocolError> {
         CredentialResponse::<RistrettoSha5123dhNoSlowHash>::deserialize(
             &parameters.credential_response[..],
         )?,
-        ClientLoginFinishParameters::WithIdentifiers(parameters.id_u, parameters.id_s),
+        ClientLoginFinishParameters::WithContextAndIdentifiers(
+            parameters.context,
+            Identifiers::ClientAndServerIdentifiers(parameters.id_u, parameters.id_s),
+        ),
     )?;
 
-    assert_eq!(
-        hex::encode(&parameters.einfo2),
-        hex::encode(&client_login_finish_result.confidential_info)
-    );
     assert_eq!(
         hex::encode(&parameters.server_s_pk),
         hex::encode(&client_login_finish_result.server_s_pk.to_arr().to_vec())
@@ -675,9 +706,10 @@ fn test_complete_flow(
     registration_password: &[u8],
     login_password: &[u8],
 ) -> Result<(), ProtocolError> {
+    let credential_identifier = b"credentialIdentifier";
     let mut client_rng = OsRng;
     let mut server_rng = OsRng;
-    let server_kp = RistrettoSha5123dhNoSlowHash::generate_random_keypair(&mut server_rng);
+    let server_setup = ServerSetup::<RistrettoSha5123dhNoSlowHash>::new(&mut server_rng);
     let client_registration_start_result =
         ClientRegistration::<RistrettoSha5123dhNoSlowHash>::start(
             &mut client_rng,
@@ -685,28 +717,24 @@ fn test_complete_flow(
         )?;
     let server_registration_start_result =
         ServerRegistration::<RistrettoSha5123dhNoSlowHash>::start(
-            &mut server_rng,
+            &server_setup,
             client_registration_start_result.message,
-            server_kp.public(),
+            credential_identifier,
         )?;
     let client_registration_finish_result = client_registration_start_result.state.finish(
         &mut client_rng,
         server_registration_start_result.message,
         ClientRegistrationFinishParameters::default(),
     )?;
-    let p_file = server_registration_start_result
-        .state
-        .finish(client_registration_finish_result.message)?;
-    let client_login_start_result = ClientLogin::<RistrettoSha5123dhNoSlowHash>::start(
-        &mut client_rng,
-        login_password,
-        ClientLoginStartParameters::default(),
-    )?;
+    let p_file = ServerRegistration::finish(client_registration_finish_result.message);
+    let client_login_start_result =
+        ClientLogin::<RistrettoSha5123dhNoSlowHash>::start(&mut client_rng, login_password)?;
     let server_login_start_result = ServerLogin::<RistrettoSha5123dhNoSlowHash>::start(
         &mut server_rng,
-        p_file,
-        &server_kp.private(),
+        &server_setup,
+        Some(p_file),
         client_login_start_result.message,
+        credential_identifier,
         ServerLoginStartParameters::default(),
     )?;
 
@@ -772,40 +800,11 @@ fn test_zeroize_client_registration_start() -> Result<(), ProtocolError> {
     Ok(())
 }
 
-#[test]
-fn test_zeroize_server_registration_start() -> Result<(), ProtocolError> {
-    let mut client_rng = OsRng;
-    let mut server_rng = OsRng;
-    let server_kp = RistrettoSha5123dhNoSlowHash::generate_random_keypair(&mut server_rng);
-    let client_registration_start_result =
-        ClientRegistration::<RistrettoSha5123dhNoSlowHash>::start(
-            &mut client_rng,
-            STR_PASSWORD.as_bytes(),
-        )?;
-    let server_registration_start_result =
-        ServerRegistration::<RistrettoSha5123dhNoSlowHash>::start(
-            &mut server_rng,
-            client_registration_start_result.message,
-            server_kp.public(),
-        )?;
-
-    let mut state = server_registration_start_result.state;
-    let ptrs = state.as_byte_ptrs();
-    state.zeroize();
-
-    for (ptr, len) in ptrs {
-        let bytes = unsafe { from_raw_parts(ptr, len) };
-        assert!(bytes.iter().all(|&x| x == 0));
-    }
-
-    Ok(())
-}
-
 #[test]
 fn test_zeroize_client_registration_finish() -> Result<(), ProtocolError> {
     let mut client_rng = OsRng;
     let mut server_rng = OsRng;
-    let server_kp = RistrettoSha5123dhNoSlowHash::generate_random_keypair(&mut server_rng);
+    let server_setup = ServerSetup::<RistrettoSha5123dhNoSlowHash>::new(&mut server_rng);
     let client_registration_start_result =
         ClientRegistration::<RistrettoSha5123dhNoSlowHash>::start(
             &mut client_rng,
@@ -813,9 +812,9 @@ fn test_zeroize_client_registration_finish() -> Result<(), ProtocolError> {
         )?;
     let server_registration_start_result =
         ServerRegistration::<RistrettoSha5123dhNoSlowHash>::start(
-            &mut server_rng,
+            &server_setup,
             client_registration_start_result.message,
-            server_kp.public(),
+            STR_CREDENTIAL_IDENTIFIER.as_bytes(),
         )?;
     let client_registration_finish_result = client_registration_start_result.state.finish(
         &mut client_rng,
@@ -839,7 +838,7 @@ fn test_zeroize_client_registration_finish() -> Result<(), ProtocolError> {
 fn test_zeroize_server_registration_finish() -> Result<(), ProtocolError> {
     let mut client_rng = OsRng;
     let mut server_rng = OsRng;
-    let server_kp = RistrettoSha5123dhNoSlowHash::generate_random_keypair(&mut server_rng);
+    let server_setup = ServerSetup::<RistrettoSha5123dhNoSlowHash>::new(&mut server_rng);
     let client_registration_start_result =
         ClientRegistration::<RistrettoSha5123dhNoSlowHash>::start(
             &mut client_rng,
@@ -847,18 +846,16 @@ fn test_zeroize_server_registration_finish() -> Result<(), ProtocolError> {
         )?;
     let server_registration_start_result =
         ServerRegistration::<RistrettoSha5123dhNoSlowHash>::start(
-            &mut server_rng,
+            &server_setup,
             client_registration_start_result.message,
-            server_kp.public(),
+            STR_CREDENTIAL_IDENTIFIER.as_bytes(),
         )?;
     let client_registration_finish_result = client_registration_start_result.state.finish(
         &mut client_rng,
         server_registration_start_result.message,
         ClientRegistrationFinishParameters::default(),
     )?;
-    let p_file = server_registration_start_result
-        .state
-        .finish(client_registration_finish_result.message)?;
+    let p_file = ServerRegistration::finish(client_registration_finish_result.message);
 
     let mut state = p_file;
     let ptrs = state.as_byte_ptrs();
@@ -878,7 +875,6 @@ fn test_zeroize_client_login_start() -> Result<(), ProtocolError> {
     let client_login_start_result = ClientLogin::<RistrettoSha5123dhNoSlowHash>::start(
         &mut client_rng,
         STR_PASSWORD.as_bytes(),
-        ClientLoginStartParameters::default(),
     )?;
 
     let mut state = client_login_start_result.state;
@@ -897,7 +893,7 @@ fn test_zeroize_client_login_start() -> Result<(), ProtocolError> {
 fn test_zeroize_server_login_start() -> Result<(), ProtocolError> {
     let mut client_rng = OsRng;
     let mut server_rng = OsRng;
-    let server_kp = RistrettoSha5123dhNoSlowHash::generate_random_keypair(&mut server_rng);
+    let server_setup = ServerSetup::<RistrettoSha5123dhNoSlowHash>::new(&mut server_rng);
     let client_registration_start_result =
         ClientRegistration::<RistrettoSha5123dhNoSlowHash>::start(
             &mut client_rng,
@@ -905,28 +901,26 @@ fn test_zeroize_server_login_start() -> Result<(), ProtocolError> {
         )?;
     let server_registration_start_result =
         ServerRegistration::<RistrettoSha5123dhNoSlowHash>::start(
-            &mut server_rng,
+            &server_setup,
             client_registration_start_result.message,
-            server_kp.public(),
+            STR_CREDENTIAL_IDENTIFIER.as_bytes(),
         )?;
     let client_registration_finish_result = client_registration_start_result.state.finish(
         &mut client_rng,
         server_registration_start_result.message,
         ClientRegistrationFinishParameters::default(),
     )?;
-    let p_file = server_registration_start_result
-        .state
-        .finish(client_registration_finish_result.message)?;
+    let p_file = ServerRegistration::finish(client_registration_finish_result.message);
     let client_login_start_result = ClientLogin::<RistrettoSha5123dhNoSlowHash>::start(
         &mut client_rng,
         STR_PASSWORD.as_bytes(),
-        ClientLoginStartParameters::default(),
     )?;
     let server_login_start_result = ServerLogin::<RistrettoSha5123dhNoSlowHash>::start(
         &mut server_rng,
-        p_file,
-        &server_kp.private(),
+        &server_setup,
+        Some(p_file),
         client_login_start_result.message,
+        STR_CREDENTIAL_IDENTIFIER.as_bytes(),
         ServerLoginStartParameters::default(),
     )?;
 
@@ -946,7 +940,7 @@ fn test_zeroize_server_login_start() -> Result<(), ProtocolError> {
 fn test_zeroize_client_login_finish() -> Result<(), ProtocolError> {
     let mut client_rng = OsRng;
     let mut server_rng = OsRng;
-    let server_kp = RistrettoSha5123dhNoSlowHash::generate_random_keypair(&mut server_rng);
+    let server_setup = ServerSetup::<RistrettoSha5123dhNoSlowHash>::new(&mut server_rng);
     let client_registration_start_result =
         ClientRegistration::<RistrettoSha5123dhNoSlowHash>::start(
             &mut client_rng,
@@ -954,28 +948,26 @@ fn test_zeroize_client_login_finish() -> Result<(), ProtocolError> {
         )?;
     let server_registration_start_result =
         ServerRegistration::<RistrettoSha5123dhNoSlowHash>::start(
-            &mut server_rng,
+            &server_setup,
             client_registration_start_result.message,
-            server_kp.public(),
+            STR_CREDENTIAL_IDENTIFIER.as_bytes(),
         )?;
     let client_registration_finish_result = client_registration_start_result.state.finish(
         &mut client_rng,
         server_registration_start_result.message,
         ClientRegistrationFinishParameters::default(),
     )?;
-    let p_file = server_registration_start_result
-        .state
-        .finish(client_registration_finish_result.message)?;
+    let p_file = ServerRegistration::finish(client_registration_finish_result.message);
     let client_login_start_result = ClientLogin::<RistrettoSha5123dhNoSlowHash>::start(
         &mut client_rng,
         STR_PASSWORD.as_bytes(),
-        ClientLoginStartParameters::default(),
     )?;
     let server_login_start_result = ServerLogin::<RistrettoSha5123dhNoSlowHash>::start(
         &mut server_rng,
-        p_file,
-        &server_kp.private(),
+        &server_setup,
+        Some(p_file),
         client_login_start_result.message,
+        STR_CREDENTIAL_IDENTIFIER.as_bytes(),
         ServerLoginStartParameters::default(),
     )?;
     let client_login_finish_result = client_login_start_result.state.finish(
@@ -999,7 +991,7 @@ fn test_zeroize_client_login_finish() -> Result<(), ProtocolError> {
 fn test_zeroize_server_login_finish() -> Result<(), ProtocolError> {
     let mut client_rng = OsRng;
     let mut server_rng = OsRng;
-    let server_kp = RistrettoSha5123dhNoSlowHash::generate_random_keypair(&mut server_rng);
+    let server_setup = ServerSetup::<RistrettoSha5123dhNoSlowHash>::new(&mut server_rng);
     let client_registration_start_result =
         ClientRegistration::<RistrettoSha5123dhNoSlowHash>::start(
             &mut client_rng,
@@ -1007,28 +999,26 @@ fn test_zeroize_server_login_finish() -> Result<(), ProtocolError> {
         )?;
     let server_registration_start_result =
         ServerRegistration::<RistrettoSha5123dhNoSlowHash>::start(
-            &mut server_rng,
+            &server_setup,
             client_registration_start_result.message,
-            server_kp.public(),
+            STR_CREDENTIAL_IDENTIFIER.as_bytes(),
         )?;
     let client_registration_finish_result = client_registration_start_result.state.finish(
         &mut client_rng,
         server_registration_start_result.message,
         ClientRegistrationFinishParameters::default(),
     )?;
-    let p_file = server_registration_start_result
-        .state
-        .finish(client_registration_finish_result.message)?;
+    let p_file = ServerRegistration::finish(client_registration_finish_result.message);
     let client_login_start_result = ClientLogin::<RistrettoSha5123dhNoSlowHash>::start(
         &mut client_rng,
         STR_PASSWORD.as_bytes(),
-        ClientLoginStartParameters::default(),
     )?;
     let server_login_start_result = ServerLogin::<RistrettoSha5123dhNoSlowHash>::start(
         &mut server_rng,
-        p_file,
-        &server_kp.private(),
+        &server_setup,
+        Some(p_file),
         client_login_start_result.message,
+        STR_CREDENTIAL_IDENTIFIER.as_bytes(),
         ServerLoginStartParameters::default(),
     )?;
     let client_login_finish_result = client_login_start_result.state.finish(
diff --git a/src/tests/opaque_test_vectors.rs b/src/tests/opaque_test_vectors.rs
index 68db601..e6c6d3b 100644
--- a/src/tests/opaque_test_vectors.rs
+++ b/src/tests/opaque_test_vectors.rs
@@ -4,16 +4,12 @@
 // LICENSE file in the root directory of this source tree.
 
 use crate::{
-    ciphersuite::CipherSuite,
-    errors::*,
-    key_exchange::tripledh::TripleDH,
-    keypair::{PrivateKey, PublicKey, SizedBytesExt},
-    opaque::*,
-    slow_hash::NoOpHash,
-    tests::mock_rng::CycleRng,
-    *,
+    ciphersuite::CipherSuite, errors::*, key_exchange::tripledh::TripleDH, keypair::PrivateKey,
+    opaque::*, slow_hash::NoOpHash, tests::mock_rng::CycleRng, *,
 };
 use curve25519_dalek::ristretto::RistrettoPoint;
+use generic_array::typenum::Unsigned;
+use generic_bytes::SizedBytes;
 use serde_json::Value;
 
 // Tests
@@ -35,20 +31,24 @@ pub enum EnvelopeMode {
 
 #[allow(non_snake_case)]
 pub struct TestVectorParameters {
+    pub dummy_private_key: Vec<u8>,
+    pub dummy_masking_key: Vec<u8>,
+    pub context: Vec<u8>,
     pub envelope_mode: EnvelopeMode,
-    pub client_public_key: Vec<u8>,
-    pub client_private_key: Vec<u8>,
+    pub client_private_key: Option<Vec<u8>>,
     pub client_keyshare: Vec<u8>,
     pub client_private_keyshare: Vec<u8>,
     pub server_public_key: Vec<u8>,
     pub server_private_key: Vec<u8>,
     pub server_keyshare: Vec<u8>,
     pub server_private_keyshare: Vec<u8>,
-    pub client_identity: Vec<u8>,
-    pub server_identity: Vec<u8>,
+    pub client_identity: Option<Vec<u8>>,
+    pub server_identity: Option<Vec<u8>>,
+    pub credential_identifier: Vec<u8>,
     pub password: Vec<u8>,
     pub blind_registration: Vec<u8>,
-    pub oprf_key: Vec<u8>,
+    pub oprf_seed: Vec<u8>,
+    pub masking_nonce: Vec<u8>,
     pub envelope_nonce: Vec<u8>,
     pub client_nonce: Vec<u8>,
     pub server_nonce: Vec<u8>,
@@ -69,206 +69,329 @@ pub struct TestVectorParameters {
 // of https://datatracker.ietf.org/doc/draft-irtf-cfrg-opaque/
 static TEST_VECTORS: &[&str] = &[
     r#"
+## OPAQUE-3DH Test Vector 1
+
+### Configuration
+
+~~~
 OPRF: 0001
 Hash: SHA512
-SlowHash: Identity
+MHF: Identity
+KDF: HKDF-SHA512
+MAC: HMAC-SHA512
 EnvelopeMode: 01
 Group: ristretto255
+Context: 4f50415155452d504f43
 Nh: 64
 Npk: 32
 Nsk: 32
+Nm: 64
+Nx: 64
+Nok: 32
+~~~
+
+### Input Values
+
+~~~
+oprf_seed: 5c4f99877d253be5817b4b03f37b6da680b0d5671d1ec5351fa61c5d82
+eab28b9de4c4e170f27e433ba377c71c49aa62ad26391ee1cac17011d8a7e9406657c
+8
+credential_identifier: 31323334
 password: 436f7272656374486f72736542617474657279537461706c65
-envelope_nonce: cc7abb200199d5071c94efa49fb62435d3e70d03cf9573a95da54
-20d3eebcd2b
-client_private_key: 8bcb0b70dac18de24eef12e737d6b28724d3e37774e0b092f
-9f70b255defaf04
-client_public_key: 360e716c676cfe4d9968d1a352ed3faf17603863e0a7aa1905
-df6ea129343b09
-server_private_key: f3a0829898a89239dce29ccc98ec8b449a34b255ba1e6f944
-829d18e0d589b0f
-server_public_key: 66e130c6eb5b41f851b235b03a0eafeaa883f64147bc62cb74
-9c22c762389c3c
-client_info: 68656c6c6f20626f62
-server_info: 6772656574696e677320616c696365
-server_nonce: 98b8081059f60ffed9336f026fd8e124737205ac73f5348ae5bebdb
-49456c70f
-client_nonce: 58dc21475ff730342f807bf031c7ae47a11f0d4dfaa63a7feb15d7e
-36427ca44
-server_keyshare: 5214e3ddc73db786480b79fa2da787f2080b82cbe922c2a9592b
-44597d9a702e
-client_keyshare: a4084c7296b1a3d5a5e4a24358750489575acfd8fcfa6e787492
-b98265a5e651
-server_private_keyshare: c4d002aa4cfcf281657cf36fe562bc60d9133e0e72a7
-4432f685b2b6a4b42a0c
-client_private_keyshare: de2e98f422bf7b99be19f7da7cac62f1599d35a225ec
-6340149a0aaff3102003
-blind_registration: 7e5bcbf82a46109ee0d24e9bcab41fc830a6ce8b82fc1e921
-3a043b743b95800
-blind_login: c4d5a15f0d5ffc354e340454ec779f575e4573a3886ab5e57e4da298
-4bdd5306
-oprf_key: 080d0a4d352de92672ab709b1ae1888cb48dfabc2d6ca5b914b335512fe
-70508
-auth_key: 7bb7f2b831ee30d3e5cc4012c8f721a4d8f9dd494932d53776e043df9bd
-2aa284025b8b006fd8449536446ff50698f46c73fccb53f20d80898f185307d1d39e5
-prk: b0aefddbb21d1b97bc40c07b172e0bf172ec740de4f6274f69d46350a447e9b1
-b3fb1e4cefc7d8e393ff58a5c45c74d0615ee0eecde116f3d4e744142eb2ee89
-pseudorandom_pad: 36a828b3b57bf242c4c47ccd9cb84e5b3cefaffe09629c6b94d
-eba0ccec5fa39
-envelope: 01cc7abb200199d5071c94efa49fb62435d3e70d03cf9573a95da5420d3
-eebcd2bbd6323c36fba7fa08a2b6e2aab6efcdc183c4c897d822cf96d29b129932a55
-3d469ffa9999fcbd37a1e8b6c1e579bcf83fed355c9ff413e6158d72d16f3ccd8699e
-906027842694b6293b6303bbb7f324e0fccb4ae0f01edb60ee1d32992696e
-handshake_secret: 2b041dcf12ac9b75dded88f891c25d76746ce9e2c1a43118ac4
-aa5721cdc1bc2f0691e6c012a1ea9eb95ab4899b3e7058d37fe9546c46b0511877e40
-f55aac6c
-handshake_encrypt_key: ceef10f15d869a4cea8174fa98d0d96c7aaf8602d006fe
-0c5274a40173db76cac820138c5890bb63fb974d1e3e925850cc2464e2c10f0a9a776
-9a45e80889b1e
-server_mac_key: f8fd7fdc349b5ae1339515e05912c89a795f561a117cdc84d8d8b
-5f05b05751abfb87fa01c799c5d367244d1e32eab67ff926833c6025c556acffa4af1
-f3871a
-client_mac_key: 92a30cc82c374c06895aa07e81f0cf5f25309a24b595faefcd225
-1f9219b47e47d17da4fe8b572dedefa350ed365f87b217973e90d0b647a2ccf1d796a
-8970f6
-registration_request: ec9027daa5e9a901d641286a7ded51364142936ac7636e1
-42e3f4368b4bd8124
-registration_response: 8867d7c8c2c576a6322d49d46078ea32f479aed917c70a
-636d3ada4397ea1c0e66e130c6eb5b41f851b235b03a0eafeaa883f64147bc62cb749
-c22c762389c3c
-registration_upload: 360e716c676cfe4d9968d1a352ed3faf17603863e0a7aa19
-05df6ea129343b0901cc7abb200199d5071c94efa49fb62435d3e70d03cf9573a95da
-5420d3eebcd2bbd6323c36fba7fa08a2b6e2aab6efcdc183c4c897d822cf96d29b129
-932a553d469ffa9999fcbd37a1e8b6c1e579bcf83fed355c9ff413e6158d72d16f3cc
-d8699e906027842694b6293b6303bbb7f324e0fccb4ae0f01edb60ee1d32992696e
-KE1: e06a32011e1b1704eb686b263e5d132fff4e9f6429cd93b98db107485006792c
-58dc21475ff730342f807bf031c7ae47a11f0d4dfaa63a7feb15d7e36427ca4400096
-8656c6c6f20626f62a4084c7296b1a3d5a5e4a24358750489575acfd8fcfa6e787492
-b98265a5e651
-KE2: 66f6b5fa1a4eb6bd7a0c93ed2639a31cba0d02e2df744003641d5a30a4a12364
-66e130c6eb5b41f851b235b03a0eafeaa883f64147bc62cb749c22c762389c3c01cc7
-abb200199d5071c94efa49fb62435d3e70d03cf9573a95da5420d3eebcd2bbd6323c3
-6fba7fa08a2b6e2aab6efcdc183c4c897d822cf96d29b129932a553d469ffa9999fcb
-d37a1e8b6c1e579bcf83fed355c9ff413e6158d72d16f3ccd8699e906027842694b62
-93b6303bbb7f324e0fccb4ae0f01edb60ee1d32992696e98b8081059f60ffed9336f0
-26fd8e124737205ac73f5348ae5bebdb49456c70f5214e3ddc73db786480b79fa2da7
-87f2080b82cbe922c2a9592b44597d9a702e000f72f38a0945819089c44c86820c51d
-89cc35f77df03d330101bbed3b2f69066112f32529bdda0998657350fc9f8da4cde73
-408ad931f4c2ea6237ccae4696483388b174f50cf96d439139b0f8680c3b
-KE3: 9f0e4f73455ca9fe06bb52ad02670b09be5a03db11a73be4422f19963be082b0
-eb55871022e8d1d87adc3ab50de7c738058eb659866d091648f2fed12e23fd53
-export_key: 66c0b72aa829f13a166fb1a1168f1e26023921f0eed1126def4f81ba0
-4924ad6012e42b63656ec199ba27670d1e7f23dc0a927714edc140134dde5a5d2063d
-fc
-session_key: 951c2bb1b876725fa7d3829db791dddd406a688507b47e24101bd0cc
-5d071760b6fba59e8758a6ea6d7e5f51a715b49a47c50fee9a7c8a0451243c3ee837f
-d30
+envelope_nonce: 71b8f14b7a1059cdadc414c409064a22cf9e970b0ffc6f1fc6fdd
+539c4676775
+masking_nonce: 54f9341ca183700f6b6acf28dbfe4a86afad788805de49f2d680ab
+86ff39ed7f
+server_private_key: 16eb9dc74a3df2033cd738bf2cfb7a3670c569d7749f284b2
+b241cb237e7d10f
+server_public_key: 18d5035fd0a9c1d6412226df037125901a43f4dff660c0549d
+402f672bcc0933
+server_nonce: f9c5ec75a8cd571370add249e99cb8a8c43f6ef05610ac6e354642b
+f4fedbf69
+client_nonce: 804133133e7ee6836c8515752e24bb44d323fef4ead34cde967798f
+2e9784f69
+server_keyshare: 6e77d4749eb304c4d74be9457c597546bc22aed699225499910f
+c913b3e90712
+client_keyshare: f67926bd036c5dc4971816b9376e9f64737f361ef8269c18f69f
+1ab555e96d4a
+server_private_keyshare: f8e3e31543dd6fc86833296726773d51158291ab9afd
+666bb55dce83474c1101
+client_private_keyshare: 4230d62ea740b13e178185fc517cf2c313e6908c4cd9
+fb42154870ff3490c608
+blind_registration: c62937d17dc9aa213c9038f84fe8c5bf3d953356db01c4d48
+acb7cae48e6a504
+blind_login: b5f458822ea11c900ad776e38e29d7be361f75b4d79b55ad74923299
+bf8d6503
+oprf_key: 23d431bab39aea4d2737ac391a50076300210730971788e3a6a8c29ad3c
+5930e
+~~~
+
+### Intermediate Values
+
+~~~
+client_public_key: f692d6b738b4e240d5f59d534371363b47817c00c7058d4a33
+439911e66c3c27
+auth_key: 27972f9b1cf2ce524d50a7afa40a2ee6957904e2bef29976bdbda452a84
+fcf01023f3ddd8182e64ea5287f99765dd39b83fa89fe189db227212a144134684783
+randomized_pwd: 750ef06299c2fb102242fd84e59613616338f83e69c09c1dc3f91
+c57ac0642876ccbe785e94aa094262efdc6aed08b3faff7c1bddfa14c434c5a908ad6
+c5f9d5
+envelope: 71b8f14b7a1059cdadc414c409064a22cf9e970b0ffc6f1fc6fdd539c46
+76775455739db882585a7c8b3e9ae7955da7135900d85ab832aa83a34b3ce481efc9e
+43d4c2276220c8bcb9d27b5a827a5a2d655700321f3b32d21f578c21316195d8
+handshake_secret: 02fb23a668b7138b029c95d21f1e0eec9e10377be933bdbf3e5
+33ea39073d3ce9d1ef16b55a8a8464f3bf6a991cc645d14c1fa3d9d6cfe36c6c0dcc2
+691d7109
+server_mac_key: e75ce46beeebd26f22540d7988de9809a69cf34fec6c050750708
+e91232297fdbb51e875cd37167d5ce661ebccf0004dbbf96311daf64ddec7faae04c4
+8bbd89
+client_mac_key: 4bce132daa031fff2a6e5ac29287c4641e3b9dc2560394b8c73f3
+b748f1e51e577b932a960b236981217b33bee220b0bce2696638cfb7791f427ade292
+d60f55
+~~~
+
+### Output Values
+
+~~~
+registration_request: 80576bce33c6ce89f9e1a06d8595cd9d09d9aef46b20dad
+d57a845dc50e7c074
+registration_response: 1a80fdb4f4eb1985587b5b95661d2cff1ef2493cdcdd88
+b5699f39048f0d6c2618d5035fd0a9c1d6412226df037125901a43f4dff660c0549d4
+02f672bcc0933
+registration_upload: f692d6b738b4e240d5f59d534371363b47817c00c7058d4a
+33439911e66c3c2795014d8fc0c710bd763c981c5b9329c95e149c6717af91bad2cec
+daf87f2c3c9c11914cb6d44aaee5679e3e61e1b65241fda74902cca908a065495c0b2
+8b799e71b8f14b7a1059cdadc414c409064a22cf9e970b0ffc6f1fc6fdd539c467677
+5455739db882585a7c8b3e9ae7955da7135900d85ab832aa83a34b3ce481efc9e43d4
+c2276220c8bcb9d27b5a827a5a2d655700321f3b32d21f578c21316195d8
+KE1: 60d71c9f5d2a14568807b869e2c251a8e5f7ad8951cd8386c7e32c0634b26b16
+804133133e7ee6836c8515752e24bb44d323fef4ead34cde967798f2e9784f69f6792
+6bd036c5dc4971816b9376e9f64737f361ef8269c18f69f1ab555e96d4a
+KE2: 78a428204f552d3532bad040c961324edb22c738d98f1dd770d65caba0bd8966
+54f9341ca183700f6b6acf28dbfe4a86afad788805de49f2d680ab86ff39ed7fbcbbb
+84a18810b8eb1dc898d9af686f5901a21d0768720b325279fde4931ee52f0d4a0d0d9
+cd1cd7c424d4622b1588ba554cd9241352a59ef52bbe85e0f865021404b115ba954f5
+540cf2d811a6566a93876cac1239b1f75f39b070250af5a84a819e08b13e9e437a80f
+c25cc130f8475dde43efe6d900c664e9bac300298bb0f9c5ec75a8cd571370add249e
+99cb8a8c43f6ef05610ac6e354642bf4fedbf696e77d4749eb304c4d74be9457c5975
+46bc22aed699225499910fc913b3e907120485942e3e077f71c1dd2d87053b39f0d31
+bfe5d5f90df0e85ad9ce771e4f4d1ab697a10a02002cd73916051b887da9554465d58
+68811fd8b22b8f457ed5a4b0
+KE3: b4f8aece9fb4f6b7b5ffe1c98747a91f4ec7bf5481fe5719ba4baad668e3fd4e
+8aba4fa227bd4c688ed9e17f6c6d28ab5e5617a883207d80979dc4797ca89304
+export_key: 045f61f4baa0a945c2e85dfb7a85fe4df8a49e6c31344920e863c286b
+c8a17fe25fc16c84836335b4b5ecc9743c5d3a221101ab004aa99ce65026b6953ad6c
+c0
+session_key: 91187690e5ea0da3110a1dd7d5ffd7c4c3111950c587d9fcf3b9f34b
+f73b86dbeafed42a05024fa875a32415c6143d20c39cd732eb0e31db5e60ea3fb2551
+cf7
+~~~
 "#,
     r#"
+## OPAQUE-3DH Test Vector 2
+
+### Configuration
+
+~~~
 OPRF: 0001
 Hash: SHA512
-SlowHash: Identity
-EnvelopeMode: 02
+MHF: Identity
+KDF: HKDF-SHA512
+MAC: HMAC-SHA512
+EnvelopeMode: 01
 Group: ristretto255
+Context: 4f50415155452d504f43
 Nh: 64
 Npk: 32
 Nsk: 32
-client_identity: 20fa92f2e4b7ea5b5e677ac4930ff3b93b0043481ab70bc613b2
-e16a6dde6b05
-server_identity: eae9dfa6b8348d34418c32d385e1eac99efbce1af320901f7c8e
-de8d6d272c65
+Nm: 64
+Nx: 64
+Nok: 32
+~~~
+
+### Input Values
+
+~~~
+client_identity: 616c696365
+server_identity: 626f62
+oprf_seed: db5c1c16e264b8933d5da56439e7cfed23ab7287b474fe3cdcd58df089
+a365a426ea849258d9f4bc13573601f2e727c90ecc19d448cf3145a662e0065f157ba
+5
+credential_identifier: 31323334
 password: 436f7272656374486f72736542617474657279537461706c65
-envelope_nonce: f41e8b3c5a999aa946f9b562a150e5c5e36748a31a79feb241809
-0438877888c
-client_private_key: dc70a99bbabf1ebe98b192e93cedceb9c0164e95b891bd8bc
-81721b83d66b00b
-client_public_key: 20fa92f2e4b7ea5b5e677ac4930ff3b93b0043481ab70bc613
-b2e16a6dde6b05
-server_private_key: 709687a36c94592ab76579f42ce1be6961f0700496e71df80
-6ebd5320554720d
-server_public_key: eae9dfa6b8348d34418c32d385e1eac99efbce1af320901f7c
-8ede8d6d272c65
-client_info: 68656c6c6f20626f62
-server_info: 6772656574696e677320616c696365
-server_nonce: ef49d83cef5f1411ea30abb82b08bd85423aadb86e2c19df5930b3c
-8498b9f97
-client_nonce: 4ab1227db632bc079f79c0f5279df2dfa75cfbd4434ab40dcf844d6
-77165cd3b
-server_keyshare: 96a9587e233e67f2397f10fec6355b68102534f1f1b115b4ddf7
-485840efcd7c
-client_keyshare: 54f35db3a52fb0cf2a97918a6987993231d227e28711eaef19a3
-e5033632611a
-server_private_keyshare: 6650d64df70618a878504ce73dcca27b1af125c67e48
-1e7bd49d0b24709b200f
-client_private_keyshare: ebb01c59f99bc955df622548e247f7ef180732909ff3
-c5f87ff8c7867b8be704
-blind_registration: 308f1d3fa1fea402f3c90b04601274050a3c6f467387c2f48
-878823949b0e109
-blind_login: 141e21373228a44b09d4c00da9a6bbaf9a5e54a1687c07f327833643
-4245510b
-oprf_key: b7126967aa0cb69c311b71343843ea041bae30e2bde41b548b8fbd8bced
-97604
-auth_key: 9f761a5a56a74269b382403aeba47c1d24b9e200e1839efcb616fb280da
-1b6ba7bd71d6455dd3cd979c545608cfbfc1c4e9ba677e1d40848054a00696c4b2589
-prk: 30bc3f37a757890ac17ce46043f3c5ed30c96fb8743205e77e84dc167d98e114
-6093150ff7b4d002f793bfe717e88d174ed2669abdd9e96af473a7ac82973b0a
-pseudorandom_pad: b909f990c94b9c5949a2b8d0874d602f846b7981b331fd79978
-b530cc46c8670
-envelope: 02f41e8b3c5a999aa946f9b562a150e5c5e36748a31a79feb2418090438
-877888c6579500b73f482e7d1132a39bba0ae96447d37140ba040f25f9c72b4f90a36
-7bdb425fa1dd4c49e17780f33b821e1e019668fe7f45520e26996ac8cb08e3d2566cc
-439c83030464effecb8350e7b1ca31087d87f6a45ed3910c185a24a89d282
-handshake_secret: ff89f264f8c3974238f4c8d736af7b0a55f2e4edc487cbf3e5c
-7b4bbf21acd7c1d28354c2c8555fba57c4d4b1fbb4b772bfdf909881f67dd517cc9f4
-f6ebaeac
-handshake_encrypt_key: 3071b181f639062cf70b74d0ffe5ec8fa695da13cd2f00
-e74b8b7ef348ae7a5df9c3a32c9f7aeaad5a28379712cf849b9707e221dce124abfad
-d0225a8e8e045
-server_mac_key: 7c37b344d189cbbeff80bbb4b78e2703d1a80dc28239923094287
-62f7ce2a93b11f6e85dd45c02809afe8583d4aad6377e72788773af92eef33c690692
-20ae76
-client_mac_key: 2c4aff12ba7aa911a51f9e5b7a7c01439d854c97e4b8ec842a9db
-d78345760328fd5a72424e49e25ec8fe1b6d9d42f774516f400948bd5a105d995d000
-2fc83b
-registration_request: 3c8b89966e261a5aaf7aeb6dcdd94c87ce311bf197221b8
-7ef44632d58f18a05
-registration_response: caf9243d7ef3e267815632bf79c85a27a23f218a438815
-2a523f6a310949807beae9dfa6b8348d34418c32d385e1eac99efbce1af320901f7c8
-ede8d6d272c65
-registration_upload: 20fa92f2e4b7ea5b5e677ac4930ff3b93b0043481ab70bc6
-13b2e16a6dde6b0502f41e8b3c5a999aa946f9b562a150e5c5e36748a31a79feb2418
-090438877888c6579500b73f482e7d1132a39bba0ae96447d37140ba040f25f9c72b4
-f90a367bdb425fa1dd4c49e17780f33b821e1e019668fe7f45520e26996ac8cb08e3d
-2566cc439c83030464effecb8350e7b1ca31087d87f6a45ed3910c185a24a89d282
-KE1: 8261a1efd78bea73faf256a23c200d729259886530fa43b875c1ca124b09bc7e
-4ab1227db632bc079f79c0f5279df2dfa75cfbd4434ab40dcf844d677165cd3b00096
-8656c6c6f20626f6254f35db3a52fb0cf2a97918a6987993231d227e28711eaef19a3
-e5033632611a
-KE2: fa1f33a43a03123ebe35345ef93aa23b57ea8bfbee7022b05a179d60768ba02e
-eae9dfa6b8348d34418c32d385e1eac99efbce1af320901f7c8ede8d6d272c6502f41
-e8b3c5a999aa946f9b562a150e5c5e36748a31a79feb2418090438877888c6579500b
-73f482e7d1132a39bba0ae96447d37140ba040f25f9c72b4f90a367bdb425fa1dd4c4
-9e17780f33b821e1e019668fe7f45520e26996ac8cb08e3d2566cc439c83030464eff
-ecb8350e7b1ca31087d87f6a45ed3910c185a24a89d282ef49d83cef5f1411ea30abb
-82b08bd85423aadb86e2c19df5930b3c8498b9f9796a9587e233e67f2397f10fec635
-5b68102534f1f1b115b4ddf7485840efcd7c000f7ebe71d4ab326006a3aeca802435d
-c995a38ac6662221f974cb920992d82b8ef8d147c77e29b628a82b5ccb01ea2f7bb60
-af94cd1860e1bd974a11a1c9bd827789f663c4758eb71058c244138de0c2
-KE3: d81f93397cdba85a43993d4d9afbdc67f147adfa2b223213b19692cb820eef48
-5073eda4c8236b2f47702404ad60d9a875d189626fc7b7cc861825385470ae54
-export_key: 03192555940b5b42e64e6200bf55cc701f1bace3d402a2f8d83977843
-51a1e3fa1f07a471b783b208acb1d92be47903b6fa3a0df9f4d4b7956ee4f431e2950
+envelope_nonce: d0c7b0f0047682bd87a87e0c3553b9bcdce7e1ae3348570df20bf
+2747829b2d2
+masking_nonce: 30635396b708ddb7fc10fb73c4e3a9258cd9c3f6f761b2c227853b
+5def228c85
+server_private_key: eeb2fcc794f98501b16139771720a0713a2750b9e528adfd3
+662ad56a7e19b04
+server_public_key: 8aa90cb321a38759fc253c444f317782962ca18d33101eab2c
+8cda04405a181f
+server_nonce: 3fa57f7ef652185f89114109f5a61cc8c9216fdd7398246bb7a0c20
+e2fbca2d8
+client_nonce: a6bcd29b5aecc3507fc1f8f7631af3d2f5105155222e48099e5e608
+5d8c1187a
+server_keyshare: ae070cdffe5bb4b1c373e71be8e7d8f356ee5de37881533f1039
+7bcd84d35445
+client_keyshare: 642e7eecf19b804a62817486663d6c6c239396f709b663a4350c
+da67d025687a
+server_private_keyshare: 0974010a8528b813f5b33ae0d791df88516c8839c152
+b030697637878b2d8b0a
+client_private_keyshare: 03b52f066898929f4aca48014b2b97365205ce691ee3
+444b0a7cecec3c7efb01
+blind_registration: a66ffb41ccf1194a8d7dda900f8b6b0652e4c7fac4610066f
+e0489a804d3bb05
+blind_login: e6f161ac189e6873a19a54efca4baa0719e801e336d929d35ca28b5b
+4f60560e
+oprf_key: 1e0550d2dbb9ce5dd9bdbb5f808afbb724c573dc03306dcfc7217796465
+ce607
+~~~
+
+### Intermediate Values
+
+~~~
+client_public_key: ba6cb41f1870e9db7e858440a664e6559d01fdbfb638bbf7e1
+c9004f20d5db71
+auth_key: 5142ae6f6bd80686039656fd7a03cdd7e39cc6e869aa637220d4b5fb64f
+afee2f284a1581fff95ad3a5261b413c5e5b91115f78a3c35486fa56023c300d1726b
+randomized_pwd: cea240b632b9c1d704034920cc3dc3c664ed8cd82cf5c0339af76
+4d6350d2ee9ba1f675ce8df7b6cf8692d1efb158bafa3c2695ac03a2d92346c19810c
+1a698b
+envelope: d0c7b0f0047682bd87a87e0c3553b9bcdce7e1ae3348570df20bf274782
+9b2d26e18240c0cbad3b4cdbd7d9d86512f87e43fac39e3785a17504aaa8508f81e3c
+1517b150259be478720935e175b1e34bbe625d0828a62ca9983f9a27aed27f5e
+handshake_secret: 7925c12d7bf3050e62fe5c8caaece3c85737754c5df79bc59a6
+0fa87929ab1f4a4730f903b87be8b7d89ded8ec97aaec97bc8e7d53a555fd4ad74c4f
+33b9bc83
+server_mac_key: 27d6036335c5654132fb08cc81d95b3067ef7fe795f017531231a
+e3fa03cd3ab72f1f5e81473318f9c01f990263d885dfce4b6ac8630fdc8ee8abc6a36
+7c2339
+client_mac_key: ebb3693bac6310075a89922c7a40599d14d03d9104b7a331106e8
+a578a32a4944751f9d3c230a6690a5747137388a86159cf587969d13dadc0a3830218
+dfbca5
+~~~
+
+### Output Values
+
+~~~
+registration_request: f841cbb85844967568c7405f3831a58c4f5f37ccddb0baa
+4972ea912c960ae66
+registration_response: 0256257cc6e2b04444edc076b9ad44d8b31593e050bea8
+06485707a818f8a93f8aa90cb321a38759fc253c444f317782962ca18d33101eab2c8
+cda04405a181f
+registration_upload: ba6cb41f1870e9db7e858440a664e6559d01fdbfb638bbf7
+e1c9004f20d5db71146e42585d25fa19913876edce4b5ee99b638eb37b1d8a8a76607
+efaa12299e828641ba4fbf1c46fc2c3776e0a0c9791f88a15b9ddfb5495d63ce92d8f
+58823bd0c7b0f0047682bd87a87e0c3553b9bcdce7e1ae3348570df20bf2747829b2d
+26e18240c0cbad3b4cdbd7d9d86512f87e43fac39e3785a17504aaa8508f81e3c1517
+b150259be478720935e175b1e34bbe625d0828a62ca9983f9a27aed27f5e
+KE1: 14cc586d982b6db9846c78e0b3c543591e95fbf2fc877fa0e5eff89897dd3050
+a6bcd29b5aecc3507fc1f8f7631af3d2f5105155222e48099e5e6085d8c1187a642e7
+eecf19b804a62817486663d6c6c239396f709b663a4350cda67d025687a
+KE2: 8ab71c17547f376ae787741c367142790087090cdde6327dabb2581197bffa59
+30635396b708ddb7fc10fb73c4e3a9258cd9c3f6f761b2c227853b5def228c85dd973
+a1ac59244f674da4a1c057961886661bd29e0c1346f0fcf75bf1c78d4781815c2f9f6
+f2f9fe0e370b256f6e82fb2e14c7ffc374d42caf26abf13dca169a6faafd5cff8baa9
+717090bc1fc5e1ba56acb93492d1a8b789f33ff29b6004c4be9a755ff590d7d00d6e8
+893e7e54e639aebf69d18f2182a9bb0f2e1c27c81ba73fa57f7ef652185f89114109f
+5a61cc8c9216fdd7398246bb7a0c20e2fbca2d8ae070cdffe5bb4b1c373e71be8e7d8
+f356ee5de37881533f10397bcd84d35445401c619d464ab3a134c71da4d9874f2f736
+189b8bbb659c28f8db25a58b9f089272132e3091efa87d6b07d10321ba464047be011
+3e91514aba299fd1553bcebb
+KE3: c4a0d5b8148f3ac0f8611b38de38bda085d4eb00d561397ae59676f36dc705be
+1c939e7bfdd7301103af5eb164bdfb70298aab889bd2ac797e419a82bfb442e6
+export_key: 6b50ae4dba956930c0465b4a26c3cee58e05afcab623c1c254ae34acc
+38babf954530a53475672ff46a1cf7fd53ef9e808f85b08793d021bb5c6d2a1bb9204
 f6
-session_key: 58a7fa98bf3b7b52da21406abfb11d98734354edd47d7b32462c0513
-f0617c89824ea6031d4147a86fc9f6c6837ce640c12fb937d764f296d1a9421ad1b2a
-5d5
+session_key: c9bc2b7e2237f6fbeccd92dc6ec6d51faeb886492f8d23f21743a967
+597025215df02a4afb75349acbafeef9dfd4f19e6d38da8bea4912f7b691b70849b0d
+78e
+~~~
 "#,
 ];
 
+static FAKE_TEST_VECTORS: &[&str] = &[r#"
+### OPAQUE-3DH Fake Test Vector 1
+
+#### Configuration
+
+~~~
+OPRF: 0001
+Hash: SHA512
+MHF: Identity
+KDF: HKDF-SHA512
+MAC: HMAC-SHA512
+EnvelopeMode: 01
+Group: ristretto255
+Context: 4f50415155452d504f43
+Nh: 64
+Npk: 32
+Nsk: 32
+Nm: 64
+Nx: 64
+Nok: 32
+~~~
+
+#### Input Values
+
+~~~
+client_identity: 616c696365
+server_identity: 626f62
+oprf_seed: d3cb00535339fe4063c7ba5506a990c243a2b5c77b06848a0be9a0568c
+252fb0d7425382babd267deeed669e56d1d5654c036211f49b42f4489f96f37100779
+f
+credential_identifier: 31323334
+masking_nonce: 3058799f42516228746821dc8c8530d0e8273ebde81941591d69ca
+5aea773090
+client_private_key: 83c9bcc31a9da0ffa4489900d3d1f85bb65c27f26e9ae4e3b
+66f6e02e098c503
+client_public_key: 56717b74a5e1770edb14c65f22cee0487046bd96e122ba97da
+ffed06c4bf4052
+server_private_key: 8d3a9355f9757e7071b3f836e3fb1461a6436e92971625b17
+cd7e580dd27c009
+server_public_key: 7a464761cb19c8b6e832fdfcfd18779b0edc246fe808f5de6c
+e7bdb54df41b67
+server_nonce: 4e2a8098173efa2968036f1762f2e5df41ab976fb1bfb91dae29950
+f8526de4c
+server_keyshare: 0e247410004d83d7cbe3af89c62ff03f942127aec4b0084c9eb5
+88e74ce6dd06
+server_private_keyshare: 326345820acc8aacf4948fce775a1fd265e4e93fd579
+cec8177d6389ee379b0a
+masking_key: e968bfe56ad934c3e1088115bcbf1af8b405fd0de94cdf301f9192cc
+2781de00617e568b14b7235cc1189265811ea354031ea39b62e31a104f181c01d3dae
+4b8
+KE1: 480b6c0066c9320c50dce20f8b6b63e4ded7681defd9da3f70ecdc15770f9e68
+05603c1acb64ea417c0dabaab858a5f9da046d4a0cdbf092034c00451ccdc6e1ee835
+5c91d5ed7aa5ea75b8a730ba8dc45f6b41ae9713e6aa7126211346e8754
+~~~
+
+#### Output Values
+
+~~~
+KE2: 04013bca360b4b9ba95b2f494927375e0f234dac23053822e466a9738f781522
+3058799f42516228746821dc8c8530d0e8273ebde81941591d69ca5aea77309078577
+13efdc95f69166737cd7a80ead60e1a1f805c1da9cccbc0d29120f34be291518798c7
+00793f232374e66182495b76b388d9e11f479580cc2297da02fecee88a99cea6bc411
+b9467e8bfa9a4006aba7f21b74b4ce3bccd686785878b0ec9b3fc4200228014d5d073
+69d42d1d1b1669ecd2ad8905734ca0a641d8f16667ca4e2a8098173efa2968036f176
+2f2e5df41ab976fb1bfb91dae29950f8526de4c0e247410004d83d7cbe3af89c62ff0
+3f942127aec4b0084c9eb588e74ce6dd06fb1a0fd81da51bc1d87c740c186d881ed79
+71fdba5ad1d5cfc94ffe6a731241c78ea7ea5dae503e987edc37355b7348883dc65cd
+b57aec04e64593007f98a405
+~~~
+"#];
+
 macro_rules! parse {
     ( $v:ident, $s:expr ) => {
+        parse_default!($v, $s, vec![])
+    };
+}
+
+macro_rules! parse_default {
+    ( $v:ident, $s:expr, $d:expr ) => {
         match decode(&$v, $s) {
             Some(x) => x,
-            None => vec![],
+            None => $d,
         }
     };
 }
@@ -298,7 +421,11 @@ fn rfc_to_json(input: &str) -> String {
             json.push(format!("    \"{}\": \"{}", key, val));
         } else {
             let s = line.trim().to_string();
-            if !s.is_empty() {
+            if s.contains("~") || s.contains("#") {
+                // Ignore comment lines
+                continue;
+            }
+            if s.len() > 0 {
                 json.push(s);
             }
         }
@@ -315,24 +442,32 @@ fn decode(values: &Value, key: &str) -> Option<Vec<u8>> {
 
 fn populate_test_vectors(values: &Value) -> TestVectorParameters {
     TestVectorParameters {
+        dummy_private_key: parse_default!(
+            values,
+            "client_private_key",
+            vec![0u8; <PrivateKey as SizedBytes>::Len::to_usize()]
+        ),
+        dummy_masking_key: parse_default!(values, "masking_key", vec![0u8; 64]),
+        context: parse!(values, "Context"),
         envelope_mode: match values["EnvelopeMode"].as_str() {
             Some("01") => EnvelopeMode::Base,
             Some("02") => EnvelopeMode::CustomIdentifier,
             _ => panic!("Could not match envelope mode"),
         },
-        client_public_key: parse!(values, "client_public_key"),
-        client_private_key: parse!(values, "client_private_key"),
+        client_private_key: decode(values, "client_private_key"),
         client_keyshare: parse!(values, "client_keyshare"),
         client_private_keyshare: parse!(values, "client_private_keyshare"),
         server_public_key: parse!(values, "server_public_key"),
         server_private_key: parse!(values, "server_private_key"),
         server_keyshare: parse!(values, "server_keyshare"),
         server_private_keyshare: parse!(values, "server_private_keyshare"),
-        client_identity: parse!(values, "client_identity"),
-        server_identity: parse!(values, "server_identity"),
+        client_identity: decode(values, "client_identity"),
+        server_identity: decode(values, "server_identity"),
+        credential_identifier: parse!(values, "credential_identifier"),
         password: parse!(values, "password"),
         blind_registration: parse!(values, "blind_registration"),
-        oprf_key: parse!(values, "oprf_key"),
+        oprf_seed: parse!(values, "oprf_seed"),
+        masking_nonce: parse!(values, "masking_nonce"),
         envelope_nonce: parse!(values, "envelope_nonce"),
         client_nonce: parse!(values, "client_nonce"),
         server_nonce: parse!(values, "server_nonce"),
@@ -351,21 +486,25 @@ fn populate_test_vectors(values: &Value) -> TestVectorParameters {
 }
 
 fn get_password_file_bytes(parameters: &TestVectorParameters) -> Result<Vec<u8>, ProtocolError> {
-    let mut oprf_key_rng = CycleRng::new(parameters.oprf_key.clone());
-    let server_registration_start_result =
-        ServerRegistration::<Ristretto255Sha512NoSlowHash>::start(
-            &mut oprf_key_rng,
-            RegistrationRequest::deserialize(&parameters.registration_request[..]).unwrap(),
-            &PublicKey::from_bytes(&parameters.server_public_key[..]).unwrap(),
-        )?;
-
-    let password_file = server_registration_start_result
-        .state
-        .finish(RegistrationUpload::deserialize(&parameters.registration_upload[..]).unwrap())?;
+    let password_file = ServerRegistration::<Ristretto255Sha512NoSlowHash>::finish(
+        RegistrationUpload::deserialize(&parameters.registration_upload[..]).unwrap(),
+    );
 
     Ok(password_file.serialize())
 }
 
+fn parse_identifiers(
+    client_identity: Option<Vec<u8>>,
+    server_identity: Option<Vec<u8>>,
+) -> Option<Identifiers> {
+    match (client_identity, server_identity) {
+        (None, None) => None,
+        (Some(x), None) => Some(Identifiers::ClientIdentifier(x)),
+        (None, Some(y)) => Some(Identifiers::ServerIdentifier(y)),
+        (Some(x), Some(y)) => Some(Identifiers::ClientAndServerIdentifiers(x, y)),
+    }
+}
+
 #[test]
 fn test_registration_request() -> Result<(), ProtocolError> {
     for parameters in rfc_to_params!(TEST_VECTORS) {
@@ -386,12 +525,19 @@ fn test_registration_request() -> Result<(), ProtocolError> {
 #[test]
 fn test_registration_response() -> Result<(), ProtocolError> {
     for parameters in rfc_to_params!(TEST_VECTORS) {
-        let mut oprf_key_rng = CycleRng::new(parameters.oprf_key);
+        let server_setup = ServerSetup::<Ristretto255Sha512NoSlowHash>::deserialize(
+            &[
+                &parameters.oprf_seed[..],
+                &parameters.server_private_key[..],
+                &parameters.dummy_private_key[..],
+            ]
+            .concat(),
+        )?;
         let server_registration_start_result =
             ServerRegistration::<Ristretto255Sha512NoSlowHash>::start(
-                &mut oprf_key_rng,
+                &server_setup,
                 RegistrationRequest::deserialize(&parameters.registration_request[..]).unwrap(),
-                &PublicKey::from_bytes(&parameters.server_public_key[..]).unwrap(),
+                &parameters.credential_identifier,
             )?;
         assert_eq!(
             hex::encode(parameters.registration_response),
@@ -411,19 +557,13 @@ fn test_registration_upload() -> Result<(), ProtocolError> {
                 &parameters.password,
             )?;
 
-        let sk_u_and_nonce: Vec<u8> =
-            [parameters.client_private_key, parameters.envelope_nonce].concat();
-        let mut finish_registration_rng = CycleRng::new(sk_u_and_nonce);
+        let mut finish_registration_rng = CycleRng::new(parameters.envelope_nonce);
         let result = client_registration_start_result.state.finish(
             &mut finish_registration_rng,
             RegistrationResponse::deserialize(&parameters.registration_response[..]).unwrap(),
-            if parameters.envelope_mode == EnvelopeMode::CustomIdentifier {
-                ClientRegistrationFinishParameters::WithIdentifiers(
-                    parameters.client_identity,
-                    parameters.server_identity,
-                )
-            } else {
-                ClientRegistrationFinishParameters::default()
+            match parse_identifiers(parameters.client_identity, parameters.server_identity) {
+                None => ClientRegistrationFinishParameters::Default,
+                Some(ids) => ClientRegistrationFinishParameters::WithIdentifiers(ids),
             },
         )?;
 
@@ -453,7 +593,6 @@ fn test_ke1() -> Result<(), ProtocolError> {
         let client_login_start_result = ClientLogin::<Ristretto255Sha512NoSlowHash>::start(
             &mut client_login_start_rng,
             &parameters.password,
-            ClientLoginStartParameters::WithInfo(parameters.client_info),
         )?;
         assert_eq!(
             hex::encode(&parameters.KE1),
@@ -466,30 +605,42 @@ fn test_ke1() -> Result<(), ProtocolError> {
 #[test]
 fn test_ke2() -> Result<(), ProtocolError> {
     for parameters in rfc_to_params!(TEST_VECTORS) {
-        let password_file_bytes = get_password_file_bytes(&parameters)?;
+        let server_setup = ServerSetup::<Ristretto255Sha512NoSlowHash>::deserialize(
+            &[
+                &parameters.oprf_seed[..],
+                &parameters.server_private_key[..],
+                &parameters.dummy_private_key[..],
+            ]
+            .concat(),
+        )?;
+
+        let record = ServerRegistration::<Ristretto255Sha512NoSlowHash>::deserialize(
+            &get_password_file_bytes(&parameters)?[..],
+        )?;
 
-        let mut server_private_keyshare_and_nonce_rng =
-            CycleRng::new([parameters.server_private_keyshare, parameters.server_nonce].concat());
+        let mut server_private_keyshare_and_nonce_rng = CycleRng::new(
+            [
+                parameters.masking_nonce,
+                parameters.server_private_keyshare,
+                parameters.server_nonce,
+            ]
+            .concat(),
+        );
         let server_login_start_result = ServerLogin::<Ristretto255Sha512NoSlowHash>::start(
             &mut server_private_keyshare_and_nonce_rng,
-            ServerRegistration::deserialize(&password_file_bytes[..]).unwrap(),
-            &PrivateKey::from_bytes(&parameters.server_private_key[..]).unwrap(),
+            &server_setup,
+            Some(record),
             CredentialRequest::<Ristretto255Sha512NoSlowHash>::deserialize(&parameters.KE1[..])
                 .unwrap(),
-            if parameters.envelope_mode == EnvelopeMode::CustomIdentifier {
-                ServerLoginStartParameters::WithInfoAndIdentifiers(
-                    parameters.server_info.to_vec(),
-                    parameters.client_identity,
-                    parameters.server_identity,
-                )
-            } else {
-                ServerLoginStartParameters::WithInfo(parameters.server_info.to_vec())
+            &parameters.credential_identifier,
+            match parse_identifiers(parameters.client_identity, parameters.server_identity) {
+                None => ServerLoginStartParameters::WithContext(parameters.context.to_vec()),
+                Some(ids) => ServerLoginStartParameters::WithContextAndIdentifiers(
+                    parameters.context.to_vec(),
+                    ids,
+                ),
             },
         )?;
-        assert_eq!(
-            hex::encode(&parameters.client_info),
-            hex::encode(server_login_start_result.plain_info),
-        );
         assert_eq!(
             hex::encode(&parameters.KE2),
             hex::encode(server_login_start_result.message.serialize())
@@ -511,25 +662,18 @@ fn test_ke3() -> Result<(), ProtocolError> {
         let client_login_start_result = ClientLogin::<Ristretto255Sha512NoSlowHash>::start(
             &mut client_login_start_rng,
             &parameters.password,
-            ClientLoginStartParameters::WithInfo(parameters.client_info),
         )?;
 
         let client_login_finish_result = client_login_start_result.state.finish(
             CredentialResponse::<Ristretto255Sha512NoSlowHash>::deserialize(&parameters.KE2[..])?,
-            if parameters.envelope_mode == EnvelopeMode::CustomIdentifier {
-                ClientLoginFinishParameters::WithIdentifiers(
-                    parameters.client_identity,
-                    parameters.server_identity,
-                )
-            } else {
-                ClientLoginFinishParameters::default()
+            match parse_identifiers(parameters.client_identity, parameters.server_identity) {
+                None => ClientLoginFinishParameters::WithContext(parameters.context),
+                Some(ids) => {
+                    ClientLoginFinishParameters::WithContextAndIdentifiers(parameters.context, ids)
+                }
             },
         )?;
 
-        assert_eq!(
-            hex::encode(&parameters.server_info),
-            hex::encode(&client_login_finish_result.confidential_info)
-        );
         assert_eq!(
             hex::encode(&parameters.session_key),
             hex::encode(&client_login_finish_result.session_key)
@@ -549,24 +693,40 @@ fn test_ke3() -> Result<(), ProtocolError> {
 #[test]
 fn test_server_login_finish() -> Result<(), ProtocolError> {
     for parameters in rfc_to_params!(TEST_VECTORS) {
-        let password_file_bytes = get_password_file_bytes(&parameters)?;
+        let server_setup = ServerSetup::<Ristretto255Sha512NoSlowHash>::deserialize(
+            &[
+                &parameters.oprf_seed[..],
+                &parameters.server_private_key[..],
+                &parameters.dummy_private_key[..],
+            ]
+            .concat(),
+        )?;
+
+        let record = ServerRegistration::<Ristretto255Sha512NoSlowHash>::deserialize(
+            &get_password_file_bytes(&parameters)?[..],
+        )?;
 
-        let mut server_private_keyshare_and_nonce_rng =
-            CycleRng::new([parameters.server_private_keyshare, parameters.server_nonce].concat());
+        let mut server_private_keyshare_and_nonce_rng = CycleRng::new(
+            [
+                parameters.masking_nonce,
+                parameters.server_private_keyshare,
+                parameters.server_nonce,
+            ]
+            .concat(),
+        );
         let server_login_start_result = ServerLogin::<Ristretto255Sha512NoSlowHash>::start(
             &mut server_private_keyshare_and_nonce_rng,
-            ServerRegistration::deserialize(&password_file_bytes[..]).unwrap(),
-            &PrivateKey::from_bytes(&parameters.server_private_key[..]).unwrap(),
+            &server_setup,
+            Some(record),
             CredentialRequest::<Ristretto255Sha512NoSlowHash>::deserialize(&parameters.KE1[..])
                 .unwrap(),
-            if parameters.envelope_mode == EnvelopeMode::CustomIdentifier {
-                ServerLoginStartParameters::WithInfoAndIdentifiers(
-                    parameters.server_info.to_vec(),
-                    parameters.client_identity,
-                    parameters.server_identity,
-                )
-            } else {
-                ServerLoginStartParameters::WithInfo(parameters.server_info.to_vec())
+            &parameters.credential_identifier,
+            match parse_identifiers(parameters.client_identity, parameters.server_identity) {
+                None => ServerLoginStartParameters::WithContext(parameters.context.to_vec()),
+                Some(ids) => ServerLoginStartParameters::WithContextAndIdentifiers(
+                    parameters.context.to_vec(),
+                    ids,
+                ),
             },
         )?;
 
@@ -581,3 +741,47 @@ fn test_server_login_finish() -> Result<(), ProtocolError> {
     }
     Ok(())
 }
+
+#[test]
+fn test_fake_vectors() -> Result<(), ProtocolError> {
+    for parameters in rfc_to_params!(FAKE_TEST_VECTORS) {
+        let server_setup = ServerSetup::<Ristretto255Sha512NoSlowHash>::deserialize(
+            &[
+                &parameters.oprf_seed[..],
+                &parameters.server_private_key[..],
+                &parameters.dummy_private_key[..],
+            ]
+            .concat(),
+        )?;
+
+        let mut server_private_keyshare_and_nonce_rng = CycleRng::new(
+            [
+                parameters.dummy_masking_key,
+                parameters.masking_nonce,
+                parameters.server_private_keyshare,
+                parameters.server_nonce,
+            ]
+            .concat(),
+        );
+        let server_login_start_result = ServerLogin::<Ristretto255Sha512NoSlowHash>::start(
+            &mut server_private_keyshare_and_nonce_rng,
+            &server_setup,
+            None,
+            CredentialRequest::<Ristretto255Sha512NoSlowHash>::deserialize(&parameters.KE1[..])
+                .unwrap(),
+            &parameters.credential_identifier,
+            match parse_identifiers(parameters.client_identity, parameters.server_identity) {
+                None => ServerLoginStartParameters::WithContext(parameters.context.to_vec()),
+                Some(ids) => ServerLoginStartParameters::WithContextAndIdentifiers(
+                    parameters.context.to_vec(),
+                    ids,
+                ),
+            },
+        )?;
+        assert_eq!(
+            hex::encode(&parameters.KE2),
+            hex::encode(server_login_start_result.message.serialize())
+        );
+    }
+    Ok(())
+}