commit ae0cfe8bf1a4106a9ba84533c485966162a43e58
parent df3a8b23866958784b0f54f93da8e93f01abac78
Author: triesap <tyson@radroots.org>
Date: Tue, 23 Jun 2026 01:05:02 +0000
simplex: retain accepted pq header material
- keep accepted KEM ciphertext on same-chain headers
- match upstream repeated accepted-KEM header behavior
- cover decrypted official headers across consecutive sends
- preserve existing recurring PQ ratchet validation
Diffstat:
1 file changed, 35 insertions(+), 2 deletions(-)
diff --git a/crates/simplex_smp_crypto/src/ratchet.rs b/crates/simplex_smp_crypto/src/ratchet.rs
@@ -403,7 +403,6 @@ impl RadrootsSimplexSmpRatchetState {
)?;
self.official_sending_chain_key = Some(chain.chain_key);
self.sending_chain_length = self.sending_chain_length.saturating_add(1);
- self.pending_outbound_pq_ciphertext = None;
encode_official_encrypted_message(
RADROOTS_SIMPLEX_OFFICIAL_E2E_CURRENT_VERSION,
&RadrootsSimplexOfficialEncryptedMessage {
@@ -867,7 +866,8 @@ mod tests {
use super::*;
use crate::official_ratchet::{
RADROOTS_SIMPLEX_OFFICIAL_E2E_CURRENT_VERSION, RADROOTS_SIMPLEX_OFFICIAL_E2E_KDF_VERSION,
- RadrootsSimplexOfficialX3dhParams, official_sntrup761_keypair_from_seed,
+ RadrootsSimplexOfficialX3dhParams, decode_official_encrypted_header,
+ decode_official_encrypted_message, official_sntrup761_keypair_from_seed,
official_x3dh_receiver_init, official_x3dh_receiver_init_accepting_pq,
official_x3dh_sender_init, official_x3dh_sender_init_accepting_pq,
official_x448_keypair_from_seed,
@@ -1175,6 +1175,39 @@ mod tests {
}
#[test]
+ fn retains_accepted_pq_ciphertext_across_same_sending_chain_headers() {
+ let (mut sender, _) = official_pq_sender_receiver_ratchets();
+ let shared_secret = [22_u8; RADROOTS_SIMPLEX_SMP_SHARED_SECRET_LENGTH];
+ let header_key = sender.official_sending_header_key.clone().unwrap();
+ let ratchet_ad = sender.official_associated_data.clone().unwrap();
+ let first = sender
+ .encrypt_official_payload(&shared_secret, b"pq first", 96)
+ .unwrap();
+ let second = sender
+ .encrypt_official_payload(&shared_secret, b"pq second", 96)
+ .unwrap();
+
+ let first_message = decode_official_encrypted_message(&first).unwrap();
+ let first_header = decrypt_official_header_with_key(
+ &decode_official_encrypted_header(&first_message.encrypted_header).unwrap(),
+ &header_key,
+ &ratchet_ad,
+ )
+ .unwrap();
+ let second_message = decode_official_encrypted_message(&second).unwrap();
+ let second_header = decrypt_official_header_with_key(
+ &decode_official_encrypted_header(&second_message.encrypted_header).unwrap(),
+ &header_key,
+ &ratchet_ad,
+ )
+ .unwrap();
+
+ assert!(first_header.pq_ciphertext.is_some());
+ assert_eq!(first_header.pq_ciphertext, second_header.pq_ciphertext);
+ assert_eq!(second_header.message_number, 1);
+ }
+
+ #[test]
fn decrypts_official_skipped_messages_once() {
let (mut sender, mut receiver) = official_sender_receiver_ratchets();
let shared_secret = [12_u8; RADROOTS_SIMPLEX_SMP_SHARED_SECRET_LENGTH];