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