bc_components/encapsulation/
encapsulation_ciphertext.rs

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