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}