lib

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

official_ratchet.rs (63383B)


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