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}