lib

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

official_ratchet.rs (63527B)


      1 use crate::error::RadrootsSimplexSmpCryptoError;
      2 use aes_gcm::aead::consts::U16;
      3 use aes_gcm::aead::{Aead, KeyInit, Payload};
      4 use aes_gcm::{AesGcm, Nonce, aes::Aes256};
      5 use alloc::borrow::ToOwned;
      6 use alloc::format;
      7 use alloc::string::String;
      8 use alloc::vec::Vec;
      9 use base64::Engine as _;
     10 use base64::engine::general_purpose::{URL_SAFE, URL_SAFE_NO_PAD};
     11 use hkdf::Hkdf;
     12 use radroots_simplex_smp_proto::prelude::RadrootsSimplexSmpVersionRange;
     13 use sha2::{Digest, Sha256, Sha512};
     14 
     15 pub const RADROOTS_SIMPLEX_OFFICIAL_E2E_KDF_VERSION: u16 = 2;
     16 pub const RADROOTS_SIMPLEX_OFFICIAL_E2E_PQ_VERSION: u16 = 3;
     17 pub const RADROOTS_SIMPLEX_OFFICIAL_E2E_CURRENT_VERSION: u16 = 3;
     18 pub const RADROOTS_SIMPLEX_OFFICIAL_X448_KEY_LENGTH: usize = 56;
     19 pub const RADROOTS_SIMPLEX_OFFICIAL_X448_SHARED_SECRET_LENGTH: usize = 56;
     20 pub const RADROOTS_SIMPLEX_OFFICIAL_AES_KEY_LENGTH: usize = 32;
     21 pub const RADROOTS_SIMPLEX_OFFICIAL_AES_IV_LENGTH: usize = 16;
     22 pub const RADROOTS_SIMPLEX_OFFICIAL_AES_AUTH_TAG_LENGTH: usize = 16;
     23 pub const RADROOTS_SIMPLEX_OFFICIAL_SNTRUP761_PUBLIC_KEY_LENGTH: usize = sntrup761::PUBLIC_KEY_SIZE;
     24 pub const RADROOTS_SIMPLEX_OFFICIAL_SNTRUP761_PRIVATE_KEY_LENGTH: usize =
     25     sntrup761::SECRET_KEY_SIZE;
     26 pub const RADROOTS_SIMPLEX_OFFICIAL_SNTRUP761_CIPHERTEXT_LENGTH: usize = sntrup761::CIPHERTEXT_SIZE;
     27 pub const RADROOTS_SIMPLEX_OFFICIAL_SNTRUP761_SHARED_SECRET_LENGTH: usize =
     28     sntrup761::SHARED_SECRET_SIZE;
     29 pub const RADROOTS_SIMPLEX_OFFICIAL_RATCHET_HEADER_LENGTH: usize = 88;
     30 pub const RADROOTS_SIMPLEX_OFFICIAL_PQ_RATCHET_HEADER_LENGTH: usize = 2_310;
     31 pub const RADROOTS_SIMPLEX_OFFICIAL_ROOT_RATCHET_INFO: &[u8] = b"SimpleXRootRatchet";
     32 pub const RADROOTS_SIMPLEX_OFFICIAL_CHAIN_RATCHET_INFO: &[u8] = b"SimpleXChainRatchet";
     33 pub const RADROOTS_SIMPLEX_OFFICIAL_X3DH_INFO: &[u8] = b"SimpleXX3DH";
     34 
     35 const RADROOTS_SIMPLEX_OFFICIAL_HKDF3_OUTPUT_LENGTH: usize =
     36     RADROOTS_SIMPLEX_OFFICIAL_AES_KEY_LENGTH * 3;
     37 const RADROOTS_SIMPLEX_OFFICIAL_PADDING_LENGTH_BYTES: usize = 2;
     38 const RADROOTS_SIMPLEX_OFFICIAL_X448_DER_PUBLIC_KEY_PREFIX: [u8; 12] = [
     39     0x30, 0x42, 0x30, 0x05, 0x06, 0x03, 0x2b, 0x65, 0x6f, 0x03, 0x39, 0x00,
     40 ];
     41 type RadrootsSimplexOfficialAes256Gcm = AesGcm<Aes256, U16>;
     42 type RadrootsSimplexOfficialHkdf3Output = (Vec<u8>, Vec<u8>, Vec<u8>);
     43 type RadrootsSimplexOfficialPqHeaderParts = (Option<Vec<u8>>, Option<Vec<u8>>);
     44 
     45 #[derive(Debug, Clone, PartialEq, Eq)]
     46 pub struct RadrootsSimplexOfficialX448Keypair {
     47     pub public_key: Vec<u8>,
     48     pub private_key: Vec<u8>,
     49 }
     50 
     51 #[derive(Debug, Clone, PartialEq, Eq)]
     52 pub struct RadrootsSimplexOfficialSntrup761Keypair {
     53     pub public_key: Vec<u8>,
     54     pub private_key: Vec<u8>,
     55 }
     56 
     57 #[derive(Debug, Clone, PartialEq, Eq)]
     58 pub struct RadrootsSimplexOfficialAesGcmPayload {
     59     pub auth_tag: Vec<u8>,
     60     pub ciphertext: Vec<u8>,
     61 }
     62 
     63 #[derive(Debug, Clone, PartialEq, Eq)]
     64 pub struct RadrootsSimplexOfficialEncryptedHeader {
     65     pub version: u16,
     66     pub iv: [u8; RADROOTS_SIMPLEX_OFFICIAL_AES_IV_LENGTH],
     67     pub auth_tag: Vec<u8>,
     68     pub body: Vec<u8>,
     69 }
     70 
     71 #[derive(Debug, Clone, PartialEq, Eq)]
     72 pub struct RadrootsSimplexOfficialEncryptedMessage {
     73     pub encrypted_header: Vec<u8>,
     74     pub auth_tag: Vec<u8>,
     75     pub body: Vec<u8>,
     76 }
     77 
     78 #[derive(Debug, Clone, PartialEq, Eq)]
     79 pub struct RadrootsSimplexOfficialX3dhParams {
     80     pub version_range: RadrootsSimplexSmpVersionRange,
     81     pub key_1: Vec<u8>,
     82     pub key_2: Vec<u8>,
     83     pub pq_public_key: Option<Vec<u8>>,
     84     pub pq_ciphertext: Option<Vec<u8>>,
     85 }
     86 
     87 #[derive(Debug, Clone, PartialEq, Eq)]
     88 pub struct RadrootsSimplexOfficialX3dhInit {
     89     pub associated_data: Vec<u8>,
     90     pub ratchet_key: Vec<u8>,
     91     pub sending_header_key: Vec<u8>,
     92     pub receiving_next_header_key: Vec<u8>,
     93     pub accepted_pq_shared_secret: Option<Vec<u8>>,
     94 }
     95 
     96 #[derive(Debug, Clone, PartialEq, Eq)]
     97 pub struct RadrootsSimplexOfficialMsgHeader {
     98     pub max_version: u16,
     99     pub dh_public_key: Vec<u8>,
    100     pub pq_public_key: Option<Vec<u8>>,
    101     pub pq_ciphertext: Option<Vec<u8>>,
    102     pub previous_sending_chain_length: u32,
    103     pub message_number: u32,
    104 }
    105 
    106 #[derive(Debug, Clone, PartialEq, Eq)]
    107 pub struct RadrootsSimplexOfficialRootKdfOutput {
    108     pub root_key: Vec<u8>,
    109     pub chain_key: Vec<u8>,
    110     pub next_header_key: Vec<u8>,
    111 }
    112 
    113 #[derive(Debug, Clone, PartialEq, Eq)]
    114 pub struct RadrootsSimplexOfficialChainKdfOutput {
    115     pub chain_key: Vec<u8>,
    116     pub message_key: Vec<u8>,
    117     pub message_iv: [u8; RADROOTS_SIMPLEX_OFFICIAL_AES_IV_LENGTH],
    118     pub header_iv: [u8; RADROOTS_SIMPLEX_OFFICIAL_AES_IV_LENGTH],
    119 }
    120 
    121 #[derive(Debug, Clone, PartialEq, Eq)]
    122 pub struct RadrootsSimplexOfficialX3dhSenderPqInit {
    123     pub init: RadrootsSimplexOfficialX3dhInit,
    124     pub sender_params: RadrootsSimplexOfficialX3dhParams,
    125     pub local_pq_keypair: RadrootsSimplexOfficialSntrup761Keypair,
    126     pub pq_shared_secret: Vec<u8>,
    127 }
    128 
    129 #[derive(Debug, Clone, PartialEq, Eq)]
    130 pub struct RadrootsSimplexOfficialX3dhReceiverPqInit {
    131     pub init: RadrootsSimplexOfficialX3dhInit,
    132     pub pq_shared_secret: Vec<u8>,
    133 }
    134 
    135 pub fn official_ratchet_header_len(
    136     version: u16,
    137     pq_enabled: bool,
    138 ) -> Result<usize, RadrootsSimplexSmpCryptoError> {
    139     if !(RADROOTS_SIMPLEX_OFFICIAL_E2E_KDF_VERSION..=RADROOTS_SIMPLEX_OFFICIAL_E2E_CURRENT_VERSION)
    140         .contains(&version)
    141     {
    142         return Err(RadrootsSimplexSmpCryptoError::InvalidOfficialRatchetVersion(version));
    143     }
    144     Ok(
    145         if pq_enabled && version >= RADROOTS_SIMPLEX_OFFICIAL_E2E_PQ_VERSION {
    146             RADROOTS_SIMPLEX_OFFICIAL_PQ_RATCHET_HEADER_LENGTH
    147         } else {
    148             RADROOTS_SIMPLEX_OFFICIAL_RATCHET_HEADER_LENGTH
    149         },
    150     )
    151 }
    152 
    153 pub fn official_full_header_len(
    154     version: u16,
    155     pq_enabled: bool,
    156 ) -> Result<usize, RadrootsSimplexSmpCryptoError> {
    157     Ok(2 + 1
    158         + official_ratchet_header_len(version, pq_enabled)?
    159         + RADROOTS_SIMPLEX_OFFICIAL_AES_AUTH_TAG_LENGTH
    160         + RADROOTS_SIMPLEX_OFFICIAL_AES_IV_LENGTH)
    161 }
    162 
    163 pub fn official_encoded_encrypted_header_len(
    164     version: u16,
    165     pq_enabled: bool,
    166 ) -> Result<usize, RadrootsSimplexSmpCryptoError> {
    167     Ok(2 + RADROOTS_SIMPLEX_OFFICIAL_AES_IV_LENGTH
    168         + RADROOTS_SIMPLEX_OFFICIAL_AES_AUTH_TAG_LENGTH
    169         + official_large_prefix_len(version)?
    170         + official_ratchet_header_len(version, pq_enabled)?)
    171 }
    172 
    173 pub fn official_encoded_encrypted_message_len(
    174     version: u16,
    175     pq_enabled: bool,
    176     padded_body_len: usize,
    177 ) -> Result<usize, RadrootsSimplexSmpCryptoError> {
    178     Ok(official_large_prefix_len(version)?
    179         + official_encoded_encrypted_header_len(version, pq_enabled)?
    180         + RADROOTS_SIMPLEX_OFFICIAL_AES_AUTH_TAG_LENGTH
    181         + padded_body_len)
    182 }
    183 
    184 pub fn official_x448_keypair_from_seed(seed: &[u8]) -> RadrootsSimplexOfficialX448Keypair {
    185     let digest = Sha512::digest(seed);
    186     let mut private_key = [0_u8; RADROOTS_SIMPLEX_OFFICIAL_X448_KEY_LENGTH];
    187     private_key.copy_from_slice(&digest[..RADROOTS_SIMPLEX_OFFICIAL_X448_KEY_LENGTH]);
    188     official_x448_keypair_from_private(private_key)
    189 }
    190 
    191 pub fn generate_official_x448_keypair()
    192 -> Result<RadrootsSimplexOfficialX448Keypair, RadrootsSimplexSmpCryptoError> {
    193     let mut private_key = [0_u8; RADROOTS_SIMPLEX_OFFICIAL_X448_KEY_LENGTH];
    194     getrandom::getrandom(&mut private_key)
    195         .map_err(|_| RadrootsSimplexSmpCryptoError::EntropyUnavailable)?;
    196     Ok(official_x448_keypair_from_private(private_key))
    197 }
    198 
    199 pub fn derive_official_x448_shared_secret(
    200     private_key: &[u8],
    201     public_key: &[u8],
    202 ) -> Result<Vec<u8>, RadrootsSimplexSmpCryptoError> {
    203     let private_key: [u8; RADROOTS_SIMPLEX_OFFICIAL_X448_KEY_LENGTH] = private_key
    204         .try_into()
    205         .map_err(|_| RadrootsSimplexSmpCryptoError::InvalidPrivateKeyLength(private_key.len()))?;
    206     let public_key = x448::PublicKey::from_bytes(public_key).ok_or(
    207         RadrootsSimplexSmpCryptoError::InvalidPublicKeyLength(public_key.len()),
    208     )?;
    209     let private = x448::StaticSecret::from(private_key);
    210     Ok(private.diffie_hellman(&public_key).as_bytes().to_vec())
    211 }
    212 
    213 pub fn encode_official_x448_public_key_der(
    214     public_key: &[u8],
    215 ) -> Result<Vec<u8>, RadrootsSimplexSmpCryptoError> {
    216     if public_key.len() != RADROOTS_SIMPLEX_OFFICIAL_X448_KEY_LENGTH {
    217         return Err(RadrootsSimplexSmpCryptoError::InvalidPublicKeyLength(
    218             public_key.len(),
    219         ));
    220     }
    221     let mut encoded = Vec::with_capacity(
    222         RADROOTS_SIMPLEX_OFFICIAL_X448_DER_PUBLIC_KEY_PREFIX.len() + public_key.len(),
    223     );
    224     encoded.extend_from_slice(&RADROOTS_SIMPLEX_OFFICIAL_X448_DER_PUBLIC_KEY_PREFIX);
    225     encoded.extend_from_slice(public_key);
    226     Ok(encoded)
    227 }
    228 
    229 pub fn decode_official_x448_public_key_der(
    230     encoded: &[u8],
    231 ) -> Result<Vec<u8>, RadrootsSimplexSmpCryptoError> {
    232     let expected_len = RADROOTS_SIMPLEX_OFFICIAL_X448_DER_PUBLIC_KEY_PREFIX.len()
    233         + RADROOTS_SIMPLEX_OFFICIAL_X448_KEY_LENGTH;
    234     if encoded.len() != expected_len
    235         || !encoded.starts_with(&RADROOTS_SIMPLEX_OFFICIAL_X448_DER_PUBLIC_KEY_PREFIX)
    236     {
    237         return Err(RadrootsSimplexSmpCryptoError::InvalidPublicKeyLength(
    238             encoded.len(),
    239         ));
    240     }
    241     Ok(encoded[RADROOTS_SIMPLEX_OFFICIAL_X448_DER_PUBLIC_KEY_PREFIX.len()..].to_vec())
    242 }
    243 
    244 pub fn encode_official_x3dh_params_uri(
    245     params: &RadrootsSimplexOfficialX3dhParams,
    246 ) -> Result<String, RadrootsSimplexSmpCryptoError> {
    247     validate_official_x3dh_params(params)?;
    248     let key_1 = encode_official_urlsafe_bytes(&encode_official_x448_public_key_der(&params.key_1)?);
    249     let key_2 = encode_official_urlsafe_bytes(&encode_official_x448_public_key_der(&params.key_2)?);
    250     let mut encoded = format!("v={}&x3dh={key_1},{key_2}", params.version_range);
    251     if let Some(pq_public_key) = params.pq_public_key.as_deref() {
    252         encoded.push_str("&kem_key=");
    253         encoded.push_str(&encode_official_urlsafe_bytes(pq_public_key));
    254     }
    255     if let Some(pq_ciphertext) = params.pq_ciphertext.as_deref() {
    256         encoded.push_str("&kem_ct=");
    257         encoded.push_str(&encode_official_urlsafe_bytes(pq_ciphertext));
    258     }
    259     Ok(encoded)
    260 }
    261 
    262 pub fn decode_official_x3dh_params_uri(
    263     encoded: &str,
    264 ) -> Result<RadrootsSimplexOfficialX3dhParams, RadrootsSimplexSmpCryptoError> {
    265     let mut version_range = None;
    266     let mut x3dh = None;
    267     let mut pq_public_key = None;
    268     let mut pq_ciphertext = None;
    269     for pair in encoded.split('&') {
    270         if pair.is_empty() {
    271             continue;
    272         }
    273         let (key, value) = pair.split_once('=').ok_or_else(|| {
    274             RadrootsSimplexSmpCryptoError::InvalidOfficialX3dhParameters(
    275                 "field is missing `=`".to_owned(),
    276             )
    277         })?;
    278         match key {
    279             "v" => {
    280                 if version_range.replace(value.parse()?).is_some() {
    281                     return Err(
    282                         RadrootsSimplexSmpCryptoError::InvalidOfficialX3dhParameters(
    283                             "duplicate `v` field".to_owned(),
    284                         ),
    285                     );
    286                 }
    287             }
    288             "x3dh" => {
    289                 if x3dh.replace(value.to_owned()).is_some() {
    290                     return Err(
    291                         RadrootsSimplexSmpCryptoError::InvalidOfficialX3dhParameters(
    292                             "duplicate `x3dh` field".to_owned(),
    293                         ),
    294                     );
    295                 }
    296             }
    297             "kem_key" => {
    298                 if pq_public_key
    299                     .replace(decode_official_urlsafe_bytes(value)?)
    300                     .is_some()
    301                 {
    302                     return Err(
    303                         RadrootsSimplexSmpCryptoError::InvalidOfficialX3dhParameters(
    304                             "duplicate `kem_key` field".to_owned(),
    305                         ),
    306                     );
    307                 }
    308             }
    309             "kem_ct" => {
    310                 if pq_ciphertext
    311                     .replace(decode_official_urlsafe_bytes(value)?)
    312                     .is_some()
    313                 {
    314                     return Err(
    315                         RadrootsSimplexSmpCryptoError::InvalidOfficialX3dhParameters(
    316                             "duplicate `kem_ct` field".to_owned(),
    317                         ),
    318                     );
    319                 }
    320             }
    321             _ => {
    322                 return Err(
    323                     RadrootsSimplexSmpCryptoError::InvalidOfficialX3dhParameters(
    324                         "unknown field".to_owned(),
    325                     ),
    326                 );
    327             }
    328         }
    329     }
    330     let x3dh = x3dh.ok_or_else(|| {
    331         RadrootsSimplexSmpCryptoError::InvalidOfficialX3dhParameters(
    332             "missing `x3dh` field".to_owned(),
    333         )
    334     })?;
    335     let keys = split_official_x3dh_keys(&x3dh)?;
    336     let params = RadrootsSimplexOfficialX3dhParams {
    337         version_range: version_range.ok_or_else(|| {
    338             RadrootsSimplexSmpCryptoError::InvalidOfficialX3dhParameters(
    339                 "missing `v` field".to_owned(),
    340             )
    341         })?,
    342         key_1: decode_official_x448_public_key_der(&decode_official_urlsafe_bytes(keys.0)?)?,
    343         key_2: decode_official_x448_public_key_der(&decode_official_urlsafe_bytes(keys.1)?)?,
    344         pq_public_key,
    345         pq_ciphertext,
    346     };
    347     validate_official_x3dh_params(&params)?;
    348     Ok(params)
    349 }
    350 
    351 pub fn official_x3dh_sender_init(
    352     local_key_1: &RadrootsSimplexOfficialX448Keypair,
    353     local_key_2: &RadrootsSimplexOfficialX448Keypair,
    354     remote_params: &RadrootsSimplexOfficialX3dhParams,
    355 ) -> Result<RadrootsSimplexOfficialX3dhInit, RadrootsSimplexSmpCryptoError> {
    356     validate_official_x3dh_keypair(local_key_1)?;
    357     validate_official_x3dh_keypair(local_key_2)?;
    358     validate_official_x3dh_params(remote_params)?;
    359     if remote_params.pq_public_key.is_some() || remote_params.pq_ciphertext.is_some() {
    360         return Err(RadrootsSimplexSmpCryptoError::IncompletePqHeader);
    361     }
    362     official_x3dh_init(
    363         &local_key_1.public_key,
    364         &remote_params.key_1,
    365         &[
    366             derive_official_x448_shared_secret(&local_key_2.private_key, &remote_params.key_1)?,
    367             derive_official_x448_shared_secret(&local_key_1.private_key, &remote_params.key_2)?,
    368             derive_official_x448_shared_secret(&local_key_2.private_key, &remote_params.key_2)?,
    369         ],
    370         None,
    371     )
    372 }
    373 
    374 pub fn official_x3dh_receiver_init(
    375     local_key_1: &RadrootsSimplexOfficialX448Keypair,
    376     local_key_2: &RadrootsSimplexOfficialX448Keypair,
    377     remote_params: &RadrootsSimplexOfficialX3dhParams,
    378 ) -> Result<RadrootsSimplexOfficialX3dhInit, RadrootsSimplexSmpCryptoError> {
    379     validate_official_x3dh_keypair(local_key_1)?;
    380     validate_official_x3dh_keypair(local_key_2)?;
    381     validate_official_x3dh_params(remote_params)?;
    382     if remote_params.pq_public_key.is_some() || remote_params.pq_ciphertext.is_some() {
    383         return Err(RadrootsSimplexSmpCryptoError::IncompletePqHeader);
    384     }
    385     official_x3dh_init(
    386         &remote_params.key_1,
    387         &local_key_1.public_key,
    388         &[
    389             derive_official_x448_shared_secret(&local_key_1.private_key, &remote_params.key_2)?,
    390             derive_official_x448_shared_secret(&local_key_2.private_key, &remote_params.key_1)?,
    391             derive_official_x448_shared_secret(&local_key_2.private_key, &remote_params.key_2)?,
    392         ],
    393         None,
    394     )
    395 }
    396 
    397 pub fn official_x3dh_sender_init_accepting_pq(
    398     local_key_1: &RadrootsSimplexOfficialX448Keypair,
    399     local_key_2: &RadrootsSimplexOfficialX448Keypair,
    400     local_pq_keypair: RadrootsSimplexOfficialSntrup761Keypair,
    401     remote_params: &RadrootsSimplexOfficialX3dhParams,
    402     encapsulation_seed: &[u8],
    403 ) -> Result<RadrootsSimplexOfficialX3dhSenderPqInit, RadrootsSimplexSmpCryptoError> {
    404     validate_official_x3dh_keypair(local_key_1)?;
    405     validate_official_x3dh_keypair(local_key_2)?;
    406     validate_official_sntrup761_keypair(&local_pq_keypair)?;
    407     validate_official_x3dh_params(remote_params)?;
    408     let remote_pq_public_key = remote_params.pq_public_key.as_deref().ok_or(
    409         RadrootsSimplexSmpCryptoError::InvalidOfficialX3dhParameters(
    410             "PQ sender init requires remote proposed KEM key".to_owned(),
    411         ),
    412     )?;
    413     if remote_params.pq_ciphertext.is_some() {
    414         return Err(
    415             RadrootsSimplexSmpCryptoError::InvalidOfficialX3dhParameters(
    416                 "PQ sender init requires proposed KEM key without ciphertext".to_owned(),
    417             ),
    418         );
    419     }
    420     let (pq_ciphertext, pq_shared_secret) =
    421         encapsulate_official_sntrup761(remote_pq_public_key, encapsulation_seed)?;
    422     let init = official_x3dh_init(
    423         &local_key_1.public_key,
    424         &remote_params.key_1,
    425         &[
    426             derive_official_x448_shared_secret(&local_key_2.private_key, &remote_params.key_1)?,
    427             derive_official_x448_shared_secret(&local_key_1.private_key, &remote_params.key_2)?,
    428             derive_official_x448_shared_secret(&local_key_2.private_key, &remote_params.key_2)?,
    429         ],
    430         Some(&pq_shared_secret),
    431     )?;
    432     let sender_params = RadrootsSimplexOfficialX3dhParams {
    433         version_range: remote_params.version_range,
    434         key_1: local_key_1.public_key.clone(),
    435         key_2: local_key_2.public_key.clone(),
    436         pq_public_key: Some(local_pq_keypair.public_key.clone()),
    437         pq_ciphertext: Some(pq_ciphertext),
    438     };
    439     validate_official_x3dh_params(&sender_params)?;
    440     Ok(RadrootsSimplexOfficialX3dhSenderPqInit {
    441         init,
    442         sender_params,
    443         local_pq_keypair,
    444         pq_shared_secret,
    445     })
    446 }
    447 
    448 pub fn official_x3dh_receiver_init_accepting_pq(
    449     local_key_1: &RadrootsSimplexOfficialX448Keypair,
    450     local_key_2: &RadrootsSimplexOfficialX448Keypair,
    451     local_pq_keypair: &RadrootsSimplexOfficialSntrup761Keypair,
    452     remote_params: &RadrootsSimplexOfficialX3dhParams,
    453 ) -> Result<RadrootsSimplexOfficialX3dhReceiverPqInit, RadrootsSimplexSmpCryptoError> {
    454     validate_official_x3dh_keypair(local_key_1)?;
    455     validate_official_x3dh_keypair(local_key_2)?;
    456     validate_official_sntrup761_keypair(local_pq_keypair)?;
    457     validate_official_x3dh_params(remote_params)?;
    458     let pq_ciphertext = remote_params.pq_ciphertext.as_deref().ok_or(
    459         RadrootsSimplexSmpCryptoError::InvalidOfficialX3dhParameters(
    460             "PQ receiver init requires accepted KEM ciphertext".to_owned(),
    461         ),
    462     )?;
    463     if remote_params.pq_public_key.is_none() {
    464         return Err(RadrootsSimplexSmpCryptoError::IncompletePqHeader);
    465     }
    466     let pq_shared_secret =
    467         decapsulate_official_sntrup761(&local_pq_keypair.private_key, pq_ciphertext)?;
    468     let init = official_x3dh_init(
    469         &remote_params.key_1,
    470         &local_key_1.public_key,
    471         &[
    472             derive_official_x448_shared_secret(&local_key_1.private_key, &remote_params.key_2)?,
    473             derive_official_x448_shared_secret(&local_key_2.private_key, &remote_params.key_1)?,
    474             derive_official_x448_shared_secret(&local_key_2.private_key, &remote_params.key_2)?,
    475         ],
    476         Some(&pq_shared_secret),
    477     )?;
    478     Ok(RadrootsSimplexOfficialX3dhReceiverPqInit {
    479         init,
    480         pq_shared_secret,
    481     })
    482 }
    483 
    484 pub fn official_sntrup761_keypair_from_seed(
    485     seed: &[u8],
    486 ) -> RadrootsSimplexOfficialSntrup761Keypair {
    487     let seed = pq_seed(seed);
    488     let (public_key, private_key) = sntrup761::generate_key_from_seed(seed);
    489     RadrootsSimplexOfficialSntrup761Keypair {
    490         public_key: public_key.as_ref().to_vec(),
    491         private_key: private_key.as_ref().to_vec(),
    492     }
    493 }
    494 
    495 pub fn generate_official_sntrup761_keypair()
    496 -> Result<RadrootsSimplexOfficialSntrup761Keypair, RadrootsSimplexSmpCryptoError> {
    497     let mut seed = [0_u8; RADROOTS_SIMPLEX_OFFICIAL_AES_KEY_LENGTH];
    498     getrandom::getrandom(&mut seed)
    499         .map_err(|_| RadrootsSimplexSmpCryptoError::EntropyUnavailable)?;
    500     Ok(official_sntrup761_keypair_from_seed(&seed))
    501 }
    502 
    503 pub fn encapsulate_official_sntrup761(
    504     public_key: &[u8],
    505     seed: &[u8],
    506 ) -> Result<(Vec<u8>, Vec<u8>), RadrootsSimplexSmpCryptoError> {
    507     let public_key = sntrup761::EncapsulationKey::try_from(public_key)
    508         .map_err(|_| RadrootsSimplexSmpCryptoError::InvalidPqKeyLength(public_key.len()))?;
    509     let (ciphertext, shared_secret) = public_key.encapsulate_deterministic(pq_seed(seed));
    510     Ok((
    511         ciphertext.as_ref().to_vec(),
    512         shared_secret.as_ref().to_vec(),
    513     ))
    514 }
    515 
    516 pub fn decapsulate_official_sntrup761(
    517     private_key: &[u8],
    518     ciphertext: &[u8],
    519 ) -> Result<Vec<u8>, RadrootsSimplexSmpCryptoError> {
    520     let private_key = sntrup761::DecapsulationKey::try_from(private_key)
    521         .map_err(|_| RadrootsSimplexSmpCryptoError::InvalidPrivateKeyLength(private_key.len()))?;
    522     let ciphertext = sntrup761::Ciphertext::try_from(ciphertext)
    523         .map_err(|_| RadrootsSimplexSmpCryptoError::InvalidPqCiphertextLength(ciphertext.len()))?;
    524     Ok(private_key.decapsulate(&ciphertext).as_ref().to_vec())
    525 }
    526 
    527 pub fn official_root_kdf(
    528     root_key: &[u8],
    529     dh_shared_secret: &[u8],
    530     pq_shared_secret: Option<&[u8]>,
    531 ) -> Result<RadrootsSimplexOfficialRootKdfOutput, RadrootsSimplexSmpCryptoError> {
    532     if root_key.len() != RADROOTS_SIMPLEX_OFFICIAL_AES_KEY_LENGTH {
    533         return Err(RadrootsSimplexSmpCryptoError::InvalidSharedSecretLength(
    534             root_key.len(),
    535         ));
    536     }
    537     let mut input =
    538         Vec::with_capacity(dh_shared_secret.len() + pq_shared_secret.map_or(0, <[u8]>::len));
    539     input.extend_from_slice(dh_shared_secret);
    540     if let Some(shared_secret) = pq_shared_secret {
    541         input.extend_from_slice(shared_secret);
    542     }
    543     let (root_key, chain_key, next_header_key) = official_hkdf3(
    544         root_key,
    545         &input,
    546         RADROOTS_SIMPLEX_OFFICIAL_ROOT_RATCHET_INFO,
    547     )?;
    548     Ok(RadrootsSimplexOfficialRootKdfOutput {
    549         root_key,
    550         chain_key,
    551         next_header_key,
    552     })
    553 }
    554 
    555 pub fn official_chain_kdf(
    556     chain_key: &[u8],
    557 ) -> Result<RadrootsSimplexOfficialChainKdfOutput, RadrootsSimplexSmpCryptoError> {
    558     if chain_key.len() != RADROOTS_SIMPLEX_OFFICIAL_AES_KEY_LENGTH {
    559         return Err(RadrootsSimplexSmpCryptoError::InvalidSharedSecretLength(
    560             chain_key.len(),
    561         ));
    562     }
    563     let (chain_key, message_key, iv_material) =
    564         official_hkdf3(b"", chain_key, RADROOTS_SIMPLEX_OFFICIAL_CHAIN_RATCHET_INFO)?;
    565     let mut message_iv = [0_u8; RADROOTS_SIMPLEX_OFFICIAL_AES_IV_LENGTH];
    566     let mut header_iv = [0_u8; RADROOTS_SIMPLEX_OFFICIAL_AES_IV_LENGTH];
    567     message_iv.copy_from_slice(&iv_material[..RADROOTS_SIMPLEX_OFFICIAL_AES_IV_LENGTH]);
    568     header_iv.copy_from_slice(
    569         &iv_material
    570             [RADROOTS_SIMPLEX_OFFICIAL_AES_IV_LENGTH..RADROOTS_SIMPLEX_OFFICIAL_AES_IV_LENGTH * 2],
    571     );
    572     Ok(RadrootsSimplexOfficialChainKdfOutput {
    573         chain_key,
    574         message_key,
    575         message_iv,
    576         header_iv,
    577     })
    578 }
    579 
    580 pub fn official_aes_gcm_encrypt_padded(
    581     key: &[u8],
    582     iv: &[u8],
    583     plaintext: &[u8],
    584     padded_len: usize,
    585     associated_data: &[u8],
    586 ) -> Result<RadrootsSimplexOfficialAesGcmPayload, RadrootsSimplexSmpCryptoError> {
    587     let padded = official_pad(plaintext, padded_len)?;
    588     let encrypted = official_aes_gcm_cipher(key)?
    589         .encrypt(
    590             official_aes_gcm_nonce(iv)?,
    591             Payload {
    592                 msg: &padded,
    593                 aad: associated_data,
    594             },
    595         )
    596         .map_err(|_| RadrootsSimplexSmpCryptoError::AesGcmAuthenticationFailed)?;
    597     split_official_aes_gcm_payload(&encrypted)
    598 }
    599 
    600 pub fn encode_official_msg_header(
    601     version: u16,
    602     header: &RadrootsSimplexOfficialMsgHeader,
    603 ) -> Result<Vec<u8>, RadrootsSimplexSmpCryptoError> {
    604     validate_official_version(version)?;
    605     validate_official_version(header.max_version)?;
    606     let public_key = encode_official_x448_public_key_der(&header.dh_public_key)?;
    607     let mut buffer = Vec::with_capacity(2 + 1 + public_key.len() + 1 + 4 + 4);
    608     buffer.extend_from_slice(&header.max_version.to_be_bytes());
    609     push_official_short_bytes(&mut buffer, &public_key)?;
    610     if version >= RADROOTS_SIMPLEX_OFFICIAL_E2E_PQ_VERSION {
    611         push_official_msg_header_pq(&mut buffer, header)?;
    612     } else if header.pq_public_key.is_some() || header.pq_ciphertext.is_some() {
    613         return Err(
    614             RadrootsSimplexSmpCryptoError::InvalidOfficialX3dhParameters(
    615                 "PQ header params require E2E version 3".to_owned(),
    616             ),
    617         );
    618     }
    619     buffer.extend_from_slice(&header.previous_sending_chain_length.to_be_bytes());
    620     buffer.extend_from_slice(&header.message_number.to_be_bytes());
    621     Ok(buffer)
    622 }
    623 
    624 pub fn decode_official_msg_header(
    625     version: u16,
    626     bytes: &[u8],
    627 ) -> Result<RadrootsSimplexOfficialMsgHeader, RadrootsSimplexSmpCryptoError> {
    628     validate_official_version(version)?;
    629     let mut cursor = OfficialCursor::new(bytes);
    630     let max_version = cursor.read_u16()?;
    631     validate_official_version(max_version)?;
    632     let dh_public_key = decode_official_x448_public_key_der(cursor.read_short_bytes()?)?;
    633     let (pq_public_key, pq_ciphertext) = if version >= RADROOTS_SIMPLEX_OFFICIAL_E2E_PQ_VERSION {
    634         read_official_msg_header_pq(&mut cursor)?
    635     } else {
    636         (None, None)
    637     };
    638     let previous_sending_chain_length = cursor.read_u32()?;
    639     let message_number = cursor.read_u32()?;
    640     cursor.finish()?;
    641     Ok(RadrootsSimplexOfficialMsgHeader {
    642         max_version,
    643         dh_public_key,
    644         pq_public_key,
    645         pq_ciphertext,
    646         previous_sending_chain_length,
    647         message_number,
    648     })
    649 }
    650 
    651 pub fn encode_official_encrypted_header(
    652     header: &RadrootsSimplexOfficialEncryptedHeader,
    653 ) -> Result<Vec<u8>, RadrootsSimplexSmpCryptoError> {
    654     validate_official_version(header.version)?;
    655     if header.auth_tag.len() != RADROOTS_SIMPLEX_OFFICIAL_AES_AUTH_TAG_LENGTH {
    656         return Err(RadrootsSimplexSmpCryptoError::InvalidSignatureLength(
    657             header.auth_tag.len(),
    658         ));
    659     }
    660     let mut buffer = Vec::with_capacity(
    661         2 + RADROOTS_SIMPLEX_OFFICIAL_AES_IV_LENGTH
    662             + RADROOTS_SIMPLEX_OFFICIAL_AES_AUTH_TAG_LENGTH
    663             + official_large_prefix_len(header.version)?
    664             + header.body.len(),
    665     );
    666     buffer.extend_from_slice(&header.version.to_be_bytes());
    667     buffer.extend_from_slice(&header.iv);
    668     buffer.extend_from_slice(&header.auth_tag);
    669     push_official_large_by_version(&mut buffer, header.version, &header.body)?;
    670     Ok(buffer)
    671 }
    672 
    673 pub fn decode_official_encrypted_header(
    674     bytes: &[u8],
    675 ) -> Result<RadrootsSimplexOfficialEncryptedHeader, RadrootsSimplexSmpCryptoError> {
    676     let mut cursor = OfficialCursor::new(bytes);
    677     let version = cursor.read_u16()?;
    678     validate_official_version(version)?;
    679     let iv = cursor.read_array::<RADROOTS_SIMPLEX_OFFICIAL_AES_IV_LENGTH>()?;
    680     let auth_tag = cursor
    681         .read_slice(RADROOTS_SIMPLEX_OFFICIAL_AES_AUTH_TAG_LENGTH)?
    682         .to_vec();
    683     let body = cursor.read_official_large()?.to_vec();
    684     cursor.finish()?;
    685     Ok(RadrootsSimplexOfficialEncryptedHeader {
    686         version,
    687         iv,
    688         auth_tag,
    689         body,
    690     })
    691 }
    692 
    693 pub fn encode_official_encrypted_message(
    694     version: u16,
    695     message: &RadrootsSimplexOfficialEncryptedMessage,
    696 ) -> Result<Vec<u8>, RadrootsSimplexSmpCryptoError> {
    697     validate_official_version(version)?;
    698     if message.auth_tag.len() != RADROOTS_SIMPLEX_OFFICIAL_AES_AUTH_TAG_LENGTH {
    699         return Err(RadrootsSimplexSmpCryptoError::InvalidSignatureLength(
    700             message.auth_tag.len(),
    701         ));
    702     }
    703     let mut buffer = Vec::with_capacity(
    704         official_large_prefix_len(version)?
    705             + message.encrypted_header.len()
    706             + RADROOTS_SIMPLEX_OFFICIAL_AES_AUTH_TAG_LENGTH
    707             + message.body.len(),
    708     );
    709     push_official_large_by_version(&mut buffer, version, &message.encrypted_header)?;
    710     buffer.extend_from_slice(&message.auth_tag);
    711     buffer.extend_from_slice(&message.body);
    712     Ok(buffer)
    713 }
    714 
    715 pub fn decode_official_encrypted_message(
    716     bytes: &[u8],
    717 ) -> Result<RadrootsSimplexOfficialEncryptedMessage, RadrootsSimplexSmpCryptoError> {
    718     let mut cursor = OfficialCursor::new(bytes);
    719     let encrypted_header = cursor.read_official_large()?.to_vec();
    720     let auth_tag = cursor
    721         .read_slice(RADROOTS_SIMPLEX_OFFICIAL_AES_AUTH_TAG_LENGTH)?
    722         .to_vec();
    723     let body = cursor.read_remaining().to_vec();
    724     Ok(RadrootsSimplexOfficialEncryptedMessage {
    725         encrypted_header,
    726         auth_tag,
    727         body,
    728     })
    729 }
    730 
    731 pub fn official_aes_gcm_decrypt_padded(
    732     key: &[u8],
    733     iv: &[u8],
    734     payload: &RadrootsSimplexOfficialAesGcmPayload,
    735     associated_data: &[u8],
    736 ) -> Result<Vec<u8>, RadrootsSimplexSmpCryptoError> {
    737     if payload.auth_tag.len() != RADROOTS_SIMPLEX_OFFICIAL_AES_AUTH_TAG_LENGTH {
    738         return Err(RadrootsSimplexSmpCryptoError::InvalidSignatureLength(
    739             payload.auth_tag.len(),
    740         ));
    741     }
    742     let mut encrypted = Vec::with_capacity(payload.ciphertext.len() + payload.auth_tag.len());
    743     encrypted.extend_from_slice(&payload.ciphertext);
    744     encrypted.extend_from_slice(&payload.auth_tag);
    745     let padded = official_aes_gcm_cipher(key)?
    746         .decrypt(
    747             official_aes_gcm_nonce(iv)?,
    748             Payload {
    749                 msg: &encrypted,
    750                 aad: associated_data,
    751             },
    752         )
    753         .map_err(|_| RadrootsSimplexSmpCryptoError::AesGcmAuthenticationFailed)?;
    754     official_unpad(&padded)
    755 }
    756 
    757 fn official_x448_keypair_from_private(
    758     private_key: [u8; RADROOTS_SIMPLEX_OFFICIAL_X448_KEY_LENGTH],
    759 ) -> RadrootsSimplexOfficialX448Keypair {
    760     let private = x448::StaticSecret::from(private_key);
    761     let public = x448::PublicKey::from(&private);
    762     RadrootsSimplexOfficialX448Keypair {
    763         public_key: public.as_bytes().to_vec(),
    764         private_key: private.as_bytes().to_vec(),
    765     }
    766 }
    767 
    768 fn official_aes_gcm_cipher(
    769     key: &[u8],
    770 ) -> Result<RadrootsSimplexOfficialAes256Gcm, RadrootsSimplexSmpCryptoError> {
    771     if key.len() != RADROOTS_SIMPLEX_OFFICIAL_AES_KEY_LENGTH {
    772         return Err(RadrootsSimplexSmpCryptoError::InvalidSharedSecretLength(
    773             key.len(),
    774         ));
    775     }
    776     RadrootsSimplexOfficialAes256Gcm::new_from_slice(key)
    777         .map_err(|_| RadrootsSimplexSmpCryptoError::InvalidSharedSecretLength(key.len()))
    778 }
    779 
    780 fn official_aes_gcm_nonce(iv: &[u8]) -> Result<&Nonce<U16>, RadrootsSimplexSmpCryptoError> {
    781     if iv.len() != RADROOTS_SIMPLEX_OFFICIAL_AES_IV_LENGTH {
    782         return Err(RadrootsSimplexSmpCryptoError::InvalidNonceLength(iv.len()));
    783     }
    784     Ok(Nonce::<U16>::from_slice(iv))
    785 }
    786 
    787 fn split_official_aes_gcm_payload(
    788     encrypted: &[u8],
    789 ) -> Result<RadrootsSimplexOfficialAesGcmPayload, RadrootsSimplexSmpCryptoError> {
    790     if encrypted.len() < RADROOTS_SIMPLEX_OFFICIAL_AES_AUTH_TAG_LENGTH {
    791         return Err(RadrootsSimplexSmpCryptoError::InvalidCiphertextLength(
    792             encrypted.len(),
    793         ));
    794     }
    795     let tag_offset = encrypted.len() - RADROOTS_SIMPLEX_OFFICIAL_AES_AUTH_TAG_LENGTH;
    796     let (ciphertext, auth_tag) = encrypted.split_at(tag_offset);
    797     Ok(RadrootsSimplexOfficialAesGcmPayload {
    798         auth_tag: auth_tag.to_vec(),
    799         ciphertext: ciphertext.to_vec(),
    800     })
    801 }
    802 
    803 fn official_pad(
    804     plaintext: &[u8],
    805     padded_len: usize,
    806 ) -> Result<Vec<u8>, RadrootsSimplexSmpCryptoError> {
    807     if plaintext.len() > u16::MAX as usize
    808         || plaintext
    809             .len()
    810             .saturating_add(RADROOTS_SIMPLEX_OFFICIAL_PADDING_LENGTH_BYTES)
    811             > padded_len
    812     {
    813         return Err(RadrootsSimplexSmpCryptoError::InvalidMessageLength {
    814             actual: plaintext.len(),
    815             padded: padded_len,
    816         });
    817     }
    818     let mut padded = Vec::with_capacity(padded_len);
    819     padded.extend_from_slice(&(plaintext.len() as u16).to_be_bytes());
    820     padded.extend_from_slice(plaintext);
    821     padded.resize(padded_len, b'#');
    822     Ok(padded)
    823 }
    824 
    825 fn official_unpad(padded: &[u8]) -> Result<Vec<u8>, RadrootsSimplexSmpCryptoError> {
    826     if padded.len() < RADROOTS_SIMPLEX_OFFICIAL_PADDING_LENGTH_BYTES {
    827         return Err(RadrootsSimplexSmpCryptoError::InvalidOfficialRatchetPadding);
    828     }
    829     let length = u16::from_be_bytes([padded[0], padded[1]]) as usize;
    830     if length
    831         > padded
    832             .len()
    833             .saturating_sub(RADROOTS_SIMPLEX_OFFICIAL_PADDING_LENGTH_BYTES)
    834     {
    835         return Err(RadrootsSimplexSmpCryptoError::InvalidOfficialRatchetPadding);
    836     }
    837     Ok(padded[RADROOTS_SIMPLEX_OFFICIAL_PADDING_LENGTH_BYTES
    838         ..RADROOTS_SIMPLEX_OFFICIAL_PADDING_LENGTH_BYTES + length]
    839         .to_vec())
    840 }
    841 
    842 fn official_hkdf3(
    843     salt: &[u8],
    844     ikm: &[u8],
    845     info: &[u8],
    846 ) -> Result<RadrootsSimplexOfficialHkdf3Output, RadrootsSimplexSmpCryptoError> {
    847     let hkdf = Hkdf::<Sha512>::new(Some(salt), ikm);
    848     let mut output = [0_u8; RADROOTS_SIMPLEX_OFFICIAL_HKDF3_OUTPUT_LENGTH];
    849     hkdf.expand(info, &mut output).map_err(|_| {
    850         RadrootsSimplexSmpCryptoError::InvalidKeyDerivationLength(
    851             RADROOTS_SIMPLEX_OFFICIAL_HKDF3_OUTPUT_LENGTH,
    852         )
    853     })?;
    854     Ok((
    855         output[..RADROOTS_SIMPLEX_OFFICIAL_AES_KEY_LENGTH].to_vec(),
    856         output[RADROOTS_SIMPLEX_OFFICIAL_AES_KEY_LENGTH
    857             ..RADROOTS_SIMPLEX_OFFICIAL_AES_KEY_LENGTH * 2]
    858             .to_vec(),
    859         output[RADROOTS_SIMPLEX_OFFICIAL_AES_KEY_LENGTH * 2..].to_vec(),
    860     ))
    861 }
    862 
    863 fn validate_official_version(version: u16) -> Result<(), RadrootsSimplexSmpCryptoError> {
    864     if !(RADROOTS_SIMPLEX_OFFICIAL_E2E_KDF_VERSION..=RADROOTS_SIMPLEX_OFFICIAL_E2E_CURRENT_VERSION)
    865         .contains(&version)
    866     {
    867         return Err(RadrootsSimplexSmpCryptoError::InvalidOfficialRatchetVersion(version));
    868     }
    869     Ok(())
    870 }
    871 
    872 fn validate_official_version_range(
    873     range: RadrootsSimplexSmpVersionRange,
    874 ) -> Result<(), RadrootsSimplexSmpCryptoError> {
    875     validate_official_version(range.min)?;
    876     validate_official_version(range.max)
    877 }
    878 
    879 fn validate_official_x3dh_params(
    880     params: &RadrootsSimplexOfficialX3dhParams,
    881 ) -> Result<(), RadrootsSimplexSmpCryptoError> {
    882     validate_official_version_range(params.version_range)?;
    883     if params.key_1.len() != RADROOTS_SIMPLEX_OFFICIAL_X448_KEY_LENGTH {
    884         return Err(RadrootsSimplexSmpCryptoError::InvalidPublicKeyLength(
    885             params.key_1.len(),
    886         ));
    887     }
    888     if params.key_2.len() != RADROOTS_SIMPLEX_OFFICIAL_X448_KEY_LENGTH {
    889         return Err(RadrootsSimplexSmpCryptoError::InvalidPublicKeyLength(
    890             params.key_2.len(),
    891         ));
    892     }
    893     if params.pq_ciphertext.is_some() && params.pq_public_key.is_none() {
    894         return Err(RadrootsSimplexSmpCryptoError::IncompletePqHeader);
    895     }
    896     if let Some(pq_public_key) = params.pq_public_key.as_deref() {
    897         if params.version_range.max < RADROOTS_SIMPLEX_OFFICIAL_E2E_PQ_VERSION {
    898             return Err(
    899                 RadrootsSimplexSmpCryptoError::InvalidOfficialX3dhParameters(
    900                     "PQ key requires E2E version 3".to_owned(),
    901                 ),
    902             );
    903         }
    904         if pq_public_key.len() != RADROOTS_SIMPLEX_OFFICIAL_SNTRUP761_PUBLIC_KEY_LENGTH {
    905             return Err(RadrootsSimplexSmpCryptoError::InvalidPqKeyLength(
    906                 pq_public_key.len(),
    907             ));
    908         }
    909     }
    910     if let Some(pq_ciphertext) = params.pq_ciphertext.as_deref()
    911         && pq_ciphertext.len() != RADROOTS_SIMPLEX_OFFICIAL_SNTRUP761_CIPHERTEXT_LENGTH
    912     {
    913         return Err(RadrootsSimplexSmpCryptoError::InvalidPqCiphertextLength(
    914             pq_ciphertext.len(),
    915         ));
    916     }
    917     Ok(())
    918 }
    919 
    920 fn validate_official_x3dh_keypair(
    921     keypair: &RadrootsSimplexOfficialX448Keypair,
    922 ) -> Result<(), RadrootsSimplexSmpCryptoError> {
    923     if keypair.public_key.len() != RADROOTS_SIMPLEX_OFFICIAL_X448_KEY_LENGTH {
    924         return Err(RadrootsSimplexSmpCryptoError::InvalidPublicKeyLength(
    925             keypair.public_key.len(),
    926         ));
    927     }
    928     if keypair.private_key.len() != RADROOTS_SIMPLEX_OFFICIAL_X448_KEY_LENGTH {
    929         return Err(RadrootsSimplexSmpCryptoError::InvalidPrivateKeyLength(
    930             keypair.private_key.len(),
    931         ));
    932     }
    933     Ok(())
    934 }
    935 
    936 fn validate_official_sntrup761_keypair(
    937     keypair: &RadrootsSimplexOfficialSntrup761Keypair,
    938 ) -> Result<(), RadrootsSimplexSmpCryptoError> {
    939     if keypair.public_key.len() != RADROOTS_SIMPLEX_OFFICIAL_SNTRUP761_PUBLIC_KEY_LENGTH {
    940         return Err(RadrootsSimplexSmpCryptoError::InvalidPqKeyLength(
    941             keypair.public_key.len(),
    942         ));
    943     }
    944     if keypair.private_key.len() != RADROOTS_SIMPLEX_OFFICIAL_SNTRUP761_PRIVATE_KEY_LENGTH {
    945         return Err(RadrootsSimplexSmpCryptoError::InvalidPrivateKeyLength(
    946             keypair.private_key.len(),
    947         ));
    948     }
    949     Ok(())
    950 }
    951 
    952 fn official_x3dh_init(
    953     sender_key_1: &[u8],
    954     receiver_key_1: &[u8],
    955     shared_secrets: &[Vec<u8>; 3],
    956     pq_shared_secret: Option<&[u8]>,
    957 ) -> Result<RadrootsSimplexOfficialX3dhInit, RadrootsSimplexSmpCryptoError> {
    958     let mut associated_data = Vec::with_capacity(sender_key_1.len() + receiver_key_1.len());
    959     associated_data.extend_from_slice(sender_key_1);
    960     associated_data.extend_from_slice(receiver_key_1);
    961     let mut input = Vec::with_capacity(
    962         RADROOTS_SIMPLEX_OFFICIAL_X448_SHARED_SECRET_LENGTH * shared_secrets.len(),
    963     );
    964     for shared_secret in shared_secrets {
    965         if shared_secret.len() != RADROOTS_SIMPLEX_OFFICIAL_X448_SHARED_SECRET_LENGTH {
    966             return Err(RadrootsSimplexSmpCryptoError::InvalidSharedSecretLength(
    967                 shared_secret.len(),
    968             ));
    969         }
    970         input.extend_from_slice(shared_secret);
    971     }
    972     if let Some(pq_shared_secret) = pq_shared_secret {
    973         if pq_shared_secret.len() != RADROOTS_SIMPLEX_OFFICIAL_SNTRUP761_SHARED_SECRET_LENGTH {
    974             return Err(RadrootsSimplexSmpCryptoError::InvalidSharedSecretLength(
    975                 pq_shared_secret.len(),
    976             ));
    977         }
    978         input.extend_from_slice(pq_shared_secret);
    979     }
    980     let zero_salt = [0_u8; 64];
    981     let (sending_header_key, receiving_next_header_key, ratchet_key) =
    982         official_hkdf3(&zero_salt, &input, RADROOTS_SIMPLEX_OFFICIAL_X3DH_INFO)?;
    983     Ok(RadrootsSimplexOfficialX3dhInit {
    984         associated_data,
    985         ratchet_key,
    986         sending_header_key,
    987         receiving_next_header_key,
    988         accepted_pq_shared_secret: pq_shared_secret.map(<[u8]>::to_vec),
    989     })
    990 }
    991 
    992 fn push_official_msg_header_pq(
    993     buffer: &mut Vec<u8>,
    994     header: &RadrootsSimplexOfficialMsgHeader,
    995 ) -> Result<(), RadrootsSimplexSmpCryptoError> {
    996     match (
    997         header.pq_public_key.as_deref(),
    998         header.pq_ciphertext.as_deref(),
    999     ) {
   1000         (None, None) => buffer.push(b'0'),
   1001         (Some(pq_public_key), None) => {
   1002             validate_official_pq_public_key(pq_public_key)?;
   1003             buffer.push(b'1');
   1004             buffer.push(b'P');
   1005             push_official_large_by_version(
   1006                 buffer,
   1007                 RADROOTS_SIMPLEX_OFFICIAL_E2E_PQ_VERSION,
   1008                 pq_public_key,
   1009             )?;
   1010         }
   1011         (Some(pq_public_key), Some(pq_ciphertext)) => {
   1012             validate_official_pq_public_key(pq_public_key)?;
   1013             validate_official_pq_ciphertext(pq_ciphertext)?;
   1014             buffer.push(b'1');
   1015             buffer.push(b'A');
   1016             push_official_large_by_version(
   1017                 buffer,
   1018                 RADROOTS_SIMPLEX_OFFICIAL_E2E_PQ_VERSION,
   1019                 pq_ciphertext,
   1020             )?;
   1021             push_official_large_by_version(
   1022                 buffer,
   1023                 RADROOTS_SIMPLEX_OFFICIAL_E2E_PQ_VERSION,
   1024                 pq_public_key,
   1025             )?;
   1026         }
   1027         (None, Some(_)) => return Err(RadrootsSimplexSmpCryptoError::IncompletePqHeader),
   1028     }
   1029     Ok(())
   1030 }
   1031 
   1032 fn read_official_msg_header_pq(
   1033     cursor: &mut OfficialCursor<'_>,
   1034 ) -> Result<RadrootsSimplexOfficialPqHeaderParts, RadrootsSimplexSmpCryptoError> {
   1035     match cursor.read_byte()? {
   1036         b'0' => Ok((None, None)),
   1037         b'1' => match cursor.read_byte()? {
   1038             b'P' => {
   1039                 let pq_public_key = cursor.read_official_large()?.to_vec();
   1040                 validate_official_pq_public_key(&pq_public_key)?;
   1041                 Ok((Some(pq_public_key), None))
   1042             }
   1043             b'A' => {
   1044                 let pq_ciphertext = cursor.read_official_large()?.to_vec();
   1045                 let pq_public_key = cursor.read_official_large()?.to_vec();
   1046                 validate_official_pq_ciphertext(&pq_ciphertext)?;
   1047                 validate_official_pq_public_key(&pq_public_key)?;
   1048                 Ok((Some(pq_public_key), Some(pq_ciphertext)))
   1049             }
   1050             value => Err(RadrootsSimplexSmpCryptoError::InvalidCiphertextLength(
   1051                 value as usize,
   1052             )),
   1053         },
   1054         value => Err(RadrootsSimplexSmpCryptoError::InvalidCiphertextLength(
   1055             value as usize,
   1056         )),
   1057     }
   1058 }
   1059 
   1060 fn validate_official_pq_public_key(value: &[u8]) -> Result<(), RadrootsSimplexSmpCryptoError> {
   1061     if value.len() != RADROOTS_SIMPLEX_OFFICIAL_SNTRUP761_PUBLIC_KEY_LENGTH {
   1062         return Err(RadrootsSimplexSmpCryptoError::InvalidPqKeyLength(
   1063             value.len(),
   1064         ));
   1065     }
   1066     Ok(())
   1067 }
   1068 
   1069 fn validate_official_pq_ciphertext(value: &[u8]) -> Result<(), RadrootsSimplexSmpCryptoError> {
   1070     if value.len() != RADROOTS_SIMPLEX_OFFICIAL_SNTRUP761_CIPHERTEXT_LENGTH {
   1071         return Err(RadrootsSimplexSmpCryptoError::InvalidPqCiphertextLength(
   1072             value.len(),
   1073         ));
   1074     }
   1075     Ok(())
   1076 }
   1077 
   1078 fn encode_official_urlsafe_bytes(bytes: &[u8]) -> String {
   1079     URL_SAFE.encode(bytes)
   1080 }
   1081 
   1082 fn decode_official_urlsafe_bytes(value: &str) -> Result<Vec<u8>, RadrootsSimplexSmpCryptoError> {
   1083     URL_SAFE
   1084         .decode(value.as_bytes())
   1085         .or_else(|_| URL_SAFE_NO_PAD.decode(value.as_bytes()))
   1086         .map_err(|_| {
   1087             RadrootsSimplexSmpCryptoError::InvalidOfficialX3dhParameters(
   1088                 "invalid base64url field".to_owned(),
   1089             )
   1090         })
   1091 }
   1092 
   1093 fn split_official_x3dh_keys(value: &str) -> Result<(&str, &str), RadrootsSimplexSmpCryptoError> {
   1094     let (key_1, rest) = value.split_once(',').ok_or_else(|| {
   1095         RadrootsSimplexSmpCryptoError::InvalidOfficialX3dhParameters(
   1096             "`x3dh` field must contain two keys".to_owned(),
   1097         )
   1098     })?;
   1099     if rest.contains(',') {
   1100         return Err(
   1101             RadrootsSimplexSmpCryptoError::InvalidOfficialX3dhParameters(
   1102                 "`x3dh` field must contain two keys".to_owned(),
   1103             ),
   1104         );
   1105     }
   1106     Ok((key_1, rest))
   1107 }
   1108 
   1109 fn official_large_prefix_len(version: u16) -> Result<usize, RadrootsSimplexSmpCryptoError> {
   1110     validate_official_version(version)?;
   1111     Ok(if version >= RADROOTS_SIMPLEX_OFFICIAL_E2E_PQ_VERSION {
   1112         2
   1113     } else {
   1114         1
   1115     })
   1116 }
   1117 
   1118 fn push_official_large_by_version(
   1119     buffer: &mut Vec<u8>,
   1120     version: u16,
   1121     value: &[u8],
   1122 ) -> Result<(), RadrootsSimplexSmpCryptoError> {
   1123     if version >= RADROOTS_SIMPLEX_OFFICIAL_E2E_PQ_VERSION {
   1124         if value.len() > u16::MAX as usize {
   1125             return Err(RadrootsSimplexSmpCryptoError::InvalidMessageLength {
   1126                 actual: value.len(),
   1127                 padded: u16::MAX as usize,
   1128             });
   1129         }
   1130         buffer.extend_from_slice(&(value.len() as u16).to_be_bytes());
   1131     } else {
   1132         if value.len() > u8::MAX as usize {
   1133             return Err(RadrootsSimplexSmpCryptoError::InvalidMessageLength {
   1134                 actual: value.len(),
   1135                 padded: u8::MAX as usize,
   1136             });
   1137         }
   1138         buffer.push(value.len() as u8);
   1139     }
   1140     buffer.extend_from_slice(value);
   1141     Ok(())
   1142 }
   1143 
   1144 fn push_official_short_bytes(
   1145     buffer: &mut Vec<u8>,
   1146     value: &[u8],
   1147 ) -> Result<(), RadrootsSimplexSmpCryptoError> {
   1148     if value.len() > u8::MAX as usize {
   1149         return Err(RadrootsSimplexSmpCryptoError::InvalidShortFieldLength(
   1150             value.len(),
   1151         ));
   1152     }
   1153     buffer.push(value.len() as u8);
   1154     buffer.extend_from_slice(value);
   1155     Ok(())
   1156 }
   1157 
   1158 struct OfficialCursor<'a> {
   1159     bytes: &'a [u8],
   1160     position: usize,
   1161 }
   1162 
   1163 impl<'a> OfficialCursor<'a> {
   1164     const fn new(bytes: &'a [u8]) -> Self {
   1165         Self { bytes, position: 0 }
   1166     }
   1167 
   1168     fn finish(&self) -> Result<(), RadrootsSimplexSmpCryptoError> {
   1169         if self.position == self.bytes.len() {
   1170             Ok(())
   1171         } else {
   1172             Err(RadrootsSimplexSmpCryptoError::InvalidCiphertextLength(
   1173                 self.bytes.len() - self.position,
   1174             ))
   1175         }
   1176     }
   1177 
   1178     fn read_u16(&mut self) -> Result<u16, RadrootsSimplexSmpCryptoError> {
   1179         let bytes = self.read_slice(2)?;
   1180         Ok(u16::from_be_bytes([bytes[0], bytes[1]]))
   1181     }
   1182 
   1183     fn read_u32(&mut self) -> Result<u32, RadrootsSimplexSmpCryptoError> {
   1184         let bytes = self.read_slice(4)?;
   1185         Ok(u32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]))
   1186     }
   1187 
   1188     fn read_byte(&mut self) -> Result<u8, RadrootsSimplexSmpCryptoError> {
   1189         let Some(value) = self.bytes.get(self.position) else {
   1190             return Err(RadrootsSimplexSmpCryptoError::InvalidCiphertextLength(0));
   1191         };
   1192         self.position += 1;
   1193         Ok(*value)
   1194     }
   1195 
   1196     fn read_short_bytes(&mut self) -> Result<&'a [u8], RadrootsSimplexSmpCryptoError> {
   1197         let length = self.read_byte()? as usize;
   1198         self.read_slice(length)
   1199     }
   1200 
   1201     fn read_array<const N: usize>(&mut self) -> Result<[u8; N], RadrootsSimplexSmpCryptoError> {
   1202         let bytes = self.read_slice(N)?;
   1203         let mut value = [0_u8; N];
   1204         value.copy_from_slice(bytes);
   1205         Ok(value)
   1206     }
   1207 
   1208     fn read_slice(&mut self, len: usize) -> Result<&'a [u8], RadrootsSimplexSmpCryptoError> {
   1209         let Some(bytes) = self.bytes.get(self.position..self.position + len) else {
   1210             return Err(RadrootsSimplexSmpCryptoError::InvalidCiphertextLength(
   1211                 self.bytes.len().saturating_sub(self.position),
   1212             ));
   1213         };
   1214         self.position += len;
   1215         Ok(bytes)
   1216     }
   1217 
   1218     fn read_official_large(&mut self) -> Result<&'a [u8], RadrootsSimplexSmpCryptoError> {
   1219         let first = *self
   1220             .bytes
   1221             .get(self.position)
   1222             .ok_or(RadrootsSimplexSmpCryptoError::InvalidCiphertextLength(0))?;
   1223         let len = if first < 32 {
   1224             self.read_u16()? as usize
   1225         } else {
   1226             self.position += 1;
   1227             first as usize
   1228         };
   1229         self.read_slice(len)
   1230     }
   1231 
   1232     fn read_remaining(&mut self) -> &'a [u8] {
   1233         let bytes = &self.bytes[self.position..];
   1234         self.position = self.bytes.len();
   1235         bytes
   1236     }
   1237 }
   1238 
   1239 fn pq_seed(seed: &[u8]) -> [u8; RADROOTS_SIMPLEX_OFFICIAL_AES_KEY_LENGTH] {
   1240     let digest = Sha256::digest(seed);
   1241     let mut value = [0_u8; RADROOTS_SIMPLEX_OFFICIAL_AES_KEY_LENGTH];
   1242     value.copy_from_slice(&digest);
   1243     value
   1244 }
   1245 
   1246 #[cfg(test)]
   1247 mod tests {
   1248     use super::*;
   1249 
   1250     #[test]
   1251     fn official_header_lengths_match_upstream_constants() {
   1252         assert_eq!(official_ratchet_header_len(2, false).unwrap(), 88);
   1253         assert_eq!(official_ratchet_header_len(3, false).unwrap(), 88);
   1254         assert_eq!(official_ratchet_header_len(3, true).unwrap(), 2_310);
   1255         assert_eq!(official_full_header_len(3, false).unwrap(), 123);
   1256         assert_eq!(official_full_header_len(3, true).unwrap(), 2_345);
   1257         assert_eq!(
   1258             official_encoded_encrypted_header_len(2, false).unwrap(),
   1259             123
   1260         );
   1261         assert_eq!(
   1262             official_encoded_encrypted_header_len(3, false).unwrap(),
   1263             124
   1264         );
   1265         assert_eq!(
   1266             official_encoded_encrypted_header_len(3, true).unwrap(),
   1267             2_346
   1268         );
   1269         assert_eq!(
   1270             official_encoded_encrypted_message_len(3, false, 15_840).unwrap(),
   1271             15_982
   1272         );
   1273     }
   1274 
   1275     #[test]
   1276     fn x448_key_agreement_roundtrips() {
   1277         let alice = official_x448_keypair_from_seed(b"rr-synth-official-alice-x448");
   1278         let bob = official_x448_keypair_from_seed(b"rr-synth-official-bob-x448");
   1279 
   1280         let alice_secret =
   1281             derive_official_x448_shared_secret(&alice.private_key, &bob.public_key).unwrap();
   1282         let bob_secret =
   1283             derive_official_x448_shared_secret(&bob.private_key, &alice.public_key).unwrap();
   1284 
   1285         assert_eq!(
   1286             alice.public_key.len(),
   1287             RADROOTS_SIMPLEX_OFFICIAL_X448_KEY_LENGTH
   1288         );
   1289         assert_eq!(
   1290             alice.private_key.len(),
   1291             RADROOTS_SIMPLEX_OFFICIAL_X448_KEY_LENGTH
   1292         );
   1293         assert_eq!(
   1294             alice_secret.len(),
   1295             RADROOTS_SIMPLEX_OFFICIAL_X448_SHARED_SECRET_LENGTH
   1296         );
   1297         assert_eq!(alice_secret, bob_secret);
   1298     }
   1299 
   1300     #[test]
   1301     fn official_x448_der_public_key_roundtrips() {
   1302         let keypair = official_x448_keypair_from_seed(b"rr-synth-official-der-x448");
   1303         let encoded = encode_official_x448_public_key_der(&keypair.public_key).unwrap();
   1304         assert_eq!(encoded.len(), 68);
   1305         assert_eq!(
   1306             decode_official_x448_public_key_der(&encoded).unwrap(),
   1307             keypair.public_key
   1308         );
   1309     }
   1310 
   1311     #[test]
   1312     fn official_no_pq_msg_header_roundtrips() {
   1313         let keypair = official_x448_keypair_from_seed(b"rr-synth-official-header-x448");
   1314         let header = RadrootsSimplexOfficialMsgHeader {
   1315             max_version: RADROOTS_SIMPLEX_OFFICIAL_E2E_CURRENT_VERSION,
   1316             dh_public_key: keypair.public_key,
   1317             pq_public_key: None,
   1318             pq_ciphertext: None,
   1319             previous_sending_chain_length: 5,
   1320             message_number: 8,
   1321         };
   1322         let encoded =
   1323             encode_official_msg_header(RADROOTS_SIMPLEX_OFFICIAL_E2E_CURRENT_VERSION, &header)
   1324                 .unwrap();
   1325         assert_eq!(encoded.len(), 80);
   1326         assert_eq!(
   1327             decode_official_msg_header(RADROOTS_SIMPLEX_OFFICIAL_E2E_CURRENT_VERSION, &encoded)
   1328                 .unwrap(),
   1329             header
   1330         );
   1331     }
   1332 
   1333     #[test]
   1334     fn official_pq_msg_headers_roundtrip_proposed_and_accepted_kem() {
   1335         let keypair = official_x448_keypair_from_seed(b"rr-synth-official-header-pq-x448");
   1336         let pq_keypair = official_sntrup761_keypair_from_seed(b"rr-synth-official-header-pq-kem");
   1337         let (pq_ciphertext, _) =
   1338             encapsulate_official_sntrup761(&pq_keypair.public_key, b"rr-synth-header-pq-ct")
   1339                 .unwrap();
   1340         let proposed = RadrootsSimplexOfficialMsgHeader {
   1341             max_version: RADROOTS_SIMPLEX_OFFICIAL_E2E_CURRENT_VERSION,
   1342             dh_public_key: keypair.public_key.clone(),
   1343             pq_public_key: Some(pq_keypair.public_key.clone()),
   1344             pq_ciphertext: None,
   1345             previous_sending_chain_length: 5,
   1346             message_number: 8,
   1347         };
   1348         let encoded_proposed =
   1349             encode_official_msg_header(RADROOTS_SIMPLEX_OFFICIAL_E2E_CURRENT_VERSION, &proposed)
   1350                 .unwrap();
   1351         assert_eq!(encoded_proposed.len(), 1_241);
   1352         assert_eq!(
   1353             decode_official_msg_header(
   1354                 RADROOTS_SIMPLEX_OFFICIAL_E2E_CURRENT_VERSION,
   1355                 &encoded_proposed
   1356             )
   1357             .unwrap(),
   1358             proposed
   1359         );
   1360 
   1361         let accepted = RadrootsSimplexOfficialMsgHeader {
   1362             max_version: RADROOTS_SIMPLEX_OFFICIAL_E2E_CURRENT_VERSION,
   1363             dh_public_key: keypair.public_key,
   1364             pq_public_key: Some(pq_keypair.public_key),
   1365             pq_ciphertext: Some(pq_ciphertext),
   1366             previous_sending_chain_length: 9,
   1367             message_number: 10,
   1368         };
   1369         let encoded_accepted =
   1370             encode_official_msg_header(RADROOTS_SIMPLEX_OFFICIAL_E2E_CURRENT_VERSION, &accepted)
   1371                 .unwrap();
   1372         assert_eq!(encoded_accepted.len(), 2_282);
   1373         assert_eq!(
   1374             decode_official_msg_header(
   1375                 RADROOTS_SIMPLEX_OFFICIAL_E2E_CURRENT_VERSION,
   1376                 &encoded_accepted
   1377             )
   1378             .unwrap(),
   1379             accepted
   1380         );
   1381     }
   1382 
   1383     #[test]
   1384     fn official_x3dh_params_uri_roundtrips() {
   1385         let keypair_1 = official_x448_keypair_from_seed(b"rr-synth-official-x3dh-1");
   1386         let keypair_2 = official_x448_keypair_from_seed(b"rr-synth-official-x3dh-2");
   1387         let params = RadrootsSimplexOfficialX3dhParams {
   1388             version_range: RadrootsSimplexSmpVersionRange::new(
   1389                 RADROOTS_SIMPLEX_OFFICIAL_E2E_KDF_VERSION,
   1390                 RADROOTS_SIMPLEX_OFFICIAL_E2E_CURRENT_VERSION,
   1391             )
   1392             .unwrap(),
   1393             key_1: keypair_1.public_key,
   1394             key_2: keypair_2.public_key,
   1395             pq_public_key: None,
   1396             pq_ciphertext: None,
   1397         };
   1398         let encoded = encode_official_x3dh_params_uri(&params).unwrap();
   1399         assert!(encoded.starts_with("v=2-3&x3dh=MEIwBQYDK2VvAzkA"));
   1400         assert!(encoded.contains(','));
   1401         assert_eq!(decode_official_x3dh_params_uri(&encoded).unwrap(), params);
   1402     }
   1403 
   1404     #[test]
   1405     fn official_x3dh_params_rejects_incomplete_pq_fields() {
   1406         let keypair_1 = official_x448_keypair_from_seed(b"rr-synth-official-x3dh-pq-1");
   1407         let keypair_2 = official_x448_keypair_from_seed(b"rr-synth-official-x3dh-pq-2");
   1408         let params = RadrootsSimplexOfficialX3dhParams {
   1409             version_range: RadrootsSimplexSmpVersionRange::single(
   1410                 RADROOTS_SIMPLEX_OFFICIAL_E2E_CURRENT_VERSION,
   1411             ),
   1412             key_1: keypair_1.public_key,
   1413             key_2: keypair_2.public_key,
   1414             pq_public_key: None,
   1415             pq_ciphertext: Some(vec![
   1416                 0_u8;
   1417                 RADROOTS_SIMPLEX_OFFICIAL_SNTRUP761_CIPHERTEXT_LENGTH
   1418             ]),
   1419         };
   1420         assert_eq!(
   1421             encode_official_x3dh_params_uri(&params).unwrap_err(),
   1422             RadrootsSimplexSmpCryptoError::IncompletePqHeader
   1423         );
   1424     }
   1425 
   1426     #[test]
   1427     fn official_x3dh_no_pq_init_matches_on_both_sides() {
   1428         let receiver_key_1 = official_x448_keypair_from_seed(b"rr-synth-x3dh-rcv-1");
   1429         let receiver_key_2 = official_x448_keypair_from_seed(b"rr-synth-x3dh-rcv-2");
   1430         let sender_key_1 = official_x448_keypair_from_seed(b"rr-synth-x3dh-snd-1");
   1431         let sender_key_2 = official_x448_keypair_from_seed(b"rr-synth-x3dh-snd-2");
   1432         let receiver_params = RadrootsSimplexOfficialX3dhParams {
   1433             version_range: RadrootsSimplexSmpVersionRange::new(
   1434                 RADROOTS_SIMPLEX_OFFICIAL_E2E_KDF_VERSION,
   1435                 RADROOTS_SIMPLEX_OFFICIAL_E2E_CURRENT_VERSION,
   1436             )
   1437             .unwrap(),
   1438             key_1: receiver_key_1.public_key.clone(),
   1439             key_2: receiver_key_2.public_key.clone(),
   1440             pq_public_key: None,
   1441             pq_ciphertext: None,
   1442         };
   1443         let sender_params = RadrootsSimplexOfficialX3dhParams {
   1444             version_range: receiver_params.version_range,
   1445             key_1: sender_key_1.public_key.clone(),
   1446             key_2: sender_key_2.public_key.clone(),
   1447             pq_public_key: None,
   1448             pq_ciphertext: None,
   1449         };
   1450 
   1451         let sender_init =
   1452             official_x3dh_sender_init(&sender_key_1, &sender_key_2, &receiver_params).unwrap();
   1453         let receiver_init =
   1454             official_x3dh_receiver_init(&receiver_key_1, &receiver_key_2, &sender_params).unwrap();
   1455 
   1456         assert_eq!(sender_init, receiver_init);
   1457         assert_eq!(
   1458             sender_init.associated_data,
   1459             [sender_key_1.public_key, receiver_key_1.public_key].concat()
   1460         );
   1461         assert_eq!(
   1462             sender_init.ratchet_key.len(),
   1463             RADROOTS_SIMPLEX_OFFICIAL_AES_KEY_LENGTH
   1464         );
   1465         assert_eq!(
   1466             sender_init.sending_header_key.len(),
   1467             RADROOTS_SIMPLEX_OFFICIAL_AES_KEY_LENGTH
   1468         );
   1469         assert_eq!(
   1470             sender_init.receiving_next_header_key.len(),
   1471             RADROOTS_SIMPLEX_OFFICIAL_AES_KEY_LENGTH
   1472         );
   1473         assert!(sender_init.accepted_pq_shared_secret.is_none());
   1474     }
   1475 
   1476     #[test]
   1477     fn official_x3dh_pq_init_matches_on_both_sides() {
   1478         let receiver_key_1 = official_x448_keypair_from_seed(b"rr-synth-x3dh-pq-rcv-1");
   1479         let receiver_key_2 = official_x448_keypair_from_seed(b"rr-synth-x3dh-pq-rcv-2");
   1480         let sender_key_1 = official_x448_keypair_from_seed(b"rr-synth-x3dh-pq-snd-1");
   1481         let sender_key_2 = official_x448_keypair_from_seed(b"rr-synth-x3dh-pq-snd-2");
   1482         let receiver_pq = official_sntrup761_keypair_from_seed(b"rr-synth-x3dh-pq-rcv-kem");
   1483         let sender_pq = official_sntrup761_keypair_from_seed(b"rr-synth-x3dh-pq-snd-kem");
   1484         let receiver_params = RadrootsSimplexOfficialX3dhParams {
   1485             version_range: RadrootsSimplexSmpVersionRange::new(
   1486                 RADROOTS_SIMPLEX_OFFICIAL_E2E_KDF_VERSION,
   1487                 RADROOTS_SIMPLEX_OFFICIAL_E2E_CURRENT_VERSION,
   1488             )
   1489             .unwrap(),
   1490             key_1: receiver_key_1.public_key.clone(),
   1491             key_2: receiver_key_2.public_key.clone(),
   1492             pq_public_key: Some(receiver_pq.public_key.clone()),
   1493             pq_ciphertext: None,
   1494         };
   1495 
   1496         let sender_init = official_x3dh_sender_init_accepting_pq(
   1497             &sender_key_1,
   1498             &sender_key_2,
   1499             sender_pq.clone(),
   1500             &receiver_params,
   1501             b"rr-synth-x3dh-pq-encap",
   1502         )
   1503         .unwrap();
   1504         let receiver_init = official_x3dh_receiver_init_accepting_pq(
   1505             &receiver_key_1,
   1506             &receiver_key_2,
   1507             &receiver_pq,
   1508             &sender_init.sender_params,
   1509         )
   1510         .unwrap();
   1511 
   1512         assert_eq!(sender_init.init, receiver_init.init);
   1513         assert_eq!(sender_init.pq_shared_secret, receiver_init.pq_shared_secret);
   1514         assert_eq!(
   1515             sender_init.init.accepted_pq_shared_secret.as_deref(),
   1516             Some(sender_init.pq_shared_secret.as_slice())
   1517         );
   1518         assert_eq!(
   1519             sender_init.sender_params.pq_public_key,
   1520             Some(sender_pq.public_key)
   1521         );
   1522         assert!(sender_init.sender_params.pq_ciphertext.is_some());
   1523     }
   1524 
   1525     #[test]
   1526     fn sntrup761_encapsulation_roundtrips() {
   1527         let recipient = official_sntrup761_keypair_from_seed(b"rr-synth-official-pq-recipient");
   1528         let (ciphertext, sender_secret) =
   1529             encapsulate_official_sntrup761(&recipient.public_key, b"rr-synth-official-pq-send")
   1530                 .unwrap();
   1531         let receiver_secret =
   1532             decapsulate_official_sntrup761(&recipient.private_key, &ciphertext).unwrap();
   1533 
   1534         assert_eq!(
   1535             recipient.public_key.len(),
   1536             RADROOTS_SIMPLEX_OFFICIAL_SNTRUP761_PUBLIC_KEY_LENGTH
   1537         );
   1538         assert_eq!(
   1539             recipient.private_key.len(),
   1540             RADROOTS_SIMPLEX_OFFICIAL_SNTRUP761_PRIVATE_KEY_LENGTH
   1541         );
   1542         assert_eq!(
   1543             ciphertext.len(),
   1544             RADROOTS_SIMPLEX_OFFICIAL_SNTRUP761_CIPHERTEXT_LENGTH
   1545         );
   1546         assert_eq!(
   1547             sender_secret.len(),
   1548             RADROOTS_SIMPLEX_OFFICIAL_SNTRUP761_SHARED_SECRET_LENGTH
   1549         );
   1550         assert_eq!(sender_secret, receiver_secret);
   1551     }
   1552 
   1553     #[test]
   1554     fn official_aes_gcm_padding_authenticates_associated_data() {
   1555         let key = [11_u8; RADROOTS_SIMPLEX_OFFICIAL_AES_KEY_LENGTH];
   1556         let iv = [12_u8; RADROOTS_SIMPLEX_OFFICIAL_AES_IV_LENGTH];
   1557         let associated_data = b"rr-synth-official-associated-data";
   1558         let payload = official_aes_gcm_encrypt_padded(
   1559             &key,
   1560             &iv,
   1561             b"hello official simplex",
   1562             96,
   1563             associated_data,
   1564         )
   1565         .unwrap();
   1566 
   1567         assert_eq!(
   1568             payload.auth_tag.len(),
   1569             RADROOTS_SIMPLEX_OFFICIAL_AES_AUTH_TAG_LENGTH
   1570         );
   1571         assert_eq!(payload.ciphertext.len(), 96);
   1572         assert_ne!(payload.ciphertext, b"hello official simplex");
   1573         assert_eq!(
   1574             official_aes_gcm_decrypt_padded(&key, &iv, &payload, associated_data).unwrap(),
   1575             b"hello official simplex"
   1576         );
   1577         assert!(matches!(
   1578             official_aes_gcm_decrypt_padded(&key, &iv, &payload, b"wrong-ad").unwrap_err(),
   1579             RadrootsSimplexSmpCryptoError::AesGcmAuthenticationFailed
   1580         ));
   1581     }
   1582 
   1583     #[test]
   1584     fn official_encrypted_header_and_message_wire_roundtrip() {
   1585         let header = RadrootsSimplexOfficialEncryptedHeader {
   1586             version: RADROOTS_SIMPLEX_OFFICIAL_E2E_CURRENT_VERSION,
   1587             iv: [21_u8; RADROOTS_SIMPLEX_OFFICIAL_AES_IV_LENGTH],
   1588             auth_tag: vec![22_u8; RADROOTS_SIMPLEX_OFFICIAL_AES_AUTH_TAG_LENGTH],
   1589             body: vec![23_u8; RADROOTS_SIMPLEX_OFFICIAL_RATCHET_HEADER_LENGTH],
   1590         };
   1591         let encoded_header = encode_official_encrypted_header(&header).unwrap();
   1592         assert_eq!(encoded_header.len(), 124);
   1593         assert_eq!(
   1594             decode_official_encrypted_header(&encoded_header).unwrap(),
   1595             header
   1596         );
   1597 
   1598         let message = RadrootsSimplexOfficialEncryptedMessage {
   1599             encrypted_header: encoded_header,
   1600             auth_tag: vec![24_u8; RADROOTS_SIMPLEX_OFFICIAL_AES_AUTH_TAG_LENGTH],
   1601             body: vec![25_u8; 96],
   1602         };
   1603         let encoded = encode_official_encrypted_message(
   1604             RADROOTS_SIMPLEX_OFFICIAL_E2E_CURRENT_VERSION,
   1605             &message,
   1606         )
   1607         .unwrap();
   1608         assert_eq!(encoded.len(), 2 + 124 + 16 + 96);
   1609         assert_eq!(
   1610             decode_official_encrypted_message(&encoded).unwrap(),
   1611             message
   1612         );
   1613     }
   1614 
   1615     #[test]
   1616     fn official_encrypted_message_rejects_malformed_wire_lengths() {
   1617         let header = RadrootsSimplexOfficialEncryptedHeader {
   1618             version: 3,
   1619             iv: [31_u8; RADROOTS_SIMPLEX_OFFICIAL_AES_IV_LENGTH],
   1620             auth_tag: vec![32_u8; RADROOTS_SIMPLEX_OFFICIAL_AES_AUTH_TAG_LENGTH],
   1621             body: vec![33_u8; RADROOTS_SIMPLEX_OFFICIAL_RATCHET_HEADER_LENGTH],
   1622         };
   1623         let mut encoded_header = encode_official_encrypted_header(&header).unwrap();
   1624         encoded_header.truncate(encoded_header.len() - 1);
   1625         assert!(matches!(
   1626             decode_official_encrypted_header(&encoded_header).unwrap_err(),
   1627             RadrootsSimplexSmpCryptoError::InvalidCiphertextLength(_)
   1628         ));
   1629 
   1630         let message = RadrootsSimplexOfficialEncryptedMessage {
   1631             encrypted_header: encode_official_encrypted_header(&header).unwrap(),
   1632             auth_tag: vec![34_u8; RADROOTS_SIMPLEX_OFFICIAL_AES_AUTH_TAG_LENGTH - 1],
   1633             body: vec![35_u8; 32],
   1634         };
   1635         assert!(matches!(
   1636             encode_official_encrypted_message(3, &message).unwrap_err(),
   1637             RadrootsSimplexSmpCryptoError::InvalidSignatureLength(_)
   1638         ));
   1639     }
   1640 
   1641     #[test]
   1642     fn official_kdfs_split_root_and_chain_material() {
   1643         let root = official_root_kdf(
   1644             &[1_u8; RADROOTS_SIMPLEX_OFFICIAL_AES_KEY_LENGTH],
   1645             &[2_u8; RADROOTS_SIMPLEX_OFFICIAL_X448_SHARED_SECRET_LENGTH],
   1646             Some(&[3_u8; RADROOTS_SIMPLEX_OFFICIAL_SNTRUP761_SHARED_SECRET_LENGTH]),
   1647         )
   1648         .unwrap();
   1649         let chain = official_chain_kdf(&root.chain_key).unwrap();
   1650 
   1651         assert_eq!(
   1652             root.root_key.len(),
   1653             RADROOTS_SIMPLEX_OFFICIAL_AES_KEY_LENGTH
   1654         );
   1655         assert_eq!(
   1656             root.chain_key.len(),
   1657             RADROOTS_SIMPLEX_OFFICIAL_AES_KEY_LENGTH
   1658         );
   1659         assert_eq!(
   1660             root.next_header_key.len(),
   1661             RADROOTS_SIMPLEX_OFFICIAL_AES_KEY_LENGTH
   1662         );
   1663         assert_eq!(
   1664             chain.chain_key.len(),
   1665             RADROOTS_SIMPLEX_OFFICIAL_AES_KEY_LENGTH
   1666         );
   1667         assert_eq!(
   1668             chain.message_key.len(),
   1669             RADROOTS_SIMPLEX_OFFICIAL_AES_KEY_LENGTH
   1670         );
   1671         assert_ne!(chain.message_iv, chain.header_iv);
   1672     }
   1673 }