Repository: novifinancial/opaque-ke
Pull request #175 (2021-06-15 08:49:52)

Enforce public vs private keys via types
With this change, it's no longer possible to pass a public key where a private one is expected, and vice-versa.

I *might* have made the mistake when using this library :) 

nitnelave: If that's the case, then I might try to rework it a bit: make it also differentiated for client and server key as well, while refactoring all to be a single (generic) type
kevinlewi: Actually, scrapping v0.5.1 plans, let's just aim for a v0.6 and bundle all of these changes together with the next branch.

Anyway, this looks ready to me, unless you wanted to make some more changes first. Let me know either way, happy to merge as-is!
nitnelave: Hmm, after digging a bit into it, it would be very complicated to have client or server-specific keys (the diffie-hellman doesn't care and mixes them happily, for instance), and sharing the implementation with a marker complicates a bunch of `#[derive]` because of the `PhantomData` that is then required :/

I think it's as good as it's going to get like this :)

Patch:
diff --git a/src/envelope.rs b/src/envelope.rs
index fcce8aa..bfed3d9 100644
--- a/src/envelope.rs
+++ b/src/envelope.rs
@@ -6,7 +6,7 @@
 use crate::{
     errors::{utils::check_slice_size_atleast, InternalPakeError, PakeError, ProtocolError},
     hash::Hash,
-    keypair::Key,
+    keypair::PublicKey,
     serialization::serialize,
 };
 use digest::Digest;
@@ -64,7 +64,7 @@ impl InnerEnvelope {
         }
         let mode = InnerEnvelopeMode::try_from(input[0])?;
 
-        let key_len = <Key as SizedBytes>::Len::to_usize();
+        let key_len = <PublicKey as SizedBytes>::Len::to_usize();
 
         let bytes = &input[1..];
         if bytes.len() < NONCE_LEN + key_len {
@@ -248,7 +248,7 @@ impl<D: Hash> Envelope<D> {
         let aad = construct_aad(server_s_pk, optional_ids);
         let opened = self.open_raw(key, &aad)?;
 
-        if opened.plaintext.len() != <Key as SizedBytes>::Len::to_usize() {
+        if opened.plaintext.len() != <PublicKey as SizedBytes>::Len::to_usize() {
             // Plaintext should consist of a single key
             return Err(InternalPakeError::UnexpectedEnvelopeContentsError);
         }
diff --git a/src/key_exchange/traits.rs b/src/key_exchange/traits.rs
index b27e02a..bb68fc3 100644
--- a/src/key_exchange/traits.rs
+++ b/src/key_exchange/traits.rs
@@ -8,7 +8,7 @@ use crate::{
     errors::{PakeError, ProtocolError},
     group::Group,
     hash::Hash,
-    keypair::Key,
+    keypair::{PrivateKey, PublicKey},
 };
 use rand::{CryptoRng, RngCore};
 use zeroize::Zeroize;
@@ -31,8 +31,8 @@ pub trait KeyExchange<D: Hash, G: Group> {
         l1_bytes: Vec<u8>,
         l2_bytes: Vec<u8>,
         ke1_message: Self::KE1Message,
-        client_s_pk: Key,
-        server_s_sk: Key,
+        client_s_pk: PublicKey,
+        server_s_sk: PrivateKey,
         id_u: Vec<u8>,
         id_s: Vec<u8>,
         e_info: Vec<u8>,
@@ -44,8 +44,8 @@ pub trait KeyExchange<D: Hash, G: Group> {
         ke2_message: Self::KE2Message,
         ke1_state: &Self::KE1State,
         serialized_credential_request: &[u8],
-        server_s_pk: Key,
-        client_s_sk: Key,
+        server_s_pk: PublicKey,
+        client_s_sk: PrivateKey,
         id_u: Vec<u8>,
         id_s: Vec<u8>,
     ) -> Result<(Vec<u8>, Vec<u8>, Self::KE3Message), ProtocolError>;
diff --git a/src/key_exchange/tripledh.rs b/src/key_exchange/tripledh.rs
index 259e3b1..0ddd1a8 100644
--- a/src/key_exchange/tripledh.rs
+++ b/src/key_exchange/tripledh.rs
@@ -13,7 +13,7 @@ use crate::{
     group::Group,
     hash::Hash,
     key_exchange::traits::{FromBytes, KeyExchange, ToBytes, ToBytesWithPointers},
-    keypair::{Key, KeyPair, SizedBytesExt},
+    keypair::{KeyPair, PrivateKey, PublicKey, SizedBytesExt},
     serialization::{serialize, tokenize},
 };
 use digest::{Digest, FixedOutput};
@@ -78,8 +78,8 @@ impl<D: Hash, G: Group> KeyExchange<D, G> for TripleDH {
         serialized_credential_request: Vec<u8>,
         l2_bytes: Vec<u8>,
         ke1_message: Self::KE1Message,
-        client_s_pk: Key,
-        server_s_sk: Key,
+        client_s_pk: PublicKey,
+        server_s_sk: PrivateKey,
         id_u: Vec<u8>,
         id_s: Vec<u8>,
         e_info: Vec<u8>,
@@ -150,8 +150,8 @@ impl<D: Hash, G: Group> KeyExchange<D, G> for TripleDH {
         ke2_message: Self::KE2Message,
         ke1_state: &Self::KE1State,
         serialized_credential_request: &[u8],
-        server_s_pk: Key,
-        client_s_sk: Key,
+        server_s_pk: PublicKey,
+        client_s_sk: PrivateKey,
         id_u: Vec<u8>,
         id_s: Vec<u8>,
     ) -> Result<(Vec<u8>, Vec<u8>, Self::KE3Message), ProtocolError> {
@@ -240,7 +240,7 @@ impl<D: Hash, G: Group> KeyExchange<D, G> for TripleDH {
 #[derive(PartialEq, Eq, Zeroize, Clone)]
 #[zeroize(drop)]
 pub struct Ke1State {
-    client_e_sk: Key,
+    client_e_sk: PrivateKey,
     client_nonce: GenericArray<u8, NonceLen>,
 }
 
@@ -249,7 +249,7 @@ pub struct Ke1State {
 pub struct Ke1Message {
     pub(crate) client_nonce: GenericArray<u8, NonceLen>,
     pub(crate) info: Vec<u8>,
-    pub(crate) client_e_pk: Key,
+    pub(crate) client_e_pk: PublicKey,
 }
 
 impl FromBytes for Ke1State {
@@ -258,7 +258,7 @@ impl FromBytes for Ke1State {
         let checked_bytes = check_slice_size_atleast(bytes, KEY_LEN + nonce_len, "ke1_state")?;
 
         Ok(Self {
-            client_e_sk: Key::from_bytes(&checked_bytes[..KEY_LEN])?,
+            client_e_sk: PrivateKey::from_bytes(&checked_bytes[..KEY_LEN])?,
             client_nonce: GenericArray::clone_from_slice(
                 &checked_bytes[KEY_LEN..KEY_LEN + nonce_len],
             ),
@@ -277,7 +277,7 @@ impl ToBytesWithPointers for Ke1State {
         vec![
             (
                 self.client_e_sk.as_ptr(),
-                <Key as SizedBytes>::Len::to_usize(),
+                <PrivateKey as SizedBytes>::Len::to_usize(),
             ),
             (self.client_nonce.as_ptr(), NonceLen::to_usize()),
         ]
@@ -307,7 +307,7 @@ impl FromBytes for Ke1Message {
         let unchecked_client_e_pk =
             check_slice_size(&remainder, KEY_LEN, "ke1_message client_e_pk")?;
         let client_e_pk =
-            KeyPair::<CS::Group>::check_public_key(Key::from_bytes(unchecked_client_e_pk)?)?;
+            KeyPair::<CS::Group>::check_public_key(PublicKey::from_bytes(unchecked_client_e_pk)?)?;
 
         Ok(Self {
             client_nonce: GenericArray::clone_from_slice(&checked_nonce[..nonce_len]),
@@ -363,7 +363,7 @@ impl<HashLen: ArrayLength<u8>> ToBytesWithPointers for Ke2State<HashLen> {
 #[derive(Clone)]
 pub struct Ke2Message<HashLen: ArrayLength<u8>> {
     server_nonce: GenericArray<u8, NonceLen>,
-    server_e_pk: Key,
+    server_e_pk: PublicKey,
     e_info: Vec<u8>,
     mac: GenericArray<u8, HashLen>,
 }
@@ -414,7 +414,7 @@ impl<HashLen: ArrayLength<u8>> FromBytes for Ke2Message<HashLen> {
         let checked_mac = check_slice_size(&remainder, HashLen::to_usize(), "ke1_message mac")?;
 
         // Check the public key bytes
-        let server_e_pk = KeyPair::<CS::Group>::check_public_key(Key::from_bytes(
+        let server_e_pk = KeyPair::<CS::Group>::check_public_key(PublicKey::from_bytes(
             &unchecked_server_e_pk[..KEY_LEN],
         )?)?;
 
@@ -430,12 +430,12 @@ impl<HashLen: ArrayLength<u8>> FromBytes for Ke2Message<HashLen> {
 #[allow(clippy::upper_case_acronyms)]
 // The triple of public and private components used in the 3DH computation
 struct TripleDHComponents {
-    pk1: Key,
-    sk1: Key,
-    pk2: Key,
-    sk2: Key,
-    pk3: Key,
-    sk3: Key,
+    pk1: PublicKey,
+    sk1: PrivateKey,
+    pk2: PublicKey,
+    sk2: PrivateKey,
+    pk3: PublicKey,
+    sk3: PrivateKey,
 }
 
 #[allow(clippy::upper_case_acronyms)]
diff --git a/src/keypair.rs b/src/keypair.rs
index 5940551..d22a983 100644
--- a/src/keypair.rs
+++ b/src/keypair.rs
@@ -37,8 +37,8 @@ impl<T> SizedBytesExt for T where T: SizedBytes {}
 /// A Keypair trait with public-private verification
 #[derive(Clone, Debug, PartialEq, Eq)]
 pub struct KeyPair<G> {
-    pk: Key,
-    sk: Key,
+    pk: PublicKey,
+    sk: PrivateKey,
     _g: PhantomData<G>,
 }
 
@@ -58,18 +58,18 @@ impl<G> Drop for KeyPair<G> {
 
 impl<G: Group> KeyPair<G> {
     /// The public key component
-    pub fn public(&self) -> &Key {
+    pub fn public(&self) -> &PublicKey {
         &self.pk
     }
 
     /// The private key component
-    pub fn private(&self) -> &Key {
+    pub fn private(&self) -> &PrivateKey {
         &self.sk
     }
 
     /// A constructor that receives public and private key independently as
     /// bytes
-    pub fn new(public: Key, private: Key) -> Result<Self, InternalPakeError> {
+    pub fn new(public: PublicKey, private: PrivateKey) -> Result<Self, InternalPakeError> {
         Ok(Self {
             pk: public,
             sk: private,
@@ -83,29 +83,35 @@ impl<G: Group> KeyPair<G> {
         let sk_bytes = G::scalar_as_bytes(&sk);
         let pk = G::base_point().mult_by_slice(sk_bytes);
         Self {
-            pk: Key(pk.to_arr().to_vec()),
-            sk: Key(sk_bytes.to_vec()),
+            pk: PublicKey(Key(pk.to_arr().to_vec())),
+            sk: PrivateKey(Key(sk_bytes.to_vec())),
             _g: PhantomData,
         }
     }
 
     /// Obtaining a public key from secret bytes. At all times, we should have
     /// &public_from_private(self.private()) == self.public()
-    pub(crate) fn public_from_private(bytes: &Key) -> Key {
+    pub(crate) fn public_from_private(bytes: &PrivateKey) -> PublicKey {
         let bytes_data = GenericArray::<u8, G::ScalarLen>::from_slice(&bytes.0[..]);
-        Key(G::base_point().mult_by_slice(bytes_data).to_arr().to_vec())
+        PublicKey(Key(G::base_point()
+            .mult_by_slice(bytes_data)
+            .to_arr()
+            .to_vec()))
     }
 
     /// Check whether a public key is valid. This is meant to be applied on
     /// material provided through the network which fits the key
     /// representation (i.e. can be mapped to a curve point), but presents
     /// some risk - e.g. small subgroup check
-    pub(crate) fn check_public_key(key: Key) -> Result<Key, InternalPakeError> {
+    pub(crate) fn check_public_key(key: PublicKey) -> Result<PublicKey, InternalPakeError> {
         G::from_element_slice(GenericArray::from_slice(&key.0)).map(|_| key)
     }
 
     /// Computes the diffie hellman function on a public key and private key
-    pub(crate) fn diffie_hellman(pk: Key, sk: Key) -> Result<Vec<u8>, InternalPakeError> {
+    pub(crate) fn diffie_hellman(
+        pk: PublicKey,
+        sk: PrivateKey,
+    ) -> Result<Vec<u8>, InternalPakeError> {
         let pk_data = GenericArray::<u8, G::ElemLen>::from_slice(&pk.0[..]);
         let point = G::from_element_slice(pk_data)?;
         let secret_data = GenericArray::<u8, G::ScalarLen>::from_slice(&sk.0[..]);
@@ -114,7 +120,7 @@ impl<G: Group> KeyPair<G> {
 
     /// Obtains a KeyPair from a slice representing the private key
     pub fn from_private_key_slice(input: &[u8]) -> Result<Self, InternalPakeError> {
-        let sk = Key::from_arr(GenericArray::from_slice(input))?;
+        let sk = PrivateKey(Key::from_arr(GenericArray::from_slice(input))?);
         let pk = Self::public_from_private(&sk);
         Self::new(pk, sk)
     }
@@ -122,8 +128,8 @@ impl<G: Group> KeyPair<G> {
     #[cfg(test)]
     pub fn as_byte_ptrs(&self) -> Vec<(*const u8, usize)> {
         vec![
-            (self.pk.as_ptr(), <Key as SizedBytes>::Len::to_usize()),
-            (self.sk.as_ptr(), <Key as SizedBytes>::Len::to_usize()),
+            (self.pk.as_ptr(), KeyLen::to_usize()),
+            (self.sk.as_ptr(), KeyLen::to_usize()),
         ]
     }
 }
@@ -145,6 +151,8 @@ impl<G: Group + Debug> KeyPair<G> {
     }
 }
 
+type KeyLen = U32;
+
 /// A minimalist key type built around a \[u8; 32\]
 #[derive(Debug, PartialEq, Eq, Clone, Zeroize)]
 // Ensure Key material is zeroed after use.
@@ -160,15 +168,68 @@ impl Deref for Key {
     }
 }
 
-impl SizedBytes for Key {
-    type Len = U32;
+// Don't make it implement SizedBytes so that it's not constructible outside of this module.
+impl Key {
+    fn to_arr(&self) -> GenericArray<u8, KeyLen> {
+        GenericArray::clone_from_slice(&self.0[..])
+    }
+
+    fn from_arr(key_bytes: &GenericArray<u8, KeyLen>) -> Result<Self, TryFromSizedBytesError> {
+        Ok(Key(key_bytes.to_vec()))
+    }
+}
+
+/// Wrapper around a Key to enforce that it's a private one.
+#[derive(Debug, PartialEq, Eq, Clone, Zeroize)]
+// Ensure Key material is zeroed after use.
+#[zeroize(drop)]
+#[repr(transparent)]
+pub struct PrivateKey(Key);
+
+impl Deref for PrivateKey {
+    type Target = Key;
+
+    fn deref(&self) -> &Self::Target {
+        &self.0
+    }
+}
+
+impl SizedBytes for PrivateKey {
+    type Len = KeyLen;
 
     fn to_arr(&self) -> GenericArray<u8, Self::Len> {
-        GenericArray::clone_from_slice(&self.0[..])
+        self.0.to_arr()
     }
 
     fn from_arr(key_bytes: &GenericArray<u8, Self::Len>) -> Result<Self, TryFromSizedBytesError> {
-        Ok(Key(key_bytes.to_vec()))
+        Ok(PrivateKey(Key::from_arr(key_bytes)?))
+    }
+}
+
+/// Wrapper around a Key to enforce that it's a public one.
+#[derive(Debug, PartialEq, Eq, Clone, Zeroize)]
+// Ensure Key material is zeroed after use.
+#[zeroize(drop)]
+#[repr(transparent)]
+pub struct PublicKey(Key);
+
+impl Deref for PublicKey {
+    type Target = Key;
+
+    fn deref(&self) -> &Self::Target {
+        &self.0
+    }
+}
+
+impl SizedBytes for PublicKey {
+    type Len = KeyLen;
+
+    fn to_arr(&self) -> GenericArray<u8, Self::Len> {
+        self.0.to_arr()
+    }
+
+    fn from_arr(key_bytes: &GenericArray<u8, Self::Len>) -> Result<Self, TryFromSizedBytesError> {
+        Ok(PublicKey(Key::from_arr(key_bytes)?))
     }
 }
 
@@ -183,7 +244,7 @@ mod tests {
 
     #[test]
     fn test_zeroize_key() -> Result<(), ProtocolError> {
-        let key_len = <Key as SizedBytes>::Len::to_usize();
+        let key_len = KeyLen::to_usize();
         let mut key = Key(vec![1u8; key_len]);
         let ptr = key.as_ptr();
 
diff --git a/src/messages.rs b/src/messages.rs
index 428aa42..dbdfcae 100644
--- a/src/messages.rs
+++ b/src/messages.rs
@@ -14,7 +14,7 @@ use crate::{
     },
     group::Group,
     key_exchange::traits::{FromBytes, KeyExchange, ToBytes},
-    keypair::{Key, KeyPair, SizedBytesExt},
+    keypair::{KeyPair, PublicKey, SizedBytesExt},
 };
 use generic_array::{typenum::Unsigned, GenericArray};
 use generic_bytes::SizedBytes;
@@ -61,7 +61,7 @@ pub struct RegistrationResponse<CS: CipherSuite> {
     /// The server's oprf output
     pub(crate) beta: CS::Group,
     /// Server's static public key
-    pub(crate) server_s_pk: Vec<u8>,
+    pub(crate) server_s_pk: PublicKey,
 }
 
 // Cannot be derived because it would require for CS to be Clone.
@@ -77,13 +77,13 @@ impl<CS: CipherSuite> Clone for RegistrationResponse<CS> {
 impl<CS: CipherSuite> RegistrationResponse<CS> {
     /// Serialization into bytes
     pub fn serialize(&self) -> Vec<u8> {
-        [self.beta.to_arr().to_vec(), self.server_s_pk.clone()].concat()
+        [self.beta.to_arr().to_vec(), self.server_s_pk.to_vec()].concat()
     }
 
     /// Deserialization from bytes
     pub fn deserialize(input: &[u8]) -> Result<Self, ProtocolError> {
         let elem_len = <CS::Group as Group>::ElemLen::to_usize();
-        let key_len = <Key as SizedBytes>::Len::to_usize();
+        let key_len = <PublicKey as SizedBytes>::Len::to_usize();
         let checked_slice =
             check_slice_size(input, elem_len + key_len, "registration_response_bytes")?;
 
@@ -91,9 +91,9 @@ impl<CS: CipherSuite> RegistrationResponse<CS> {
         // correct subgroup
         let arr = GenericArray::from_slice(&checked_slice[..elem_len]);
         let beta = CS::Group::from_element_slice(arr)?;
-        let server_s_pk =
-            KeyPair::<CS::Group>::check_public_key(Key::from_bytes(&checked_slice[elem_len..])?)?
-                .to_vec();
+        let server_s_pk = KeyPair::<CS::Group>::check_public_key(PublicKey::from_bytes(
+            &checked_slice[elem_len..],
+        )?)?;
 
         Ok(Self { server_s_pk, beta })
     }
@@ -108,7 +108,7 @@ pub struct RegistrationUpload<CS: CipherSuite> {
     /// cryptographic identifiers
     pub(crate) envelope: Envelope<CS::Hash>,
     /// The user's public key
-    pub(crate) client_s_pk: Key,
+    pub(crate) client_s_pk: PublicKey,
 }
 
 // Cannot be derived because it would require for CS to be Clone.
@@ -133,7 +133,7 @@ impl<CS: CipherSuite> RegistrationUpload<CS> {
 
     /// Deserialization from bytes
     pub fn deserialize(input: &[u8]) -> Result<Self, ProtocolError> {
-        let key_len = <Key as SizedBytes>::Len::to_usize();
+        let key_len = <PublicKey as SizedBytes>::Len::to_usize();
 
         let checked_slice = check_slice_size_atleast(input, key_len, "registration_upload_bytes")?;
 
@@ -145,7 +145,7 @@ impl<CS: CipherSuite> RegistrationUpload<CS> {
 
         Ok(Self {
             envelope,
-            client_s_pk: KeyPair::<CS::Group>::check_public_key(Key::from_bytes(
+            client_s_pk: KeyPair::<CS::Group>::check_public_key(PublicKey::from_bytes(
                 &checked_slice[..key_len],
             )?)?,
         })
@@ -204,7 +204,7 @@ impl_serialize_and_deserialize_for!(CredentialRequest);
 pub struct CredentialResponse<CS: CipherSuite> {
     /// the server's oprf output
     pub(crate) beta: CS::Group,
-    pub(crate) server_s_pk: Key,
+    pub(crate) server_s_pk: PublicKey,
     /// the user's sealed information,
     pub(crate) envelope: Envelope<CS::Hash>,
     pub(crate) ke2_message: <CS::KeyExchange as KeyExchange<CS::Hash, CS::Group>>::KE2Message,
@@ -234,7 +234,7 @@ impl<CS: CipherSuite> CredentialResponse<CS> {
 
     pub(crate) fn serialize_without_ke(
         beta: &CS::Group,
-        server_s_pk: &Key,
+        server_s_pk: &PublicKey,
         envelope: &Envelope<CS::Hash>,
     ) -> Vec<u8> {
         [
@@ -248,7 +248,7 @@ impl<CS: CipherSuite> CredentialResponse<CS> {
     /// Deserialization from bytes
     pub fn deserialize(input: &[u8]) -> Result<Self, ProtocolError> {
         let elem_len = <CS::Group as Group>::ElemLen::to_usize();
-        let key_len = <Key as SizedBytes>::Len::to_usize();
+        let key_len = <PublicKey as SizedBytes>::Len::to_usize();
         let checked_slice =
             check_slice_size_atleast(input, elem_len + key_len, "login_second_message_bytes")?;
 
@@ -258,7 +258,8 @@ impl<CS: CipherSuite> CredentialResponse<CS> {
         let arr = GenericArray::from_slice(beta_bytes);
         let beta = CS::Group::from_element_slice(arr)?;
 
-        let unchecked_server_s_pk = Key::from_bytes(&checked_slice[elem_len..elem_len + key_len])?;
+        let unchecked_server_s_pk =
+            PublicKey::from_bytes(&checked_slice[elem_len..elem_len + key_len])?;
         let server_s_pk = KeyPair::<CS::Group>::check_public_key(unchecked_server_s_pk)?;
 
         let (envelope, remainder) =
diff --git a/src/opaque.rs b/src/opaque.rs
index 822ba57..35caaa0 100644
--- a/src/opaque.rs
+++ b/src/opaque.rs
@@ -12,7 +12,7 @@ use crate::{
     group::Group,
     hash::Hash,
     key_exchange::traits::{FromBytes, KeyExchange, ToBytesWithPointers},
-    keypair::{Key, KeyPair, SizedBytesExt},
+    keypair::{KeyPair, PrivateKey, PublicKey, SizedBytesExt},
     map_to_curve::GroupWithMapToCurve,
     oprf,
     serialization::{serialize, tokenize},
@@ -275,7 +275,7 @@ impl<CS: CipherSuite> Clone for ServerRegistrationStartResult<CS> {
 /// The state elements the server holds to record a registration
 pub struct ServerRegistration<CS: CipherSuite> {
     envelope: Option<Envelope<CS::Hash>>,
-    client_s_pk: Option<Key>,
+    client_s_pk: Option<PublicKey>,
     pub(crate) oprf_key: <CS::Group as Group>::Scalar,
 }
 
@@ -315,7 +315,7 @@ impl<CS: CipherSuite> ServerRegistration<CS> {
         }
 
         // Need to do this check manually because envelope is variable-size
-        let key_len = <Key as SizedBytes>::Len::to_usize();
+        let key_len = <PublicKey as SizedBytes>::Len::to_usize();
 
         let checked_bytes =
             check_slice_size_atleast(input, scalar_len + key_len, "server_registration_bytes")?;
@@ -323,7 +323,7 @@ impl<CS: CipherSuite> ServerRegistration<CS> {
         let oprf_key_bytes = GenericArray::from_slice(&checked_bytes[..scalar_len]);
         let oprf_key = CS::Group::from_scalar_slice(oprf_key_bytes)?;
         let unchecked_client_s_pk =
-            Key::from_bytes(&checked_bytes[scalar_len..scalar_len + key_len])?;
+            PublicKey::from_bytes(&checked_bytes[scalar_len..scalar_len + key_len])?;
         let client_s_pk = KeyPair::<CS::Group>::check_public_key(unchecked_client_s_pk)?;
 
         let envelope = Envelope::<CS::Hash>::from_bytes(&checked_bytes[scalar_len + key_len..])?;
@@ -380,7 +380,7 @@ impl<CS: CipherSuite> ServerRegistration<CS> {
     pub fn start<R: RngCore + CryptoRng>(
         rng: &mut R,
         message: RegistrationRequest<CS>,
-        server_s_pk: &Key,
+        server_s_pk: &PublicKey,
     ) -> Result<ServerRegistrationStartResult<CS>, ProtocolError> {
         // RFC: generate oprf_key (salt) and v_u = g^oprf_key
         let oprf_key = CS::Group::random_scalar(rng);
@@ -391,7 +391,7 @@ impl<CS: CipherSuite> ServerRegistration<CS> {
         Ok(ServerRegistrationStartResult {
             message: RegistrationResponse {
                 beta,
-                server_s_pk: server_s_pk.to_arr().to_vec(),
+                server_s_pk: server_s_pk.clone(),
             },
             state: Self {
                 envelope: None,
@@ -580,7 +580,7 @@ pub struct ClientLoginFinishResult<CS: CipherSuite> {
     /// The client-side export key
     pub export_key: GenericArray<u8, <CS::Hash as Digest>::OutputSize>,
     /// The server's static public key
-    pub server_s_pk: Key,
+    pub server_s_pk: PublicKey,
     /// The confidential info sent by the client
     pub confidential_info: Vec<u8>,
     /// Instance of the ClientLogin, only used in tests for checking zeroize
@@ -707,7 +707,7 @@ impl<CS: CipherSuite> ClientLogin<CS> {
                 err => PakeError::from(err),
             })?;
 
-        let client_s_sk = Key::from_bytes(&opened_envelope.client_s_sk)?;
+        let client_s_sk = PrivateKey::from_bytes(&opened_envelope.client_s_sk)?;
 
         let (id_u, id_s) = match optional_ids {
             None => (
@@ -875,7 +875,7 @@ impl<CS: CipherSuite> ServerLogin<CS> {
     pub fn start<R: RngCore + CryptoRng>(
         rng: &mut R,
         password_file: ServerRegistration<CS>,
-        server_s_sk: &Key,
+        server_s_sk: &PrivateKey,
         l1: CredentialRequest<CS>,
         params: ServerLoginStartParameters,
     ) -> Result<ServerLoginStartResult<CS>, ProtocolError> {


Test patch:
diff --git a/src/tests/full_test.rs b/src/tests/full_test.rs
index d8b1c3b..08862e2 100644
--- a/src/tests/full_test.rs
+++ b/src/tests/full_test.rs
@@ -10,7 +10,7 @@ use crate::{
     errors::*,
     group::Group,
     key_exchange::tripledh::{NonceLen, TripleDH},
-    keypair::{Key, SizedBytesExt},
+    keypair::{PrivateKey, PublicKey, SizedBytesExt},
     opaque::*,
     slow_hash::NoOpHash,
     tests::mock_rng::CycleRng,
@@ -494,7 +494,7 @@ fn test_registration_response() -> Result<(), ProtocolError> {
         ServerRegistration::<RistrettoSha5123dhNoSlowHash>::start(
             &mut oprf_key_rng,
             RegistrationRequest::deserialize(&parameters.registration_request[..])?,
-            &Key::from_bytes(&parameters.server_s_pk[..])?,
+            &PublicKey::from_bytes(&parameters.server_s_pk[..])?,
         )?;
     assert_eq!(
         hex::encode(parameters.registration_response),
@@ -589,7 +589,7 @@ fn test_credential_response() -> Result<(), ProtocolError> {
     let server_login_start_result = ServerLogin::<RistrettoSha5123dhNoSlowHash>::start(
         &mut server_e_sk_and_nonce_rng,
         ServerRegistration::deserialize(&parameters.password_file[..])?,
-        &Key::from_bytes(&parameters.server_s_sk[..])?,
+        &PrivateKey::from_bytes(&parameters.server_s_sk[..])?,
         CredentialRequest::<RistrettoSha5123dhNoSlowHash>::deserialize(
             &parameters.credential_request[..],
         )?,
diff --git a/src/tests/opaque_test_vectors.rs b/src/tests/opaque_test_vectors.rs
index 466a6a7..68db601 100644
--- a/src/tests/opaque_test_vectors.rs
+++ b/src/tests/opaque_test_vectors.rs
@@ -7,7 +7,7 @@ use crate::{
     ciphersuite::CipherSuite,
     errors::*,
     key_exchange::tripledh::TripleDH,
-    keypair::{Key, SizedBytesExt},
+    keypair::{PrivateKey, PublicKey, SizedBytesExt},
     opaque::*,
     slow_hash::NoOpHash,
     tests::mock_rng::CycleRng,
@@ -356,7 +356,7 @@ fn get_password_file_bytes(parameters: &TestVectorParameters) -> Result<Vec<u8>,
         ServerRegistration::<Ristretto255Sha512NoSlowHash>::start(
             &mut oprf_key_rng,
             RegistrationRequest::deserialize(&parameters.registration_request[..]).unwrap(),
-            &Key::from_bytes(&parameters.server_public_key[..]).unwrap(),
+            &PublicKey::from_bytes(&parameters.server_public_key[..]).unwrap(),
         )?;
 
     let password_file = server_registration_start_result
@@ -391,7 +391,7 @@ fn test_registration_response() -> Result<(), ProtocolError> {
             ServerRegistration::<Ristretto255Sha512NoSlowHash>::start(
                 &mut oprf_key_rng,
                 RegistrationRequest::deserialize(&parameters.registration_request[..]).unwrap(),
-                &Key::from_bytes(&parameters.server_public_key[..]).unwrap(),
+                &PublicKey::from_bytes(&parameters.server_public_key[..]).unwrap(),
             )?;
         assert_eq!(
             hex::encode(parameters.registration_response),
@@ -473,7 +473,7 @@ fn test_ke2() -> Result<(), ProtocolError> {
         let server_login_start_result = ServerLogin::<Ristretto255Sha512NoSlowHash>::start(
             &mut server_private_keyshare_and_nonce_rng,
             ServerRegistration::deserialize(&password_file_bytes[..]).unwrap(),
-            &Key::from_bytes(&parameters.server_private_key[..]).unwrap(),
+            &PrivateKey::from_bytes(&parameters.server_private_key[..]).unwrap(),
             CredentialRequest::<Ristretto255Sha512NoSlowHash>::deserialize(&parameters.KE1[..])
                 .unwrap(),
             if parameters.envelope_mode == EnvelopeMode::CustomIdentifier {
@@ -556,7 +556,7 @@ fn test_server_login_finish() -> Result<(), ProtocolError> {
         let server_login_start_result = ServerLogin::<Ristretto255Sha512NoSlowHash>::start(
             &mut server_private_keyshare_and_nonce_rng,
             ServerRegistration::deserialize(&password_file_bytes[..]).unwrap(),
-            &Key::from_bytes(&parameters.server_private_key[..]).unwrap(),
+            &PrivateKey::from_bytes(&parameters.server_private_key[..]).unwrap(),
             CredentialRequest::<Ristretto255Sha512NoSlowHash>::deserialize(&parameters.KE1[..])
                 .unwrap(),
             if parameters.envelope_mode == EnvelopeMode::CustomIdentifier {