Skip to main content

rings_core/message/
e2e.rs

1//! End-to-end ElGamal encryption for byte payloads.
2//!
3//! This module models E2E encryption as two separate protocol facts:
4//!
5//! - peers discover encryption public keys with signed handshake messages;
6//! - message bodies are encrypted directly with secp256k1 ElGamal stream
7//!   frames, each carried by its own signed
8//!   [`MessagePayload`](crate::message::MessagePayload).
9//!
10//! Rings intentionally uses the DID/account secp256k1 public key as the
11//! ElGamal key. The public key carried by a handshake or encrypted message must
12//! resolve to the signed DID; this is the protocol's key-ownership proof. The
13//! module does not derive a separate encryption subkey.
14//!
15//! The direct ElGamal body is deliberately not KEM/DEM or AEAD. Keeping the
16//! ciphertext in group-element form preserves the algebraic structure needed by
17//! future homomorphic operations. That also means ciphertext frames are
18//! malleable if detached from the signed message envelope. Integrity, sender
19//! authentication, public-key ownership, and per-frame integrity are provided by
20//! the surrounding [`MessagePayload`](crate::message::MessagePayload)
21//! signature. The stream id, sequence, and final marker make truncation
22//! observable and let the decryptor release reordered frames as a gapless
23//! plaintext stream.
24
25use std::collections::BTreeMap;
26
27use rand::RngCore;
28use serde::Deserialize;
29use serde::Serialize;
30
31use crate::dht::Did;
32use crate::ecc::elgamal::impls::secp256k1;
33use crate::ecc::group::Point;
34use crate::ecc::group::Secp256k1;
35use crate::ecc::PublicKey;
36use crate::ecc::SecretKey;
37use crate::error::Error;
38use crate::error::Result;
39
40/// Plaintext bytes carried by one ElGamal body block.
41pub const E2E_PLAINTEXT_BLOCK_LEN: usize = secp256k1::PLAINTEXT_BLOCK_SIZE;
42
43/// Default plaintext bytes per encrypted E2E stream frame.
44pub const DEFAULT_E2E_PLAINTEXT_FRAME_LEN: usize = E2E_PLAINTEXT_BLOCK_LEN * 16;
45
46/// Default maximum number of out-of-order future frames buffered per stream.
47pub const DEFAULT_E2E_REORDER_WINDOW_FRAMES: u64 = 64;
48
49/// Identifier shared by all frames of one encrypted E2E stream.
50pub type E2eStreamId = uuid::Uuid;
51
52/// An invitation to start E2E encryption with the requester's public key.
53#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq)]
54pub struct E2eHandshakeRequest {
55    /// Public key owned by the requester DID.
56    pub requester_public_key: PublicKey<33>,
57}
58
59/// An acceptance of an E2E handshake with the responder's public key.
60#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq)]
61pub struct E2eHandshakeResponse {
62    /// Public key owned by the responder DID.
63    pub responder_public_key: PublicKey<33>,
64}
65
66/// One ordered ElGamal-encrypted stream frame.
67#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
68pub struct E2eStreamFrame {
69    /// Stream identifier shared by all frames in this body stream.
70    pub stream_id: E2eStreamId,
71    /// Public key owned by the signed sender DID.
72    pub sender_public_key: PublicKey<33>,
73    /// Monotonic frame counter checked by the decryptor.
74    pub sequence: u64,
75    /// End-of-stream marker authenticated by this frame's message signature.
76    pub is_final: bool,
77    /// ElGamal ciphertext blocks for this frame's plaintext bytes.
78    pub ciphertext: Vec<secp256k1::CiphertextBlock>,
79}
80
81#[derive(Debug, Clone, Copy, PartialEq, Eq)]
82struct E2ePlaintextFrame<'a> {
83    plaintext: &'a [u8],
84    is_final: bool,
85}
86
87struct E2ePlaintextFrames<'a> {
88    chunks: std::iter::Peekable<std::slice::Chunks<'a, u8>>,
89    plaintext_is_empty: bool,
90    emitted_empty_final: bool,
91}
92
93/// Lazy iterator that encrypts one E2E stream frame per iteration.
94pub struct E2eStreamFrames<'a, R: RngCore> {
95    plaintext_frames: E2ePlaintextFrames<'a>,
96    encryptor: E2eStreamEncryptor,
97    rng: R,
98}
99
100/// Stateful streaming encryptor for direct ElGamal body frames.
101pub struct E2eStreamEncryptor {
102    stream_id: E2eStreamId,
103    sender_public_key: PublicKey<33>,
104    recipient_public_key: PublicKey<33>,
105    next_sequence: u64,
106    closed: bool,
107}
108
109/// Stateful streaming decryptor for direct ElGamal body frames.
110pub struct E2eStreamDecryptor {
111    stream_id: E2eStreamId,
112    expected_sender: Did,
113    recipient_secret_key: SecretKey,
114    next_sequence: u64,
115    final_sequence: Option<u64>,
116    seen_final: bool,
117    reorder_window: u64,
118    pending_frames: BTreeMap<u64, E2eStreamFrame>,
119}
120
121#[derive(Debug, Clone, Copy, PartialEq, Eq)]
122enum FrameAcceptance {
123    New,
124    Duplicate,
125}
126
127impl<'a> E2ePlaintextFrames<'a> {
128    fn new(plaintext: &'a [u8], max_plaintext_frame_len: usize) -> Self {
129        Self {
130            chunks: plaintext.chunks(max_plaintext_frame_len.max(1)).peekable(),
131            plaintext_is_empty: plaintext.is_empty(),
132            emitted_empty_final: false,
133        }
134    }
135}
136
137impl<R: RngCore> Iterator for E2eStreamFrames<'_, R> {
138    type Item = Result<E2eStreamFrame>;
139
140    fn next(&mut self) -> Option<Self::Item> {
141        self.plaintext_frames
142            .next()
143            .map(|frame| self.encryptor.encrypt_plaintext_frame(frame, &mut self.rng))
144    }
145}
146
147impl<'a> Iterator for E2ePlaintextFrames<'a> {
148    type Item = E2ePlaintextFrame<'a>;
149
150    fn next(&mut self) -> Option<Self::Item> {
151        if self.plaintext_is_empty {
152            if self.emitted_empty_final {
153                return None;
154            }
155            self.emitted_empty_final = true;
156            return Some(E2ePlaintextFrame {
157                plaintext: &[],
158                is_final: true,
159            });
160        }
161
162        let plaintext = self.chunks.next()?;
163        Some(E2ePlaintextFrame {
164            plaintext,
165            is_final: self.chunks.peek().is_none(),
166        })
167    }
168}
169
170impl E2eHandshakeRequest {
171    /// Build a handshake request for a sender public key.
172    pub fn new(requester_public_key: PublicKey<33>) -> Self {
173        Self {
174            requester_public_key,
175        }
176    }
177
178    /// Verify that the requester public key belongs to the signed requester DID.
179    pub fn verify_requester(&self, requester: Did) -> Result<()> {
180        ensure_public_key_matches_did(self.requester_public_key, requester)
181    }
182}
183
184impl E2eHandshakeResponse {
185    /// Build a handshake response for a responder public key.
186    pub fn new(responder_public_key: PublicKey<33>) -> Self {
187        Self {
188            responder_public_key,
189        }
190    }
191
192    /// Verify that the responder public key belongs to the signed responder DID.
193    pub fn verify_responder(&self, responder: Did) -> Result<()> {
194        ensure_public_key_matches_did(self.responder_public_key, responder)
195    }
196}
197
198impl E2eStreamFrame {
199    /// Verify that the carried sender key belongs to the signed sender DID.
200    pub fn verify_sender(&self, sender: Did) -> Result<()> {
201        ensure_public_key_matches_did(self.sender_public_key, sender)
202    }
203}
204
205impl E2eStreamEncryptor {
206    /// Create a direct ElGamal streaming encryptor for `recipient_public_key`.
207    pub fn new(
208        stream_id: E2eStreamId,
209        sender_public_key: PublicKey<33>,
210        recipient_public_key: PublicKey<33>,
211    ) -> Result<Self> {
212        ensure_valid_public_key(sender_public_key)?;
213        ensure_valid_public_key(recipient_public_key)?;
214        Ok(Self {
215            stream_id,
216            sender_public_key,
217            recipient_public_key,
218            next_sequence: 0,
219            closed: false,
220        })
221    }
222
223    /// Encrypt one non-final plaintext frame.
224    pub fn encrypt_next(
225        &mut self,
226        plaintext: &[u8],
227        rng: &mut impl RngCore,
228    ) -> Result<E2eStreamFrame> {
229        self.encrypt_frame(plaintext, false, rng)
230    }
231
232    /// Encrypt one non-final plaintext frame with the default thread-local RNG.
233    pub fn encrypt_next_with_default_rng(&mut self, plaintext: &[u8]) -> Result<E2eStreamFrame> {
234        let mut rng = rand::thread_rng();
235        self.encrypt_next(plaintext, &mut rng)
236    }
237
238    /// Encrypt the final plaintext frame and close this stream.
239    pub fn encrypt_final(
240        &mut self,
241        plaintext: &[u8],
242        rng: &mut impl RngCore,
243    ) -> Result<E2eStreamFrame> {
244        self.encrypt_frame(plaintext, true, rng)
245    }
246
247    /// Encrypt the final plaintext frame with the default thread-local RNG.
248    pub fn encrypt_final_with_default_rng(&mut self, plaintext: &[u8]) -> Result<E2eStreamFrame> {
249        let mut rng = rand::thread_rng();
250        self.encrypt_final(plaintext, &mut rng)
251    }
252
253    fn encrypt_plaintext_frame(
254        &mut self,
255        frame: E2ePlaintextFrame<'_>,
256        rng: &mut impl RngCore,
257    ) -> Result<E2eStreamFrame> {
258        if frame.is_final {
259            self.encrypt_final(frame.plaintext, rng)
260        } else {
261            self.encrypt_next(frame.plaintext, rng)
262        }
263    }
264
265    fn encrypt_frame(
266        &mut self,
267        plaintext: &[u8],
268        is_final: bool,
269        rng: &mut impl RngCore,
270    ) -> Result<E2eStreamFrame> {
271        if self.closed {
272            return Err(Error::E2eFrameAfterFinal);
273        }
274
275        let sequence = self.next_sequence;
276        if is_final {
277            self.closed = true;
278        } else {
279            self.next_sequence = next_sequence(sequence)?;
280        }
281
282        let ciphertext =
283            secp256k1::encrypt_bytes_with_rng(plaintext, self.recipient_public_key, rng)?;
284        Ok(E2eStreamFrame {
285            stream_id: self.stream_id,
286            sender_public_key: self.sender_public_key,
287            sequence,
288            is_final,
289            ciphertext,
290        })
291    }
292}
293
294impl E2eStreamDecryptor {
295    /// Create a direct ElGamal streaming decryptor for a recipient secret key.
296    pub fn new(
297        stream_id: E2eStreamId,
298        expected_sender: Did,
299        recipient_secret_key: SecretKey,
300    ) -> Self {
301        Self::with_reorder_window(
302            stream_id,
303            expected_sender,
304            recipient_secret_key,
305            DEFAULT_E2E_REORDER_WINDOW_FRAMES,
306        )
307    }
308
309    /// Create a decryptor with an explicit future-frame reorder window.
310    pub fn with_reorder_window(
311        stream_id: E2eStreamId,
312        expected_sender: Did,
313        recipient_secret_key: SecretKey,
314        reorder_window: u64,
315    ) -> Self {
316        Self {
317            stream_id,
318            expected_sender,
319            recipient_secret_key,
320            next_sequence: 0,
321            final_sequence: None,
322            seen_final: false,
323            reorder_window,
324            pending_frames: BTreeMap::new(),
325        }
326    }
327
328    /// Decrypt one frame and return newly contiguous plaintext bytes.
329    ///
330    /// Out-of-order future frames are buffered and return an empty vector until
331    /// the missing lower sequence numbers arrive.
332    pub fn decrypt_next(&mut self, frame: &E2eStreamFrame) -> Result<Vec<u8>> {
333        if self.validate_frame(frame)? == FrameAcceptance::Duplicate {
334            return Ok(Vec::new());
335        }
336
337        if frame.is_final {
338            self.final_sequence = Some(frame.sequence);
339        }
340        self.pending_frames.insert(frame.sequence, frame.clone());
341        self.decrypt_ready_frames()
342    }
343
344    /// Verify that the stream ended with an authenticated final frame.
345    pub fn finish(&self) -> Result<()> {
346        if self.seen_final {
347            Ok(())
348        } else {
349            Err(Error::E2eMissingFinalFrame)
350        }
351    }
352
353    fn validate_frame(&self, frame: &E2eStreamFrame) -> Result<FrameAcceptance> {
354        if frame.stream_id != self.stream_id {
355            return Err(Error::E2eStreamIdMismatch {
356                expected: self.stream_id,
357                actual: frame.stream_id,
358            });
359        }
360
361        frame.verify_sender(self.expected_sender)?;
362
363        if self.is_consumed_duplicate(frame) {
364            return Ok(FrameAcceptance::Duplicate);
365        }
366
367        if let Some(pending_frame) = self.pending_frames.get(&frame.sequence) {
368            if pending_frame == frame {
369                return Ok(FrameAcceptance::Duplicate);
370            }
371
372            return Err(Error::E2eFrameSequenceMismatch {
373                expected: self.next_sequence,
374                actual: frame.sequence,
375            });
376        }
377
378        if self.seen_final {
379            return Err(Error::E2eFrameAfterFinal);
380        }
381
382        if self.exceeds_reorder_window(frame.sequence) {
383            return Err(Error::E2eFrameReorderWindowExceeded {
384                next_sequence: self.next_sequence,
385                actual: frame.sequence,
386                window: self.reorder_window,
387            });
388        }
389
390        if let Some(final_sequence) = self.final_sequence {
391            if frame.sequence > final_sequence {
392                return Err(Error::E2eFrameAfterFinal);
393            }
394
395            if frame.is_final && frame.sequence != final_sequence {
396                return Err(Error::E2eFrameSequenceMismatch {
397                    expected: final_sequence,
398                    actual: frame.sequence,
399                });
400            }
401        }
402
403        if frame.is_final && self.has_pending_frame_after(frame.sequence) {
404            return Err(Error::E2eFrameAfterFinal);
405        }
406
407        Ok(FrameAcceptance::New)
408    }
409
410    fn decrypt_ready_frames(&mut self) -> Result<Vec<u8>> {
411        let mut plaintext = Vec::new();
412
413        while let Some(frame) = self.pending_frames.get(&self.next_sequence) {
414            let frame_plaintext =
415                secp256k1::decrypt_bytes(&frame.ciphertext, self.recipient_secret_key)?;
416            let is_final = frame.is_final;
417            plaintext.extend_from_slice(&frame_plaintext);
418            self.pending_frames.remove(&self.next_sequence);
419
420            if is_final {
421                self.seen_final = true;
422                break;
423            }
424
425            self.next_sequence = next_sequence(self.next_sequence)?;
426        }
427
428        Ok(plaintext)
429    }
430
431    fn has_pending_frame_after(&self, sequence: u64) -> bool {
432        self.pending_frames
433            .last_key_value()
434            .is_some_and(|(pending_sequence, _)| *pending_sequence > sequence)
435    }
436
437    fn is_consumed_duplicate(&self, frame: &E2eStreamFrame) -> bool {
438        if frame.sequence < self.next_sequence {
439            return true;
440        }
441
442        self.seen_final
443            && self
444                .final_sequence
445                .is_some_and(|final_sequence| frame.sequence <= final_sequence)
446    }
447
448    fn exceeds_reorder_window(&self, sequence: u64) -> bool {
449        sequence.saturating_sub(self.next_sequence) > self.reorder_window
450    }
451}
452
453fn plaintext_stream_frames(
454    plaintext: &[u8],
455    max_plaintext_frame_len: usize,
456) -> E2ePlaintextFrames<'_> {
457    E2ePlaintextFrames::new(plaintext, max_plaintext_frame_len)
458}
459
460/// Encrypt a byte slice lazily into direct-ElGamal E2E stream frames.
461pub fn encrypt_stream_frames_with_rng<R: RngCore>(
462    plaintext: &[u8],
463    stream_id: E2eStreamId,
464    sender_public_key: PublicKey<33>,
465    recipient_public_key: PublicKey<33>,
466    max_plaintext_frame_len: usize,
467    rng: R,
468) -> Result<E2eStreamFrames<'_, R>> {
469    Ok(E2eStreamFrames {
470        plaintext_frames: plaintext_stream_frames(plaintext, max_plaintext_frame_len),
471        encryptor: E2eStreamEncryptor::new(stream_id, sender_public_key, recipient_public_key)?,
472        rng,
473    })
474}
475
476/// Encrypt a byte slice lazily with the default thread-local RNG.
477pub fn encrypt_stream_frames(
478    plaintext: &[u8],
479    stream_id: E2eStreamId,
480    sender_public_key: PublicKey<33>,
481    recipient_public_key: PublicKey<33>,
482    max_plaintext_frame_len: usize,
483) -> Result<E2eStreamFrames<'_, rand::rngs::ThreadRng>> {
484    encrypt_stream_frames_with_rng(
485        plaintext,
486        stream_id,
487        sender_public_key,
488        recipient_public_key,
489        max_plaintext_frame_len,
490        rand::thread_rng(),
491    )
492}
493
494/// Encrypt a byte slice into direct-ElGamal E2E stream frames.
495pub fn encrypt_stream_with_rng(
496    plaintext: &[u8],
497    stream_id: E2eStreamId,
498    sender_public_key: PublicKey<33>,
499    recipient_public_key: PublicKey<33>,
500    max_plaintext_frame_len: usize,
501    rng: &mut impl RngCore,
502) -> Result<Vec<E2eStreamFrame>> {
503    encrypt_stream_frames_with_rng(
504        plaintext,
505        stream_id,
506        sender_public_key,
507        recipient_public_key,
508        max_plaintext_frame_len,
509        rng,
510    )?
511    .collect()
512}
513
514/// Decrypt a complete direct-ElGamal E2E frame sequence into bytes.
515pub fn decrypt_stream(
516    frames: &[E2eStreamFrame],
517    stream_id: E2eStreamId,
518    expected_sender: Did,
519    recipient_secret_key: SecretKey,
520) -> Result<Vec<u8>> {
521    let mut decryptor = E2eStreamDecryptor::new(stream_id, expected_sender, recipient_secret_key);
522    let mut plaintext = Vec::new();
523
524    for frame in frames {
525        let frame_plaintext = decryptor.decrypt_next(frame)?;
526        plaintext.extend_from_slice(&frame_plaintext);
527    }
528
529    decryptor.finish()?;
530    Ok(plaintext)
531}
532
533/// Verify that a public key hashes to the expected DID.
534pub fn ensure_public_key_matches_did(public_key: PublicKey<33>, expected: Did) -> Result<()> {
535    let actual = Did::from(public_key.address());
536    if actual == expected {
537        Ok(())
538    } else {
539        Err(Error::E2ePublicKeyDidMismatch { expected, actual })
540    }
541}
542
543fn ensure_valid_public_key(public_key: PublicKey<33>) -> Result<()> {
544    let _: Point<Secp256k1> = public_key.try_into()?;
545    Ok(())
546}
547
548fn next_sequence(sequence: u64) -> Result<u64> {
549    sequence
550        .checked_add(1)
551        .ok_or(Error::E2eFrameSequenceOverflow)
552}
553
554#[cfg(test)]
555mod tests {
556    use rand::SeedableRng;
557    use rand_hc::Hc128Rng;
558
559    use super::*;
560
561    fn recipient_key() -> SecretKey {
562        SecretKey::try_from("65860affb4b570dba06db294aa7c676f68e04a5bf2721243ad3cbc05a79c68c0")
563            .unwrap()
564    }
565
566    fn sender_key() -> SecretKey {
567        SecretKey::try_from("1f9275dbafdfba81942eb3330b07f38cbee4ebb86bdc2174af9648d5f5509a54")
568            .unwrap()
569    }
570
571    #[test]
572    fn public_key_must_match_did() {
573        let sender = sender_key();
574        let recipient = recipient_key();
575
576        assert!(ensure_public_key_matches_did(sender.pubkey(), sender.address().into()).is_ok());
577        assert!(matches!(
578            ensure_public_key_matches_did(sender.pubkey(), recipient.address().into()),
579            Err(Error::E2ePublicKeyDidMismatch { .. })
580        ));
581    }
582
583    #[test]
584    fn handshake_messages_verify_signed_owner() {
585        let sender = sender_key();
586        let recipient = recipient_key();
587        let request = E2eHandshakeRequest::new(sender.pubkey());
588        let response = E2eHandshakeResponse::new(recipient.pubkey());
589
590        request.verify_requester(sender.address().into()).unwrap();
591        response
592            .verify_responder(recipient.address().into())
593            .unwrap();
594        assert!(request
595            .verify_requester(recipient.address().into())
596            .is_err());
597        assert!(response.verify_responder(sender.address().into()).is_err());
598    }
599
600    #[test]
601    fn round_trip_random_binary_payloads_and_frame_sizes() {
602        let sender = sender_key();
603        let recipient = recipient_key();
604        let mut rng = Hc128Rng::seed_from_u64(608);
605        let payload_lens = [0usize, 1, 15, 16, 17, 31, 32, 255, 1024, 4097];
606        let frame_limits = [1usize, E2E_PLAINTEXT_BLOCK_LEN, 64, 511];
607
608        for payload_len in payload_lens {
609            for frame_limit in frame_limits {
610                let mut payload = vec![0u8; payload_len];
611                rng.fill_bytes(&mut payload);
612                let stream_id = uuid::Uuid::new_v4();
613
614                let frames = encrypt_stream_with_rng(
615                    &payload,
616                    stream_id,
617                    sender.pubkey(),
618                    recipient.pubkey(),
619                    frame_limit,
620                    &mut rng,
621                )
622                .unwrap();
623                assert_eq!(
624                    frames.len(),
625                    payload_len.div_ceil(frame_limit.max(1)).max(1)
626                );
627                assert_eq!(
628                    decrypt_stream(&frames, stream_id, sender.address().into(), recipient).unwrap(),
629                    payload
630                );
631            }
632        }
633    }
634
635    #[test]
636    fn empty_plaintext_sends_final_stream_frame() {
637        let sender = sender_key();
638        let recipient = recipient_key();
639        let mut rng = Hc128Rng::seed_from_u64(7);
640        let stream_id = uuid::Uuid::new_v4();
641
642        let frames = encrypt_stream_with_rng(
643            &[],
644            stream_id,
645            sender.pubkey(),
646            recipient.pubkey(),
647            8,
648            &mut rng,
649        )
650        .unwrap();
651
652        assert_eq!(frames.len(), 1);
653        assert_eq!(frames[0].stream_id, stream_id);
654        assert_eq!(frames[0].sequence, 0);
655        assert!(frames[0].is_final);
656        assert_eq!(
657            decrypt_stream(&frames, stream_id, sender.address().into(), recipient).unwrap(),
658            Vec::<u8>::new()
659        );
660    }
661
662    #[test]
663    fn streaming_decryptor_accepts_ordered_final_frame() {
664        let sender = sender_key();
665        let recipient = recipient_key();
666        let stream_id = uuid::Uuid::new_v4();
667        let mut rng = Hc128Rng::seed_from_u64(42);
668        let mut encryptor =
669            E2eStreamEncryptor::new(stream_id, sender.pubkey(), recipient.pubkey()).unwrap();
670
671        let first = encryptor.encrypt_next(b"\0hello", &mut rng).unwrap();
672        let second = encryptor.encrypt_final(b"\xFFworld", &mut rng).unwrap();
673        let mut decryptor = E2eStreamDecryptor::new(stream_id, sender.address().into(), recipient);
674
675        let mut plaintext = decryptor.decrypt_next(&first).unwrap();
676        plaintext.extend_from_slice(&decryptor.decrypt_next(&second).unwrap());
677        decryptor.finish().unwrap();
678
679        assert_eq!(plaintext, b"\0hello\xFFworld");
680    }
681
682    #[test]
683    fn truncation_is_rejected_without_final_frame() {
684        let sender = sender_key();
685        let recipient = recipient_key();
686        let mut rng = Hc128Rng::seed_from_u64(9);
687        let stream_id = uuid::Uuid::new_v4();
688        let payload = vec![9u8; 96];
689        let mut frames = encrypt_stream_with_rng(
690            &payload,
691            stream_id,
692            sender.pubkey(),
693            recipient.pubkey(),
694            16,
695            &mut rng,
696        )
697        .unwrap();
698
699        frames.pop();
700
701        assert!(matches!(
702            decrypt_stream(&frames, stream_id, sender.address().into(), recipient),
703            Err(Error::E2eMissingFinalFrame)
704        ));
705    }
706
707    #[test]
708    fn reordered_frames_are_buffered_until_contiguous() {
709        let sender = sender_key();
710        let recipient = recipient_key();
711        let mut rng = Hc128Rng::seed_from_u64(10);
712        let stream_id = uuid::Uuid::new_v4();
713        let payload = vec![10u8; 96];
714        let mut frames = encrypt_stream_with_rng(
715            &payload,
716            stream_id,
717            sender.pubkey(),
718            recipient.pubkey(),
719            16,
720            &mut rng,
721        )
722        .unwrap();
723
724        frames.swap(0, 1);
725
726        assert_eq!(
727            decrypt_stream(&frames, stream_id, sender.address().into(), recipient).unwrap(),
728            payload
729        );
730    }
731
732    #[test]
733    fn replayed_consumed_frame_is_idempotent() {
734        let sender = sender_key();
735        let recipient = recipient_key();
736        let mut rng = Hc128Rng::seed_from_u64(16);
737        let stream_id = uuid::Uuid::new_v4();
738        let payload = vec![16u8; 96];
739        let frames = encrypt_stream_with_rng(
740            &payload,
741            stream_id,
742            sender.pubkey(),
743            recipient.pubkey(),
744            16,
745            &mut rng,
746        )
747        .unwrap();
748        let final_frame = frames.last().unwrap().clone();
749        let mut decryptor = E2eStreamDecryptor::new(stream_id, sender.address().into(), recipient);
750        let mut plaintext = Vec::new();
751
752        plaintext.extend_from_slice(&decryptor.decrypt_next(&frames[0]).unwrap());
753        assert_eq!(
754            decryptor.decrypt_next(&frames[0]).unwrap(),
755            Vec::<u8>::new()
756        );
757
758        for frame in &frames[1..] {
759            plaintext.extend_from_slice(&decryptor.decrypt_next(frame).unwrap());
760        }
761        decryptor.finish().unwrap();
762
763        assert_eq!(
764            decryptor.decrypt_next(&final_frame).unwrap(),
765            Vec::<u8>::new()
766        );
767        assert_eq!(plaintext, payload);
768    }
769
770    #[test]
771    fn replayed_buffered_frame_is_idempotent() {
772        let sender = sender_key();
773        let recipient = recipient_key();
774        let mut rng = Hc128Rng::seed_from_u64(17);
775        let stream_id = uuid::Uuid::new_v4();
776        let payload = vec![17u8; 96];
777        let frames = encrypt_stream_with_rng(
778            &payload,
779            stream_id,
780            sender.pubkey(),
781            recipient.pubkey(),
782            16,
783            &mut rng,
784        )
785        .unwrap();
786        let mut decryptor = E2eStreamDecryptor::new(stream_id, sender.address().into(), recipient);
787        let mut plaintext = Vec::new();
788
789        assert_eq!(
790            decryptor.decrypt_next(&frames[1]).unwrap(),
791            Vec::<u8>::new()
792        );
793        assert_eq!(
794            decryptor.decrypt_next(&frames[1]).unwrap(),
795            Vec::<u8>::new()
796        );
797
798        for frame in &frames {
799            plaintext.extend_from_slice(&decryptor.decrypt_next(frame).unwrap());
800        }
801        decryptor.finish().unwrap();
802
803        assert_eq!(plaintext, payload);
804    }
805
806    #[test]
807    fn future_frame_outside_reorder_window_is_rejected() {
808        let sender = sender_key();
809        let recipient = recipient_key();
810        let mut rng = Hc128Rng::seed_from_u64(18);
811        let stream_id = uuid::Uuid::new_v4();
812        let frames = encrypt_stream_with_rng(
813            &[18u8; 5],
814            stream_id,
815            sender.pubkey(),
816            recipient.pubkey(),
817            1,
818            &mut rng,
819        )
820        .unwrap();
821        let mut decryptor = E2eStreamDecryptor::with_reorder_window(
822            stream_id,
823            sender.address().into(),
824            recipient,
825            2,
826        );
827
828        assert!(matches!(
829            decryptor.decrypt_next(&frames[3]),
830            Err(Error::E2eFrameReorderWindowExceeded {
831                next_sequence: 0,
832                actual: 3,
833                window: 2
834            })
835        ));
836    }
837
838    #[test]
839    fn final_frame_can_arrive_before_gap_is_filled() {
840        let sender = sender_key();
841        let recipient = recipient_key();
842        let mut rng = Hc128Rng::seed_from_u64(14);
843        let stream_id = uuid::Uuid::new_v4();
844        let payload = vec![14u8; 96];
845        let mut frames = encrypt_stream_with_rng(
846            &payload,
847            stream_id,
848            sender.pubkey(),
849            recipient.pubkey(),
850            16,
851            &mut rng,
852        )
853        .unwrap();
854        frames.rotate_right(1);
855
856        let mut decryptor = E2eStreamDecryptor::new(stream_id, sender.address().into(), recipient);
857        let mut plaintext = Vec::new();
858
859        assert_eq!(
860            decryptor.decrypt_next(&frames[0]).unwrap(),
861            Vec::<u8>::new()
862        );
863        assert!(matches!(
864            decryptor.finish(),
865            Err(Error::E2eMissingFinalFrame)
866        ));
867
868        for frame in &frames[1..] {
869            plaintext.extend_from_slice(&decryptor.decrypt_next(frame).unwrap());
870        }
871        decryptor.finish().unwrap();
872
873        assert_eq!(plaintext, payload);
874    }
875
876    #[test]
877    fn frame_after_buffered_final_is_rejected() {
878        let sender = sender_key();
879        let recipient = recipient_key();
880        let mut rng = Hc128Rng::seed_from_u64(15);
881        let stream_id = uuid::Uuid::new_v4();
882        let frames = encrypt_stream_with_rng(
883            &[15u8; 96],
884            stream_id,
885            sender.pubkey(),
886            recipient.pubkey(),
887            16,
888            &mut rng,
889        )
890        .unwrap();
891        let final_frame = frames.last().unwrap();
892        let mut frame_after_final = frames.first().unwrap().clone();
893        frame_after_final.sequence = final_frame.sequence.checked_add(1).unwrap();
894
895        let mut decryptor = E2eStreamDecryptor::new(stream_id, sender.address().into(), recipient);
896        assert_eq!(
897            decryptor.decrypt_next(final_frame).unwrap(),
898            Vec::<u8>::new()
899        );
900        assert!(matches!(
901            decryptor.decrypt_next(&frame_after_final),
902            Err(Error::E2eFrameAfterFinal)
903        ));
904    }
905
906    #[test]
907    fn wrong_stream_id_is_rejected() {
908        let sender = sender_key();
909        let recipient = recipient_key();
910        let mut rng = Hc128Rng::seed_from_u64(12);
911        let stream_id = uuid::Uuid::new_v4();
912        let other_stream_id = uuid::Uuid::new_v4();
913        let frames = encrypt_stream_with_rng(
914            b"hello",
915            stream_id,
916            sender.pubkey(),
917            recipient.pubkey(),
918            16,
919            &mut rng,
920        )
921        .unwrap();
922
923        assert!(matches!(
924            decrypt_stream(&frames, other_stream_id, sender.address().into(), recipient),
925            Err(Error::E2eStreamIdMismatch { .. })
926        ));
927    }
928
929    #[test]
930    fn wrong_sender_key_is_rejected() {
931        let sender = sender_key();
932        let recipient = recipient_key();
933        let mut rng = Hc128Rng::seed_from_u64(11);
934        let stream_id = uuid::Uuid::new_v4();
935        let payload = vec![11u8; 32];
936        let mut frames = encrypt_stream_with_rng(
937            &payload,
938            stream_id,
939            sender.pubkey(),
940            recipient.pubkey(),
941            32,
942            &mut rng,
943        )
944        .unwrap();
945        frames[0].sender_public_key = recipient.pubkey();
946
947        assert!(matches!(
948            decrypt_stream(&frames, stream_id, sender.address().into(), recipient),
949            Err(Error::E2ePublicKeyDidMismatch { .. })
950        ));
951    }
952}