lib

Core libraries for Radroots
git clone https://radroots.dev/git/lib.git
Log | Files | Refs | README | LICENSE

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:
Mcrates/simplex_smp_crypto/src/ratchet.rs | 37+++++++++++++++++++++++++++++++++++--
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];