spqr_syft/
lib.rs

1// Copyright 2025 Signal Messenger, LLC
2// SPDX-License-Identifier: AGPL-3.0-only
3
4pub mod authenticator;
5pub mod chain;
6pub mod encoding;
7pub(crate) mod incremental_mlkem768;
8pub(crate) mod kdf;
9pub mod proto;
10pub mod serialize;
11pub(crate) mod test;
12pub(crate) mod util;
13mod v1;
14
15use crate::chain::Chain;
16pub use crate::chain::ChainParams;
17use crate::proto::pq_ratchet as pqrpb;
18pub use crate::proto::pq_ratchet::{Direction, Version};
19use prost::Message;
20use rand::{CryptoRng, Rng};
21use std::cmp::Ordering;
22use v1::chunked::states as v1states;
23
24pub type Epoch = u64;
25pub type Secret = Vec<u8>;
26pub type MessageKey = Option<Vec<u8>>;
27pub type SerializedState = Vec<u8>;
28pub type SerializedMessage = Vec<u8>;
29
30pub fn empty_state() -> SerializedState {
31    SerializedState::new()
32}
33
34pub struct EpochSecret {
35    pub epoch: Epoch,
36    pub secret: Secret,
37}
38
39pub struct Params<'a> {
40    pub direction: Direction,
41    pub version: Version,
42    pub min_version: Version,
43    pub auth_key: &'a [u8],
44    pub chain_params: ChainParams,
45}
46
47impl Direction {
48    pub fn switch(&self) -> Self {
49        match self {
50            Direction::A2B => Direction::B2A,
51            Direction::B2A => Direction::A2B,
52        }
53    }
54}
55
56#[derive(PartialEq, Debug)]
57pub enum SecretOutput {
58    /// Receipt of the message has resulted in no additional shared secrets
59    /// to mix in.
60    None,
61    /// Receipt of the message has resulted in a shared secret which should
62    /// be mixed into the sending chain before using it to encrypt/send the
63    /// next message sent by this client.
64    Send(Secret),
65    /// Receipt of the message has resulted in a shared secret which will be
66    /// used to encrypt the next message we receive, and thus should be mixed
67    /// into our new receiving chain.
68    Recv(Secret),
69}
70
71#[derive(Debug)]
72pub enum CurrentVersion {
73    StillNegotiating {
74        version: Version,
75        min_version: Version,
76    },
77    NegotiationComplete(Version),
78}
79
80#[derive(Debug, thiserror::Error)]
81pub enum Error {
82    #[error("state decode failed")]
83    StateDecode,
84    #[error("not yet implemented")]
85    NotImplemented,
86    #[error("message decode failed")]
87    MsgDecode,
88    #[error("MAC verification failed")]
89    MacVerifyFailed,
90    #[error("epoch not in valid range: {0}")]
91    EpochOutOfRange(Epoch),
92    #[error("Encoding error: {0}")]
93    EncodingDecoding(encoding::EncodingError),
94    #[error("Serialization: {0}")]
95    Serialization(serialize::Error),
96    #[error("Version mismatch after negotiation")]
97    VersionMismatch,
98    #[error("Minimum version")]
99    MinimumVersion,
100    #[error("Key jump: {0} - {1}")]
101    KeyJump(u32, u32),
102    #[error("Key trimmed: {0}")]
103    KeyTrimmed(u32),
104    #[error("Key already requested: {0}")]
105    KeyAlreadyRequested(u32),
106    #[error("Erroneous data received from remote party")]
107    ErroneousDataReceived,
108    #[error("Send key epoch decreased ({0} -> {1})")]
109    SendKeyEpochDecreased(u64, u64),
110    #[error("Invalid params: {0}")]
111    InvalidParams(&'static str),
112    #[error("Chain not available")]
113    ChainNotAvailable,
114}
115
116impl From<encoding::EncodingError> for Error {
117    fn from(e: encoding::EncodingError) -> Error {
118        Error::EncodingDecoding(e)
119    }
120}
121
122impl From<serialize::Error> for Error {
123    fn from(v: serialize::Error) -> Self {
124        Error::Serialization(v)
125    }
126}
127
128impl From<authenticator::Error> for Error {
129    fn from(_v: authenticator::Error) -> Self {
130        Error::MacVerifyFailed
131    }
132}
133
134impl SecretOutput {
135    pub fn send_secret(&self) -> Option<&Secret> {
136        match self {
137            SecretOutput::Send(s) => Some(s),
138            SecretOutput::Recv(_) => None,
139            SecretOutput::None => None,
140        }
141    }
142    pub fn recv_secret(&self) -> Option<&Secret> {
143        match self {
144            SecretOutput::Send(_) => None,
145            SecretOutput::Recv(s) => Some(s),
146            SecretOutput::None => None,
147        }
148    }
149
150    pub fn secret(&self) -> Option<&Secret> {
151        match self {
152            SecretOutput::Send(s) | SecretOutput::Recv(s) => Some(s),
153            _ => None,
154        }
155    }
156    pub fn has_secret(&self) -> bool {
157        !matches!(self, Self::None)
158    }
159}
160
161#[hax_lib::opaque]
162impl TryFrom<u8> for Version {
163    type Error = String;
164    fn try_from(value: u8) -> Result<Self, Self::Error> {
165        match value {
166            0 => Ok(Version::V0),
167            1 => Ok(Version::V1),
168            _ => Err("Expected 0 or 1".to_owned()),
169        }
170    }
171}
172
173impl From<Version> for u8 {
174    fn from(v: Version) -> u8 {
175        match v {
176            Version::V0 => 0,
177            Version::V1 => 1,
178        }
179    }
180}
181
182fn init_inner(v: Version, d: Direction, auth_key: &[u8]) -> Option<pqrpb::pq_ratchet_state::Inner> {
183    match v {
184        Version::V0 => None,
185        Version::V1 => match d {
186            Direction::A2B => Some(pqrpb::pq_ratchet_state::Inner::V1(
187                v1states::States::init_a(auth_key).into_pb(),
188            )),
189            Direction::B2A => Some(pqrpb::pq_ratchet_state::Inner::V1(
190                v1states::States::init_b(auth_key).into_pb(),
191            )),
192        },
193    }
194}
195
196pub fn initial_state(params: Params) -> Result<SerializedState, Error> {
197    hax_lib::fstar!("admit()");
198    log::info!(
199        "spqr initiating state with version {:?} and direction {:?}",
200        params.version,
201        params.direction
202    );
203    match params.version {
204        Version::V0 => Ok(empty_state()),
205        _ => {
206            let version_negotiation = Some(pqrpb::pq_ratchet_state::VersionNegotiation {
207                auth_key: params.auth_key.to_vec(),
208                direction: params.direction.into(),
209                min_version: params.min_version.into(),
210                chain_params: Some(params.chain_params.into_pb()),
211            });
212            Ok(pqrpb::PqRatchetState {
213                inner: init_inner(params.version, params.direction, params.auth_key),
214                chain: None,
215                version_negotiation,
216            }
217            .encode_to_vec())
218        }
219    }
220}
221
222impl Version {
223    pub const DISABLED: Version = Self::V0;
224    pub const MAX: Version = Self::V1;
225}
226
227pub struct Send {
228    pub state: SerializedState,
229    pub msg: SerializedMessage,
230    pub key: MessageKey,
231}
232
233pub fn current_version(state: &SerializedState) -> Result<CurrentVersion, Error> {
234    let state_pb = decode_state(state)?;
235    let version = match state_pb.inner {
236        None => Version::V0,
237        Some(pqrpb::pq_ratchet_state::Inner::V1(_)) => Version::V1,
238    };
239    Ok(match state_pb.version_negotiation {
240        None => CurrentVersion::NegotiationComplete(version),
241        Some(vn) => CurrentVersion::StillNegotiating {
242            version,
243            min_version: vn.min_version.try_into().map_err(|_| Error::StateDecode)?,
244        },
245    })
246}
247
248#[hax_lib::fstar::verification_status(lax)]
249pub fn send<R: Rng + CryptoRng>(state: &SerializedState, rng: &mut R) -> Result<Send, Error> {
250    let state_pb = decode_state(state)?;
251    match state_pb.inner {
252        None => Ok(Send {
253            state: vec![],
254            msg: vec![],
255            key: None,
256        }),
257        Some(pqrpb::pq_ratchet_state::Inner::V1(pb)) => {
258            let v1states::Send { msg, key, state } = v1states::States::from_pb(pb)?.send(rng)?;
259            let chain = match state_pb.chain {
260                None => match state_pb.version_negotiation.as_ref() {
261                    Some(vn) => {
262                        if vn.min_version > Version::V0 as i32 {
263                            Some(chain_from_version_negotiation(vn)?)
264                        } else {
265                            None
266                        }
267                    }
268                    None => {
269                        return Err(Error::ChainNotAvailable);
270                    }
271                },
272                Some(pb) => Some(Chain::from_pb(pb)?),
273            };
274            let (index, msg_key, chain_pb) = match chain {
275                None => {
276                    assert!(key.is_none());
277                    (0, vec![], None)
278                }
279                Some(mut chain) => {
280                    if let Some(epoch_secret) = key {
281                        chain.add_epoch(epoch_secret);
282                    }
283                    let (index, msg_key) = chain.send_key(msg.epoch - 1)?;
284                    (index, msg_key, Some(chain.into_pb()))
285                }
286            };
287
288            let msg = msg.serialize(index);
289            assert!(!msg.is_empty());
290            assert_eq!(msg[0], Version::V1.into());
291            Ok(Send {
292                state: pqrpb::PqRatchetState {
293                    inner: Some(pqrpb::pq_ratchet_state::Inner::V1(state.into_pb())),
294                    // Sending never changes our version negotiation.
295                    version_negotiation: state_pb.version_negotiation,
296                    chain: chain_pb,
297                }
298                .encode_to_vec(),
299                msg,
300                // hax does not like `filter`
301                key: if msg_key.is_empty() {
302                    None
303                } else {
304                    Some(msg_key)
305                },
306            })
307        }
308    }
309}
310
311pub struct Recv {
312    pub state: SerializedState,
313    pub key: MessageKey,
314}
315
316fn chain_from_version_negotiation(
317    vn: &pqrpb::pq_ratchet_state::VersionNegotiation,
318) -> Result<Chain, Error> {
319    Chain::new(
320        &vn.auth_key,
321        vn.direction.try_into().map_err(|_| Error::StateDecode)?,
322        vn.chain_params.ok_or(Error::ChainNotAvailable)?,
323    )
324}
325
326fn chain_from(
327    pb: Option<pqrpb::Chain>,
328    vn: Option<&pqrpb::pq_ratchet_state::VersionNegotiation>,
329) -> Result<Chain, Error> {
330    match pb {
331        Some(pb) => Ok(Chain::from_pb(pb)?),
332        None => match vn {
333            None => Err(Error::ChainNotAvailable),
334            Some(vn) => chain_from_version_negotiation(vn),
335        },
336    }
337}
338
339#[hax_lib::fstar::verification_status(lax)]
340pub fn recv(state: &SerializedState, msg: &SerializedMessage) -> Result<Recv, Error> {
341    // Perform version negotiation.  At the beginning of our interaction
342    // with a remote party, we are set to allow negotiation.  This
343    // allows either side to downgrade the connection to a protocol version
344    // that that side supports, while still using the highest protocol
345    // version supported by both sides.
346    let prenegotiated_state_pb = decode_state(state)?;
347    let state_pb = match msg_version(msg) {
348        None => {
349            // They have presented a version we don't support; it's too high for us,
350            // so ignore it and keep sending our current version's format.
351            return Ok(Recv {
352                state: state.to_vec(),
353                key: None,
354            });
355        }
356        Some(v) => match (v as u8).cmp(&(state_version(&prenegotiated_state_pb) as u8)) {
357            Ordering::Equal | Ordering::Greater => {
358                // Our versions are equal; proceed with existing state
359                prenegotiated_state_pb
360            }
361            Ordering::Less => {
362                // Their version is less than ours.  If we are allowed to negotiate, we
363                // should.  Otherwise, we should error out.
364                //
365                // When negotiating down a level, we disallow future negotiation.
366                match prenegotiated_state_pb.version_negotiation {
367                    None => {
368                        return Err(Error::VersionMismatch);
369                    }
370                    Some(ref vn) => {
371                        if (v as i32) < vn.min_version {
372                            return Err(Error::MinimumVersion);
373                        }
374                        log::info!("spqr negotiating version down to {v:?}");
375                        pqrpb::PqRatchetState {
376                            inner: init_inner(
377                                v,
378                                vn.direction.try_into().map_err(|_| Error::StateDecode)?,
379                                &vn.auth_key,
380                            ),
381                            // This is our negotiation; we disallow any further.
382                            version_negotiation: None,
383                            chain: Some(
384                                chain_from(
385                                    prenegotiated_state_pb.chain,
386                                    prenegotiated_state_pb.version_negotiation.as_ref(),
387                                )?
388                                .into_pb(),
389                            ),
390                        }
391                    }
392                }
393            }
394        },
395    };
396
397    // At this point, we have finished version negotiation and have made sure
398    // that our state version matches.  Proceed with receiving and processing
399    // the associated message.
400    match state_pb.inner {
401        None => Ok(Recv {
402            state: vec![],
403            key: None,
404        }),
405        Some(pqrpb::pq_ratchet_state::Inner::V1(pb)) => {
406            let (scka_msg, index, _) = v1states::Message::deserialize(msg)?;
407
408            let v1states::Recv { key, state } = v1states::States::from_pb(pb)?.recv(&scka_msg)?;
409            let msg_key_epoch = scka_msg.epoch - 1;
410            let mut chain = chain_from(state_pb.chain, state_pb.version_negotiation.as_ref())?;
411            if let Some(epoch_secret) = key {
412                chain.add_epoch(epoch_secret);
413            }
414            let msg_key = if msg_key_epoch == 0 && index == 0 {
415                vec![]
416            } else {
417                chain.recv_key(msg_key_epoch, index)?
418            };
419
420            Ok(Recv {
421                state: pqrpb::PqRatchetState {
422                    inner: Some(pqrpb::pq_ratchet_state::Inner::V1(state.into_pb())),
423                    // Receiving clears our version negotiation.
424                    version_negotiation: None,
425                    chain: Some(chain.into_pb()),
426                }
427                .encode_to_vec(),
428                // hax does not like `filter`
429                key: if msg_key.is_empty() {
430                    None
431                } else {
432                    Some(msg_key)
433                },
434            })
435        }
436    }
437}
438
439fn state_version(state: &pqrpb::PqRatchetState) -> Version {
440    match state.inner {
441        None => Version::V0,
442        Some(proto::pq_ratchet::pq_ratchet_state::Inner::V1(_)) => Version::V1,
443    }
444}
445
446#[hax_lib::fstar::verification_status(lax)]
447fn msg_version(msg: &SerializedMessage) -> Option<Version> {
448    if msg.is_empty() {
449        Some(Version::V0)
450    } else {
451        msg[0].try_into().ok()
452    }
453}
454
455#[hax_lib::fstar::verification_status(lax)]
456fn decode_state(s: &SerializedState) -> Result<pqrpb::PqRatchetState, Error> {
457    if s.is_empty() {
458        Ok(proto::pq_ratchet::PqRatchetState {
459            inner: None,
460            version_negotiation: None,
461            chain: None,
462        })
463    } else {
464        proto::pq_ratchet::PqRatchetState::decode(s.as_slice()).map_err(|_| Error::StateDecode)
465    }
466}
467
468#[cfg(test)]
469mod lib_test {
470    use rand::Rng;
471    use rand::TryRngCore;
472    use rand_core::OsRng;
473
474    use super::*;
475
476    #[test]
477    fn ratchet() -> Result<(), Error> {
478        let mut rng = OsRng.unwrap_err();
479
480        let version = Version::V1;
481
482        let alex_pq_state = initial_state(Params {
483            version,
484            min_version: version,
485            direction: Direction::A2B,
486            auth_key: &[41u8; 32],
487            chain_params: ChainParams::default(),
488        })?;
489        let blake_pq_state = initial_state(Params {
490            version,
491            min_version: version,
492            direction: Direction::B2A,
493            auth_key: &[41u8; 32],
494            chain_params: ChainParams::default(),
495        })?;
496
497        // Now let's send some messages
498        let Send {
499            state: alex_pq_state,
500            msg,
501            key: alex_key,
502        } = send(&alex_pq_state, &mut rng)?;
503
504        let Recv {
505            state: blake_pq_state,
506            key: blake_key,
507        } = recv(&blake_pq_state, &msg)?;
508
509        assert_eq!(alex_key, blake_key);
510
511        let Send {
512            state: mut blake_pq_state,
513            msg,
514            key: blake_key,
515        } = send(&blake_pq_state, &mut rng)?;
516
517        let Recv {
518            state: mut alex_pq_state,
519            key: alex_key,
520        } = recv(&alex_pq_state, &msg)?;
521
522        assert_eq!(alex_key, blake_key);
523
524        // now let's mix it up a little
525        for _ in 0..1000 {
526            let a_send = rng.random_bool(0.5);
527            let b_send = rng.random_bool(0.5);
528            let a_recv = rng.random_bool(0.7);
529            let b_recv = rng.random_bool(0.7);
530
531            if a_send {
532                let Send {
533                    state,
534                    msg,
535                    key: alex_key,
536                } = send(&alex_pq_state, &mut rng)?;
537                alex_pq_state = state;
538                if b_recv {
539                    let Recv {
540                        state,
541                        key: blake_key,
542                    } = recv(&blake_pq_state, &msg)?;
543                    blake_pq_state = state;
544
545                    assert_eq!(alex_key, blake_key);
546                }
547            }
548
549            if b_send {
550                let Send {
551                    state,
552                    msg,
553                    key: blake_key,
554                } = send(&blake_pq_state, &mut rng)?;
555                blake_pq_state = state;
556                if a_recv {
557                    let Recv {
558                        state,
559                        key: alex_key,
560                    } = recv(&alex_pq_state, &msg)?;
561                    alex_pq_state = state;
562
563                    assert_eq!(alex_key, blake_key);
564                }
565            }
566        }
567
568        Ok(())
569    }
570
571    #[test]
572    fn ratchet_v0_empty_states() -> Result<(), Error> {
573        let mut rng = OsRng.unwrap_err();
574
575        // SPQR should treat empty states as V0.
576
577        let alex_pq_state = SerializedState::new();
578        let blake_pq_state = SerializedState::new();
579
580        // Now let's send some messages
581        let Send {
582            state: alex_pq_state,
583            msg,
584            key: alex_key,
585        } = send(&alex_pq_state, &mut rng)?;
586
587        let Recv {
588            state: blake_pq_state,
589            key: blake_key,
590        } = recv(&blake_pq_state, &msg)?;
591
592        assert_eq!(alex_key, blake_key);
593
594        let Send {
595            state: mut blake_pq_state,
596            msg,
597            key: blake_key,
598        } = send(&blake_pq_state, &mut rng)?;
599
600        let Recv {
601            state: mut alex_pq_state,
602            key: alex_key,
603        } = recv(&alex_pq_state, &msg)?;
604
605        assert_eq!(alex_key, blake_key);
606
607        // now let's mix it up a little
608        for _ in 0..1000 {
609            let a_send = rng.random_bool(0.5);
610            let b_send = rng.random_bool(0.5);
611            let a_recv = rng.random_bool(0.7);
612            let b_recv = rng.random_bool(0.7);
613
614            if a_send {
615                let Send {
616                    state,
617                    msg,
618                    key: alex_key,
619                } = send(&alex_pq_state, &mut rng)?;
620                alex_pq_state = state;
621                if b_recv {
622                    let Recv {
623                        state,
624                        key: blake_key,
625                    } = recv(&blake_pq_state, &msg)?;
626                    blake_pq_state = state;
627
628                    assert_eq!(alex_key, blake_key);
629                }
630            }
631
632            if b_send {
633                let Send {
634                    state,
635                    msg,
636                    key: blake_key,
637                } = send(&blake_pq_state, &mut rng)?;
638                blake_pq_state = state;
639                if a_recv {
640                    let Recv {
641                        state,
642                        key: alex_key,
643                    } = recv(&alex_pq_state, &msg)?;
644                    alex_pq_state = state;
645
646                    assert_eq!(alex_key, blake_key);
647                }
648            }
649        }
650
651        Ok(())
652    }
653
654    #[test]
655    fn empty_constructor_for_state() {
656        let v = empty_state();
657        assert!(v.is_empty());
658    }
659
660    #[test]
661    fn empty_key_until_version_negotiation() -> Result<(), Error> {
662        let mut rng = OsRng.unwrap_err();
663
664        let version = Version::V1;
665
666        let alex_pq_state = initial_state(Params {
667            version,
668            min_version: Version::V0,
669            direction: Direction::A2B,
670            auth_key: &[41u8; 32],
671            chain_params: ChainParams::default(),
672        })?;
673        let blake_pq_state = initial_state(Params {
674            version,
675            min_version: Version::V0,
676            direction: Direction::B2A,
677            auth_key: &[41u8; 32],
678            chain_params: ChainParams::default(),
679        })?;
680
681        // Now let's send some messages
682        let Send {
683            state: alex_pq_state,
684            msg: msg_a1,
685            key: key_a1,
686        } = send(&alex_pq_state, &mut rng)?;
687        let Send {
688            state: alex_pq_state,
689            msg: msg_a2,
690            key: key_a2,
691        } = send(&alex_pq_state, &mut rng)?;
692        let Send {
693            state: alex_pq_state,
694            msg: msg_a3,
695            key: key_a3,
696        } = send(&alex_pq_state, &mut rng)?;
697
698        let Send {
699            state: blake_pq_state,
700            msg: msg_b1,
701            key: key_b1,
702        } = send(&blake_pq_state, &mut rng)?;
703        let Send {
704            state: blake_pq_state,
705            msg: msg_b2,
706            key: key_b2,
707        } = send(&blake_pq_state, &mut rng)?;
708        let Send {
709            state: blake_pq_state,
710            msg: msg_b3,
711            key: key_b3,
712        } = send(&blake_pq_state, &mut rng)?;
713
714        assert_eq!(key_a1, None);
715        assert_eq!(key_a2, None);
716        assert_eq!(key_a3, None);
717        assert_eq!(key_b1, None);
718        assert_eq!(key_b2, None);
719        assert_eq!(key_b3, None);
720
721        let Recv {
722            state: alex_pq_state,
723            key: key_b2,
724        } = recv(&alex_pq_state, &msg_b2)?;
725        assert_eq!(key_b2, None);
726        // After our first Recv, keys are now non-empty.
727        let Send {
728            state: alex_pq_state,
729            msg: msg_a4,
730            key: key_a4,
731        } = send(&alex_pq_state, &mut rng)?;
732        assert!(key_a4.is_some());
733        let Send {
734            state: mut alex_pq_state,
735            msg: msg_a5,
736            key: key_a5,
737        } = send(&alex_pq_state, &mut rng)?;
738        assert!(key_a5.is_some());
739
740        let Recv {
741            state: blake_pq_state,
742            key: key_a1,
743        } = recv(&blake_pq_state, &msg_a1)?;
744        assert_eq!(key_a1, None);
745        // After our first Recv, keys are now non-empty.
746        let Send {
747            state: blake_pq_state,
748            msg: msg_b4,
749            key: key_b4,
750        } = send(&blake_pq_state, &mut rng)?;
751        assert!(key_b4.is_some());
752        let Send {
753            state: mut blake_pq_state,
754            msg: msg_b5,
755            key: key_b5,
756        } = send(&blake_pq_state, &mut rng)?;
757        assert!(key_b5.is_some());
758
759        for (msg, want_key) in [
760            (msg_a3, key_a3),
761            (msg_a4, key_a4),
762            (msg_a2, key_a2),
763            (msg_a5, key_a5),
764        ] {
765            let Recv { state, key } = recv(&blake_pq_state, &msg)?;
766            assert_eq!(want_key, key);
767            blake_pq_state = state;
768        }
769
770        for (msg, want_key) in [
771            (msg_b1, key_b1),
772            (msg_b3, key_b3),
773            (msg_b4, key_b4),
774            (msg_b5, key_b5),
775        ] {
776            let Recv { state, key } = recv(&alex_pq_state, &msg)?;
777            assert_eq!(want_key, key);
778            alex_pq_state = state;
779        }
780
781        Ok(())
782    }
783
784    #[test]
785    fn min_version_v1_always_creates_keys_a2b() -> Result<(), Error> {
786        let mut rng = OsRng.unwrap_err();
787
788        let alex_pq_state = initial_state(Params {
789            version: Version::MAX,
790            min_version: Version::V1,
791            direction: Direction::A2B,
792            auth_key: &[41u8; 32],
793            chain_params: ChainParams::default(),
794        })?;
795        let blake_pq_state = initial_state(Params {
796            version: Version::MAX,
797            min_version: Version::V0,
798            direction: Direction::B2A,
799            auth_key: &[41u8; 32],
800            chain_params: ChainParams::default(),
801        })?;
802        let Send {
803            msg: msg_a1,
804            key: key_a1,
805            ..
806        } = send(&alex_pq_state, &mut rng)?;
807        assert!(key_a1.is_some());
808        let Send {
809            state: blake_pq_state,
810            key: key_b1,
811            ..
812        } = send(&blake_pq_state, &mut rng)?;
813        assert!(key_b1.is_none());
814        let Recv {
815            state: blake_pq_state,
816            ..
817        } = recv(&blake_pq_state, &msg_a1)?;
818        // After our first Recv, keys are now non-empty.
819        let Send { key: key_b2, .. } = send(&blake_pq_state, &mut rng)?;
820        assert!(key_b2.is_some());
821        Ok(())
822    }
823
824    #[test]
825    fn min_version_v1_always_creates_keys_b2a() -> Result<(), Error> {
826        let mut rng = OsRng.unwrap_err();
827
828        let alex_pq_state = initial_state(Params {
829            version: Version::MAX,
830            min_version: Version::V0,
831            direction: Direction::A2B,
832            auth_key: &[41u8; 32],
833            chain_params: ChainParams::default(),
834        })?;
835        let blake_pq_state = initial_state(Params {
836            version: Version::MAX,
837            min_version: Version::V1,
838            direction: Direction::B2A,
839            auth_key: &[41u8; 32],
840            chain_params: ChainParams::default(),
841        })?;
842        let Send {
843            msg: msg_b1,
844            key: key_b1,
845            ..
846        } = send(&blake_pq_state, &mut rng)?;
847        assert!(key_b1.is_some());
848        let Send {
849            state: alex_pq_state,
850            key: key_a1,
851            ..
852        } = send(&alex_pq_state, &mut rng)?;
853        assert!(key_a1.is_none());
854        let Recv {
855            state: alex_pq_state,
856            ..
857        } = recv(&alex_pq_state, &msg_b1)?;
858        // After our first Recv, keys are now non-empty.
859        let Send { key: key_a2, .. } = send(&alex_pq_state, &mut rng)?;
860        assert!(key_a2.is_some());
861        Ok(())
862    }
863
864    #[test]
865    fn negotiate_to_v0_a2b() -> Result<(), Error> {
866        let mut rng = OsRng.unwrap_err();
867
868        let alex_pq_state = initial_state(Params {
869            version: Version::MAX,
870            min_version: Version::V0,
871            direction: Direction::A2B,
872            auth_key: &[41u8; 32],
873            chain_params: ChainParams::default(),
874        })?;
875        let blake_pq_state = initial_state(Params {
876            version: Version::V0,
877            min_version: Version::V0,
878            direction: Direction::B2A,
879            auth_key: &[41u8; 32],
880            chain_params: ChainParams::default(),
881        })?;
882        assert!(matches!(
883            current_version(&alex_pq_state)?,
884            CurrentVersion::StillNegotiating {
885                version: Version::MAX,
886                min_version: Version::V0
887            },
888        ));
889        assert!(matches!(
890            current_version(&blake_pq_state)?,
891            CurrentVersion::NegotiationComplete(Version::V0),
892        ));
893        let Send {
894            msg: msg_a1,
895            state: alex_pq_state,
896            ..
897        } = send(&alex_pq_state, &mut rng)?;
898        let Recv {
899            state: blake_pq_state,
900            ..
901        } = recv(&blake_pq_state, &msg_a1)?;
902        let Send { msg: msg_b1, .. } = send(&blake_pq_state, &mut rng)?;
903        let Recv {
904            state: alex_pq_state,
905            ..
906        } = recv(&alex_pq_state, &msg_b1)?;
907        assert!(matches!(
908            current_version(&alex_pq_state)?,
909            CurrentVersion::NegotiationComplete(Version::V0),
910        ));
911        assert!(matches!(
912            current_version(&alex_pq_state)?,
913            CurrentVersion::NegotiationComplete(Version::V0),
914        ));
915        Ok(())
916    }
917
918    #[test]
919    fn negotiate_to_v0_b2a() -> Result<(), Error> {
920        let mut rng = OsRng.unwrap_err();
921
922        let alex_pq_state = initial_state(Params {
923            version: Version::V0,
924            min_version: Version::V0,
925            direction: Direction::A2B,
926            auth_key: &[41u8; 32],
927            chain_params: ChainParams::default(),
928        })?;
929        let blake_pq_state = initial_state(Params {
930            version: Version::MAX,
931            min_version: Version::V0,
932            direction: Direction::B2A,
933            auth_key: &[41u8; 32],
934            chain_params: ChainParams::default(),
935        })?;
936        assert!(matches!(
937            current_version(&alex_pq_state)?,
938            CurrentVersion::NegotiationComplete(Version::V0),
939        ));
940        assert!(matches!(
941            current_version(&blake_pq_state)?,
942            CurrentVersion::StillNegotiating {
943                version: Version::MAX,
944                min_version: Version::V0
945            },
946        ));
947        let Send {
948            msg: msg_a1,
949            state: alex_pq_state,
950            ..
951        } = send(&alex_pq_state, &mut rng)?;
952        let Recv {
953            state: blake_pq_state,
954            ..
955        } = recv(&blake_pq_state, &msg_a1)?;
956        let Send { msg: msg_b1, .. } = send(&blake_pq_state, &mut rng)?;
957        let Recv {
958            state: alex_pq_state,
959            ..
960        } = recv(&alex_pq_state, &msg_b1)?;
961        assert!(matches!(
962            current_version(&alex_pq_state)?,
963            CurrentVersion::NegotiationComplete(Version::V0),
964        ));
965        assert!(matches!(
966            current_version(&alex_pq_state)?,
967            CurrentVersion::NegotiationComplete(Version::V0),
968        ));
969        Ok(())
970    }
971
972    #[test]
973    fn negotiation_refused_a2b() -> Result<(), Error> {
974        let mut rng = OsRng.unwrap_err();
975
976        let alex_pq_state = initial_state(Params {
977            version: Version::MAX,
978            min_version: Version::V1,
979            direction: Direction::A2B,
980            auth_key: &[41u8; 32],
981            chain_params: ChainParams::default(),
982        })?;
983        let blake_pq_state = initial_state(Params {
984            version: Version::V0,
985            min_version: Version::V0,
986            direction: Direction::B2A,
987            auth_key: &[41u8; 32],
988            chain_params: ChainParams::default(),
989        })?;
990        assert!(matches!(
991            current_version(&alex_pq_state)?,
992            CurrentVersion::StillNegotiating {
993                version: Version::MAX,
994                min_version: Version::V1
995            },
996        ));
997        assert!(matches!(
998            current_version(&blake_pq_state)?,
999            CurrentVersion::NegotiationComplete(Version::V0),
1000        ));
1001        let Send {
1002            msg: msg_a1,
1003            state: alex_pq_state,
1004            ..
1005        } = send(&alex_pq_state, &mut rng)?;
1006        let Recv {
1007            state: blake_pq_state,
1008            ..
1009        } = recv(&blake_pq_state, &msg_a1)?;
1010        let Send { msg: msg_b1, .. } = send(&blake_pq_state, &mut rng)?;
1011        assert!(matches!(
1012            recv(&alex_pq_state, &msg_b1),
1013            Err(Error::MinimumVersion),
1014        ));
1015        Ok(())
1016    }
1017
1018    #[test]
1019    fn negotiation_refused_b2a() -> Result<(), Error> {
1020        let mut rng = OsRng.unwrap_err();
1021
1022        let alex_pq_state = initial_state(Params {
1023            version: Version::V0,
1024            min_version: Version::V0,
1025            direction: Direction::A2B,
1026            auth_key: &[41u8; 32],
1027            chain_params: ChainParams::default(),
1028        })?;
1029        let blake_pq_state = initial_state(Params {
1030            version: Version::MAX,
1031            min_version: Version::V1,
1032            direction: Direction::B2A,
1033            auth_key: &[41u8; 32],
1034            chain_params: ChainParams::default(),
1035        })?;
1036        assert!(matches!(
1037            current_version(&alex_pq_state)?,
1038            CurrentVersion::NegotiationComplete(Version::V0),
1039        ));
1040        assert!(matches!(
1041            current_version(&blake_pq_state)?,
1042            CurrentVersion::StillNegotiating {
1043                version: Version::MAX,
1044                min_version: Version::V1
1045            },
1046        ));
1047        let Send { msg: msg_a1, .. } = send(&alex_pq_state, &mut rng)?;
1048        assert!(matches!(
1049            recv(&blake_pq_state, &msg_a1),
1050            Err(Error::MinimumVersion)
1051        ));
1052        Ok(())
1053    }
1054}