Skip to main content

opaque_vx/key_exchange/
tripledh_kem.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2// Copyright (c) VexaHub and contributors.
3// Copyright (c) Meta Platforms, Inc. and affiliates.
4
5//! TripleDH-KEM is a variant of the OPAQUE Triple Diffie-Hellman handshake in
6//! which the client supplies a KEM public key in KE1 and the server performs a
7//! KEM encapsulation in KE2 instead of relying solely on the final Diffie-
8//! Hellman hop. The server bundles the KEM ciphertext alongside the classic
9//! `TripleDH` payload, both parties absorb the ciphertext into the transcript
10//! and mix the encapsulated shared secret with the three Diffie-Hellman
11//! products when deriving handshake keys, and the client decapsulates during
12//! KE3 to recover that shared secret before validating the server MAC. This
13//! file contains the data model and trait glue that layer
14//! the generic `ml-kem` abstractions into the existing OPAQUE key-exchange
15//! pipeline.
16
17use core::fmt::Debug;
18use core::marker::PhantomData;
19use core::ops::Add;
20
21use derive_where::derive_where;
22use digest::Output;
23use digest::block_api::{CoreProxy, SmallBlockSizeUser};
24use generic_array::typenum::{Cmp, IsLess, Le, NonZero, Sum, U256};
25use generic_array::{ArrayLength, GenericArray};
26use hybrid_array::ArraySize;
27use ml_kem::kem::{
28    Ciphertext as MlKemCiphertext, Decapsulate, Encapsulate, Kem as MlKemTrait, KeyExport, KeyInit,
29    KeySizeUser, TryKeyInit,
30};
31use rand::{CryptoRng, Rng};
32use subtle::{ConstantTimeEq, CtOption};
33
34use super::shared::{self, Ke1Message, Ke1State, NonceLen};
35use super::{
36    Deserialize, GenerateKe1Result, GenerateKe2Result, GenerateKe3Result, KeyExchange, Serialize,
37    SerializedContext, SerializedCredentialRequest, SerializedCredentialResponse,
38    SerializedIdentifiers,
39};
40use crate::ciphersuite::{CipherSuite, KeGroup};
41use crate::errors::ProtocolError;
42use crate::hash::{Hash, OutputSize, ProxyHash};
43use crate::key_exchange::group::Group;
44use crate::keypair::{PrivateKey, PublicKey};
45use crate::opaque::Identifiers;
46use crate::serialization::{ConcatExt, SliceExt};
47
48/// Adapter trait that augments the `ml-kem` core traits with the metadata
49/// required by OPAQUE (e.g. fixed lengths and serialization hooks).
50pub trait KemCoreWrapper {
51    /// Public key type used for encapsulation operations.
52    type EncapsulationKey: Clone;
53
54    /// Secret key type used for decapsulation operations.
55    type DecapsulationKey: Clone + zeroize::ZeroizeOnDrop;
56
57    /// Length (in bytes) of the serialized public key.
58    type EncapsulationKeyLen: ArrayLength + ArraySize;
59    /// Length (in bytes) of the serialized secret key.
60    type DecapsulationKeyLen: ArrayLength + ArraySize;
61    /// Length (in bytes) of the encapsulated ciphertext.
62    type CiphertextLen: ArrayLength + ArraySize;
63    /// Length (in bytes) of the shared secret output by the KEM.
64    type SharedSecretLen: ArrayLength + ArraySize;
65
66    /// Generates a fresh KEM key pair.
67    fn generate<R: Rng + CryptoRng>(
68        rng: &mut R,
69    ) -> Result<(Self::DecapsulationKey, Self::EncapsulationKey), ProtocolError>;
70
71    /// Serializes the public encapsulation key.
72    fn serialize_encapsulation_key(
73        key: &Self::EncapsulationKey,
74    ) -> GenericArray<u8, Self::EncapsulationKeyLen>;
75
76    /// Deserializes the public encapsulation key, advancing the input slice.
77    fn deserialize_encapsulation_key(
78        input: &mut &[u8],
79    ) -> Result<Self::EncapsulationKey, ProtocolError>;
80
81    /// Serializes the secret decapsulation key.
82    fn serialize_decapsulation_key(
83        key: &Self::DecapsulationKey,
84    ) -> GenericArray<u8, Self::DecapsulationKeyLen>;
85
86    /// Deserializes the secret decapsulation key, advancing the input slice.
87    fn deserialize_decapsulation_key(
88        input: &mut &[u8],
89    ) -> Result<Self::DecapsulationKey, ProtocolError>;
90
91    /// Encapsulates to the given public key, returning the ciphertext and
92    /// shared secret.
93    #[allow(clippy::type_complexity)]
94    fn encapsulate<R: Rng + CryptoRng>(
95        key: &Self::EncapsulationKey,
96        rng: &mut R,
97    ) -> Result<
98        (
99            GenericArray<u8, Self::CiphertextLen>,
100            GenericArray<u8, Self::SharedSecretLen>,
101        ),
102        ProtocolError,
103    >;
104
105    /// Decapsulates the shared secret from the provided ciphertext.
106    fn decapsulate(
107        key: &Self::DecapsulationKey,
108        encapsulated_key: &GenericArray<u8, Self::CiphertextLen>,
109    ) -> Result<GenericArray<u8, Self::SharedSecretLen>, ProtocolError>;
110}
111
112/// Adapter to bridge `rand 0.8` (`rand_core 0.6`) RNGs to `rand_core 0.10`
113/// which is required by `ml-kem 0.3.x`.
114struct RngCompat<'a, R>(&'a mut R);
115
116impl<R: Rng> rand::rand_core::TryRng for RngCompat<'_, R> {
117    type Error = core::convert::Infallible;
118
119    fn try_next_u32(&mut self) -> Result<u32, Self::Error> {
120        Ok(self.0.next_u32())
121    }
122
123    fn try_next_u64(&mut self) -> Result<u64, Self::Error> {
124        Ok(self.0.next_u64())
125    }
126
127    fn try_fill_bytes(&mut self, dst: &mut [u8]) -> Result<(), Self::Error> {
128        self.0.fill_bytes(dst);
129        Ok(())
130    }
131}
132
133impl<R: Rng + CryptoRng> rand::rand_core::TryCryptoRng for RngCompat<'_, R> {}
134
135type RcEncapsulationKeyLen<K> = <<K as MlKemTrait>::EncapsulationKey as KeySizeUser>::KeySize;
136type RcDecapsulationKeyLen<K> = <<K as MlKemTrait>::DecapsulationKey as KeySizeUser>::KeySize;
137type RcCiphertextLen<K> = <K as MlKemTrait>::CiphertextSize;
138type RcSharedSecretLen<K> = <K as MlKemTrait>::SharedKeySize;
139
140impl<K> KemCoreWrapper for K
141where
142    K: MlKemTrait,
143    K::EncapsulationKey: Encapsulate<Kem = K> + KeyExport + TryKeyInit + Clone,
144    K::DecapsulationKey:
145        Decapsulate<Kem = K> + KeyExport + KeyInit + Clone + zeroize::ZeroizeOnDrop,
146    RcEncapsulationKeyLen<K>: ArrayLength + ArraySize,
147    RcDecapsulationKeyLen<K>: ArrayLength + ArraySize,
148    RcCiphertextLen<K>: ArrayLength + ArraySize,
149    RcSharedSecretLen<K>: ArrayLength + ArraySize,
150{
151    type EncapsulationKey = K::EncapsulationKey;
152    type DecapsulationKey = K::DecapsulationKey;
153    type EncapsulationKeyLen = RcEncapsulationKeyLen<K>;
154    type DecapsulationKeyLen = RcDecapsulationKeyLen<K>;
155    type CiphertextLen = RcCiphertextLen<K>;
156    type SharedSecretLen = RcSharedSecretLen<K>;
157
158    fn generate<R: Rng + CryptoRng>(
159        rng: &mut R,
160    ) -> Result<(Self::DecapsulationKey, Self::EncapsulationKey), ProtocolError> {
161        Ok(K::generate_keypair_from_rng(&mut RngCompat(rng)))
162    }
163
164    fn serialize_encapsulation_key(
165        key: &Self::EncapsulationKey,
166    ) -> GenericArray<u8, Self::EncapsulationKeyLen> {
167        GenericArray::from_slice(key.to_bytes().as_slice()).clone()
168    }
169
170    fn deserialize_encapsulation_key(
171        input: &mut &[u8],
172    ) -> Result<Self::EncapsulationKey, ProtocolError> {
173        let bytes: GenericArray<u8, RcEncapsulationKeyLen<K>> =
174            input.take_array("kem encapsulation key")?;
175        let key = ml_kem::array::Array::try_from(bytes.as_slice())
176            .map_err(|_| ProtocolError::SerializationError)?;
177        TryKeyInit::new(&key).map_err(|_| ProtocolError::SerializationError)
178    }
179
180    fn serialize_decapsulation_key(
181        key: &Self::DecapsulationKey,
182    ) -> GenericArray<u8, Self::DecapsulationKeyLen> {
183        GenericArray::from_slice(key.to_bytes().as_slice()).clone()
184    }
185
186    fn deserialize_decapsulation_key(
187        input: &mut &[u8],
188    ) -> Result<Self::DecapsulationKey, ProtocolError> {
189        let bytes: GenericArray<u8, RcDecapsulationKeyLen<K>> =
190            input.take_array("kem decapsulation key")?;
191        let seed = ml_kem::array::Array::try_from(bytes.as_slice())
192            .map_err(|_| ProtocolError::SerializationError)?;
193        Ok(KeyInit::new(&seed))
194    }
195
196    fn encapsulate<R: Rng + CryptoRng>(
197        key: &Self::EncapsulationKey,
198        rng: &mut R,
199    ) -> Result<
200        (
201            GenericArray<u8, Self::CiphertextLen>,
202            GenericArray<u8, Self::SharedSecretLen>,
203        ),
204        ProtocolError,
205    > {
206        let (ciphertext, shared) = key.encapsulate_with_rng(&mut RngCompat(rng));
207        Ok((
208            GenericArray::from_slice(ciphertext.as_slice()).clone(),
209            GenericArray::from_slice(shared.as_slice()).clone(),
210        ))
211    }
212
213    fn decapsulate(
214        key: &Self::DecapsulationKey,
215        encapsulated_key: &GenericArray<u8, Self::CiphertextLen>,
216    ) -> Result<GenericArray<u8, Self::SharedSecretLen>, ProtocolError> {
217        let ciphertext = MlKemCiphertext::<K>::try_from(encapsulated_key.as_slice())
218            .map_err(|_| ProtocolError::SerializationError)?;
219        let shared = key.decapsulate(&ciphertext);
220        Ok(GenericArray::from_slice(shared.as_slice()).clone())
221    }
222}
223/// Triple Diffie-Hellman-style key exchange that offloads the second hop to a
224/// generic KEM.
225#[derive(Clone, Debug)]
226pub struct TripleDhKem<G, H, K>(PhantomData<(G, H, K)>);
227
228/// Client state combining the classic `TripleDH` state with a KEM secret key.
229#[cfg_attr(
230    feature = "serde",
231    derive(serde::Deserialize, serde::Serialize),
232    serde(bound(
233        deserialize = "Ke1State<G>: serde::Deserialize<'de>, K::DecapsulationKey: \
234                       serde::Deserialize<'de>",
235        serialize = "Ke1State<G>: serde::Serialize, K::DecapsulationKey: serde::Serialize",
236    ))
237)]
238#[derive_where(Clone, ZeroizeOnDrop)]
239#[derive_where(Debug, Eq, Hash, Ord, PartialEq, PartialOrd; Ke1State<G>, K::DecapsulationKey)]
240pub struct KemKe1State<G: Group, K: KemCoreWrapper> {
241    dh_state: Ke1State<G>,
242    kem_decapsulation_key: K::DecapsulationKey,
243}
244
245/// Client message including the ephemeral Diffie-Hellman component alongside a
246/// serialized KEM public key.
247#[cfg_attr(
248    feature = "serde",
249    derive(serde::Deserialize, serde::Serialize),
250    serde(bound(
251        deserialize = "Ke1Message<G>: serde::Deserialize<'de>",
252        serialize = "Ke1Message<G>: serde::Serialize",
253    ))
254)]
255#[derive_where(Clone, ZeroizeOnDrop)]
256#[derive_where(Debug, Eq, Hash, Ord, PartialEq, PartialOrd; Ke1Message<G>)]
257pub struct KemKe1Message<G: Group, K: KemCoreWrapper> {
258    dh_message: Ke1Message<G>,
259    kem_encapsulation_key: GenericArray<u8, K::EncapsulationKeyLen>,
260}
261
262/// Server state mirrors the `TripleDH` state and carries the client’s KEM
263/// public key for later use.
264#[cfg_attr(
265    feature = "serde",
266    derive(serde::Deserialize, serde::Serialize),
267    serde(bound = "")
268)]
269#[derive_where(Clone, ZeroizeOnDrop)]
270#[derive_where(Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
271pub struct KemKe2State<K: KemCoreWrapper, H: Hash>
272where
273    H::Core: ProxyHash,
274    <<H as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize: IsLess<U256>,
275    Le<<<H as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize, U256>: NonZero,
276    <<H as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize: Cmp<U256>,
277    OutputSize<H>: ArrayLength,
278{
279    base_state: super::tripledh::Ke2State<H>,
280    kem_encapsulation_key: GenericArray<u8, K::EncapsulationKeyLen>,
281    server_kem_ciphertext: GenericArray<u8, K::CiphertextLen>,
282}
283
284/// Server builder placeholder capturing the data needed to finish the KEM
285/// exchange.
286#[derive_where(Clone, ZeroizeOnDrop)]
287pub struct KemKe2Builder<G: Group, H: Hash, K: KemCoreWrapper>
288where
289    H::Core: ProxyHash,
290    <<H as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize: IsLess<U256>,
291    Le<<<H as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize, U256>: NonZero,
292    <<H as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize: Cmp<U256>,
293    OutputSize<H>: ArrayLength,
294{
295    server_nonce: GenericArray<u8, NonceLen>,
296    transcript_hasher: H,
297    #[derive_where(skip(Zeroize))]
298    client_e_pk: PublicKey<G>,
299    #[derive_where(skip(Zeroize))]
300    server_e_pk: PublicKey<G>,
301    shared_secret_1: GenericArray<u8, G::PkLen>,
302    shared_secret_3: GenericArray<u8, G::PkLen>,
303    #[derive_where(skip(Zeroize))]
304    kem_encapsulation_key: GenericArray<u8, K::EncapsulationKeyLen>,
305    kem_ciphertext: GenericArray<u8, K::CiphertextLen>,
306    kem_shared_secret: GenericArray<u8, K::SharedSecretLen>,
307}
308
309/// Server message bundles the `TripleDH` payload with the KEM encapsulation.
310#[cfg_attr(
311    feature = "serde",
312    derive(serde::Deserialize, serde::Serialize),
313    serde(bound(
314        deserialize = "super::tripledh::Ke2Message<G, H>: serde::Deserialize<'de>",
315        serialize = "super::tripledh::Ke2Message<G, H>: serde::Serialize",
316    ))
317)]
318#[derive_where(Clone, ZeroizeOnDrop)]
319#[derive_where(Debug, Eq, Hash, Ord, PartialEq, PartialOrd; super::tripledh::Ke2Message<G, H>)]
320pub struct KemKe2Message<G: Group, H: Hash, K: KemCoreWrapper>
321where
322    H::Core: ProxyHash,
323    <<H as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize: IsLess<U256>,
324    Le<<<H as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize, U256>: NonZero,
325    <<H as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize: Cmp<U256>,
326    OutputSize<H>: ArrayLength,
327{
328    dh_message: super::tripledh::Ke2Message<G, H>,
329    kem_ciphertext: GenericArray<u8, K::CiphertextLen>,
330}
331
332/// Third message remains the same as `TripleDH`.
333pub type KemKe3Message<H> = super::tripledh::Ke3Message<H>;
334
335impl<G, H, K> KeyExchange for TripleDhKem<G, H, K>
336where
337    G: Group + 'static,
338    G::Sk: shared::DiffieHellman<G>,
339    H: Hash,
340    H::Core: ProxyHash,
341    <<H as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize: IsLess<U256>,
342    Le<<<H as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize, U256>: NonZero,
343    <<H as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize: Cmp<U256>,
344    OutputSize<H>: ArrayLength,
345    K: KemCoreWrapper,
346    NonceLen: Add<K::EncapsulationKeyLen>,
347    Sum<NonceLen, K::EncapsulationKeyLen>: ArrayLength,
348{
349    type Group = G;
350    type Hash = H;
351
352    type KE1State = KemKe1State<G, K>;
353    type KE2State<CS: CipherSuite> = KemKe2State<K, H>;
354    type KE1Message = KemKe1Message<G, K>;
355    type KE2Builder<'a, CS: CipherSuite<KeyExchange = Self>> = KemKe2Builder<G, H, K>;
356    type KE2BuilderData<'a, CS: 'static + CipherSuite> = (
357        &'a PublicKey<G>,
358        &'a GenericArray<u8, K::EncapsulationKeyLen>,
359    );
360    type KE2BuilderInput<CS: CipherSuite> = GenericArray<u8, G::PkLen>;
361    type KE2Message = KemKe2Message<G, H, K>;
362    type KE3Message = KemKe3Message<H>;
363
364    fn generate_ke1<R: Rng + CryptoRng>(
365        rng: &mut R,
366    ) -> Result<GenerateKe1Result<Self>, ProtocolError> {
367        let base = super::tripledh::TripleDh::<G, H>::generate_ke1(rng)?;
368        let (kem_secret, kem_public) = K::generate(rng)?;
369        let kem_encapsulation_key = K::serialize_encapsulation_key(&kem_public);
370
371        Ok(GenerateKe1Result {
372            state: KemKe1State {
373                dh_state: base.state,
374                kem_decapsulation_key: kem_secret,
375            },
376            message: KemKe1Message {
377                dh_message: base.message,
378                kem_encapsulation_key,
379            },
380        })
381    }
382
383    fn ke2_builder<'a, CS: CipherSuite<KeyExchange = Self>, R: Rng + CryptoRng>(
384        rng: &mut R,
385        credential_request: SerializedCredentialRequest<CS>,
386        ke1_message: Self::KE1Message,
387        credential_response: SerializedCredentialResponse<CS>,
388        client_s_pk: PublicKey<G>,
389        identifiers: SerializedIdentifiers<'_, KeGroup<CS>>,
390        context: SerializedContext<'a>,
391    ) -> Result<Self::KE2Builder<'a, CS>, ProtocolError> {
392        let shared::Ke2BuilderCommon {
393            server_nonce,
394            transcript_hasher,
395            client_e_pk,
396            server_e_pk,
397            shared_secret_1,
398            shared_secret_3,
399        } = shared::ke2_builder_common::<G, H, CS, R>(
400            rng,
401            credential_request,
402            ke1_message.dh_message.clone(),
403            credential_response,
404            client_s_pk,
405            identifiers,
406            context,
407        )?;
408
409        let mut kem_bytes_slice: &[u8] = ke1_message.kem_encapsulation_key.as_slice();
410        let encapsulation_key = K::deserialize_encapsulation_key(&mut kem_bytes_slice)?;
411        let (kem_ciphertext, kem_shared_secret) = K::encapsulate(&encapsulation_key, rng)?;
412
413        let mut transcript_hasher = transcript_hasher;
414        digest::Digest::update(
415            &mut transcript_hasher,
416            ke1_message.kem_encapsulation_key.as_slice(),
417        );
418        digest::Digest::update(&mut transcript_hasher, kem_ciphertext.as_slice());
419
420        Ok(KemKe2Builder {
421            server_nonce,
422            transcript_hasher,
423            client_e_pk,
424            server_e_pk,
425            shared_secret_1,
426            shared_secret_3,
427            kem_encapsulation_key: ke1_message.kem_encapsulation_key.clone(),
428            kem_ciphertext,
429            kem_shared_secret,
430        })
431    }
432
433    fn ke2_builder_data<'a, CS: 'static + CipherSuite<KeyExchange = Self>>(
434        builder: &'a Self::KE2Builder<'_, CS>,
435    ) -> Self::KE2BuilderData<'a, CS> {
436        (&builder.client_e_pk, &builder.kem_encapsulation_key)
437    }
438
439    fn generate_ke2_input<CS: CipherSuite<KeyExchange = Self>, R: CryptoRng + Rng>(
440        builder: &Self::KE2Builder<'_, CS>,
441        _: &mut R,
442        server_s_sk: &PrivateKey<G>,
443    ) -> Self::KE2BuilderInput<CS> {
444        server_s_sk.ke_diffie_hellman(&builder.client_e_pk)
445    }
446
447    fn build_ke2<CS: CipherSuite<KeyExchange = Self>>(
448        mut builder: Self::KE2Builder<'_, CS>,
449        shared_secret_2: Self::KE2BuilderInput<CS>,
450    ) -> Result<GenerateKe2Result<CS>, ProtocolError> {
451        let transcript_digest = builder.transcript_hasher.clone().finalize();
452        let derived_keys = shared::derive_keys::<H>(
453            [
454                builder.shared_secret_1.as_slice(),
455                shared_secret_2.as_slice(),
456                builder.shared_secret_3.as_slice(),
457                builder.kem_shared_secret.as_slice(),
458            ]
459            .into_iter(),
460            &transcript_digest,
461        )?;
462
463        let (mac, expected_mac) = shared::compute_ke2_macs(
464            &mut builder.transcript_hasher,
465            &derived_keys,
466            &transcript_digest,
467        )?;
468
469        Ok(GenerateKe2Result {
470            state: KemKe2State {
471                base_state: super::tripledh::Ke2State {
472                    session_key: derived_keys.session_key.clone(),
473                    expected_mac,
474                },
475                kem_encapsulation_key: builder.kem_encapsulation_key.clone(),
476                server_kem_ciphertext: builder.kem_ciphertext.clone(),
477            },
478            message: KemKe2Message {
479                dh_message: super::tripledh::Ke2Message {
480                    server_nonce: builder.server_nonce,
481                    server_e_pk: builder.server_e_pk.clone(),
482                    mac,
483                },
484                kem_ciphertext: builder.kem_ciphertext.clone(),
485            },
486            #[cfg(test)]
487            handshake_secret: derived_keys.handshake_secret,
488            #[cfg(test)]
489            km2: derived_keys.km2,
490        })
491    }
492
493    fn generate_ke3<CS: CipherSuite<KeyExchange = Self>, R: CryptoRng + Rng>(
494        _rng: &mut R,
495        credential_request: SerializedCredentialRequest<CS>,
496        ke1_message: Self::KE1Message,
497        credential_response: SerializedCredentialResponse<CS>,
498        ke1_state: &Self::KE1State,
499        ke2_message: Self::KE2Message,
500        server_s_pk: PublicKey<G>,
501        client_s_sk: PrivateKey<G>,
502        identifiers: SerializedIdentifiers<'_, KeGroup<CS>>,
503        context: SerializedContext<'_>,
504    ) -> Result<GenerateKe3Result<Self>, ProtocolError> {
505        let mut transcript_hasher = shared::transcript(
506            &context,
507            &identifiers,
508            &credential_request,
509            &ke1_message.dh_message.to_iter(),
510            &credential_response,
511            ke2_message.dh_message.server_nonce,
512            &ke2_message.dh_message.server_e_pk.serialize(),
513        );
514        digest::Digest::update(
515            &mut transcript_hasher,
516            ke1_message.kem_encapsulation_key.as_slice(),
517        );
518        digest::Digest::update(
519            &mut transcript_hasher,
520            ke2_message.kem_ciphertext.as_slice(),
521        );
522
523        let shared_secret_1 = ke1_state
524            .dh_state
525            .client_e_sk
526            .ke_diffie_hellman(&ke2_message.dh_message.server_e_pk);
527        let shared_secret_2 = ke1_state
528            .dh_state
529            .client_e_sk
530            .ke_diffie_hellman(&server_s_pk);
531        let shared_secret_3 = client_s_sk.ke_diffie_hellman(&ke2_message.dh_message.server_e_pk);
532        let kem_shared_secret = K::decapsulate(
533            &ke1_state.kem_decapsulation_key,
534            &ke2_message.kem_ciphertext,
535        )?;
536
537        let (derived_keys, client_mac) = shared::finalize_ke3_transcript(
538            &mut transcript_hasher,
539            [
540                shared_secret_1.as_slice(),
541                shared_secret_2.as_slice(),
542                shared_secret_3.as_slice(),
543                kem_shared_secret.as_slice(),
544            ]
545            .into_iter(),
546            &ke2_message.dh_message.mac,
547        )?;
548
549        Ok(GenerateKe3Result {
550            session_key: derived_keys.session_key,
551            message: super::tripledh::Ke3Message { mac: client_mac },
552            #[cfg(test)]
553            handshake_secret: derived_keys.handshake_secret,
554            #[cfg(test)]
555            km3: derived_keys.km3,
556        })
557    }
558
559    fn finish_ke<CS: CipherSuite>(
560        ke2_state: &Self::KE2State<CS>,
561        ke3_message: Self::KE3Message,
562        _identifiers: Identifiers<'_>,
563        _context: SerializedContext<'_>,
564    ) -> Result<Output<Self::Hash>, ProtocolError> {
565        CtOption::new(
566            ke2_state.base_state.session_key.clone(),
567            ke2_state.base_state.expected_mac.ct_eq(&ke3_message.mac),
568        )
569        .into_option()
570        .ok_or(ProtocolError::InvalidLoginError)
571    }
572}
573
574/// Serialization logic will be implemented once the concrete KEM wiring is in
575/// place.
576impl<G: Group, K: KemCoreWrapper> Deserialize for KemKe1State<G, K> {
577    fn deserialize_take(input: &mut &[u8]) -> Result<Self, ProtocolError> {
578        Ok(Self {
579            dh_state: Ke1State::<G>::deserialize_take(input)?,
580            kem_decapsulation_key: K::deserialize_decapsulation_key(input)?,
581        })
582    }
583}
584
585impl<G: Group, K: KemCoreWrapper> Serialize for KemKe1State<G, K>
586where
587    Ke1State<G>: Serialize,
588    <Ke1State<G> as Serialize>::Len: Add<K::DecapsulationKeyLen>,
589    Sum<<Ke1State<G> as Serialize>::Len, K::DecapsulationKeyLen>: ArrayLength,
590{
591    type Len = Sum<<Ke1State<G> as Serialize>::Len, K::DecapsulationKeyLen>;
592
593    fn serialize(&self) -> GenericArray<u8, Self::Len> {
594        self.dh_state
595            .serialize()
596            .cat(K::serialize_decapsulation_key(&self.kem_decapsulation_key))
597    }
598}
599
600impl<G: Group, K: KemCoreWrapper> Deserialize for KemKe1Message<G, K> {
601    fn deserialize_take(input: &mut &[u8]) -> Result<Self, ProtocolError> {
602        Ok(Self {
603            dh_message: Ke1Message::<G>::deserialize_take(input)?,
604            kem_encapsulation_key: input.take_array("kem encapsulation key")?,
605        })
606    }
607}
608
609impl<G: Group, K: KemCoreWrapper> Serialize for KemKe1Message<G, K>
610where
611    Ke1Message<G>: Serialize,
612    <Ke1Message<G> as Serialize>::Len: Add<K::EncapsulationKeyLen>,
613    Sum<<Ke1Message<G> as Serialize>::Len, K::EncapsulationKeyLen>: ArrayLength,
614{
615    type Len = Sum<<Ke1Message<G> as Serialize>::Len, K::EncapsulationKeyLen>;
616
617    fn serialize(&self) -> GenericArray<u8, Self::Len> {
618        self.dh_message
619            .serialize()
620            .cat(self.kem_encapsulation_key.clone())
621    }
622}
623
624impl<K: KemCoreWrapper, H: Hash> Deserialize for KemKe2State<K, H>
625where
626    H::Core: ProxyHash,
627    <<H as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize: IsLess<U256>,
628    Le<<<H as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize, U256>: NonZero,
629    <<H as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize: Cmp<U256>,
630    OutputSize<H>: ArrayLength,
631{
632    fn deserialize_take(input: &mut &[u8]) -> Result<Self, ProtocolError> {
633        Ok(Self {
634            base_state: super::tripledh::Ke2State::<H>::deserialize_take(input)?,
635            kem_encapsulation_key: input.take_array("kem encapsulation key")?,
636            server_kem_ciphertext: input.take_array("kem ciphertext")?,
637        })
638    }
639}
640
641impl<K: KemCoreWrapper, H: Hash> Serialize for KemKe2State<K, H>
642where
643    H::Core: ProxyHash,
644    <<H as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize: IsLess<U256>,
645    Le<<<H as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize, U256>: NonZero,
646    <<H as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize: Cmp<U256>,
647    OutputSize<H>: ArrayLength,
648    super::tripledh::Ke2State<H>: Serialize,
649    <super::tripledh::Ke2State<H> as Serialize>::Len: Add<K::EncapsulationKeyLen>,
650    Sum<<super::tripledh::Ke2State<H> as Serialize>::Len, K::EncapsulationKeyLen>:
651        ArrayLength + Add<K::CiphertextLen>,
652    Sum<
653        Sum<<super::tripledh::Ke2State<H> as Serialize>::Len, K::EncapsulationKeyLen>,
654        K::CiphertextLen,
655    >: ArrayLength,
656{
657    type Len = Sum<
658        Sum<<super::tripledh::Ke2State<H> as Serialize>::Len, K::EncapsulationKeyLen>,
659        K::CiphertextLen,
660    >;
661
662    fn serialize(&self) -> GenericArray<u8, Self::Len> {
663        self.base_state
664            .serialize()
665            .cat(self.kem_encapsulation_key.clone())
666            .cat(self.server_kem_ciphertext.clone())
667    }
668}
669
670impl<G: Group, H: Hash, K: KemCoreWrapper> Deserialize for KemKe2Message<G, H, K>
671where
672    H::Core: ProxyHash,
673    <<H as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize: IsLess<U256>,
674    Le<<<H as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize, U256>: NonZero,
675    <<H as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize: Cmp<U256>,
676    OutputSize<H>: ArrayLength,
677{
678    fn deserialize_take(input: &mut &[u8]) -> Result<Self, ProtocolError> {
679        Ok(Self {
680            dh_message: super::tripledh::Ke2Message::<G, H>::deserialize_take(input)?,
681            kem_ciphertext: input.take_array("kem ciphertext")?,
682        })
683    }
684}
685
686impl<G: Group, H: Hash, K: KemCoreWrapper> Serialize for KemKe2Message<G, H, K>
687where
688    H::Core: ProxyHash,
689    <<H as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize: IsLess<U256>,
690    Le<<<H as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize, U256>: NonZero,
691    <<H as CoreProxy>::Core as SmallBlockSizeUser>::_BlockSize: Cmp<U256>,
692    OutputSize<H>: ArrayLength,
693    NonceLen: Add<G::PkLen>,
694    Sum<NonceLen, G::PkLen>: ArrayLength + Add<OutputSize<H>>,
695    Sum<Sum<NonceLen, G::PkLen>, OutputSize<H>>: ArrayLength,
696    super::tripledh::Ke2Message<G, H>: Serialize,
697    <super::tripledh::Ke2Message<G, H> as Serialize>::Len: Add<K::CiphertextLen>,
698    <<super::tripledh::Ke2Message<G, H> as Serialize>::Len as Add<K::CiphertextLen>>::Output:
699        ArrayLength,
700{
701    type Len = Sum<<super::tripledh::Ke2Message<G, H> as Serialize>::Len, K::CiphertextLen>;
702
703    fn serialize(&self) -> GenericArray<u8, Self::Len> {
704        self.dh_message.serialize().cat(self.kem_ciphertext.clone())
705    }
706}