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}