bc_components/encapsulation/
encapsulation_ciphertext.rs

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