Repository: novifinancial/opaque-ke
Pull request #88 (2020-11-16 19:40:23)

Updating API to include enums for parameters and structs for return types

kevinlewi: Adding default() implementations

Patch:
diff --git a/examples/simple_login.rs b/examples/simple_login.rs
index 90a3aee..e724629 100644
--- a/examples/simple_login.rs
+++ b/examples/simple_login.rs
@@ -28,13 +28,11 @@ use std::convert::TryFrom;
 use std::process::exit;
 
 use opaque_ke::{
-    ciphersuite::CipherSuite,
-    keypair::KeyPair,
-    opaque::{
-        ClientLogin, ClientRegistration, LoginFirstMessage, LoginSecondMessage, LoginThirdMessage,
-        RegisterFirstMessage, RegisterSecondMessage, RegisterThirdMessage, ServerLogin,
-        ServerRegistration,
-    },
+    ciphersuite::CipherSuite, keypair::KeyPair, ClientLogin, ClientLoginFinishParameters,
+    ClientLoginStartParameters, ClientRegistration, ClientRegistrationStartParameters,
+    LoginFirstMessage, LoginSecondMessage, LoginThirdMessage, RegisterFirstMessage,
+    RegisterSecondMessage, RegisterThirdMessage, ServerLogin, ServerLoginStartParameters,
+    ServerRegistration,
 };
 
 // The ciphersuite trait allows to specify the underlying primitives
@@ -55,8 +53,12 @@ fn account_registration(
     password: String,
 ) -> Vec<u8> {
     let mut client_rng = OsRng;
-    let (r1, client_state) =
-        ClientRegistration::<Default>::start(password.as_bytes(), &mut client_rng).unwrap();
+    let (r1, client_state) = ClientRegistration::<Default>::start(
+        password.as_bytes(),
+        ClientRegistrationStartParameters::default(),
+        &mut client_rng,
+    )
+    .unwrap();
     let r1_bytes = r1.serialize();
 
     // Client sends r1_bytes to server
@@ -64,6 +66,7 @@ fn account_registration(
     let mut server_rng = OsRng;
     let (r2, server_state) = ServerRegistration::<Default>::start(
         RegisterFirstMessage::deserialize(&r1_bytes[..]).unwrap(),
+        server_kp.public(),
         &mut server_rng,
     )
     .unwrap();
@@ -74,7 +77,6 @@ fn account_registration(
     let (r3, _) = client_state
         .finish(
             RegisterSecondMessage::deserialize(&r2_bytes[..]).unwrap(),
-            server_kp.public(),
             &mut client_rng,
         )
         .unwrap();
@@ -95,45 +97,50 @@ fn account_login(
     password_file_bytes: &[u8],
 ) -> bool {
     let mut client_rng = OsRng;
-    let (l1, client_state) =
-        ClientLogin::<Default>::start(password.as_bytes(), &mut client_rng).unwrap();
-    let l1_bytes = l1.serialize();
+    let client_login_start_result = ClientLogin::<Default>::start(
+        password.as_bytes(),
+        &mut client_rng,
+        ClientLoginStartParameters::default(),
+    )
+    .unwrap();
+    let l1_bytes = client_login_start_result.credential_request.serialize();
 
     // Client sends l1_bytes to server
 
     let password_file = ServerRegistration::<Default>::try_from(password_file_bytes).unwrap();
     let mut server_rng = OsRng;
-    let (l2, server_state) = ServerLogin::start(
+    let server_login_start_result = ServerLogin::start(
         password_file,
         &server_kp.private(),
         LoginFirstMessage::deserialize(&l1_bytes[..]).unwrap(),
         &mut server_rng,
+        ServerLoginStartParameters::default(),
     )
     .unwrap();
-    let l2_bytes = l2.serialize();
+    let l2_bytes = server_login_start_result.credential_response.serialize();
 
     // Server sends l2_bytes to client
 
-    let result = client_state.finish(
+    let result = client_login_start_result.client_login_state.finish(
         LoginSecondMessage::deserialize(&l2_bytes[..]).unwrap(),
-        &server_kp.public(),
-        &mut client_rng,
+        ClientLoginFinishParameters::default(),
     );
 
     if result.is_err() {
         // Client-detected login failure
         return false;
     }
-    let (l3, client_shared_secret, _) = result.unwrap();
-    let l3_bytes = l3.serialize();
+    let client_login_finish_result = result.unwrap();
+    let l3_bytes = client_login_finish_result.key_exchange.serialize();
 
     // Client sends l3_bytes to server
 
-    let server_shared_secret = server_state
+    let server_login_finish_result = server_login_start_result
+        .server_login_state
         .finish(LoginThirdMessage::deserialize(&l3_bytes[..]).unwrap())
         .unwrap();
 
-    client_shared_secret == server_shared_secret
+    client_login_finish_result.session_secret == server_login_finish_result.session_secret
 }
 
 // A function run on the client which extracts a username and password from the CLI
diff --git a/src/key_exchange/traits.rs b/src/key_exchange/traits.rs
index b168ad8..11c0ef1 100644
--- a/src/key_exchange/traits.rs
+++ b/src/key_exchange/traits.rs
@@ -25,7 +25,7 @@ pub trait KeyExchange<D: Hash, KeyFormat: KeyPair> {
         rng: &mut R,
     ) -> Result<(Self::KE1State, Self::KE1Message), ProtocolError>;
 
-    #[allow(clippy::too_many_arguments)]
+    #[allow(clippy::too_many_arguments, clippy::type_complexity)]
     fn generate_ke2<R: RngCore + CryptoRng>(
         rng: &mut R,
         l1_bytes: Vec<u8>,
@@ -33,24 +33,30 @@ pub trait KeyExchange<D: Hash, KeyFormat: KeyPair> {
         ke1_message: Self::KE1Message,
         client_s_pk: KeyFormat::Repr,
         server_s_sk: KeyFormat::Repr,
+        id_u: Vec<u8>,
+        id_s: Vec<u8>,
         info: Vec<u8>,
         e_info: Vec<u8>,
-    ) -> Result<(Self::KE2State, Self::KE2Message), ProtocolError>;
+    ) -> Result<(Vec<u8>, Self::KE2State, Self::KE2Message), ProtocolError>;
 
+    #[allow(clippy::too_many_arguments, clippy::type_complexity)]
     fn generate_ke3(
         l2_component: Vec<u8>,
         ke2_message: Self::KE2Message,
         ke1_state: &Self::KE1State,
         server_s_pk: KeyFormat::Repr,
         client_s_sk: KeyFormat::Repr,
+        id_u: Vec<u8>,
+        id_s: Vec<u8>,
         info: Vec<u8>,
         e_info: Vec<u8>,
-    ) -> Result<(Vec<u8>, Self::KE3Message), ProtocolError>;
+    ) -> Result<(Vec<u8>, Vec<u8>, Vec<u8>, Self::KE3Message), ProtocolError>;
 
+    #[allow(clippy::type_complexity)]
     fn finish_ke(
         ke3_message: Self::KE3Message,
         ke2_state: &Self::KE2State,
-    ) -> Result<Vec<u8>, ProtocolError>;
+    ) -> Result<(Vec<u8>, Vec<u8>, Vec<u8>), ProtocolError>;
 
     fn ke1_state_size() -> usize;
 
diff --git a/src/key_exchange/tripledh.rs b/src/key_exchange/tripledh.rs
index d45c5e9..1acc052 100644
--- a/src/key_exchange/tripledh.rs
+++ b/src/key_exchange/tripledh.rs
@@ -81,6 +81,7 @@ impl<D: Hash, KeyFormat: KeyPair> KeyExchange<D, KeyFormat> for TripleDH {
         ))
     }
 
+    #[allow(clippy::type_complexity)]
     fn generate_ke2<R: RngCore + CryptoRng>(
         rng: &mut R,
         l1_bytes: Vec<u8>,
@@ -88,9 +89,11 @@ impl<D: Hash, KeyFormat: KeyPair> KeyExchange<D, KeyFormat> for TripleDH {
         ke1_message: Self::KE1Message,
         client_s_pk: KeyFormat::Repr,
         server_s_sk: KeyFormat::Repr,
+        id_u: Vec<u8>,
+        id_s: Vec<u8>,
         info: Vec<u8>,
         e_info: Vec<u8>,
-    ) -> Result<(Self::KE2State, Self::KE2Message), ProtocolError> {
+    ) -> Result<(Vec<u8>, Self::KE2State, Self::KE2Message), ProtocolError> {
         let server_e_kp = KeyFormat::generate_random(rng)?;
         let server_nonce: GenericArray<u8, NonceLen> = {
             let mut server_nonce_bytes = [0u8; NONCE_LEN];
@@ -103,14 +106,14 @@ impl<D: Hash, KeyFormat: KeyPair> KeyExchange<D, KeyFormat> for TripleDH {
                 pk1: ke1_message.client_e_pk.clone(),
                 sk1: server_e_kp.private().clone(),
                 pk2: ke1_message.client_e_pk,
-                sk2: server_s_sk.clone(),
-                pk3: client_s_pk.clone(),
+                sk2: server_s_sk,
+                pk3: client_s_pk,
                 sk3: server_e_kp.private().clone(),
             },
             &ke1_message.client_nonce,
             &server_nonce,
-            &client_s_pk.to_arr(),
-            &KeyFormat::public_from_private(&server_s_sk).to_arr(),
+            &id_u,
+            &id_s,
         )?;
 
         let mut hasher = D::new();
@@ -142,6 +145,7 @@ impl<D: Hash, KeyFormat: KeyPair> KeyExchange<D, KeyFormat> for TripleDH {
         let hashed_transcript = hasher3.finalize();
 
         Ok((
+            ke1_message.info,
             KE2State {
                 km3,
                 hashed_transcript,
@@ -157,28 +161,31 @@ impl<D: Hash, KeyFormat: KeyPair> KeyExchange<D, KeyFormat> for TripleDH {
         ))
     }
 
+    #[allow(clippy::type_complexity)]
     fn generate_ke3(
         l2_component: Vec<u8>,
         ke2_message: Self::KE2Message,
         ke1_state: &Self::KE1State,
         server_s_pk: KeyFormat::Repr,
         client_s_sk: KeyFormat::Repr,
+        id_u: Vec<u8>,
+        id_s: Vec<u8>,
         info: Vec<u8>,
         e_info: Vec<u8>,
-    ) -> Result<(Vec<u8>, Self::KE3Message), ProtocolError> {
+    ) -> Result<(Vec<u8>, Vec<u8>, Vec<u8>, Self::KE3Message), ProtocolError> {
         let (session_secret, km2, km3) = derive_3dh_keys::<KeyFormat, D>(
             TripleDHComponents {
                 pk1: ke2_message.server_e_pk.clone(),
                 sk1: ke1_state.client_e_sk.clone(),
-                pk2: server_s_pk.clone(),
+                pk2: server_s_pk,
                 sk2: ke1_state.client_e_sk.clone(),
                 pk3: ke2_message.server_e_pk.clone(),
-                sk3: client_s_sk.clone(),
+                sk3: client_s_sk,
             },
             &ke1_state.client_nonce,
             &ke2_message.server_nonce,
-            &KeyFormat::public_from_private(&client_s_sk).to_arr(),
-            &server_s_pk.to_arr(),
+            &id_u,
+            &id_s,
         )?;
 
         let transcript: Vec<u8> = [
@@ -219,6 +226,8 @@ impl<D: Hash, KeyFormat: KeyPair> KeyExchange<D, KeyFormat> for TripleDH {
         client_mac.update(&transcript_with_ke3);
 
         Ok((
+            ke2_message.info,
+            ke2_message.e_info, // TODO: need to decrypt
             session_secret.to_vec(),
             KE3Message {
                 info,
@@ -228,10 +237,11 @@ impl<D: Hash, KeyFormat: KeyPair> KeyExchange<D, KeyFormat> for TripleDH {
         ))
     }
 
+    #[allow(clippy::type_complexity)]
     fn finish_ke(
         ke3_message: Self::KE3Message,
         ke2_state: &Self::KE2State,
-    ) -> Result<Vec<u8>, ProtocolError> {
+    ) -> Result<(Vec<u8>, Vec<u8>, Vec<u8>), ProtocolError> {
         let transcript_with_ke3 = [
             ke2_state.hashed_transcript.to_vec(),
             ke3_message.to_bytes_without_mac(),
@@ -247,7 +257,11 @@ impl<D: Hash, KeyFormat: KeyPair> KeyExchange<D, KeyFormat> for TripleDH {
             ));
         }
 
-        Ok(ke2_state.session_secret.to_vec())
+        Ok((
+            ke3_message.info,
+            ke3_message.e_info, // TODO: need to decrypt
+            ke2_state.session_secret.to_vec(),
+        ))
     }
 
     fn ke1_state_size() -> usize {
diff --git a/src/lib.rs b/src/lib.rs
index 7505315..6cbd4a4 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -71,7 +71,7 @@
 //! ```
 //! # use opaque_ke::{
 //! #   errors::ProtocolError,
-//! #   opaque::ServerRegistration,
+//! #   ServerRegistration,
 //! #   keypair::{KeyPair, X25519KeyPair},
 //! #   slow_hash::NoOpHash,
 //! # };
@@ -84,11 +84,12 @@
 //! #     type Hash = sha2::Sha256;
 //! #     type SlowHash = opaque_ke::slow_hash::NoOpHash;
 //! # }
-//! use opaque_ke::opaque::ClientRegistration;
+//! use opaque_ke::{ClientRegistration, ClientRegistrationStartParameters};
 //! use rand_core::{OsRng, RngCore};
 //! let mut client_rng = OsRng;
 //! let (r1, client_state) = ClientRegistration::<Default>::start(
 //!     b"password",
+//!     ClientRegistrationStartParameters::default(),
 //!     &mut client_rng,
 //! )?;
 //! # Ok::<(), ProtocolError>(())
@@ -101,7 +102,7 @@
 //! ```
 //! # use opaque_ke::{
 //! #   errors::ProtocolError,
-//! #   opaque::ClientRegistration,
+//! #   ClientRegistration, ClientRegistrationStartParameters,
 //! #   keypair::{KeyPair, X25519KeyPair},
 //! #   slow_hash::NoOpHash,
 //! # };
@@ -118,11 +119,13 @@
 //! # let mut client_rng = OsRng;
 //! # let (r1, client_state) = ClientRegistration::<Default>::start(
 //! #     b"password",
+//! #     ClientRegistrationStartParameters::default(),
 //! #     &mut client_rng,
 //! # )?;
-//! use opaque_ke::opaque::ServerRegistration;
+//! use opaque_ke::ServerRegistration;
 //! let mut server_rng = OsRng;
-//! let (r2, server_state) = ServerRegistration::<Default>::start(r1, &mut server_rng)?;
+//! let server_kp = Default::generate_random_keypair(&mut server_rng)?;
+//! let (r2, server_state) = ServerRegistration::<Default>::start(r1, server_kp.public(), &mut server_rng)?;
 //! # Ok::<(), ProtocolError>(())
 //! ```
 //! `r2` is returned to the client, and `server_state` must be persisted on the server for the final step of server
@@ -134,7 +137,7 @@
 //! ```
 //! # use opaque_ke::{
 //! #   errors::ProtocolError,
-//! #   opaque::{ClientRegistration, ServerRegistration},
+//! #   ClientRegistration, ClientRegistrationStartParameters, ServerRegistration,
 //! #   keypair::{KeyPair, X25519KeyPair},
 //! #   slow_hash::NoOpHash,
 //! # };
@@ -151,13 +154,14 @@
 //! # let mut client_rng = OsRng;
 //! # let (r1, client_state) = ClientRegistration::<Default>::start(
 //! #     b"password",
+//! #     ClientRegistrationStartParameters::default(),
 //! #     &mut client_rng,
 //! # )?;
 //! # let mut server_rng = OsRng;
-//! # let (r2, server_state) = ServerRegistration::<Default>::start(r1, &mut server_rng)?;
 //! # let server_kp = Default::generate_random_keypair(&mut server_rng)?;
+//! # let (r2, server_state) = ServerRegistration::<Default>::start(r1, server_kp.public(), &mut server_rng)?;
 //! let (r3, export_key_registration) =
-//!     client_state.finish(r2, server_kp.public(), &mut client_rng)?;
+//!     client_state.finish(r2, &mut client_rng)?;
 //! # Ok::<(), ProtocolError>(())
 //! ```
 //! `r3` is sent to the server, and the client can optionally use `export_key_registration` for applications that choose to
@@ -168,7 +172,7 @@
 //! ```
 //! # use opaque_ke::{
 //! #   errors::ProtocolError,
-//! #   opaque::{ClientRegistration, ServerRegistration},
+//! #   ClientRegistration, ClientRegistrationStartParameters, ServerRegistration,
 //! #   keypair::{KeyPair, X25519KeyPair},
 //! #   slow_hash::NoOpHash,
 //! # };
@@ -185,12 +189,13 @@
 //! # let mut client_rng = OsRng;
 //! # let (r1, client_state) = ClientRegistration::<Default>::start(
 //! #     b"password",
+//! #     ClientRegistrationStartParameters::default(),
 //! #     &mut client_rng,
 //! # )?;
 //! # let mut server_rng = OsRng;
-//! # let (r2, server_state) = ServerRegistration::<Default>::start(r1, &mut server_rng)?;
 //! # let server_kp = Default::generate_random_keypair(&mut server_rng)?;
-//! # let (r3, export_key_registration) = client_state.finish(r2, server_kp.public(), &mut client_rng)?;
+//! # let (r2, server_state) = ServerRegistration::<Default>::start(r1, server_kp.public(), &mut server_rng)?;
+//! # let (r3, export_key_registration) = client_state.finish(r2, &mut client_rng)?;
 //! let password_file = server_state.finish(r3)?;
 //! # Ok::<(), ProtocolError>(())
 //! ```
@@ -210,7 +215,7 @@
 //! ```
 //! # use opaque_ke::{
 //! #   errors::ProtocolError,
-//! #   opaque::{ClientRegistration, ServerRegistration, ServerLogin, LoginThirdMessage},
+//! #   ClientRegistration, ServerRegistration, ServerLogin, LoginThirdMessage,
 //! #   keypair::{KeyPair, X25519KeyPair},
 //! #   slow_hash::NoOpHash,
 //! # };
@@ -224,23 +229,25 @@
 //! #     type SlowHash = opaque_ke::slow_hash::NoOpHash;
 //! # }
 //! # use rand_core::{OsRng, RngCore};
-//! use opaque_ke::opaque::ClientLogin;
+//! use opaque_ke::{ClientLogin, ClientLoginStartParameters};
 //! let mut client_rng = OsRng;
-//! let (l1, client_state) = ClientLogin::<Default>::start(
+//! let client_login_start_result = ClientLogin::<Default>::start(
 //!   b"password",
 //!   &mut client_rng,
+//!   ClientLoginStartParameters::default(),
 //! )?;
 //! # Ok::<(), ProtocolError>(())
 //! ```
-//! `l1` is sent to the server, and `client_state` must be persisted on the client for the final step of client login.
+//! `client_login_start_result.credential_request` is sent to the server, and `client_login_start_result.client_login_state`
+//! must be persisted on the client for the final step of client login.
 //!
 //! In the second step (server login start), the server takes as input the `l1` message from the client, the server's
 //! private key `server_kp.private()`, along with a serialized version of the password file, `password_file_bytes`, and
-//! runs `ServerLogin::start` to produce `l2`:
+//! runs `ServerLogin::start` to produce `server_login_start_result`:
 //! ```
 //! # use opaque_ke::{
 //! #   errors::ProtocolError,
-//! #   opaque::{ClientRegistration, ServerRegistration, ClientLogin, LoginThirdMessage},
+//! #   ClientRegistration, ClientRegistrationStartParameters, ServerRegistration, ClientLogin, ClientLoginStartParameters, LoginThirdMessage,
 //! #   keypair::{KeyPair, X25519KeyPair},
 //! #   slow_hash::NoOpHash,
 //! # };
@@ -257,26 +264,29 @@
 //! # let mut client_rng = OsRng;
 //! # let (r1, client_state) = ClientRegistration::<Default>::start(
 //! #     b"password",
+//! #     ClientRegistrationStartParameters::default(),
 //! #     &mut client_rng,
 //! # )?;
 //! # let mut server_rng = OsRng;
-//! # let (r2, server_state) = ServerRegistration::<Default>::start(r1, &mut server_rng)?;
 //! # let server_kp = Default::generate_random_keypair(&mut server_rng)?;
-//! # let (r3, export_key_registration) = client_state.finish(r2, server_kp.public(), &mut client_rng)?;
+//! # let (r2, server_state) = ServerRegistration::<Default>::start(r1, server_kp.public(), &mut server_rng)?;
+//! # let (r3, export_key_registration) = client_state.finish(r2, &mut client_rng)?;
 //! # let password_file_bytes = server_state.finish(r3)?.to_bytes();
-//! # let (l1, client_state) = ClientLogin::<Default>::start(
+//! # let client_login_start_result = ClientLogin::<Default>::start(
 //! #   b"password",
 //! #   &mut client_rng,
+//! #   ClientLoginStartParameters::default(),
 //! # )?;
-//! use opaque_ke::opaque::ServerLogin;
+//! use opaque_ke::{ServerLogin, ServerLoginStartParameters};
 //! use std::convert::TryFrom;
 //! let password_file = ServerRegistration::<Default>::try_from(&password_file_bytes[..])?;
 //! let mut server_rng = OsRng;
-//! let (l2, server_state) =
-//!     ServerLogin::start(password_file, &server_kp.private(), l1, &mut server_rng)?;
+//! let server_login_start_result =
+//!     ServerLogin::start(password_file, &server_kp.private(), client_login_start_result.credential_request, &mut server_rng, ServerLoginStartParameters::default())?;
 //! # Ok::<(), ProtocolError>(())
 //! ```
-//! `l2` is returned to the client, and `server_state` must be persisted on the server for the final step of server login.
+//! `server_login_start_result.credential_response` is returned to the client, and `server_login_start_result.server_login_state`
+//! must be persisted on the server for the final step of server login.
 //!
 //! In the third step (client login finish), the client takes as input the `l2` message from the server, along with the
 //! server's static public key `server_kp.public()`, and uses `client_state` from the first step to run `finish` and produce
@@ -284,7 +294,7 @@
 //! ```
 //! # use opaque_ke::{
 //! #   errors::ProtocolError,
-//! #   opaque::{ClientRegistration, ServerRegistration, ClientLogin, ServerLogin, LoginThirdMessage},
+//! #   ClientRegistration, ClientRegistrationStartParameters, ServerRegistration, ClientLogin, ClientLoginStartParameters, ClientLoginFinishParameters, ServerLogin, ServerLoginStartParameters, LoginThirdMessage,
 //! #   keypair::{KeyPair, X25519KeyPair},
 //! #   slow_hash::NoOpHash,
 //! # };
@@ -301,30 +311,31 @@
 //! # let mut client_rng = OsRng;
 //! # let (r1, client_state) = ClientRegistration::<Default>::start(
 //! #     b"password",
+//! #     ClientRegistrationStartParameters::default(),
 //! #     &mut client_rng,
 //! # )?;
 //! # let mut server_rng = OsRng;
-//! # let (r2, server_state) = ServerRegistration::<Default>::start(r1, &mut server_rng)?;
 //! # let server_kp = Default::generate_random_keypair(&mut server_rng)?;
-//! # let (r3, export_key_registration) = client_state.finish(r2, server_kp.public(), &mut client_rng)?;
+//! # let (r2, server_state) = ServerRegistration::<Default>::start(r1, server_kp.public(), &mut server_rng)?;
+//! # let (r3, export_key_registration) = client_state.finish(r2, &mut client_rng)?;
 //! # let password_file_bytes = server_state.finish(r3)?.to_bytes();
-//! # let (l1, client_state) = ClientLogin::<Default>::start(
+//! # let client_login_start_result = ClientLogin::<Default>::start(
 //! #   b"password",
 //! #   &mut client_rng,
+//! #   ClientLoginStartParameters::default(),
 //! # )?;
 //! # use std::convert::TryFrom;
 //! # let password_file =
 //! #   ServerRegistration::<Default>::try_from(
 //! #     &password_file_bytes[..],
 //! #   )?;
-//! # let (l2, server_state) =
-//! #     ServerLogin::start(password_file, &server_kp.private(), l1, &mut server_rng)?;
-//! let (l3, client_shared_secret, export_key_login) = client_state.finish(
-//!   l2,
-//!   &server_kp.public(),
-//!   &mut client_rng,
+//! # let server_login_start_result =
+//! #     ServerLogin::start(password_file, &server_kp.private(), client_login_start_result.credential_request, &mut server_rng, ServerLoginStartParameters::default())?;
+//! let client_login_finish_result = client_login_start_result.client_login_state.finish(
+//!   server_login_start_result.credential_response,
+//!   ClientLoginFinishParameters::default(),
 //! )?;
-//! assert_eq!(export_key_registration, export_key_login);
+//! assert_eq!(export_key_registration, client_login_finish_result.export_key);
 //! # Ok::<(), ProtocolError>(())
 //! ```
 //! Note that if the client supplies a tuple (password, pepper, server public key) that does not match the tuple
@@ -339,7 +350,7 @@
 //! ```
 //! # use opaque_ke::{
 //! #   errors::ProtocolError,
-//! #   opaque::{ClientRegistration, ServerRegistration, ClientLogin, ServerLogin, LoginThirdMessage},
+//! #   ClientRegistration, ClientRegistrationStartParameters, ServerRegistration, ClientLogin, ClientLoginStartParameters, ClientLoginFinishParameters, ServerLogin, ServerLoginStartParameters, LoginThirdMessage,
 //! #   keypair::{KeyPair, X25519KeyPair},
 //! #   slow_hash::NoOpHash,
 //! # };
@@ -356,31 +367,32 @@
 //! # let mut client_rng = OsRng;
 //! # let (r1, client_state) = ClientRegistration::<Default>::start(
 //! #     b"password",
+//! #     ClientRegistrationStartParameters::default(),
 //! #     &mut client_rng,
 //! # )?;
 //! # let mut server_rng = OsRng;
-//! # let (r2, server_state) = ServerRegistration::<Default>::start(r1, &mut server_rng)?;
 //! # let server_kp = Default::generate_random_keypair(&mut server_rng)?;
-//! # let (r3, export_key) = client_state.finish(r2, server_kp.public(), &mut client_rng)?;
+//! # let (r2, server_state) = ServerRegistration::<Default>::start(r1, server_kp.public(), &mut server_rng)?;
+//! # let (r3, export_key) = client_state.finish(r2, &mut client_rng)?;
 //! # let password_file_bytes = server_state.finish(r3)?.to_bytes();
-//! # let (l1, client_state) = ClientLogin::<Default>::start(
+//! # let client_login_start_result = ClientLogin::<Default>::start(
 //! #   b"password",
 //! #   &mut client_rng,
+//! #   ClientLoginStartParameters::default(),
 //! # )?;
 //! # use std::convert::TryFrom;
 //! # let password_file =
 //! #   ServerRegistration::<Default>::try_from(
 //! #     &password_file_bytes[..],
 //! #   )?;
-//! # let (l2, server_state) =
-//! #     ServerLogin::start(password_file, &server_kp.private(), l1, &mut server_rng)?;
-//! # let (l3, client_shared_secret, export_key) = client_state.finish(
-//! #   l2,
-//! #   &server_kp.public(),
-//! #   &mut client_rng,
+//! # let server_login_start_result =
+//! #     ServerLogin::start(password_file, &server_kp.private(), client_login_start_result.credential_request, &mut server_rng, ServerLoginStartParameters::default())?;
+//! # let client_login_finish_result = client_login_start_result.client_login_state.finish(
+//! #   server_login_start_result.credential_response,
+//! #   ClientLoginFinishParameters::default(),
 //! # )?;
-//! let server_shared_secret = server_state.finish(l3)?;
-//! assert_eq!(client_shared_secret, server_shared_secret);
+//! let server_login_finish_result = server_login_start_result.server_login_state.finish(client_login_finish_result.key_exchange)?;
+//! assert_eq!(client_login_finish_result.session_secret, server_login_finish_result.session_secret);
 //! # Ok::<(), ProtocolError>(())
 //! ```
 //! If the protocol completes successfully, then the server obtains a `server_shared_secret` which is guaranteed to
@@ -400,7 +412,9 @@ compile_error!(
 pub mod errors;
 
 // High-level API
-pub mod opaque;
+mod opaque;
+
+mod messages;
 
 pub mod ciphersuite;
 mod envelope;
@@ -425,3 +439,15 @@ mod serialization;
 
 #[cfg(test)]
 mod tests;
+
+// Exports
+
+pub use crate::messages::{
+    LoginFirstMessage, LoginSecondMessage, LoginThirdMessage, RegisterFirstMessage,
+    RegisterSecondMessage, RegisterThirdMessage,
+};
+pub use crate::opaque::{ClientLogin, ClientRegistration, ServerLogin, ServerRegistration};
+pub use crate::opaque::{
+    ClientLoginFinishParameters, ClientLoginStartParameters, ClientRegistrationStartParameters,
+    ServerLoginStartParameters,
+};
diff --git a/src/messages.rs b/src/messages.rs
new file mode 100644
index 0000000..68d63cf
--- /dev/null
+++ b/src/messages.rs
@@ -0,0 +1,471 @@
+// Copyright (c) Facebook, Inc. and its affiliates.
+//
+// This source code is licensed under the MIT license found in the
+// LICENSE file in the root directory of this source tree.
+
+//! Contains the messages used for OPAQUE
+
+use crate::{
+    ciphersuite::CipherSuite,
+    envelope::{Envelope, EnvelopeCredentialsFormat},
+    errors::{
+        utils::{check_slice_size, check_slice_size_atleast},
+        PakeError, ProtocolError,
+    },
+    group::Group,
+    hash::Hash,
+    key_exchange::traits::{KeyExchange, ToBytes},
+    keypair::{KeyPair, SizedBytesExt},
+    serialization::{
+        serialize, tokenize, u8_to_credential_type, CredentialType, ProtocolMessageType,
+    },
+};
+use generic_array::{typenum::Unsigned, GenericArray};
+use generic_bytes::SizedBytes;
+use std::convert::TryFrom;
+
+// Messages
+// =========
+
+/// The message sent by the client to the server, to initiate registration
+pub struct RegisterFirstMessage<Grp> {
+    /// blinded password information
+    pub(crate) alpha: Grp,
+}
+
+impl<Grp: Group> TryFrom<&[u8]> for RegisterFirstMessage<Grp> {
+    type Error = ProtocolError;
+    fn try_from(first_message_bytes: &[u8]) -> Result<Self, Self::Error> {
+        let elem_len = Grp::ElemLen::to_usize();
+        let checked_slice = check_slice_size(first_message_bytes, elem_len, "first_message_bytes")?;
+
+        // Check that the message is actually containing an element of the
+        // correct subgroup
+        let arr = GenericArray::from_slice(&checked_slice[checked_slice.len() - elem_len..]);
+        let alpha = Grp::from_element_slice(arr)?;
+        Ok(Self { alpha })
+    }
+}
+
+impl<Grp: Group> RegisterFirstMessage<Grp> {
+    /// Byte representation for the registration request
+    pub fn to_bytes(&self) -> Vec<u8> {
+        self.alpha.to_arr().to_vec()
+    }
+
+    /// Serialization into bytes
+    pub fn serialize(&self) -> Vec<u8> {
+        let mut registration_request: Vec<u8> = Vec::new();
+        registration_request.extend_from_slice(&serialize(&self.alpha.to_arr(), 2));
+
+        let mut output: Vec<u8> = Vec::new();
+        output.push(ProtocolMessageType::from(self) as u8 + 1);
+        output.extend_from_slice(&serialize(&registration_request, 3));
+        output
+    }
+
+    /// Deserialization from bytes
+    pub fn deserialize(input: &[u8]) -> Result<Self, ProtocolError> {
+        if input.is_empty()
+            || input.is_empty()
+            || input[0] != ProtocolMessageType::RegistrationRequest as u8 + 1
+        {
+            return Err(PakeError::SerializationError.into());
+        }
+
+        let (data, remainder) = tokenize(&input[1..], 3)?;
+        if !remainder.is_empty() {
+            return Err(PakeError::SerializationError.into());
+        }
+        let (alpha_bytes, remainder) = tokenize(&data, 2)?;
+
+        if !remainder.is_empty() {
+            return Err(PakeError::SerializationError.into());
+        }
+
+        let checked_slice = check_slice_size(
+            &alpha_bytes,
+            Grp::ElemLen::to_usize(),
+            "first_message_bytes",
+        )?;
+        // Check that the message is actually containing an element of the
+        // correct subgroup
+        let arr = GenericArray::from_slice(checked_slice);
+        let alpha = Grp::from_element_slice(arr)?;
+        Ok(Self { alpha })
+    }
+}
+
+/// The answer sent by the server to the user, upon reception of the
+/// registration attempt
+pub struct RegisterSecondMessage<Grp> {
+    /// The server's oprf output
+    pub(crate) beta: Grp,
+    /// Server's static public key
+    pub(crate) server_s_pk: Vec<u8>,
+    /// Envelope credentials format
+    pub(crate) ecf: EnvelopeCredentialsFormat,
+}
+
+impl<Grp> TryFrom<&[u8]> for RegisterSecondMessage<Grp>
+where
+    Grp: Group,
+{
+    type Error = ProtocolError;
+
+    fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
+        let elem_len = Grp::ElemLen::to_usize();
+        let checked_slice = check_slice_size_atleast(bytes, elem_len, "second_message_bytes")?;
+
+        // Check that the message is actually containing an element of the
+        // correct subgroup
+        let arr = GenericArray::from_slice(&checked_slice[..elem_len]);
+        let beta = Grp::from_element_slice(arr)?;
+
+        let server_s_pk = checked_slice[elem_len..].to_vec();
+
+        // Note that we use a default envelope credentials format here, since it
+        // is not included in the byte representation
+        let ecf = EnvelopeCredentialsFormat::default()?;
+        Ok(Self {
+            beta,
+            server_s_pk,
+            ecf,
+        })
+    }
+}
+
+impl<Grp> RegisterSecondMessage<Grp>
+where
+    Grp: Group,
+{
+    /// Byte representation for the registration response message. This does not
+    /// include the envelope credentials format
+    pub fn to_bytes(&self) -> Vec<u8> {
+        [&self.beta.to_arr().to_vec()[..], &self.server_s_pk[..]].concat()
+    }
+
+    /// Serialization into bytes
+    pub fn serialize(&self) -> Vec<u8> {
+        let mut registration_response: Vec<u8> = Vec::new();
+        registration_response.extend_from_slice(&serialize(&self.beta.to_arr(), 2));
+        registration_response.extend_from_slice(&serialize(&self.server_s_pk, 2));
+
+        // Handle ecf serialization
+        let secret_credentials: Vec<u8> = self
+            .ecf
+            .secret_credentials
+            .iter()
+            .map(|&x| x as u8 + 1)
+            .collect();
+        let cleartext_credentials: Vec<u8> = self
+            .ecf
+            .cleartext_credentials
+            .iter()
+            .map(|&x| x as u8 + 1)
+            .collect();
+        let ecf_serialized = [
+            serialize(&secret_credentials, 1),
+            serialize(&cleartext_credentials, 1),
+        ]
+        .concat();
+        registration_response.extend_from_slice(&ecf_serialized);
+
+        let mut output: Vec<u8> = Vec::new();
+        output.push(ProtocolMessageType::from(self) as u8 + 1);
+        output.extend_from_slice(&serialize(&registration_response, 3));
+        output
+    }
+
+    /// Deserialization from bytes
+    pub fn deserialize(input: &[u8]) -> Result<Self, ProtocolError> {
+        if input.is_empty() || input[0] != ProtocolMessageType::RegistrationResponse as u8 + 1 {
+            return Err(PakeError::SerializationError.into());
+        }
+
+        let (data, remainder) = tokenize(&input[1..], 3)?;
+        if !remainder.is_empty() {
+            return Err(PakeError::SerializationError.into());
+        }
+
+        let (beta_bytes, remainder) = tokenize(&data, 2)?;
+        let (server_s_pk, remainder) = tokenize(&remainder, 2)?;
+
+        // Handle ecf deserialization
+        let (secret_credentials, remainder) = tokenize(&remainder, 1)?;
+        let (cleartext_credentials, remainder) = tokenize(&remainder, 1)?;
+        let sc = secret_credentials
+            .iter()
+            .map(|x| u8_to_credential_type(*x).ok_or(PakeError::SerializationError))
+            .collect::<Result<Vec<CredentialType>, _>>()?;
+        let cc = cleartext_credentials
+            .iter()
+            .map(|x| u8_to_credential_type(*x).ok_or(PakeError::SerializationError))
+            .collect::<Result<Vec<CredentialType>, _>>()?;
+        let ecf = EnvelopeCredentialsFormat::new(sc, cc)?;
+
+        if !remainder.is_empty() {
+            return Err(PakeError::SerializationError.into());
+        }
+
+        let checked_slice = check_slice_size(
+            &beta_bytes,
+            Grp::ElemLen::to_usize(),
+            "second_message_bytes",
+        )?;
+        // Check that the message is actually containing an element of the
+        // correct subgroup
+        let arr = GenericArray::from_slice(&checked_slice);
+        let beta = Grp::from_element_slice(arr)?;
+        Ok(Self {
+            ecf,
+            server_s_pk,
+            beta,
+        })
+    }
+}
+
+/// The final message from the client, containing sealed cryptographic
+/// identifiers
+pub struct RegisterThirdMessage<KeyFormat: KeyPair, D: Hash> {
+    /// The "envelope" generated by the user, containing sealed
+    /// cryptographic identifiers
+    pub(crate) envelope: Envelope<D>,
+    /// The user's public key
+    pub(crate) client_s_pk: KeyFormat::Repr,
+}
+
+impl<KeyFormat, D> TryFrom<&[u8]> for RegisterThirdMessage<KeyFormat, D>
+where
+    KeyFormat: KeyPair,
+    D: Hash,
+{
+    type Error = ProtocolError;
+
+    fn try_from(third_message_bytes: &[u8]) -> Result<Self, Self::Error> {
+        let key_len = <KeyFormat::Repr as SizedBytes>::Len::to_usize();
+        let envelope_size = key_len + Envelope::<D>::additional_size();
+        let checked_bytes = check_slice_size(
+            third_message_bytes,
+            envelope_size + key_len,
+            "third_message",
+        )?;
+        let unchecked_client_s_pk = KeyFormat::Repr::from_bytes(&checked_bytes[envelope_size..])?;
+        let client_s_pk = KeyFormat::check_public_key(unchecked_client_s_pk)?;
+
+        Ok(Self {
+            envelope: Envelope::<D>::from_bytes(&checked_bytes[..envelope_size])?,
+            client_s_pk,
+        })
+    }
+}
+
+impl<KeyFormat, D> RegisterThirdMessage<KeyFormat, D>
+where
+    KeyFormat: KeyPair,
+    D: Hash,
+{
+    /// Serialization into bytes
+    pub fn serialize(&self) -> Vec<u8> {
+        let mut registration_upload: Vec<u8> = Vec::new();
+        registration_upload.extend_from_slice(&self.envelope.serialize());
+        registration_upload.extend_from_slice(&serialize(&self.client_s_pk.to_arr(), 2));
+
+        let mut output: Vec<u8> = Vec::new();
+        output.push(ProtocolMessageType::from(self) as u8 + 1);
+        output.extend_from_slice(&serialize(&registration_upload, 3));
+        output
+    }
+
+    /// Deserialization from bytes
+    pub fn deserialize(input: &[u8]) -> Result<Self, ProtocolError> {
+        if input.is_empty() || input[0] != ProtocolMessageType::RegistrationUpload as u8 + 1 {
+            return Err(PakeError::SerializationError.into());
+        }
+
+        let (data, remainder) = tokenize(&input[1..], 3)?;
+        if !remainder.is_empty() {
+            return Err(PakeError::SerializationError.into());
+        }
+
+        let (envelope, remainder) = Envelope::<D>::deserialize(&data)?;
+        let (client_s_pk, remainder) = tokenize(&remainder, 2)?;
+
+        if !remainder.is_empty() {
+            return Err(PakeError::SerializationError.into());
+        }
+
+        Ok(Self {
+            envelope,
+            client_s_pk: KeyFormat::check_public_key(KeyFormat::Repr::from_bytes(&client_s_pk)?)?,
+        })
+    }
+}
+
+/// The message sent by the user to the server, to initiate registration
+pub struct LoginFirstMessage<CS: CipherSuite> {
+    /// blinded password information
+    pub(crate) alpha: CS::Group,
+    pub(crate) ke1_message: <CS::KeyExchange as KeyExchange<CS::Hash, CS::KeyFormat>>::KE1Message,
+}
+
+impl<CS: CipherSuite> TryFrom<&[u8]> for LoginFirstMessage<CS> {
+    type Error = ProtocolError;
+    fn try_from(first_message_bytes: &[u8]) -> Result<Self, Self::Error> {
+        Self::deserialize(first_message_bytes)
+    }
+}
+
+impl<CS: CipherSuite> LoginFirstMessage<CS> {
+    /// byte representation for the login request
+    pub(crate) fn to_bytes(&self) -> Vec<u8> {
+        [&self.alpha.to_arr()[..], &self.ke1_message.to_bytes()].concat()
+    }
+
+    /// Serialization into bytes
+    pub fn serialize(&self) -> Vec<u8> {
+        let mut credential_request: Vec<u8> = Vec::new();
+        credential_request.extend_from_slice(&serialize(&self.alpha.to_arr(), 2));
+
+        let mut output: Vec<u8> = Vec::new();
+        output.push(ProtocolMessageType::from(self) as u8 + 1);
+        output.extend_from_slice(&serialize(&credential_request, 3));
+        output.extend_from_slice(&self.ke1_message.to_bytes());
+        output
+    }
+
+    /// Deserialization from bytes
+    pub fn deserialize(input: &[u8]) -> Result<Self, ProtocolError> {
+        if input.is_empty() || input[0] != ProtocolMessageType::CredentialRequest as u8 + 1 {
+            return Err(PakeError::SerializationError.into());
+        }
+
+        let (data, ke1m) = tokenize(&input[1..], 3)?;
+        let (alpha_bytes, remainder) = tokenize(&data, 2)?;
+
+        if !remainder.is_empty() {
+            return Err(PakeError::SerializationError.into());
+        }
+
+        let elem_len = <CS::Group as Group>::ElemLen::to_usize();
+        let checked_slice = check_slice_size(&alpha_bytes, elem_len, "login_first_message_bytes")?;
+        let arr = GenericArray::from_slice(&checked_slice[..elem_len]);
+        let alpha = <CS::Group as Group>::from_element_slice(arr)?;
+
+        let ke1_message =
+            <CS::KeyExchange as KeyExchange<CS::Hash, CS::KeyFormat>>::KE1Message::try_from(
+                &ke1m[..],
+            )?;
+
+        Ok(Self { alpha, ke1_message })
+    }
+}
+
+/// The answer sent by the server to the user, upon reception of the
+/// login attempt.
+pub struct LoginSecondMessage<CS: CipherSuite> {
+    /// the server's oprf output
+    pub(crate) beta: CS::Group,
+    /// the user's sealed information,
+    pub(crate) envelope: Envelope<CS::Hash>,
+    pub(crate) ke2_message: <CS::KeyExchange as KeyExchange<CS::Hash, CS::KeyFormat>>::KE2Message,
+}
+
+impl<CS: CipherSuite> LoginSecondMessage<CS> {
+    /// Serialization into bytes
+    pub fn serialize(&self) -> Vec<u8> {
+        let mut credential_response: Vec<u8> = Vec::new();
+        credential_response.extend_from_slice(&serialize(&self.beta.to_arr(), 2));
+        credential_response.extend_from_slice(&self.envelope.to_bytes());
+
+        let mut output: Vec<u8> = Vec::new();
+        output.push(ProtocolMessageType::from(self) as u8 + 1);
+        output.extend_from_slice(&serialize(&credential_response, 3));
+        output.extend_from_slice(&self.ke2_message.to_bytes());
+        output
+    }
+
+    /// Deserialization from bytes
+    pub fn deserialize(input: &[u8]) -> Result<Self, ProtocolError> {
+        if input.is_empty() || input[0] != ProtocolMessageType::CredentialResponse as u8 + 1 {
+            return Err(PakeError::SerializationError.into());
+        }
+
+        let (data, ke2m) = tokenize(&input[1..], 3)?;
+        let (beta_bytes, envelope_bytes) = tokenize(&data, 2)?;
+
+        let concatenated = [&beta_bytes[..], &envelope_bytes[..], &ke2m[..]].concat();
+        Self::try_from(&concatenated[..])
+    }
+}
+
+impl<CS: CipherSuite> TryFrom<&[u8]> for LoginSecondMessage<CS> {
+    type Error = ProtocolError;
+    fn try_from(second_message_bytes: &[u8]) -> Result<Self, Self::Error> {
+        let elem_len = <CS::Group as Group>::ElemLen::to_usize();
+        let checked_slice =
+            check_slice_size_atleast(second_message_bytes, elem_len, "login_second_message_bytes")?;
+
+        // Check that the message is actually containing an element of the
+        // correct subgroup
+        let beta_bytes = &checked_slice[..elem_len];
+        let arr = GenericArray::from_slice(beta_bytes);
+        let beta = CS::Group::from_element_slice(arr)?;
+
+        let (envelope, remainder) = Envelope::<CS::Hash>::deserialize(&checked_slice[elem_len..])?;
+
+        let ke2_message_size = CS::KeyExchange::ke2_message_size();
+        let checked_remainder =
+            check_slice_size_atleast(&remainder, ke2_message_size, "login_second_message_bytes")?;
+        let ke2_message =
+            <CS::KeyExchange as KeyExchange<CS::Hash, CS::KeyFormat>>::KE2Message::try_from(
+                &checked_remainder,
+            )?;
+
+        Ok(Self {
+            beta,
+            envelope,
+            ke2_message,
+        })
+    }
+}
+
+/// The answer sent by the client to the server, upon reception of the
+/// sealed envelope
+pub struct LoginThirdMessage<CS: CipherSuite> {
+    pub(crate) ke3_message: <CS::KeyExchange as KeyExchange<CS::Hash, CS::KeyFormat>>::KE3Message,
+}
+
+impl<CS: CipherSuite> TryFrom<&[u8]> for LoginThirdMessage<CS> {
+    type Error = ProtocolError;
+
+    fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
+        let ke3_message =
+            <CS::KeyExchange as KeyExchange<CS::Hash, CS::KeyFormat>>::KE3Message::try_from(bytes)?;
+        Ok(Self { ke3_message })
+    }
+}
+
+impl<CS: CipherSuite> LoginThirdMessage<CS> {
+    /// Serialization into bytes
+    pub fn serialize(&self) -> Vec<u8> {
+        let mut output: Vec<u8> = Vec::new();
+        output.push(ProtocolMessageType::from(self) as u8 + 1);
+        output.extend_from_slice(&self.ke3_message.to_bytes());
+        output
+    }
+
+    /// Deserialization from bytes
+    pub fn deserialize(input: &[u8]) -> Result<Self, ProtocolError> {
+        if input.is_empty() || input[0] != ProtocolMessageType::KeyExchange as u8 + 1 {
+            return Err(PakeError::SerializationError.into());
+        }
+
+        Self::try_from(&input[1..])
+    }
+
+    /// byte representation for the login finalization
+    pub fn to_bytes(&self) -> Vec<u8> {
+        self.ke3_message.to_bytes()
+    }
+}
diff --git a/src/opaque.rs b/src/opaque.rs
index 5d9f04d..8933cb9 100644
--- a/src/opaque.rs
+++ b/src/opaque.rs
@@ -8,20 +8,17 @@
 use crate::{
     ciphersuite::CipherSuite,
     envelope::{Envelope, EnvelopeCredentialsFormat, ExportKeySize},
-    errors::{
-        utils::{check_slice_size, check_slice_size_atleast},
-        InternalPakeError, PakeError, ProtocolError,
-    },
+    errors::{utils::check_slice_size_atleast, InternalPakeError, PakeError, ProtocolError},
     group::Group,
     hash::Hash,
     key_exchange::traits::{KeyExchange, ToBytes},
     keypair::{KeyPair, SizedBytesExt},
     map_to_curve::GroupWithMapToCurve,
     oprf,
-    serialization::{
-        serialize, tokenize, u8_to_credential_type, CredentialType, ProtocolMessageType,
-    },
+    serialization::{serialize, tokenize, CredentialType},
     slow_hash::SlowHash,
+    LoginFirstMessage, LoginSecondMessage, LoginThirdMessage, RegisterFirstMessage,
+    RegisterSecondMessage, RegisterThirdMessage,
 };
 use generic_array::{typenum::Unsigned, GenericArray};
 use generic_bytes::SizedBytes;
@@ -32,452 +29,6 @@ use zeroize::Zeroize;
 
 static STR_OPAQUE_VERSION: &[u8] = b"OPAQUE00";
 
-// Messages
-// =========
-
-/// The message sent by the client to the server, to initiate registration
-pub struct RegisterFirstMessage<Grp> {
-    /// blinded password information
-    alpha: Grp,
-}
-
-impl<Grp: Group> TryFrom<&[u8]> for RegisterFirstMessage<Grp> {
-    type Error = ProtocolError;
-    fn try_from(first_message_bytes: &[u8]) -> Result<Self, Self::Error> {
-        let elem_len = Grp::ElemLen::to_usize();
-        let checked_slice = check_slice_size(first_message_bytes, elem_len, "first_message_bytes")?;
-
-        // Check that the message is actually containing an element of the
-        // correct subgroup
-        let arr = GenericArray::from_slice(&checked_slice[checked_slice.len() - elem_len..]);
-        let alpha = Grp::from_element_slice(arr)?;
-        Ok(Self { alpha })
-    }
-}
-
-impl<Grp: Group> RegisterFirstMessage<Grp> {
-    /// Byte representation for the registration request
-    pub fn to_bytes(&self) -> Vec<u8> {
-        self.alpha.to_arr().to_vec()
-    }
-
-    /// Serialization into bytes
-    pub fn serialize(&self) -> Vec<u8> {
-        let mut registration_request: Vec<u8> = Vec::new();
-        registration_request.extend_from_slice(&serialize(&self.alpha.to_arr(), 2));
-
-        let mut output: Vec<u8> = Vec::new();
-        output.push(ProtocolMessageType::from(self) as u8 + 1);
-        output.extend_from_slice(&serialize(&registration_request, 3));
-        output
-    }
-
-    /// Deserialization from bytes
-    pub fn deserialize(input: &[u8]) -> Result<Self, ProtocolError> {
-        if input.is_empty()
-            || input.is_empty()
-            || input[0] != ProtocolMessageType::RegistrationRequest as u8 + 1
-        {
-            return Err(PakeError::SerializationError.into());
-        }
-
-        let (data, remainder) = tokenize(&input[1..], 3)?;
-        if !remainder.is_empty() {
-            return Err(PakeError::SerializationError.into());
-        }
-        let (alpha_bytes, remainder) = tokenize(&data, 2)?;
-
-        if !remainder.is_empty() {
-            return Err(PakeError::SerializationError.into());
-        }
-
-        let checked_slice = check_slice_size(
-            &alpha_bytes,
-            Grp::ElemLen::to_usize(),
-            "first_message_bytes",
-        )?;
-        // Check that the message is actually containing an element of the
-        // correct subgroup
-        let arr = GenericArray::from_slice(checked_slice);
-        let alpha = Grp::from_element_slice(arr)?;
-        Ok(Self { alpha })
-    }
-}
-
-/// The answer sent by the server to the user, upon reception of the
-/// registration attempt
-pub struct RegisterSecondMessage<Grp> {
-    /// The server's oprf output
-    beta: Grp,
-    /// Server's static public key
-    server_s_pk: Vec<u8>,
-    /// Envelope credentials format
-    ecf: EnvelopeCredentialsFormat,
-}
-
-impl<Grp> TryFrom<&[u8]> for RegisterSecondMessage<Grp>
-where
-    Grp: Group,
-{
-    type Error = ProtocolError;
-
-    fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
-        let elem_len = Grp::ElemLen::to_usize();
-        let checked_slice = check_slice_size_atleast(bytes, elem_len, "second_message_bytes")?;
-
-        // Check that the message is actually containing an element of the
-        // correct subgroup
-        let arr = GenericArray::from_slice(&checked_slice[..elem_len]);
-        let beta = Grp::from_element_slice(arr)?;
-
-        let server_s_pk = checked_slice[elem_len..].to_vec();
-
-        // Note that we use a default envelope credentials format here, since it
-        // is not included in the byte representation
-        let ecf = EnvelopeCredentialsFormat::default()?;
-        Ok(Self {
-            beta,
-            server_s_pk,
-            ecf,
-        })
-    }
-}
-
-impl<Grp> RegisterSecondMessage<Grp>
-where
-    Grp: Group,
-{
-    /// Byte representation for the registration response message. This does not
-    /// include the envelope credentials format
-    pub fn to_bytes(&self) -> Vec<u8> {
-        [&self.beta.to_arr().to_vec()[..], &self.server_s_pk[..]].concat()
-    }
-
-    /// Serialization into bytes
-    pub fn serialize(&self) -> Vec<u8> {
-        let mut registration_response: Vec<u8> = Vec::new();
-        registration_response.extend_from_slice(&serialize(&self.beta.to_arr(), 2));
-        registration_response.extend_from_slice(&serialize(&self.server_s_pk, 2));
-
-        // Handle ecf serialization
-        let secret_credentials: Vec<u8> = self
-            .ecf
-            .secret_credentials
-            .iter()
-            .map(|&x| x as u8 + 1)
-            .collect();
-        let cleartext_credentials: Vec<u8> = self
-            .ecf
-            .cleartext_credentials
-            .iter()
-            .map(|&x| x as u8 + 1)
-            .collect();
-        let ecf_serialized = [
-            serialize(&secret_credentials, 1),
-            serialize(&cleartext_credentials, 1),
-        ]
-        .concat();
-        registration_response.extend_from_slice(&ecf_serialized);
-
-        let mut output: Vec<u8> = Vec::new();
-        output.push(ProtocolMessageType::from(self) as u8 + 1);
-        output.extend_from_slice(&serialize(&registration_response, 3));
-        output
-    }
-
-    /// Deserialization from bytes
-    pub fn deserialize(input: &[u8]) -> Result<Self, ProtocolError> {
-        if input.is_empty() || input[0] != ProtocolMessageType::RegistrationResponse as u8 + 1 {
-            return Err(PakeError::SerializationError.into());
-        }
-
-        let (data, remainder) = tokenize(&input[1..], 3)?;
-        if !remainder.is_empty() {
-            return Err(PakeError::SerializationError.into());
-        }
-
-        let (beta_bytes, remainder) = tokenize(&data, 2)?;
-        let (server_s_pk, remainder) = tokenize(&remainder, 2)?;
-
-        // Handle ecf deserialization
-        let (secret_credentials, remainder) = tokenize(&remainder, 1)?;
-        let (cleartext_credentials, remainder) = tokenize(&remainder, 1)?;
-        let sc = secret_credentials
-            .iter()
-            .map(|x| u8_to_credential_type(*x).ok_or(PakeError::SerializationError))
-            .collect::<Result<Vec<CredentialType>, _>>()?;
-        let cc = cleartext_credentials
-            .iter()
-            .map(|x| u8_to_credential_type(*x).ok_or(PakeError::SerializationError))
-            .collect::<Result<Vec<CredentialType>, _>>()?;
-        let ecf = EnvelopeCredentialsFormat::new(sc, cc)?;
-
-        if !remainder.is_empty() {
-            return Err(PakeError::SerializationError.into());
-        }
-
-        let checked_slice = check_slice_size(
-            &beta_bytes,
-            Grp::ElemLen::to_usize(),
-            "second_message_bytes",
-        )?;
-        // Check that the message is actually containing an element of the
-        // correct subgroup
-        let arr = GenericArray::from_slice(&checked_slice);
-        let beta = Grp::from_element_slice(arr)?;
-        Ok(Self {
-            ecf,
-            server_s_pk,
-            beta,
-        })
-    }
-}
-
-/// The final message from the client, containing sealed cryptographic
-/// identifiers
-pub struct RegisterThirdMessage<KeyFormat: KeyPair, D: Hash> {
-    /// The "envelope" generated by the user, containing sealed
-    /// cryptographic identifiers
-    envelope: Envelope<D>,
-    /// The user's public key
-    client_s_pk: KeyFormat::Repr,
-}
-
-impl<KeyFormat, D> TryFrom<&[u8]> for RegisterThirdMessage<KeyFormat, D>
-where
-    KeyFormat: KeyPair,
-    D: Hash,
-{
-    type Error = ProtocolError;
-
-    fn try_from(third_message_bytes: &[u8]) -> Result<Self, Self::Error> {
-        let key_len = <KeyFormat::Repr as SizedBytes>::Len::to_usize();
-        let envelope_size = key_len + Envelope::<D>::additional_size();
-        let checked_bytes = check_slice_size(
-            third_message_bytes,
-            envelope_size + key_len,
-            "third_message",
-        )?;
-        let unchecked_client_s_pk = KeyFormat::Repr::from_bytes(&checked_bytes[envelope_size..])?;
-        let client_s_pk = KeyFormat::check_public_key(unchecked_client_s_pk)?;
-
-        Ok(Self {
-            envelope: Envelope::<D>::from_bytes(&checked_bytes[..envelope_size])?,
-            client_s_pk,
-        })
-    }
-}
-
-impl<KeyFormat, D> RegisterThirdMessage<KeyFormat, D>
-where
-    KeyFormat: KeyPair,
-    D: Hash,
-{
-    /// Serialization into bytes
-    pub fn serialize(&self) -> Vec<u8> {
-        let mut registration_upload: Vec<u8> = Vec::new();
-        registration_upload.extend_from_slice(&self.envelope.serialize());
-        registration_upload.extend_from_slice(&serialize(&self.client_s_pk.to_arr(), 2));
-
-        let mut output: Vec<u8> = Vec::new();
-        output.push(ProtocolMessageType::from(self) as u8 + 1);
-        output.extend_from_slice(&serialize(&registration_upload, 3));
-        output
-    }
-
-    /// Deserialization from bytes
-    pub fn deserialize(input: &[u8]) -> Result<Self, ProtocolError> {
-        if input.is_empty() || input[0] != ProtocolMessageType::RegistrationUpload as u8 + 1 {
-            return Err(PakeError::SerializationError.into());
-        }
-
-        let (data, remainder) = tokenize(&input[1..], 3)?;
-        if !remainder.is_empty() {
-            return Err(PakeError::SerializationError.into());
-        }
-
-        let (envelope, remainder) = Envelope::<D>::deserialize(&data)?;
-        let (client_s_pk, remainder) = tokenize(&remainder, 2)?;
-
-        if !remainder.is_empty() {
-            return Err(PakeError::SerializationError.into());
-        }
-
-        Ok(Self {
-            envelope,
-            client_s_pk: KeyFormat::check_public_key(KeyFormat::Repr::from_bytes(&client_s_pk)?)?,
-        })
-    }
-}
-
-/// The message sent by the user to the server, to initiate registration
-pub struct LoginFirstMessage<CS: CipherSuite> {
-    /// blinded password information
-    alpha: CS::Group,
-    ke1_message: <CS::KeyExchange as KeyExchange<CS::Hash, CS::KeyFormat>>::KE1Message,
-}
-
-impl<CS: CipherSuite> TryFrom<&[u8]> for LoginFirstMessage<CS> {
-    type Error = ProtocolError;
-    fn try_from(first_message_bytes: &[u8]) -> Result<Self, Self::Error> {
-        Self::deserialize(first_message_bytes)
-    }
-}
-
-impl<CS: CipherSuite> LoginFirstMessage<CS> {
-    /// byte representation for the login request
-    fn to_bytes(&self) -> Vec<u8> {
-        [&self.alpha.to_arr()[..], &self.ke1_message.to_bytes()].concat()
-    }
-
-    /// Serialization into bytes
-    pub fn serialize(&self) -> Vec<u8> {
-        let mut credential_request: Vec<u8> = Vec::new();
-        credential_request.extend_from_slice(&serialize(&self.alpha.to_arr(), 2));
-
-        let mut output: Vec<u8> = Vec::new();
-        output.push(ProtocolMessageType::from(self) as u8 + 1);
-        output.extend_from_slice(&serialize(&credential_request, 3));
-        output.extend_from_slice(&self.ke1_message.to_bytes());
-        output
-    }
-
-    /// Deserialization from bytes
-    pub fn deserialize(input: &[u8]) -> Result<Self, ProtocolError> {
-        if input.is_empty() || input[0] != ProtocolMessageType::CredentialRequest as u8 + 1 {
-            return Err(PakeError::SerializationError.into());
-        }
-
-        let (data, ke1m) = tokenize(&input[1..], 3)?;
-        let (alpha_bytes, remainder) = tokenize(&data, 2)?;
-
-        if !remainder.is_empty() {
-            return Err(PakeError::SerializationError.into());
-        }
-
-        let elem_len = <CS::Group as Group>::ElemLen::to_usize();
-        let checked_slice = check_slice_size(&alpha_bytes, elem_len, "login_first_message_bytes")?;
-        let arr = GenericArray::from_slice(&checked_slice[..elem_len]);
-        let alpha = <CS::Group as Group>::from_element_slice(arr)?;
-
-        let ke1_message =
-            <CS::KeyExchange as KeyExchange<CS::Hash, CS::KeyFormat>>::KE1Message::try_from(
-                &ke1m[..],
-            )?;
-
-        Ok(Self { alpha, ke1_message })
-    }
-}
-
-/// The answer sent by the server to the user, upon reception of the
-/// login attempt.
-pub struct LoginSecondMessage<CS: CipherSuite> {
-    /// the server's oprf output
-    beta: CS::Group,
-    /// the user's sealed information,
-    envelope: Envelope<CS::Hash>,
-    ke2_message: <CS::KeyExchange as KeyExchange<CS::Hash, CS::KeyFormat>>::KE2Message,
-}
-
-impl<CS: CipherSuite> LoginSecondMessage<CS> {
-    /// Serialization into bytes
-    pub fn serialize(&self) -> Vec<u8> {
-        let mut credential_response: Vec<u8> = Vec::new();
-        credential_response.extend_from_slice(&serialize(&self.beta.to_arr(), 2));
-        credential_response.extend_from_slice(&self.envelope.to_bytes());
-
-        let mut output: Vec<u8> = Vec::new();
-        output.push(ProtocolMessageType::from(self) as u8 + 1);
-        output.extend_from_slice(&serialize(&credential_response, 3));
-        output.extend_from_slice(&self.ke2_message.to_bytes());
-        output
-    }
-
-    /// Deserialization from bytes
-    pub fn deserialize(input: &[u8]) -> Result<Self, ProtocolError> {
-        if input.is_empty() || input[0] != ProtocolMessageType::CredentialResponse as u8 + 1 {
-            return Err(PakeError::SerializationError.into());
-        }
-
-        let (data, ke2m) = tokenize(&input[1..], 3)?;
-        let (beta_bytes, envelope_bytes) = tokenize(&data, 2)?;
-
-        let concatenated = [&beta_bytes[..], &envelope_bytes[..], &ke2m[..]].concat();
-        Self::try_from(&concatenated[..])
-    }
-}
-
-impl<CS: CipherSuite> TryFrom<&[u8]> for LoginSecondMessage<CS> {
-    type Error = ProtocolError;
-    fn try_from(second_message_bytes: &[u8]) -> Result<Self, Self::Error> {
-        let elem_len = <CS::Group as Group>::ElemLen::to_usize();
-        let checked_slice =
-            check_slice_size_atleast(second_message_bytes, elem_len, "login_second_message_bytes")?;
-
-        // Check that the message is actually containing an element of the
-        // correct subgroup
-        let beta_bytes = &checked_slice[..elem_len];
-        let arr = GenericArray::from_slice(beta_bytes);
-        let beta = CS::Group::from_element_slice(arr)?;
-
-        let (envelope, remainder) = Envelope::<CS::Hash>::deserialize(&checked_slice[elem_len..])?;
-
-        let ke2_message_size = CS::KeyExchange::ke2_message_size();
-        let checked_remainder =
-            check_slice_size_atleast(&remainder, ke2_message_size, "login_second_message_bytes")?;
-        let ke2_message =
-            <CS::KeyExchange as KeyExchange<CS::Hash, CS::KeyFormat>>::KE2Message::try_from(
-                &checked_remainder,
-            )?;
-
-        Ok(Self {
-            beta,
-            envelope,
-            ke2_message,
-        })
-    }
-}
-
-/// The answer sent by the client to the server, upon reception of the
-/// sealed envelope
-pub struct LoginThirdMessage<CS: CipherSuite> {
-    ke3_message: <CS::KeyExchange as KeyExchange<CS::Hash, CS::KeyFormat>>::KE3Message,
-}
-
-impl<CS: CipherSuite> TryFrom<&[u8]> for LoginThirdMessage<CS> {
-    type Error = ProtocolError;
-
-    fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
-        let ke3_message =
-            <CS::KeyExchange as KeyExchange<CS::Hash, CS::KeyFormat>>::KE3Message::try_from(bytes)?;
-        Ok(Self { ke3_message })
-    }
-}
-
-impl<CS: CipherSuite> LoginThirdMessage<CS> {
-    /// Serialization into bytes
-    pub fn serialize(&self) -> Vec<u8> {
-        let mut output: Vec<u8> = Vec::new();
-        output.push(ProtocolMessageType::from(self) as u8 + 1);
-        output.extend_from_slice(&self.ke3_message.to_bytes());
-        output
-    }
-
-    /// Deserialization from bytes
-    pub fn deserialize(input: &[u8]) -> Result<Self, ProtocolError> {
-        if input.is_empty() || input[0] != ProtocolMessageType::KeyExchange as u8 + 1 {
-            return Err(PakeError::SerializationError.into());
-        }
-
-        Self::try_from(&input[1..])
-    }
-
-    /// byte representation for the login finalization
-    pub fn to_bytes(&self) -> Vec<u8> {
-        self.ke3_message.to_bytes()
-    }
-}
-
 // Registration
 // ============
 
@@ -539,6 +90,18 @@ impl<CS: CipherSuite> ClientRegistration<CS> {
     }
 }
 
+/// Optional parameters for client registration start
+pub enum ClientRegistrationStartParameters {
+    /// Specifying the identifiers idU and idS
+    WithIdentifiers(Vec<u8>, Vec<u8>),
+}
+
+impl Default for ClientRegistrationStartParameters {
+    fn default() -> Self {
+        Self::WithIdentifiers(Vec::new(), Vec::new())
+    }
+}
+
 impl<CS: CipherSuite> ClientRegistration<CS> {
     /// Returns an initial "blinded" request to send to the server, as well as a ClientRegistration
     ///
@@ -548,7 +111,7 @@ impl<CS: CipherSuite> ClientRegistration<CS> {
     /// # Example
     ///
     /// ```
-    /// use opaque_ke::opaque::ClientRegistration;
+    /// use opaque_ke::{ClientRegistration, ClientRegistrationStartParameters};
     /// # use opaque_ke::errors::ProtocolError;
     /// use rand_core::{OsRng, RngCore};
     /// use opaque_ke::ciphersuite::CipherSuite;
@@ -560,34 +123,20 @@ impl<CS: CipherSuite> ClientRegistration<CS> {
     ///     type Hash = sha2::Sha256;
     ///     type SlowHash = opaque_ke::slow_hash::NoOpHash;
     /// }
-    /// let mut rng = OsRng;
-    /// let (register_m1, registration_state) = ClientRegistration::<Default>::start(b"hunter2", &mut rng)?;
+    /// let mut client_rng = OsRng;
+    /// let (register_m1, registration_state) = ClientRegistration::<Default>::start(b"hunter2", ClientRegistrationStartParameters::default(), &mut client_rng)?;
     /// # Ok::<(), ProtocolError>(())
     /// ```
     pub fn start<R: RngCore + CryptoRng>(
         password: &[u8],
-        blinding_factor_rng: &mut R,
-    ) -> Result<(RegisterFirstMessage<CS::Group>, Self), ProtocolError> {
-        Self::start_with_user_and_server_name(
-            &Vec::new(),
-            &Vec::new(),
-            password,
-            blinding_factor_rng,
-            #[cfg(test)]
-            std::convert::identity,
-        )
-    }
-
-    /// Same as ClientRegistration::start, but also accepts a username and
-    /// server name as input
-    /// as well as an optional postprocessing function for the blinding factor(used in tests)
-    pub fn start_with_user_and_server_name<R: RngCore + CryptoRng>(
-        user_name: &[u8],
-        server_name: &[u8],
-        password: &[u8],
+        params: ClientRegistrationStartParameters,
         blinding_factor_rng: &mut R,
         #[cfg(test)] postprocess: fn(<CS::Group as Group>::Scalar) -> <CS::Group as Group>::Scalar,
     ) -> Result<(RegisterFirstMessage<CS::Group>, Self), ProtocolError> {
+        let (id_u, id_s) = match params {
+            ClientRegistrationStartParameters::WithIdentifiers(id_u, id_s) => (id_u, id_s),
+        };
+
         let (token, alpha) = oprf::blind::<R, CS::Group>(
             &password,
             blinding_factor_rng,
@@ -597,11 +146,7 @@ impl<CS: CipherSuite> ClientRegistration<CS> {
 
         Ok((
             RegisterFirstMessage::<CS::Group> { alpha },
-            Self {
-                id_u: user_name.to_vec(),
-                id_s: server_name.to_vec(),
-                token,
-            },
+            Self { id_u, id_s, token },
         ))
     }
 }
@@ -621,7 +166,7 @@ impl<CS: CipherSuite> ClientRegistration<CS> {
     /// # Example
     ///
     /// ```
-    /// use opaque_ke::{opaque::{ClientRegistration, ServerRegistration}, keypair::X25519KeyPair};
+    /// use opaque_ke::{ClientRegistration, ClientRegistrationStartParameters, ServerRegistration, keypair::X25519KeyPair};
     /// # use opaque_ke::errors::ProtocolError;
     /// # use opaque_ke::keypair::KeyPair;
     /// use rand_core::{OsRng, RngCore};
@@ -637,26 +182,14 @@ impl<CS: CipherSuite> ClientRegistration<CS> {
     /// let mut client_rng = OsRng;
     /// let mut server_rng = OsRng;
     /// let server_kp = X25519KeyPair::generate_random(&mut server_rng)?;
-    /// let (register_m1, client_state) = ClientRegistration::<Default>::start(b"hunter2", &mut client_rng)?;
+    /// let (register_m1, client_state) = ClientRegistration::<Default>::start(b"hunter2", ClientRegistrationStartParameters::default(), &mut client_rng)?;
     /// let (register_m2, server_state) =
-    /// ServerRegistration::<Default>::start(register_m1, &mut server_rng)?;
+    /// ServerRegistration::<Default>::start(register_m1, server_kp.public(), &mut server_rng)?;
     /// let mut client_rng = OsRng;
-    /// let register_m3 = client_state.finish(register_m2, server_kp.public(), &mut client_rng)?;
+    /// let register_m3 = client_state.finish(register_m2, &mut client_rng)?;
     /// # Ok::<(), ProtocolError>(())
     /// ```
     pub fn finish<R: CryptoRng + RngCore>(
-        self,
-        r2: RegisterSecondMessage<CS::Group>,
-        server_s_pk: &<CS::KeyFormat as KeyPair>::Repr,
-        rng: &mut R,
-    ) -> Result<ClientRegistrationFinishResult<CS::KeyFormat, CS::Hash>, ProtocolError> {
-        let mut r2_cloned = r2;
-        r2_cloned.server_s_pk = server_s_pk.to_arr().to_vec();
-        self.finish_using_transmitted_server_public_key(r2_cloned, rng)
-    }
-
-    /// Same as finish, but without the server public key check
-    pub fn finish_using_transmitted_server_public_key<R: CryptoRng + RngCore>(
         self,
         r2: RegisterSecondMessage<CS::Group>,
         rng: &mut R,
@@ -803,7 +336,7 @@ where
     /// # Example
     ///
     /// ```
-    /// use opaque_ke::{opaque::*, keypair::X25519KeyPair};
+    /// use opaque_ke::{*, keypair::{KeyPair, X25519KeyPair}};
     /// # use opaque_ke::errors::ProtocolError;
     /// use rand_core::{OsRng, RngCore};
     /// use opaque_ke::ciphersuite::CipherSuite;
@@ -817,37 +350,15 @@ where
     /// }
     /// let mut client_rng = OsRng;
     /// let mut server_rng = OsRng;
-    /// let (register_m1, client_state) = ClientRegistration::<Default>::start(b"hunter2", &mut client_rng)?;
+    /// let server_kp = X25519KeyPair::generate_random(&mut server_rng)?;
+    /// let (register_m1, client_state) = ClientRegistration::<Default>::start(b"hunter2", ClientRegistrationStartParameters::default(), &mut client_rng)?;
     /// let (register_m2, server_state) =
-    /// ServerRegistration::<Default>::start(register_m1, &mut server_rng)?;
+    /// ServerRegistration::<Default>::start(register_m1, server_kp.public(), &mut server_rng)?;
     /// # Ok::<(), ProtocolError>(())
     /// ```
     pub fn start<R: RngCore + CryptoRng>(
         message: RegisterFirstMessage<CS::Group>,
-        rng: &mut R,
-    ) -> Result<(RegisterSecondMessage<CS::Group>, Self), ProtocolError> {
-        Self::start_with_server_pk(message, &Vec::new(), rng)
-    }
-
-    /// Same as start, but with the ability to supply a server_s_pk as input
-    pub fn start_with_server_pk<R: RngCore + CryptoRng>(
-        message: RegisterFirstMessage<CS::Group>,
-        server_s_pk: &[u8],
-        rng: &mut R,
-    ) -> Result<(RegisterSecondMessage<CS::Group>, Self), ProtocolError> {
-        Self::start_with_server_pk_and_ecf(
-            message,
-            server_s_pk,
-            EnvelopeCredentialsFormat::default()?,
-            rng,
-        )
-    }
-
-    /// Same as start, but with the ability to supply a server_s_pk as input and envelope credentials format
-    pub fn start_with_server_pk_and_ecf<R: RngCore + CryptoRng>(
-        message: RegisterFirstMessage<CS::Group>,
-        server_s_pk: &[u8],
-        ecf: EnvelopeCredentialsFormat,
+        server_s_pk: &<CS::KeyFormat as KeyPair>::Repr,
         rng: &mut R,
     ) -> Result<(RegisterSecondMessage<CS::Group>, Self), ProtocolError> {
         // RFC: generate oprf_key (salt) and v_u = g^oprf_key
@@ -859,8 +370,8 @@ where
         Ok((
             RegisterSecondMessage {
                 beta,
-                server_s_pk: server_s_pk.to_vec(),
-                ecf,
+                server_s_pk: server_s_pk.to_arr().to_vec(),
+                ecf: EnvelopeCredentialsFormat::default()?,
             },
             Self {
                 envelope: None,
@@ -879,7 +390,7 @@ where
     /// # Example
     ///
     /// ```
-    /// use opaque_ke::{opaque::*, keypair::{KeyPair, X25519KeyPair}};
+    /// use opaque_ke::{*, keypair::{KeyPair, X25519KeyPair}};
     /// # use opaque_ke::errors::ProtocolError;
     /// use rand_core::{OsRng, RngCore};
     /// use opaque_ke::ciphersuite::CipherSuite;
@@ -894,11 +405,11 @@ where
     /// let mut client_rng = OsRng;
     /// let mut server_rng = OsRng;
     /// let server_kp = X25519KeyPair::generate_random(&mut server_rng)?;
-    /// let (register_m1, client_state) = ClientRegistration::<Default>::start(b"hunter2", &mut client_rng)?;
+    /// let (register_m1, client_state) = ClientRegistration::<Default>::start(b"hunter2", ClientRegistrationStartParameters::default(), &mut client_rng)?;
     /// let (register_m2, server_state) =
-    /// ServerRegistration::<Default>::start(register_m1, &mut server_rng)?;
+    /// ServerRegistration::<Default>::start(register_m1, server_kp.public(), &mut server_rng)?;
     /// let mut client_rng = OsRng;
-    /// let (register_m3, _export_key) = client_state.finish(register_m2, server_kp.public(), &mut client_rng)?;
+    /// let (register_m3, _export_key) = client_state.finish(register_m2, &mut client_rng)?;
     /// let client_record = server_state.finish(register_m3)?;
     /// # Ok::<(), ProtocolError>(())
     /// ```
@@ -983,11 +494,53 @@ impl<CS: CipherSuite> ClientLogin<CS> {
     }
 }
 
-type ClientLoginFinishResult<CS> = (
-    LoginThirdMessage<CS>,
-    Vec<u8>,
-    GenericArray<u8, ExportKeySize>,
-);
+/// Optional parameters for client login start
+pub enum ClientLoginStartParameters {
+    /// Specifying an info field that will be sent to the server
+    WithInfo(Vec<u8>),
+    /// Specifying the info field along with idU and idS
+    WithIdentifiersAndInfo(Vec<u8>, Vec<u8>, Vec<u8>),
+}
+
+impl Default for ClientLoginStartParameters {
+    fn default() -> Self {
+        Self::WithIdentifiersAndInfo(Vec::new(), Vec::new(), 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
+    pub credential_request: LoginFirstMessage<CS>,
+    /// The state that the client must keep in order to complete the protocol
+    pub client_login_state: ClientLogin<CS>,
+}
+
+/// Optional parameters for client login finish
+pub enum ClientLoginFinishParameters {
+    /// Specifying an info and confidential info field that will be sent to the server
+    WithInfo(Vec<u8>, Vec<u8>),
+}
+
+impl Default for ClientLoginFinishParameters {
+    fn default() -> Self {
+        Self::WithInfo(Vec::new(), Vec::new())
+    }
+}
+
+/// Contains the fields that are returned by a client login finish
+pub struct ClientLoginFinishResult<CS: CipherSuite> {
+    /// The plaintext info sent by the client
+    pub plain_info: Vec<u8>,
+    /// The message to send back to the client
+    pub confidential_info: Vec<u8>,
+    /// The message to send to the server to complete the protocol
+    pub key_exchange: LoginThirdMessage<CS>,
+    /// The shared session secret
+    pub session_secret: Vec<u8>,
+    /// The client-side export key
+    pub export_key: GenericArray<u8, ExportKeySize>,
+}
 
 impl<CS: CipherSuite> ClientLogin<CS> {
     /// Returns an initial "blinded" password request to send to the server, as well as a ClientLogin
@@ -998,7 +551,7 @@ impl<CS: CipherSuite> ClientLogin<CS> {
     /// # Example
     ///
     /// ```
-    /// use opaque_ke::opaque::ClientLogin;
+    /// use opaque_ke::{ClientLogin, ClientLoginStartParameters};
     /// # use opaque_ke::errors::ProtocolError;
     /// use rand_core::{OsRng, RngCore};
     /// use opaque_ke::ciphersuite::CipherSuite;
@@ -1011,32 +564,22 @@ impl<CS: CipherSuite> ClientLogin<CS> {
     ///     type SlowHash = opaque_ke::slow_hash::NoOpHash;
     /// }
     /// let mut client_rng = OsRng;
-    /// let (login_m1, client_login_state) = ClientLogin::<Default>::start(b"hunter2", &mut client_rng)?;
+    /// let client_login_start_result = ClientLogin::<Default>::start(b"hunter2", &mut client_rng, ClientLoginStartParameters::default())?;
     /// # Ok::<(), ProtocolError>(())
     /// ```
     pub fn start<R: RngCore + CryptoRng>(
         password: &[u8],
         rng: &mut R,
-    ) -> Result<(LoginFirstMessage<CS>, Self), ProtocolError> {
-        Self::start_with_user_and_server_name(
-            &Vec::new(),
-            &Vec::new(),
-            password,
-            rng,
-            #[cfg(test)]
-            std::convert::identity,
-        )
-    }
-
-    /// Same as start, but allows the user to supply a username and server name
-    /// and, in tests, a postprocessing function
-    pub fn start_with_user_and_server_name<R: RngCore + CryptoRng>(
-        user_name: &[u8],
-        server_name: &[u8],
-        password: &[u8],
-        rng: &mut R,
+        params: ClientLoginStartParameters,
         #[cfg(test)] postprocess: fn(<CS::Group as Group>::Scalar) -> <CS::Group as Group>::Scalar,
-    ) -> Result<(LoginFirstMessage<CS>, Self), ProtocolError> {
+    ) -> Result<ClientLoginStartResult<CS>, ProtocolError> {
+        let (info, id_u, id_s) = match params {
+            ClientLoginStartParameters::WithInfo(info) => (info, Vec::new(), Vec::new()),
+            ClientLoginStartParameters::WithIdentifiersAndInfo(info, id_u, id_s) => {
+                (info, id_u, id_s)
+            }
+        };
+
         let (token, alpha) = oprf::blind::<R, CS::Group>(
             &password,
             rng,
@@ -1044,23 +587,20 @@ impl<CS: CipherSuite> ClientLogin<CS> {
             postprocess,
         )?;
 
-        let (ke1_state, ke1_message) = CS::KeyExchange::generate_ke1(
-            alpha.to_arr().to_vec(),
-            Vec::new(), /* TODO set this */
-            rng,
-        )?;
+        let (ke1_state, ke1_message) =
+            CS::KeyExchange::generate_ke1(alpha.to_arr().to_vec(), info, rng)?;
 
         let l1 = LoginFirstMessage { alpha, ke1_message };
 
-        Ok((
-            l1,
-            Self {
-                id_u: user_name.to_vec(),
-                id_s: server_name.to_vec(),
+        Ok(ClientLoginStartResult {
+            credential_request: l1,
+            client_login_state: Self {
+                id_u,
+                id_s,
                 token,
                 ke1_state,
             },
-        ))
+        })
     }
 
     /// "Unblinds" the server's answer and returns the opened assets from
@@ -1072,8 +612,8 @@ impl<CS: CipherSuite> ClientLogin<CS> {
     /// # Example
     ///
     /// ```
-    /// use opaque_ke::opaque::{ClientLogin, ServerLogin};
-    /// # use opaque_ke::opaque::{ClientRegistration, ServerRegistration};
+    /// use opaque_ke::{ClientLogin, ClientLoginStartParameters, ClientLoginFinishParameters, ServerLogin, ServerLoginStartParameters};
+    /// # use opaque_ke::{ClientRegistration, ClientRegistrationStartParameters, ServerRegistration};
     /// # use opaque_ke::errors::ProtocolError;
     /// # use opaque_ke::keypair::{X25519KeyPair, KeyPair};
     /// use rand_core::{OsRng, RngCore};
@@ -1088,22 +628,25 @@ impl<CS: CipherSuite> ClientLogin<CS> {
     /// }
     /// let mut client_rng = OsRng;
     /// # let mut server_rng = OsRng;
-    /// # let (register_m1, client_state) = ClientRegistration::<Default>::start(b"hunter2", &mut client_rng)?;
+    /// # let (register_m1, client_state) = ClientRegistration::<Default>::start(b"hunter2", ClientRegistrationStartParameters::default(), &mut client_rng)?;
     /// # let server_kp = X25519KeyPair::generate_random(&mut server_rng)?;
-    /// # let (register_m2, server_state) = ServerRegistration::<Default>::start(register_m1, &mut server_rng)?;
-    /// # let (register_m3, _export_key) = client_state.finish(register_m2, server_kp.public(), &mut client_rng)?;
+    /// # let (register_m2, server_state) = ServerRegistration::<Default>::start(register_m1, server_kp.public(), &mut server_rng)?;
+    /// # let (register_m3, _export_key) = client_state.finish(register_m2, &mut client_rng)?;
     /// # let p_file = server_state.finish(register_m3)?;
-    /// let (login_m1, client_login_state) = ClientLogin::<Default>::start(b"hunter2", &mut client_rng)?;
-    /// let (login_m2, server_login_state) = ServerLogin::start(p_file, &server_kp.private(), login_m1, &mut server_rng)?;
-    /// let (login_m3, client_transport, _export_key) = client_login_state.finish(login_m2, &server_kp.public(), &mut client_rng)?;
+    /// let client_login_start_result = ClientLogin::<Default>::start(b"hunter2", &mut client_rng, ClientLoginStartParameters::default())?;
+    /// let server_login_start_result = ServerLogin::start(p_file, &server_kp.private(), client_login_start_result.credential_request, &mut server_rng, ServerLoginStartParameters::default())?;
+    /// let client_login_finish_result = client_login_start_result.client_login_state.finish(server_login_start_result.credential_response, ClientLoginFinishParameters::default())?;
     /// # Ok::<(), ProtocolError>(())
     /// ```
-    pub fn finish<R: RngCore + CryptoRng>(
+    pub fn finish(
         self,
         l2: LoginSecondMessage<CS>,
-        _server_s_pk: &<<CS as CipherSuite>::KeyFormat as KeyPair>::Repr,
-        _client_e_sk_rng: &mut R,
+        params: ClientLoginFinishParameters,
     ) -> Result<ClientLoginFinishResult<CS>, ProtocolError> {
+        let (info, e_info) = match params {
+            ClientLoginFinishParameters::WithInfo(info, e_info) => (info, e_info),
+        };
+
         let l2_bytes: Vec<u8> = [&l2.beta.to_arr()[..], &l2.envelope.to_bytes()].concat();
 
         let password_derived_key =
@@ -1117,25 +660,45 @@ impl<CS: CipherSuite> ClientLogin<CS> {
                 err => PakeError::from(err),
             })?;
 
-        let (shared_secret, ke3_message) = CS::KeyExchange::generate_ke3(
-            l2_bytes,
-            l2.ke2_message,
-            &self.ke1_state,
-            <CS::KeyFormat as KeyPair>::Repr::from_bytes(
-                &opened_envelope.credentials_map[&CredentialType::PkS],
-            )?,
-            <CS::KeyFormat as KeyPair>::Repr::from_bytes(
-                &opened_envelope.credentials_map[&CredentialType::SkU],
-            )?,
-            Vec::new(), // TODO: fill in
-            Vec::new(), // TODO: fill in
+        let client_s_sk = <CS::KeyFormat as KeyPair>::Repr::from_bytes(
+            &opened_envelope.credentials_map[&CredentialType::SkU],
+        )?;
+        let server_s_pk = <CS::KeyFormat as KeyPair>::Repr::from_bytes(
+            &opened_envelope.credentials_map[&CredentialType::PkS],
         )?;
 
-        Ok((
-            LoginThirdMessage { ke3_message },
-            shared_secret,
-            opened_envelope.export_key,
-        ))
+        let id_u = match opened_envelope.credentials_map.get(&CredentialType::IdU) {
+            Some(id_u) => id_u.clone(),
+            None => CS::KeyFormat::public_from_private(&client_s_sk)
+                .to_arr()
+                .to_vec(),
+        };
+
+        let id_s = match opened_envelope.credentials_map.get(&CredentialType::IdS) {
+            Some(id_s) => id_s.clone(),
+            None => server_s_pk.to_arr().to_vec(),
+        };
+
+        let (plain_info, confidential_info, session_secret, ke3_message) =
+            CS::KeyExchange::generate_ke3(
+                l2_bytes,
+                l2.ke2_message,
+                &self.ke1_state,
+                server_s_pk,
+                client_s_sk,
+                id_u,
+                id_s,
+                info,
+                e_info,
+            )?;
+
+        Ok(ClientLoginFinishResult {
+            plain_info,
+            confidential_info,
+            key_exchange: LoginThirdMessage { ke3_message },
+            session_secret,
+            export_key: opened_envelope.export_key,
+        })
     }
 }
 
@@ -1158,7 +721,40 @@ impl<CS: CipherSuite> TryFrom<&[u8]> for ServerLogin<CS> {
     }
 }
 
-type ServerLoginStartResult<CS> = (LoginSecondMessage<CS>, ServerLogin<CS>);
+/// Optional parameters for server login start
+pub enum ServerLoginStartParameters {
+    /// Specifying an info and confidential info field that will be sent to the client
+    WithInfo(Vec<u8>, Vec<u8>),
+    /// Specifying an info, confidential info that will be sent to the client,
+    /// along with an id_u and id_s that will be matched against the client
+    WithInfoAndIdentifiers(Vec<u8>, Vec<u8>, Vec<u8>, Vec<u8>),
+}
+
+impl Default for ServerLoginStartParameters {
+    fn default() -> Self {
+        Self::WithInfo(Vec::new(), Vec::new())
+    }
+}
+
+/// Contains the fields that are returned by a server login start
+pub struct ServerLoginStartResult<CS: CipherSuite> {
+    /// The plaintext info sent by the client
+    pub plain_info: Vec<u8>,
+    /// The message to send back to the client
+    pub credential_response: LoginSecondMessage<CS>,
+    /// The state that the server must keep in order to finish the protocl
+    pub server_login_state: ServerLogin<CS>,
+}
+
+/// Contains the fields that are returned by a server login finish
+pub struct ServerLoginFinishResult {
+    /// The plaintext info sent by the client
+    pub plain_info: Vec<u8>,
+    /// The confidential info sent by the client
+    pub confidential_info: Vec<u8>,
+    /// The shared session secret between client and server
+    pub session_secret: Vec<u8>,
+}
 
 impl<CS: CipherSuite> ServerLogin<CS> {
     /// byte representation for the server's login state
@@ -1175,8 +771,8 @@ impl<CS: CipherSuite> ServerLogin<CS> {
     /// # Example
     ///
     /// ```
-    /// use opaque_ke::opaque::{ClientLogin, ServerLogin};
-    /// # use opaque_ke::opaque::{ClientRegistration,  ServerRegistration};
+    /// use opaque_ke::{ClientLogin, ClientLoginStartParameters, ServerLogin, ServerLoginStartParameters};
+    /// # use opaque_ke::{ClientRegistration, ClientRegistrationStartParameters, ServerRegistration};
     /// # use opaque_ke::errors::ProtocolError;
     /// # use opaque_ke::keypair::{KeyPair, X25519KeyPair};
     /// use rand_core::{OsRng, RngCore};
@@ -1192,13 +788,13 @@ impl<CS: CipherSuite> ServerLogin<CS> {
     /// let mut client_rng = OsRng;
     /// let mut server_rng = OsRng;
     /// let server_kp = X25519KeyPair::generate_random(&mut server_rng)?;
-    /// # let (register_m1, client_state) = ClientRegistration::<Default>::start(b"hunter2", &mut client_rng)?;
+    /// # let (register_m1, client_state) = ClientRegistration::<Default>::start(b"hunter2", ClientRegistrationStartParameters::default(), &mut client_rng)?;
     /// # let (register_m2, server_state) =
-    /// ServerRegistration::<Default>::start(register_m1, &mut server_rng)?;
-    /// # let (register_m3, _export_key) = client_state.finish(register_m2, server_kp.public(), &mut client_rng)?;
+    /// ServerRegistration::<Default>::start(register_m1, server_kp.public(), &mut server_rng)?;
+    /// # let (register_m3, _export_key) = client_state.finish(register_m2, &mut client_rng)?;
     /// # let p_file = server_state.finish(register_m3)?;
-    /// let (login_m1, client_login_state) = ClientLogin::<Default>::start(b"hunter2", &mut client_rng)?;
-    /// let (login_m2, server_login_state) = ServerLogin::start(p_file, &server_kp.private(), login_m1, &mut server_rng)?;
+    /// let client_login_start_result = ClientLogin::<Default>::start(b"hunter2", &mut client_rng, ClientLoginStartParameters::default())?;
+    /// let server_login_start_result = ServerLogin::start(p_file, &server_kp.private(), client_login_start_result.credential_request, &mut server_rng, ServerLoginStartParameters::default())?;
     /// # Ok::<(), ProtocolError>(())
     /// ```
     pub fn start<R: RngCore + CryptoRng>(
@@ -1206,26 +802,47 @@ impl<CS: CipherSuite> ServerLogin<CS> {
         server_s_sk: &<CS::KeyFormat as KeyPair>::Repr,
         l1: LoginFirstMessage<CS>,
         rng: &mut R,
+        params: ServerLoginStartParameters,
     ) -> Result<ServerLoginStartResult<CS>, ProtocolError> {
-        let l1_bytes = &l1.to_bytes();
-        let beta = oprf::evaluate(l1.alpha, &password_file.oprf_key)?;
-
         let client_s_pk = password_file
             .client_s_pk
             .ok_or(InternalPakeError::SealError)?;
-        let envelope = password_file.envelope.ok_or(InternalPakeError::SealError)?;
 
+        let (info, e_info, id_u, id_s) = match params {
+            ServerLoginStartParameters::WithInfo(info, e_info) => (info, e_info, None, None),
+            ServerLoginStartParameters::WithInfoAndIdentifiers(info, e_info, id_u, id_s) => {
+                (info, e_info, Some(id_u), Some(id_s))
+            }
+        };
+
+        let id_u = match id_u {
+            Some(id_u) => id_u,
+            None => client_s_pk.to_arr().to_vec(),
+        };
+
+        let id_s = match id_s {
+            Some(id_s) => id_s,
+            None => CS::KeyFormat::public_from_private(server_s_sk)
+                .to_arr()
+                .to_vec(),
+        };
+
+        let l1_bytes = &l1.to_bytes();
+        let beta = oprf::evaluate(l1.alpha, &password_file.oprf_key)?;
+        let envelope = password_file.envelope.ok_or(InternalPakeError::SealError)?;
         let l2_component: Vec<u8> = [&beta.to_arr()[..], &envelope.to_bytes()].concat();
 
-        let (ke2_state, ke2_message) = CS::KeyExchange::generate_ke2(
+        let (plain_info, ke2_state, ke2_message) = CS::KeyExchange::generate_ke2(
             rng,
             l1_bytes.to_vec(),
             l2_component,
             l1.ke1_message,
             client_s_pk,
             server_s_sk.clone(),
-            Vec::new(), // TODO populate this
-            Vec::new(), // TODO populate this
+            id_u,
+            id_s,
+            info,
+            e_info,
         )?;
 
         let l2 = LoginSecondMessage {
@@ -1234,17 +851,18 @@ impl<CS: CipherSuite> ServerLogin<CS> {
             ke2_message,
         };
 
-        Ok((
-            l2,
-            Self {
+        Ok(ServerLoginStartResult {
+            plain_info,
+            credential_response: l2,
+            server_login_state: Self {
                 _cs: PhantomData,
                 ke2_state,
             },
-        ))
+        })
     }
 
-    /// From the client's second & final message, check the client's
-    /// authentication & produce a message transport
+    /// 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
@@ -1252,8 +870,8 @@ impl<CS: CipherSuite> ServerLogin<CS> {
     /// # Example
     ///
     /// ```
-    /// use opaque_ke::opaque::{ClientLogin, ServerLogin};
-    /// # use opaque_ke::opaque::{ClientRegistration,  ServerRegistration};
+    /// use opaque_ke::{ClientLogin, ClientLoginFinishParameters, ClientLoginStartParameters, ServerLogin, ServerLoginStartParameters};
+    /// # use opaque_ke::{ClientRegistration, ClientRegistrationStartParameters, ServerRegistration};
     /// # use opaque_ke::errors::ProtocolError;
     /// # use opaque_ke::keypair::{KeyPair, X25519KeyPair};
     /// use rand_core::{OsRng, RngCore};
@@ -1269,27 +887,37 @@ impl<CS: CipherSuite> ServerLogin<CS> {
     /// let mut client_rng = OsRng;
     /// let mut server_rng = OsRng;
     /// let server_kp = X25519KeyPair::generate_random(&mut server_rng)?;
-    /// # let (register_m1, client_state) = ClientRegistration::<Default>::start(b"hunter2", &mut client_rng)?;
+    /// # let (register_m1, client_state) = ClientRegistration::<Default>::start(b"hunter2", ClientRegistrationStartParameters::default(), &mut client_rng)?;
     /// # let (register_m2, server_state) =
-    /// ServerRegistration::<Default>::start(register_m1, &mut server_rng)?;
-    /// # let (register_m3, _export_key) = client_state.finish(register_m2, server_kp.public(), &mut client_rng)?;
+    /// ServerRegistration::<Default>::start(register_m1, server_kp.public(), &mut server_rng)?;
+    /// # let (register_m3, _export_key) = client_state.finish(register_m2, &mut client_rng)?;
     /// # let p_file = server_state.finish(register_m3)?;
-    /// let (login_m1, client_login_state) = ClientLogin::<Default>::start(b"hunter2", &mut client_rng)?;
-    /// let (login_m2, server_login_state) = ServerLogin::start(p_file, &server_kp.private(), login_m1, &mut server_rng)?;
-    /// let (login_m3, client_transport, _export_key) = client_login_state.finish(login_m2, &server_kp.public(), &mut client_rng)?;
-    /// let mut server_transport = server_login_state.finish(login_m3)?;
+    /// let client_login_start_result = ClientLogin::<Default>::start(b"hunter2", &mut client_rng, ClientLoginStartParameters::default())?;
+    /// let server_login_start_result = ServerLogin::start(p_file, &server_kp.private(), client_login_start_result.credential_request, &mut server_rng, ServerLoginStartParameters::default())?;
+    /// let client_login_finish_result = client_login_start_result.client_login_state.finish(server_login_start_result.credential_response, ClientLoginFinishParameters::default())?;
+    /// let mut server_transport = server_login_start_result.server_login_state.finish(client_login_finish_result.key_exchange)?;
     /// # Ok::<(), ProtocolError>(())
     /// ```
-    pub fn finish(&self, message: LoginThirdMessage<CS>) -> Result<Vec<u8>, ProtocolError> {
-        <CS::KeyExchange as KeyExchange<CS::Hash, CS::KeyFormat>>::finish_ke(
-            message.ke3_message,
-            &self.ke2_state,
-        )
-        .map_err(|e| match e {
-            ProtocolError::VerificationError(PakeError::KeyExchangeMacValidationError) => {
-                ProtocolError::VerificationError(PakeError::InvalidLoginError)
-            }
-            err => err,
+    pub fn finish(
+        &self,
+        message: LoginThirdMessage<CS>,
+    ) -> Result<ServerLoginFinishResult, ProtocolError> {
+        let (plain_info, confidential_info, session_secret) =
+            <CS::KeyExchange as KeyExchange<CS::Hash, CS::KeyFormat>>::finish_ke(
+                message.ke3_message,
+                &self.ke2_state,
+            )
+            .map_err(|e| match e {
+                ProtocolError::VerificationError(PakeError::KeyExchangeMacValidationError) => {
+                    ProtocolError::VerificationError(PakeError::InvalidLoginError)
+                }
+                err => err,
+            })?;
+
+        Ok(ServerLoginFinishResult {
+            plain_info,
+            confidential_info,
+            session_secret,
         })
     }
 }
diff --git a/src/serialization/mod.rs b/src/serialization/mod.rs
index a345157..0e2d77d 100644
--- a/src/serialization/mod.rs
+++ b/src/serialization/mod.rs
@@ -6,13 +6,8 @@
 use crate::errors::PakeError;
 
 use crate::{
-    ciphersuite::CipherSuite,
-    hash::Hash,
-    keypair::KeyPair,
-    opaque::{
-        LoginFirstMessage, LoginSecondMessage, LoginThirdMessage, RegisterFirstMessage,
-        RegisterSecondMessage, RegisterThirdMessage,
-    },
+    ciphersuite::CipherSuite, hash::Hash, keypair::KeyPair, LoginFirstMessage, LoginSecondMessage,
+    LoginThirdMessage, RegisterFirstMessage, RegisterSecondMessage, RegisterThirdMessage,
 };
 
 pub enum ProtocolMessageType {


Test patch:
diff --git a/src/serialization/tests.rs b/src/serialization/tests.rs
index 76a0211..77bacce 100644
--- a/src/serialization/tests.rs
+++ b/src/serialization/tests.rs
@@ -14,6 +14,7 @@ use crate::{
     keypair::{KeyPair, X25519KeyPair},
     opaque::*,
     serialization::{i2osp, os2ip, serialize, ProtocolMessageType},
+    *,
 };
 
 use curve25519_dalek::ristretto::RistrettoPoint;
diff --git a/src/tests/opaque_ke_test.rs b/src/tests/opaque_ke_test.rs
index 9155199..3797460 100644
--- a/src/tests/opaque_ke_test.rs
+++ b/src/tests/opaque_ke_test.rs
@@ -12,6 +12,7 @@ use crate::{
     opaque::*,
     slow_hash::NoOpHash,
     tests::mock_rng::CycleRng,
+    *,
 };
 use curve25519_dalek::edwards::EdwardsPoint;
 use generic_array::GenericArray;
@@ -83,7 +84,7 @@ static TEST_VECTOR: &str = r#"
     "client_nonce": "8bdd8689f4ea9486aaa3bac182d31f31e3c62d76b3869ae1dd05d5c708b13e86",
     "server_nonce": "d529ed9480524236ed6f6cafd92ebab3c6091afde54f47f9079575c090cad7d6",
     "r1": "010000220020ada5910d73be53a8a6c50d32e9efcc0eb3e871103c05fb993fcb8312aff7fc79",
-    "r2": "02000028002035104271fe25c0074ea715af805c019772a97b6e975376f411e1c37831a56fa7000001010103",
+    "r2": "02000048002035104271fe25c0074ea715af805c019772a97b6e975376f411e1c37831a56fa700209ef0646ad66cf13ce4052294c7325e044853f0c1fd03e91288f8339d812d4a5701010103",
     "r3": "030000aeb326061dfc120f5b261b28bbae4262c26ad343a640b5fb4b90ba3f16600e1c89002375ba1dad2970f60479c4ce0fe1e62271f2c5561757557c88941d9278ededbc981de07c00230300209ef0646ad66cf13ce4052294c7325e044853f0c1fd03e91288f8339d812d4a570020c575cad080bafce229c0de9700269d48d8102d3f0f70497a770d9c0bcb5d181e0020aee30cf198cc78b5b1a5ed5a4bda21bceb5e4732fffbddf867e32ee814d67317",
     "l1": "040000220020ada5910d73be53a8a6c50d32e9efcc0eb3e871103c05fb993fcb8312aff7fc798bdd8689f4ea9486aaa3bac182d31f31e3c62d76b3869ae1dd05d5c708b13e8600001fb90ca40034a0ee0d9d087a2c12743329d792b21e4c4424e561a665a1031b00",
     "l2": "050000ae002035104271fe25c0074ea715af805c019772a97b6e975376f411e1c37831a56fa7b326061dfc120f5b261b28bbae4262c26ad343a640b5fb4b90ba3f16600e1c89002375ba1dad2970f60479c4ce0fe1e62271f2c5561757557c88941d9278ededbc981de07c00230300209ef0646ad66cf13ce4052294c7325e044853f0c1fd03e91288f8339d812d4a570020c575cad080bafce229c0de9700269d48d8102d3f0f70497a770d9c0bcb5d181ef021332310d7f6a1c8272d11610e0244cf462efe1a7d9e6657cf12f74d30f2720000b2df778b631c83d1fdd76feb9978d156ad0c1d18fedbe2b15a309f673e3d846400003bc4221316aaad7b6110910bae5a3bc0e498738634916021546f6fce652adc78",
@@ -250,10 +251,9 @@ where
     rng.fill_bytes(&mut server_nonce);
 
     let mut blinding_factor_registration_rng = CycleRng::new(blinding_factor_raw.to_vec());
-    let (r1, client_registration) = ClientRegistration::<CS>::start_with_user_and_server_name(
-        id_u,
-        id_s,
+    let (r1, client_registration) = ClientRegistration::<CS>::start(
         password,
+        ClientRegistrationStartParameters::WithIdentifiers(id_u.to_vec(), id_s.to_vec()),
         &mut blinding_factor_registration_rng,
         std::convert::identity,
     )
@@ -264,7 +264,8 @@ where
     let client_registration_state = client_registration.to_bytes().to_vec();
 
     let mut oprf_key_rng = CycleRng::new(oprf_key_raw.to_vec());
-    let (r2, server_registration) = ServerRegistration::<CS>::start(r1, &mut oprf_key_rng).unwrap();
+    let (r2, server_registration) =
+        ServerRegistration::<CS>::start(r1, server_s_kp.public(), &mut oprf_key_rng).unwrap();
     let r2_bytes = r2.serialize().to_vec();
     let oprf_key_bytes = CS::Group::scalar_as_bytes(&server_registration.oprf_key).clone();
     let server_registration_state = server_registration.to_bytes().to_vec();
@@ -275,7 +276,7 @@ where
 
     let mut finish_registration_rng = CycleRng::new(client_s_sk_and_nonce);
     let (r3, export_key_registration) = client_registration
-        .finish(r2, server_s_kp.public(), &mut finish_registration_rng)
+        .finish(r2, &mut finish_registration_rng)
         .unwrap();
     let r3_bytes = r3.serialize().to_vec();
 
@@ -288,33 +289,52 @@ where
     client_login_start.extend_from_slice(&client_nonce);
 
     let mut client_login_start_rng = CycleRng::new(client_login_start);
-    let (l1, client_login) = ClientLogin::<CS>::start_with_user_and_server_name(
-        id_u,
-        id_s,
+    let client_login_start_result = ClientLogin::<CS>::start(
         password,
         &mut client_login_start_rng,
+        ClientLoginStartParameters::WithIdentifiersAndInfo(
+            Vec::new(),
+            id_u.to_vec(),
+            id_s.to_vec(),
+        ),
         std::convert::identity,
     )
     .unwrap();
-    let l1_bytes = l1.serialize().to_vec();
-    let client_login_state = client_login.to_bytes().to_vec();
+    let l1_bytes = client_login_start_result
+        .credential_request
+        .serialize()
+        .to_vec();
+    let client_login_state = client_login_start_result
+        .client_login_state
+        .to_bytes()
+        .to_vec();
 
     let mut server_e_sk_rng = CycleRng::new(server_e_kp.private().to_arr().to_vec());
-    let (l2, server_login) = ServerLogin::<CS>::start(
+    let server_login_start_result = ServerLogin::<CS>::start(
         password_file,
         server_s_kp.private(),
-        l1,
+        client_login_start_result.credential_request,
         &mut server_e_sk_rng,
+        ServerLoginStartParameters::default(),
     )
     .unwrap();
-    let l2_bytes = l2.serialize().to_vec();
-    let server_login_state = server_login.to_bytes().to_vec();
-
-    let mut client_e_sk_rng = CycleRng::new(client_e_kp.private().to_arr().to_vec());
-    let (l3, client_shared_secret, _export_key_login) = client_login
-        .finish(l2, server_s_kp.public(), &mut client_e_sk_rng)
+    let l2_bytes = server_login_start_result
+        .credential_response
+        .serialize()
+        .to_vec();
+    let server_login_state = server_login_start_result
+        .server_login_state
+        .to_bytes()
+        .to_vec();
+
+    let client_login_finish_result = client_login_start_result
+        .client_login_state
+        .finish(
+            server_login_start_result.credential_response,
+            ClientLoginFinishParameters::default(),
+        )
         .unwrap();
-    let l3_bytes = l3.to_bytes().to_vec();
+    let l3_bytes = client_login_finish_result.key_exchange.to_bytes().to_vec();
 
     TestVectorParameters {
         client_s_pk: client_s_kp.public().to_arr().to_vec(),
@@ -344,7 +364,7 @@ where
         server_registration_state,
         client_login_state,
         server_login_state,
-        shared_secret: client_shared_secret,
+        shared_secret: client_login_finish_result.session_secret,
         export_key: export_key_registration.to_vec(),
     }
 }
@@ -365,15 +385,13 @@ fn postprocess_blinding_factor<G: Group>(_: G::Scalar) -> G::Scalar {
 fn test_r1() -> Result<(), PakeError> {
     let parameters = populate_test_vectors(&serde_json::from_str(TEST_VECTOR).unwrap());
     let mut rng = OsRng;
-    let (r1, client_registration) =
-        ClientRegistration::<X255193dhNoSlowHash>::start_with_user_and_server_name(
-            &parameters.id_u,
-            &parameters.id_s,
-            &parameters.password,
-            &mut rng,
-            postprocess_blinding_factor::<<X255193dhNoSlowHash as CipherSuite>::Group>,
-        )
-        .unwrap();
+    let (r1, client_registration) = ClientRegistration::<X255193dhNoSlowHash>::start(
+        &parameters.password,
+        ClientRegistrationStartParameters::WithIdentifiers(parameters.id_u, parameters.id_s),
+        &mut rng,
+        postprocess_blinding_factor::<<X255193dhNoSlowHash as CipherSuite>::Group>,
+    )
+    .unwrap();
     assert_eq!(hex::encode(&parameters.r1), hex::encode(r1.serialize()));
     assert_eq!(
         hex::encode(&parameters.client_registration_state),
@@ -388,6 +406,7 @@ fn test_r2() -> Result<(), PakeError> {
     let mut oprf_key_rng = CycleRng::new(parameters.oprf_key);
     let (r2, server_registration) = ServerRegistration::<X255193dhNoSlowHash>::start(
         RegisterFirstMessage::deserialize(&parameters.r1[..]).unwrap(),
+        &Key::try_from(&parameters.server_s_pk[..]).unwrap(),
         &mut oprf_key_rng,
     )
     .unwrap();
@@ -412,7 +431,6 @@ fn test_r3() -> Result<(), PakeError> {
     .unwrap()
     .finish(
         RegisterSecondMessage::deserialize(&parameters.r2[..]).unwrap(),
-        &Key::try_from(&parameters.server_s_pk[..]).unwrap(),
         &mut finish_registration_rng,
     )
     .unwrap();
@@ -456,18 +474,24 @@ fn test_l1() -> Result<(), PakeError> {
     ]
     .concat();
     let mut client_login_start_rng = CycleRng::new(client_login_start);
-    let (l1, client_login) = ClientLogin::<X255193dhNoSlowHash>::start_with_user_and_server_name(
-        &parameters.id_u,
-        &parameters.id_s,
+    let client_login_start_result = ClientLogin::<X255193dhNoSlowHash>::start(
         &parameters.password,
         &mut client_login_start_rng,
+        ClientLoginStartParameters::WithIdentifiersAndInfo(
+            Vec::new(),
+            parameters.id_u,
+            parameters.id_s,
+        ),
         postprocess_blinding_factor::<<X255193dhNoSlowHash as CipherSuite>::Group>,
     )
     .unwrap();
-    assert_eq!(hex::encode(&parameters.l1), hex::encode(l1.serialize()));
+    assert_eq!(
+        hex::encode(&parameters.l1),
+        hex::encode(client_login_start_result.credential_request.serialize())
+    );
     assert_eq!(
         hex::encode(&parameters.client_login_state),
-        hex::encode(client_login.to_bytes())
+        hex::encode(client_login_start_result.client_login_state.to_bytes())
     );
     Ok(())
 }
@@ -477,18 +501,22 @@ fn test_l2() -> Result<(), PakeError> {
     let parameters = populate_test_vectors(&serde_json::from_str(TEST_VECTOR).unwrap());
 
     let mut server_e_sk_rng = CycleRng::new(parameters.server_e_sk);
-    let (l2, server_login) = ServerLogin::<X255193dhNoSlowHash>::start(
+    let server_login_start_result = ServerLogin::<X255193dhNoSlowHash>::start(
         ServerRegistration::try_from(&parameters.password_file[..]).unwrap(),
         &Key::try_from(&parameters.server_s_sk[..]).unwrap(),
         LoginFirstMessage::<X255193dhNoSlowHash>::deserialize(&parameters.l1[..]).unwrap(),
         &mut server_e_sk_rng,
+        ServerLoginStartParameters::default(),
     )
     .unwrap();
 
-    assert_eq!(hex::encode(&parameters.l2), hex::encode(l2.serialize()));
+    assert_eq!(
+        hex::encode(&parameters.l2),
+        hex::encode(server_login_start_result.credential_response.serialize())
+    );
     assert_eq!(
         hex::encode(&parameters.server_login_state),
-        hex::encode(server_login.to_bytes())
+        hex::encode(server_login_start_result.server_login_state.to_bytes())
     );
     Ok(())
 }
@@ -497,25 +525,26 @@ fn test_l2() -> Result<(), PakeError> {
 fn test_l3() -> Result<(), PakeError> {
     let parameters = populate_test_vectors(&serde_json::from_str(TEST_VECTOR).unwrap());
 
-    let mut client_e_sk_rng = CycleRng::new(parameters.client_e_sk.to_vec());
-    let (l3, shared_secret, export_key_login) =
+    let client_login_finish_result =
         ClientLogin::<X255193dhNoSlowHash>::try_from(&parameters.client_login_state[..])
             .unwrap()
             .finish(
                 LoginSecondMessage::<X255193dhNoSlowHash>::deserialize(&parameters.l2[..]).unwrap(),
-                &Key::try_from(&parameters.server_s_pk[..])?,
-                &mut client_e_sk_rng,
+                ClientLoginFinishParameters::default(),
             )
             .unwrap();
 
     assert_eq!(
         hex::encode(&parameters.shared_secret),
-        hex::encode(&shared_secret)
+        hex::encode(&client_login_finish_result.session_secret)
+    );
+    assert_eq!(
+        hex::encode(&parameters.l3),
+        hex::encode(client_login_finish_result.key_exchange.to_bytes())
     );
-    assert_eq!(hex::encode(&parameters.l3), hex::encode(l3.to_bytes()));
     assert_eq!(
         hex::encode(&parameters.export_key),
-        hex::encode(export_key_login)
+        hex::encode(client_login_finish_result.export_key)
     );
 
     Ok(())
@@ -525,7 +554,7 @@ fn test_l3() -> Result<(), PakeError> {
 fn test_server_login_finish() -> Result<(), ProtocolError> {
     let parameters = populate_test_vectors(&serde_json::from_str(TEST_VECTOR).unwrap());
 
-    let shared_secret =
+    let server_login_result =
         ServerLogin::<X255193dhNoSlowHash>::try_from(&parameters.server_login_state[..])
             .unwrap()
             .finish(LoginThirdMessage::try_from(&parameters.l3[..])?)
@@ -533,7 +562,7 @@ fn test_server_login_finish() -> Result<(), ProtocolError> {
 
     assert_eq!(
         hex::encode(parameters.shared_secret),
-        hex::encode(shared_secret)
+        hex::encode(server_login_result.session_secret)
     );
 
     Ok(())
@@ -546,36 +575,52 @@ fn test_complete_flow(
     let mut client_rng = OsRng;
     let mut server_rng = OsRng;
     let server_kp = X255193dhNoSlowHash::generate_random_keypair(&mut server_rng)?;
-    let (register_m1, client_state) =
-        ClientRegistration::<X255193dhNoSlowHash>::start(registration_password, &mut client_rng)?;
-    let (register_m2, server_state) =
-        ServerRegistration::<X255193dhNoSlowHash>::start(register_m1, &mut server_rng)?;
+    let (register_m1, client_state) = ClientRegistration::<X255193dhNoSlowHash>::start(
+        registration_password,
+        ClientRegistrationStartParameters::default(),
+        &mut client_rng,
+        std::convert::identity,
+    )?;
+    let (register_m2, server_state) = ServerRegistration::<X255193dhNoSlowHash>::start(
+        register_m1,
+        server_kp.public(),
+        &mut server_rng,
+    )?;
     let (register_m3, registration_export_key) =
-        client_state.finish(register_m2, server_kp.public(), &mut client_rng)?;
+        client_state.finish(register_m2, &mut client_rng)?;
     let p_file = server_state.finish(register_m3)?;
-    let (login_m1, client_login_state) =
-        ClientLogin::<X255193dhNoSlowHash>::start(login_password, &mut client_rng)?;
-    let (login_m2, server_login_state) = ServerLogin::<X255193dhNoSlowHash>::start(
+    let client_login_start_result = ClientLogin::<X255193dhNoSlowHash>::start(
+        login_password,
+        &mut client_rng,
+        ClientLoginStartParameters::default(),
+        std::convert::identity,
+    )?;
+    let server_login_start_result = ServerLogin::<X255193dhNoSlowHash>::start(
         p_file,
         &server_kp.private(),
-        login_m1,
+        client_login_start_result.credential_request,
         &mut server_rng,
+        ServerLoginStartParameters::default(),
     )?;
 
-    let client_login_result =
-        client_login_state.finish(login_m2, &server_kp.public(), &mut client_rng);
+    let client_login_result = client_login_start_result.client_login_state.finish(
+        server_login_start_result.credential_response,
+        ClientLoginFinishParameters::default(),
+    );
 
     if hex::encode(registration_password) == hex::encode(login_password) {
-        let (login_m3, client_shared_secret, login_export_key) = client_login_result?;
-        let server_shared_secret = server_login_state.finish(login_m3)?;
+        let client_login_finish_result = client_login_result?;
+        let server_login_finish_result = server_login_start_result
+            .server_login_state
+            .finish(client_login_finish_result.key_exchange)?;
 
         assert_eq!(
-            hex::encode(server_shared_secret),
-            hex::encode(client_shared_secret)
+            hex::encode(server_login_finish_result.session_secret),
+            hex::encode(client_login_finish_result.session_secret)
         );
         assert_eq!(
             hex::encode(registration_export_key),
-            hex::encode(login_export_key)
+            hex::encode(client_login_finish_result.export_key)
         );
     } else {
         let res = matches!(