bc_components/encapsulation/sealed_message.rs
1use bc_ur::prelude::*;
2
3use super::EncapsulationScheme;
4use crate::{
5 Decrypter, EncapsulationCiphertext, EncryptedMessage, Encrypter, Nonce,
6 Result, tags,
7};
8
9/// A sealed message that can only be decrypted by the intended recipient.
10///
11/// `SealedMessage` provides a public key encryption mechanism where a message
12/// is encrypted with a symmetric key, and that key is then encapsulated using
13/// the recipient's public key. This ensures that only the recipient can decrypt
14/// the message by first decapsulating the shared secret using their private
15/// key.
16///
17/// Features:
18/// - Anonymous sender: The sender's identity is not revealed in the sealed
19/// message
20/// - Authenticated encryption: Message integrity and authenticity are
21/// guaranteed
22/// - Forward secrecy: Each message uses a different ephemeral key
23/// - Post-quantum security options: Can use ML-KEM for quantum-resistant
24/// encryption
25///
26/// The structure internally contains:
27/// - An `EncryptedMessage` containing the actual encrypted data
28/// - An `EncapsulationCiphertext` containing the encapsulated shared secret
29#[derive(Clone, PartialEq, Debug)]
30pub struct SealedMessage {
31 /// The encrypted message content
32 message: EncryptedMessage,
33 /// The encapsulated key used to encrypt the message
34 encapsulated_key: EncapsulationCiphertext,
35}
36
37impl SealedMessage {
38 /// Creates a new `SealedMessage` from the given plaintext and recipient.
39 ///
40 /// This method performs the following steps:
41 /// 1. Generates a new shared secret key and encapsulates it for the
42 /// recipient
43 /// 2. Encrypts the plaintext using that shared secret
44 ///
45 /// # Parameters
46 ///
47 /// * `plaintext` - The message data to encrypt
48 /// * `recipient` - The recipient who will be able to decrypt the message
49 ///
50 /// # Returns
51 ///
52 /// A new `SealedMessage` containing the encrypted message and encapsulated
53 /// key
54 ///
55 /// # Example
56 ///
57 /// ```
58 /// use bc_components::{EncapsulationScheme, SealedMessage};
59 ///
60 /// // Generate a keypair for the recipient
61 /// let (recipient_private_key, recipient_public_key) =
62 /// EncapsulationScheme::default().keypair();
63 ///
64 /// // Create a sealed message for the recipient
65 /// let plaintext = b"For your eyes only";
66 /// let sealed_message = SealedMessage::new(plaintext, &recipient_public_key);
67 ///
68 /// // The recipient can decrypt the message
69 /// let decrypted = sealed_message.decrypt(&recipient_private_key).unwrap();
70 /// assert_eq!(decrypted, plaintext);
71 /// ```
72 pub fn new(plaintext: impl AsRef<[u8]>, recipient: &dyn Encrypter) -> Self {
73 Self::new_with_aad(plaintext, recipient, None::<Vec<u8>>)
74 }
75
76 /// Creates a new `SealedMessage` with additional authenticated data (AAD).
77 ///
78 /// AAD is data that is authenticated but not encrypted. It can be used to
79 /// bind the encrypted message to some context.
80 ///
81 /// # Parameters
82 ///
83 /// * `plaintext` - The message data to encrypt
84 /// * `recipient` - The recipient who will be able to decrypt the message
85 /// * `aad` - Additional authenticated data that will be bound to the
86 /// encryption
87 ///
88 /// # Returns
89 ///
90 /// A new `SealedMessage` containing the encrypted message and encapsulated
91 /// key
92 ///
93 /// # Example
94 ///
95 /// ```
96 /// use bc_components::{EncapsulationScheme, SealedMessage};
97 ///
98 /// // Generate a keypair for the recipient
99 /// let (recipient_private_key, recipient_public_key) =
100 /// EncapsulationScheme::default().keypair();
101 ///
102 /// // Create a sealed message with additional authenticated data
103 /// let plaintext = b"For your eyes only";
104 /// let aad = b"Message ID: 12345";
105 /// let sealed_message = SealedMessage::new_with_aad(
106 /// plaintext,
107 /// &recipient_public_key,
108 /// Some(aad),
109 /// );
110 ///
111 /// // The recipient can decrypt the message
112 /// let decrypted = sealed_message.decrypt(&recipient_private_key).unwrap();
113 /// assert_eq!(decrypted, plaintext);
114 /// ```
115 pub fn new_with_aad(
116 plaintext: impl AsRef<[u8]>,
117 recipient: &dyn Encrypter,
118 aad: Option<impl AsRef<[u8]>>,
119 ) -> Self {
120 Self::new_opt(plaintext, recipient, aad, None::<Nonce>)
121 }
122
123 /// Creates a new `SealedMessage` with options for testing.
124 ///
125 /// This method is similar to `new_with_aad` but allows specifying a test
126 /// nonce, which is useful for deterministic testing.
127 ///
128 /// # Parameters
129 ///
130 /// * `plaintext` - The message data to encrypt
131 /// * `recipient` - The recipient who will be able to decrypt the message
132 /// * `aad` - Additional authenticated data that will be bound to the
133 /// encryption
134 /// * `test_nonce` - Optional nonce for deterministic encryption (testing
135 /// only)
136 ///
137 /// # Returns
138 ///
139 /// A new `SealedMessage` containing the encrypted message and encapsulated
140 /// key
141 pub fn new_opt(
142 plaintext: impl AsRef<[u8]>,
143 recipient: &dyn Encrypter,
144 aad: Option<impl AsRef<[u8]>>,
145 test_nonce: Option<impl AsRef<Nonce>>,
146 ) -> Self {
147 let (shared_key, encapsulated_key) =
148 recipient.encapsulate_new_shared_secret();
149 let message = shared_key.encrypt(plaintext, aad, test_nonce);
150 Self { message, encapsulated_key }
151 }
152
153 /// Decrypts the message using the recipient's private key.
154 ///
155 /// This method performs the following steps:
156 /// 1. Decapsulates the shared secret using the recipient's private key
157 /// 2. Uses the shared secret to decrypt the message
158 ///
159 /// # Parameters
160 ///
161 /// * `private_key` - The private key of the intended recipient
162 ///
163 /// # Returns
164 ///
165 /// A `Result` containing the decrypted message data if successful,
166 /// or an error if decryption fails
167 ///
168 /// # Errors
169 ///
170 /// Returns an error if:
171 /// - The private key doesn't match the one used for encapsulation
172 /// - The decapsulation process fails
173 /// - The decryption process fails (e.g., message tampering)
174 ///
175 /// # Example
176 ///
177 /// ```
178 /// use bc_components::{EncapsulationScheme, SealedMessage};
179 ///
180 /// // Generate keypairs for different users
181 /// let (alice_private_key, _) = EncapsulationScheme::default().keypair();
182 /// let (bob_private_key, bob_public_key) =
183 /// EncapsulationScheme::default().keypair();
184 ///
185 /// // Alice sends a message to Bob
186 /// let plaintext = b"Secret message for Bob";
187 /// let sealed_message = SealedMessage::new(plaintext, &bob_public_key);
188 ///
189 /// // Bob can decrypt the message
190 /// let decrypted = sealed_message.decrypt(&bob_private_key).unwrap();
191 /// assert_eq!(decrypted, plaintext);
192 ///
193 /// // Alice cannot decrypt the message she sent
194 /// assert!(sealed_message.decrypt(&alice_private_key).is_err());
195 /// ```
196 pub fn decrypt(&self, private_key: &dyn Decrypter) -> Result<Vec<u8>> {
197 let shared_key =
198 private_key.decapsulate_shared_secret(&self.encapsulated_key)?;
199 shared_key.decrypt(&self.message)
200 }
201
202 /// Returns the encapsulation scheme used for this sealed message.
203 ///
204 /// # Returns
205 ///
206 /// The encapsulation scheme (X25519, MLKEM512, MLKEM768, or MLKEM1024)
207 /// that was used to create this sealed message.
208 ///
209 /// # Example
210 ///
211 /// ```
212 /// use bc_components::{EncapsulationScheme, SealedMessage};
213 ///
214 /// // Generate a keypair using ML-KEM768
215 /// let (_, public_key) = EncapsulationScheme::MLKEM768.keypair();
216 ///
217 /// // Create a sealed message
218 /// let sealed_message =
219 /// SealedMessage::new(b"Quantum-resistant message", &public_key);
220 ///
221 /// // Check the encapsulation scheme
222 /// assert_eq!(
223 /// sealed_message.encapsulation_scheme(),
224 /// EncapsulationScheme::MLKEM768
225 /// );
226 /// ```
227 pub fn encapsulation_scheme(&self) -> EncapsulationScheme {
228 self.encapsulated_key.encapsulation_scheme()
229 }
230}
231
232/// Implementation of `AsRef` trait for `SealedMessage`.
233impl AsRef<SealedMessage> for SealedMessage {
234 fn as_ref(&self) -> &SealedMessage { self }
235}
236
237/// Implementation of CBOR tagging for `SealedMessage`.
238impl CBORTagged for SealedMessage {
239 fn cbor_tags() -> Vec<Tag> { tags_for_values(&[tags::TAG_SEALED_MESSAGE]) }
240}
241
242/// Conversion from `SealedMessage` to CBOR for serialization.
243impl From<SealedMessage> for CBOR {
244 fn from(value: SealedMessage) -> Self { value.tagged_cbor() }
245}
246
247/// Conversion from CBOR to `SealedMessage` for deserialization.
248impl TryFrom<CBOR> for SealedMessage {
249 type Error = dcbor::Error;
250
251 fn try_from(cbor: CBOR) -> dcbor::Result<Self> {
252 Self::from_tagged_cbor(cbor)
253 }
254}
255
256/// Implementation of CBOR encoding for `SealedMessage`.
257impl CBORTaggedEncodable for SealedMessage {
258 fn untagged_cbor(&self) -> CBOR {
259 let message: CBOR = self.message.clone().into();
260 let ephemeral_public_key: CBOR = self.encapsulated_key.clone().into();
261 [message, ephemeral_public_key].into()
262 }
263}
264
265/// Implementation of CBOR decoding for `SealedMessage`.
266impl CBORTaggedDecodable for SealedMessage {
267 fn from_untagged_cbor(cbor: CBOR) -> dcbor::Result<Self> {
268 match cbor.as_case() {
269 CBORCase::Array(elements) => {
270 if elements.len() != 2 {
271 return Err("SealedMessage must have two elements".into());
272 }
273 let message = elements[0].clone().try_into()?;
274 let ephemeral_public_key = elements[1].clone().try_into()?;
275 Ok(Self { message, encapsulated_key: ephemeral_public_key })
276 }
277 _ => Err("SealedMessage must be an array".into()),
278 }
279 }
280}
281
282#[cfg(test)]
283mod tests {
284 use crate::{EncapsulationScheme, SealedMessage};
285
286 #[test]
287 fn test_sealed_message_x25519() {
288 let plaintext = b"Some mysteries aren't meant to be solved.";
289
290 let encapsulation = EncapsulationScheme::X25519;
291 let (alice_private_key, _) = encapsulation.keypair();
292 let (bob_private_key, bob_public_key) = encapsulation.keypair();
293 let (carol_private_key, _) = encapsulation.keypair();
294
295 // Alice constructs a message for Bob's eyes only.
296 let sealed_message = SealedMessage::new(plaintext, &bob_public_key);
297
298 // Bob decrypts and reads the message.
299 assert_eq!(
300 sealed_message.decrypt(&bob_private_key).unwrap(),
301 plaintext
302 );
303
304 // No one else can decrypt the message, not even the sender.
305 assert!(sealed_message.decrypt(&alice_private_key).is_err());
306 assert!(sealed_message.decrypt(&carol_private_key).is_err());
307 }
308
309 #[test]
310 fn test_sealed_message_mlkem512() {
311 let plaintext = b"Some mysteries aren't meant to be solved.";
312
313 let encapsulation = EncapsulationScheme::MLKEM512;
314 let (alice_private_key, _) = encapsulation.keypair();
315 let (bob_private_key, bob_public_key) = encapsulation.keypair();
316 let (carol_private_key, _) = encapsulation.keypair();
317
318 // Alice constructs a message for Bob's eyes only.
319 let sealed_message = SealedMessage::new(plaintext, &bob_public_key);
320
321 // Bob decrypts and reads the message.
322 assert_eq!(
323 sealed_message.decrypt(&bob_private_key).unwrap(),
324 plaintext
325 );
326
327 // No one else can decrypt the message, not even the sender.
328 assert!(sealed_message.decrypt(&alice_private_key).is_err());
329 assert!(sealed_message.decrypt(&carol_private_key).is_err());
330 }
331}