bc_components/encapsulation/
encapsulation_ciphertext.rs

1use anyhow::{Result, bail};
2use dcbor::prelude::*;
3
4use crate::{EncapsulationScheme, MLKEMCiphertext, X25519PublicKey, tags};
5
6/// A ciphertext produced by a key encapsulation mechanism (KEM).
7///
8/// `EncapsulationCiphertext` represents the output of a key encapsulation
9/// operation where a shared secret has been encapsulated for secure
10/// transmission. The ciphertext can only be used to recover the shared secret
11/// by the holder of the corresponding private key.
12///
13/// This enum has two variants:
14/// - `X25519`: For X25519 key agreement, this is the ephemeral public key
15///   generated during encapsulation
16/// - `MLKEM`: For ML-KEM post-quantum key encapsulation, this is the ML-KEM
17///   ciphertext
18#[derive(Debug, Clone, PartialEq)]
19pub enum EncapsulationCiphertext {
20    /// X25519 key agreement ciphertext (ephemeral public key)
21    X25519(X25519PublicKey),
22    /// ML-KEM post-quantum ciphertext
23    MLKEM(MLKEMCiphertext),
24}
25
26impl EncapsulationCiphertext {
27    /// Returns the X25519 public key if this is an X25519 ciphertext.
28    ///
29    /// # Returns
30    ///
31    /// A `Result` containing a reference to the X25519 public key if this is an
32    /// X25519 ciphertext, or an error if it's not.
33    ///
34    /// # Errors
35    ///
36    /// Returns an error if this is not an X25519 ciphertext.
37    ///
38    /// # Example
39    ///
40    /// ```
41    /// use bc_components::{
42    ///     EncapsulationScheme, X25519PrivateKey, X25519PublicKey,
43    /// };
44    ///
45    /// // Generate a keypair
46    /// let (private_key, public_key) = EncapsulationScheme::X25519.keypair();
47    ///
48    /// // Encapsulate a shared secret (creates an ephemeral keypair internally)
49    /// let (_, ciphertext) = public_key.encapsulate_new_shared_secret();
50    ///
51    /// // Get the X25519 public key from the ciphertext
52    /// if let Ok(ephemeral_public_key) = ciphertext.x25519_public_key() {
53    ///     // This is an X25519 ephemeral public key
54    ///     assert_eq!(ephemeral_public_key.data().len(), 32);
55    /// }
56    /// ```
57    pub fn x25519_public_key(&self) -> Result<&X25519PublicKey> {
58        match self {
59            Self::X25519(public_key) => Ok(public_key),
60            _ => bail!("Invalid key encapsulation type"),
61        }
62    }
63
64    /// Returns the ML-KEM ciphertext if this is an ML-KEM ciphertext.
65    ///
66    /// # Returns
67    ///
68    /// A `Result` containing a reference to the ML-KEM ciphertext if this is an
69    /// ML-KEM ciphertext, or an error if it's not.
70    ///
71    /// # Errors
72    ///
73    /// Returns an error if this is not an ML-KEM ciphertext.
74    ///
75    /// # Example
76    ///
77    /// ```
78    /// use bc_components::EncapsulationScheme;
79    ///
80    /// // Generate an ML-KEM keypair
81    /// let (private_key, public_key) = EncapsulationScheme::MLKEM768.keypair();
82    ///
83    /// // Encapsulate a shared secret
84    /// let (_, ciphertext) = public_key.encapsulate_new_shared_secret();
85    ///
86    /// // Check if it's an ML-KEM ciphertext
87    /// assert!(ciphertext.is_mlkem());
88    ///
89    /// // Get the ML-KEM ciphertext
90    /// if let Ok(mlkem_ciphertext) = ciphertext.mlkem_ciphertext() {
91    ///     // This is an ML-KEM ciphertext
92    ///     assert_eq!(mlkem_ciphertext.level(), bc_components::MLKEM::MLKEM768);
93    /// }
94    /// ```
95    pub fn mlkem_ciphertext(&self) -> Result<&MLKEMCiphertext> {
96        match self {
97            Self::MLKEM(ciphertext) => Ok(ciphertext),
98            _ => bail!("Invalid key encapsulation type"),
99        }
100    }
101
102    /// Returns true if this is an X25519 ciphertext.
103    ///
104    /// # Returns
105    ///
106    /// `true` if this is an X25519 ciphertext, `false` otherwise.
107    ///
108    /// # Example
109    ///
110    /// ```
111    /// use bc_components::EncapsulationScheme;
112    ///
113    /// // Generate an X25519 keypair
114    /// let (_, public_key) = EncapsulationScheme::X25519.keypair();
115    ///
116    /// // Encapsulate a shared secret
117    /// let (_, ciphertext) = public_key.encapsulate_new_shared_secret();
118    ///
119    /// // Check if it's an X25519 ciphertext
120    /// assert!(ciphertext.is_x25519());
121    /// assert!(!ciphertext.is_mlkem());
122    /// ```
123    pub fn is_x25519(&self) -> bool { matches!(self, Self::X25519(_)) }
124
125    /// Returns true if this is an ML-KEM ciphertext.
126    ///
127    /// # Returns
128    ///
129    /// `true` if this is an ML-KEM ciphertext, `false` otherwise.
130    ///
131    /// # Example
132    ///
133    /// ```
134    /// use bc_components::EncapsulationScheme;
135    ///
136    /// // Generate an ML-KEM keypair
137    /// let (_, public_key) = EncapsulationScheme::MLKEM768.keypair();
138    ///
139    /// // Encapsulate a shared secret
140    /// let (_, ciphertext) = public_key.encapsulate_new_shared_secret();
141    ///
142    /// // Check if it's an ML-KEM ciphertext
143    /// assert!(ciphertext.is_mlkem());
144    /// assert!(!ciphertext.is_x25519());
145    /// ```
146    pub fn is_mlkem(&self) -> bool { matches!(self, Self::MLKEM(_)) }
147
148    /// Returns the encapsulation scheme associated with this ciphertext.
149    ///
150    /// # Returns
151    ///
152    /// The encapsulation scheme (X25519, MLKEM512, MLKEM768, or MLKEM1024)
153    /// that corresponds to this ciphertext.
154    ///
155    /// # Example
156    ///
157    /// ```
158    /// use bc_components::EncapsulationScheme;
159    ///
160    /// // Generate a key pair using ML-KEM768
161    /// let (_, public_key) = EncapsulationScheme::MLKEM768.keypair();
162    ///
163    /// // Encapsulate a shared secret
164    /// let (_, ciphertext) = public_key.encapsulate_new_shared_secret();
165    ///
166    /// // Check the scheme
167    /// assert_eq!(
168    ///     ciphertext.encapsulation_scheme(),
169    ///     EncapsulationScheme::MLKEM768
170    /// );
171    /// ```
172    pub fn encapsulation_scheme(&self) -> EncapsulationScheme {
173        match self {
174            Self::X25519(_) => EncapsulationScheme::X25519,
175            Self::MLKEM(ct) => match ct.level() {
176                crate::MLKEM::MLKEM512 => EncapsulationScheme::MLKEM512,
177                crate::MLKEM::MLKEM768 => EncapsulationScheme::MLKEM768,
178                crate::MLKEM::MLKEM1024 => EncapsulationScheme::MLKEM1024,
179            },
180        }
181    }
182}
183
184/// Conversion from `EncapsulationCiphertext` to CBOR for serialization.
185impl From<EncapsulationCiphertext> for CBOR {
186    fn from(ciphertext: EncapsulationCiphertext) -> Self {
187        match ciphertext {
188            EncapsulationCiphertext::X25519(public_key) => public_key.into(),
189            EncapsulationCiphertext::MLKEM(ciphertext) => ciphertext.into(),
190        }
191    }
192}
193
194/// Conversion from CBOR to `EncapsulationCiphertext` for deserialization.
195impl TryFrom<CBOR> for EncapsulationCiphertext {
196    type Error = anyhow::Error;
197
198    fn try_from(cbor: CBOR) -> Result<Self> {
199        match cbor.as_case() {
200            CBORCase::Tagged(tag, _) => match tag.value() {
201                tags::TAG_X25519_PUBLIC_KEY => {
202                    Ok(EncapsulationCiphertext::X25519(
203                        X25519PublicKey::try_from(cbor)?,
204                    ))
205                }
206                tags::TAG_MLKEM_CIPHERTEXT => {
207                    Ok(EncapsulationCiphertext::MLKEM(
208                        MLKEMCiphertext::try_from(cbor)?,
209                    ))
210                }
211                _ => bail!("Invalid encapsulation ciphertext"),
212            },
213            _ => bail!("Invalid encapsulation ciphertext"),
214        }
215    }
216}