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}