dkls23/proto/encrypted.rs
1// Copyright (c) Silence Laboratories Pte. Ltd. All Rights Reserved.
2// This software is licensed under the Silence Laboratories License Agreement.
3
4//! Module for handling encrypted messages in the protocol.
5//! This module provides functionality for encrypting and decrypting messages
6//! with support for additional data and trailers. It uses a pluggable encryption
7//! scheme interface to allow for different encryption implementations.
8
9use std::marker::PhantomData;
10
11use bytemuck::{AnyBitPattern, NoUninit};
12use chacha20poly1305::ChaCha20Poly1305;
13
14use sl_mpc_mate::message::*;
15
16pub use crate::proto::scheme::EncryptionScheme;
17
18/// Default encryption scheme using X25519 key exchange and ChaCha20Poly1305 for encryption.
19pub type Scheme = crate::proto::scheme::AeadX25519<ChaCha20Poly1305>;
20
21/// A wrapper for a message of type T with support for in-place encryption/decryption.
22///
23/// This struct provides functionality for encrypting and decrypting messages while
24/// maintaining a specific format:
25///
26/// ```text
27/// [ msg-hdr | additional-data | payload | trailer | tag + nonce ]
28/// ```
29///
30/// Where:
31/// - `msg-hdr`: Message header containing ID, TTL, and flags
32/// - `additional-data`: Optional unencrypted data
33/// - `payload`: The encrypted external representation of type T
34/// - `trailer`: Optional encrypted variable-sized data
35/// - `tag + nonce`: Authentication tag and nonce for the encryption scheme
36///
37/// The `payload` and `trailer` sections are encrypted, while the header and
38/// additional data remain in plaintext.
39pub struct EncryptedMessage<T> {
40 buffer: Vec<u8>,
41 additional_data: usize, // size of additional-data
42 marker: PhantomData<T>,
43}
44
45impl<T: AnyBitPattern + NoUninit> EncryptedMessage<T> {
46 const T_SIZE: usize = core::mem::size_of::<T>();
47
48 /// Calculates the total size of an encrypted message.
49 ///
50 /// # Arguments
51 /// * `ad` - Size of additional data in bytes
52 /// * `trailer` - Size of trailer data in bytes
53 /// * `scheme` - The encryption scheme to use
54 ///
55 /// # Returns
56 /// The total size in bytes needed for the encrypted message
57 pub fn size(
58 ad: usize,
59 trailer: usize,
60 scheme: &dyn EncryptionScheme,
61 ) -> usize {
62 MESSAGE_HEADER_SIZE + ad + Self::T_SIZE + trailer + scheme.overhead()
63 }
64
65 /// Creates a new encrypted message with the specified parameters.
66 ///
67 /// # Arguments
68 /// * `id` - Message identifier
69 /// * `ttl` - Time-to-live value
70 /// * `flags` - Message flags
71 /// * `trailer` - Size of trailer data in bytes
72 /// * `scheme` - The encryption scheme to use
73 ///
74 /// # Returns
75 /// A new `EncryptedMessage` instance
76 pub fn new(
77 id: &MsgId,
78 ttl: u32,
79 flags: u16,
80 trailer: usize,
81 scheme: &dyn EncryptionScheme,
82 ) -> Self {
83 let buffer = vec![0u8; Self::size(0, trailer, scheme)];
84
85 Self::from_buffer(buffer, id, ttl, flags, 0, trailer, scheme)
86 }
87
88 /// Creates a new encrypted message with additional data.
89 ///
90 /// # Arguments
91 /// * `id` - Message identifier
92 /// * `ttl` - Time-to-live value
93 /// * `flags` - Message flags
94 /// * `additional_data` - Size of additional data in bytes
95 /// * `trailer` - Size of trailer data in bytes
96 /// * `scheme` - The encryption scheme to use
97 ///
98 /// # Returns
99 /// A new `EncryptedMessage` instance with space for additional data
100 pub fn new_with_ad(
101 id: &MsgId,
102 ttl: u32,
103 flags: u16,
104 additional_data: usize,
105 trailer: usize,
106 scheme: &dyn EncryptionScheme,
107 ) -> Self {
108 let buffer = vec![0u8; Self::size(additional_data, trailer, scheme)];
109
110 Self::from_buffer(
111 buffer,
112 id,
113 ttl,
114 flags,
115 additional_data,
116 trailer,
117 scheme,
118 )
119 }
120
121 /// Creates an encrypted message from an existing buffer.
122 ///
123 /// # Arguments
124 /// * `buffer` - Existing buffer to use
125 /// * `id` - Message identifier
126 /// * `ttl` - Time-to-live value
127 /// * `flags` - Message flags
128 /// * `additional_data` - Size of additional data in bytes
129 /// * `trailer` - Size of trailer data in bytes
130 /// * `scheme` - The encryption scheme to use
131 ///
132 /// # Returns
133 /// A new `EncryptedMessage` instance using the provided buffer
134 pub fn from_buffer(
135 mut buffer: Vec<u8>,
136 id: &MsgId,
137 ttl: u32,
138 flags: u16,
139 additional_data: usize,
140 trailer: usize,
141 scheme: &dyn EncryptionScheme,
142 ) -> Self {
143 buffer.resize(Self::size(additional_data, trailer, scheme), 0);
144
145 if let Some(hdr) = buffer.first_chunk_mut::<MESSAGE_HEADER_SIZE>() {
146 MsgHdr::encode(hdr, id, ttl, flags);
147 }
148
149 Self {
150 buffer,
151 additional_data,
152 marker: PhantomData,
153 }
154 }
155
156 /// Returns mutable references to the message payload, trailer, and additional data.
157 ///
158 /// # Arguments
159 /// * `scheme` - The encryption scheme to use
160 ///
161 /// # Returns
162 /// A tuple containing:
163 /// - Mutable reference to the payload object
164 /// - Mutable reference to the trailer bytes
165 /// - Mutable reference to the additional data bytes
166 pub fn payload_with_ad(
167 &mut self,
168 scheme: &dyn EncryptionScheme,
169 ) -> (&mut T, &mut [u8], &mut [u8]) {
170 let tag_offset = self.buffer.len() - scheme.overhead();
171
172 // body = ad | payload | trailer
173 let body = &mut self.buffer[MESSAGE_HEADER_SIZE..tag_offset];
174
175 let (additional_data, msg_and_trailer) =
176 body.split_at_mut(self.additional_data);
177
178 let (msg, trailer) = msg_and_trailer.split_at_mut(Self::T_SIZE);
179
180 (bytemuck::from_bytes_mut(msg), trailer, additional_data)
181 }
182
183 /// Returns mutable references to the message payload and trailer.
184 ///
185 /// # Arguments
186 /// * `scheme` - The encryption scheme to use
187 ///
188 /// # Returns
189 /// A tuple containing:
190 /// - Mutable reference to the payload object
191 /// - Mutable reference to the trailer bytes
192 pub fn payload(
193 &mut self,
194 scheme: &dyn EncryptionScheme,
195 ) -> (&mut T, &mut [u8]) {
196 let (msg, trailer, _) = self.payload_with_ad(scheme);
197
198 (msg, trailer)
199 }
200
201 /// Encrypts the message using the provided encryption scheme.
202 ///
203 /// # Arguments
204 /// * `scheme` - The encryption scheme to use
205 /// * `receiver` - The ID of the intended receiver
206 ///
207 /// # Returns
208 /// The encrypted message as a byte vector, or `None` if encryption failed
209 pub fn encrypt(
210 self,
211 scheme: &mut dyn EncryptionScheme,
212 receiver: usize,
213 ) -> Option<Vec<u8>> {
214 let mut buffer = self.buffer;
215
216 let last = buffer.len() - scheme.overhead();
217 let (msg, tail) = buffer.split_at_mut(last);
218
219 let (associated_data, plaintext) =
220 msg.split_at_mut(MESSAGE_HEADER_SIZE + self.additional_data);
221
222 scheme
223 .encrypt(associated_data, plaintext, tail, receiver)
224 .ok()?;
225
226 Some(buffer)
227 }
228
229 /// Decrypts a message and returns references to the payload, trailer, and additional data.
230 ///
231 /// # Arguments
232 /// * `buffer` - The encrypted message buffer
233 /// * `additional_data` - Size of additional data in bytes
234 /// * `trailer` - Size of trailer data in bytes
235 /// * `scheme` - The encryption scheme to use
236 /// * `sender` - The ID of the message sender
237 ///
238 /// # Returns
239 /// A tuple containing references to the decrypted payload, trailer, and additional data,
240 /// or `None` if decryption failed
241 pub fn decrypt_with_ad<'msg>(
242 buffer: &'msg mut [u8],
243 additional_data: usize,
244 trailer: usize,
245 scheme: &dyn EncryptionScheme,
246 sender: usize,
247 ) -> Option<(&'msg T, &'msg [u8], &'msg [u8])> {
248 if buffer.len() != Self::size(additional_data, trailer, scheme) {
249 return None;
250 }
251
252 let (associated_data, body) =
253 buffer.split_at_mut(MESSAGE_HEADER_SIZE + additional_data);
254
255 let (ciphertext, tail) =
256 body.split_at_mut(body.len() - scheme.overhead());
257
258 scheme
259 .decrypt(associated_data, ciphertext, tail, sender)
260 .ok()?;
261
262 let (msg, trailer) = ciphertext.split_at_mut(Self::T_SIZE);
263
264 Some((
265 bytemuck::from_bytes_mut(msg),
266 trailer,
267 &associated_data[MESSAGE_HEADER_SIZE..],
268 ))
269 }
270
271 /// Decrypts a message and returns references to the payload and trailer.
272 ///
273 /// # Arguments
274 /// * `buffer` - The encrypted message buffer
275 /// * `trailer` - Size of trailer data in bytes
276 /// * `scheme` - The encryption scheme to use
277 /// * `sender` - The ID of the message sender
278 ///
279 /// # Returns
280 /// A tuple containing references to the decrypted payload and trailer,
281 /// or `None` if decryption failed
282 pub fn decrypt<'msg>(
283 buffer: &'msg mut [u8],
284 trailer: usize,
285 scheme: &dyn EncryptionScheme,
286 sender: usize,
287 ) -> Option<(&'msg T, &'msg [u8])> {
288 Self::decrypt_with_ad(buffer, 0, trailer, scheme, sender)
289 .map(|(msg, trailer, _)| (msg, trailer))
290 }
291}