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}