p2panda_encryption/message_scheme/
ratchet.rs1use std::collections::VecDeque;
5
6use serde::{Deserialize, Serialize};
7use thiserror::Error;
8
9use crate::crypto::Secret;
10use crate::crypto::aead::AeadNonce;
11use crate::crypto::hkdf::{HkdfError, hkdf};
12
13pub const MESSAGE_KEY_SIZE: usize = 32;
15
16pub type RatchetKey = Secret<MESSAGE_KEY_SIZE>;
18
19pub type RatchetNonce = AeadNonce;
21
22pub type RatchetKeyMaterial = (RatchetKey, RatchetNonce);
24
25pub type Generation = u32;
27
28#[derive(Debug)]
30pub struct RatchetSecret;
31
32#[derive(Debug, Serialize, Deserialize)]
33#[cfg_attr(any(test, feature = "test_utils"), derive(Clone))]
34pub struct RatchetSecretState {
35 secret: Secret<MESSAGE_KEY_SIZE>,
36 generation: Generation,
37}
38
39impl RatchetSecret {
40 pub fn init(secret: Secret<MESSAGE_KEY_SIZE>) -> RatchetSecretState {
41 RatchetSecretState {
42 secret,
43 generation: 0,
44 }
45 }
46
47 pub fn ratchet_forward(
50 mut y: RatchetSecretState,
51 ) -> Result<(RatchetSecretState, Generation, RatchetKeyMaterial), RatchetError> {
52 let generation = y.generation;
53
54 let nonce: AeadNonce = hkdf(b"nonce", y.secret.as_bytes(), None)?;
56 let key: [u8; MESSAGE_KEY_SIZE] = hkdf(b"key", y.secret.as_bytes(), None)?;
57
58 y.generation += 1;
60 y.secret = Secret::from_bytes(hkdf(b"chain", y.secret.as_bytes(), None)?);
61
62 Ok((y, generation, (Secret::from_bytes(key), nonce)))
63 }
64}
65
66#[derive(Debug)]
94pub struct DecryptionRatchet;
95
96#[derive(Debug, Serialize, Deserialize)]
97#[cfg_attr(any(test, feature = "test_utils"), derive(Clone))]
98pub struct DecryptionRatchetState {
99 past_secrets: VecDeque<Option<RatchetKeyMaterial>>,
100 ratchet_head: RatchetSecretState,
101}
102
103impl DecryptionRatchet {
104 pub fn init(secret: Secret<MESSAGE_KEY_SIZE>) -> DecryptionRatchetState {
105 DecryptionRatchetState {
106 past_secrets: VecDeque::new(),
107 ratchet_head: RatchetSecret::init(secret),
108 }
109 }
110
111 pub fn secret_for_decryption(
125 mut y: DecryptionRatchetState,
126 generation: Generation,
127 maximum_forward_distance: u32,
128 ooo_tolerance: u32,
129 ) -> Result<(DecryptionRatchetState, RatchetKeyMaterial), RatchetError> {
130 let generation_head = y.ratchet_head.generation;
131
132 if generation_head < u32::MAX - maximum_forward_distance
134 && generation > generation_head + maximum_forward_distance
135 {
136 return Err(RatchetError::TooDistantInTheFuture);
137 }
138
139 if generation < generation_head && (generation_head - generation) > ooo_tolerance {
141 return Err(RatchetError::TooDistantInThePast);
142 }
143
144 if generation >= generation_head {
146 for _ in 0..(generation - generation_head) {
148 let (y_ratchet_head_i, _, ratchet_secrets) =
150 RatchetSecret::ratchet_forward(y.ratchet_head)?;
151 y.ratchet_head = y_ratchet_head_i;
152 y.past_secrets.push_front(Some(ratchet_secrets));
154 }
155 let (y_ratchet_head_i, _, ratchet_secrets) =
156 RatchetSecret::ratchet_forward(y.ratchet_head)?;
157 y.ratchet_head = y_ratchet_head_i;
158 y.past_secrets.push_front(None);
160 y.past_secrets.truncate(ooo_tolerance as usize);
162 Ok((y, ratchet_secrets))
163 } else {
164 let window_index = ((generation_head - generation) as i32) - 1;
167 let index = if window_index >= 0 {
170 window_index as usize
171 } else {
172 return Err(RatchetError::TooDistantInThePast);
173 };
174 let ratchet_secrets = y
176 .past_secrets
177 .get_mut(index)
178 .ok_or(RatchetError::IndexOutOfBounds)?
179 .take()
182 .ok_or(RatchetError::SecretReuse)?;
185 Ok((y, ratchet_secrets))
186 }
187 }
188}
189
190#[derive(Debug, Error)]
191pub enum RatchetError {
192 #[error(transparent)]
193 Hkdf(#[from] HkdfError),
194
195 #[error("generation for message ratchet is too far into the future")]
196 TooDistantInTheFuture,
197
198 #[error("generation for message ratchet is too far into the past")]
199 TooDistantInThePast,
200
201 #[error("unknown message secret")]
202 IndexOutOfBounds,
203
204 #[error("tried to re-use secret for same generation")]
205 SecretReuse,
206}
207
208#[cfg(test)]
209mod tests {
210 use crate::Rng;
211 use crate::crypto::Secret;
212
213 use super::{DecryptionRatchet, MESSAGE_KEY_SIZE, RatchetError, RatchetSecret};
214
215 #[test]
216 fn ratchet_forward() {
217 let rng = Rng::from_seed([1; 32]);
218
219 let update_secret = Secret::from_bytes(rng.random_array::<MESSAGE_KEY_SIZE>().unwrap());
220
221 let ratchet = RatchetSecret::init(update_secret);
222 let (ratchet, generation, secret_0) = RatchetSecret::ratchet_forward(ratchet).unwrap();
223 assert_eq!(generation, 0);
224 assert_eq!(ratchet.generation, 1);
225
226 let (ratchet, generation, secret_1) = RatchetSecret::ratchet_forward(ratchet).unwrap();
227 assert_eq!(generation, 1);
228 assert_eq!(ratchet.generation, 2);
229
230 assert_ne!(secret_0, secret_1);
232 }
233
234 #[test]
235 fn forward_secrecy() {
236 let rng = Rng::from_seed([1; 32]);
237
238 let update_secret = Secret::from_bytes(rng.random_array::<MESSAGE_KEY_SIZE>().unwrap());
239
240 let ooo_tolerance = 4;
241 let max_forward = 100;
242
243 let ratchet = DecryptionRatchet::init(update_secret);
244
245 let (ratchet, secret) =
246 DecryptionRatchet::secret_for_decryption(ratchet, 0, max_forward, ooo_tolerance)
247 .unwrap();
248
249 assert_eq!(ratchet.ratchet_head.generation, 1);
251
252 assert_ne!(ratchet.ratchet_head.secret, secret.0);
254 assert!(!ratchet.past_secrets.iter().any(|secret| secret.is_some()));
255
256 assert!(matches!(
258 DecryptionRatchet::secret_for_decryption(
259 ratchet.clone(),
260 0,
261 max_forward,
262 ooo_tolerance
263 ),
264 Err(RatchetError::SecretReuse),
265 ));
266
267 let jump = 10;
269 let (mut ratchet, _) =
270 DecryptionRatchet::secret_for_decryption(ratchet, jump, max_forward, ooo_tolerance)
271 .unwrap();
272
273 for generation in jump - ooo_tolerance + 1..jump {
276 let (ratchet_i, _) = DecryptionRatchet::secret_for_decryption(
277 ratchet,
278 generation,
279 max_forward,
280 ooo_tolerance,
281 )
282 .unwrap();
283
284 assert!(matches!(
285 DecryptionRatchet::secret_for_decryption(
286 ratchet_i.clone(),
287 generation,
288 max_forward,
289 ooo_tolerance
290 ),
291 Err(RatchetError::SecretReuse),
292 ));
293
294 ratchet = ratchet_i;
295 }
296
297 assert!(!ratchet.past_secrets.iter().any(|secret| secret.is_some()));
299 }
300
301 #[test]
302 fn out_of_order() {
303 let rng = Rng::from_seed([1; 32]);
304
305 let update_secret = Secret::from_bytes(rng.random_array::<MESSAGE_KEY_SIZE>().unwrap());
306
307 let max_forward = 3;
308 let ooo_tolerance = 3;
309
310 let alice = RatchetSecret::init(update_secret.clone());
311 let bob = DecryptionRatchet::init(update_secret);
312
313 let (alice, _, alice_secret_0) = RatchetSecret::ratchet_forward(alice).unwrap();
314 let (alice, _, _alice_secret_1) = RatchetSecret::ratchet_forward(alice).unwrap();
315 let (alice, _, alice_secret_2) = RatchetSecret::ratchet_forward(alice).unwrap();
316 let (alice, _, alice_secret_3) = RatchetSecret::ratchet_forward(alice).unwrap();
317 let (alice, _, alice_secret_4) = RatchetSecret::ratchet_forward(alice).unwrap();
318 assert_eq!(alice.generation, 5);
319
320 let (bob, bob_secret_0) =
322 DecryptionRatchet::secret_for_decryption(bob, 0, max_forward, ooo_tolerance).unwrap();
323 assert_eq!(alice_secret_0, bob_secret_0);
324
325 let (bob, bob_secret_4) =
327 DecryptionRatchet::secret_for_decryption(bob, 4, max_forward, ooo_tolerance).unwrap();
328 assert_eq!(alice_secret_4, bob_secret_4);
329 assert_eq!(bob.ratchet_head.generation, 5);
330 let (bob, bob_secret_3) =
331 DecryptionRatchet::secret_for_decryption(bob, 3, max_forward, ooo_tolerance).unwrap();
332 assert_eq!(alice_secret_3, bob_secret_3);
333 let (bob, bob_secret_2) =
334 DecryptionRatchet::secret_for_decryption(bob, 2, max_forward, ooo_tolerance).unwrap();
335 assert_eq!(alice_secret_2, bob_secret_2);
336
337 assert!(matches!(
340 DecryptionRatchet::secret_for_decryption(bob.clone(), 1, max_forward, ooo_tolerance),
341 Err(RatchetError::TooDistantInThePast)
342 ));
343
344 assert!(matches!(
347 DecryptionRatchet::secret_for_decryption(
348 bob.clone(),
349 bob.ratchet_head.generation + max_forward + 1,
350 max_forward,
351 ooo_tolerance
352 ),
353 Err(RatchetError::TooDistantInTheFuture)
354 ));
355 }
356}