bc_components/encapsulation/
encapsulation_ciphertext.rs

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