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