lib

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

commit bf533e60cc795cc2779e061f5ec4e9c52a526019
parent 2e5af48626716b7e4a90bf5cd94be1dbc9b5f90d
Author: triesap <tyson@radroots.org>
Date:   Tue, 23 Jun 2026 06:40:16 +0000

simplex: verify short invitation link data

Diffstat:
Mcrates/simplex_agent_runtime/src/lib.rs | 5++++-
Mcrates/simplex_agent_runtime/src/runtime.rs | 71+++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------
Mcrates/simplex_smp_crypto/src/lib.rs | 1+
Mcrates/simplex_smp_crypto/src/short_link.rs | 2+-
4 files changed, 61 insertions(+), 18 deletions(-)

diff --git a/crates/simplex_agent_runtime/src/lib.rs b/crates/simplex_agent_runtime/src/lib.rs @@ -9,6 +9,9 @@ pub mod types; pub mod prelude { pub use crate::error::RadrootsSimplexAgentRuntimeError; - pub use crate::runtime::{RadrootsSimplexAgentRuntime, RadrootsSimplexAgentRuntimeBuilder}; + pub use crate::runtime::{ + RadrootsSimplexAgentRuntime, RadrootsSimplexAgentRuntimeBuilder, + decrypt_short_invitation_link_data, + }; pub use crate::types::{RadrootsSimplexAgentCommandOutcome, RadrootsSimplexAgentRuntimeEvent}; } diff --git a/crates/simplex_agent_runtime/src/runtime.rs b/crates/simplex_agent_runtime/src/runtime.rs @@ -14,7 +14,8 @@ use radroots_simplex_agent_proto::prelude::{ RadrootsSimplexAgentMessage, RadrootsSimplexAgentMessageFrame, RadrootsSimplexAgentMessageHeader, RadrootsSimplexAgentMessageReceipt, RadrootsSimplexAgentQueueAddress, RadrootsSimplexAgentQueueDescriptor, - RadrootsSimplexAgentShortLinkScheme, decode_decrypted_message, decode_envelope, + RadrootsSimplexAgentShortInvitationLink, RadrootsSimplexAgentShortLinkScheme, + decode_decrypted_message, decode_envelope, decode_short_invitation_fixed_data, encode_decrypted_message, encode_envelope, encode_short_invitation_fixed_data, encode_short_invitation_user_data, }; @@ -26,17 +27,18 @@ use radroots_simplex_agent_store::prelude::{ }; use radroots_simplex_smp_crypto::prelude::{ RADROOTS_SIMPLEX_OFFICIAL_E2E_CURRENT_VERSION, RADROOTS_SIMPLEX_OFFICIAL_E2E_KDF_VERSION, - RADROOTS_SIMPLEX_SMP_NONCE_LENGTH, RadrootsSimplexOfficialSntrup761Keypair, - RadrootsSimplexOfficialX3dhParams, RadrootsSimplexOfficialX448Keypair, - RadrootsSimplexSmpCommandAuthorization, RadrootsSimplexSmpCryptoError, - RadrootsSimplexSmpEd25519Keypair, RadrootsSimplexSmpRatchetState, - RadrootsSimplexSmpX25519Keypair, decode_x25519_public_key_x509, decrypt_padded, - derive_invitation_short_link_data_key, derive_shared_secret, encode_ed25519_public_key_x509, - encode_x25519_public_key_x509, encrypt_padded, encrypt_short_link_data, - official_sntrup761_keypair_from_seed, official_x3dh_receiver_init, - official_x3dh_receiver_init_accepting_pq, official_x3dh_sender_init, - official_x3dh_sender_init_accepting_pq, official_x448_keypair_from_seed, random_nonce, - sign_short_link_data, + RADROOTS_SIMPLEX_SMP_NONCE_LENGTH, RADROOTS_SIMPLEX_SMP_SHORT_LINK_SIGNATURE_LENGTH, + RadrootsSimplexOfficialSntrup761Keypair, RadrootsSimplexOfficialX3dhParams, + RadrootsSimplexOfficialX448Keypair, RadrootsSimplexSmpCommandAuthorization, + RadrootsSimplexSmpCryptoError, RadrootsSimplexSmpEd25519Keypair, + RadrootsSimplexSmpRatchetState, RadrootsSimplexSmpX25519Keypair, decode_x25519_public_key_x509, + decrypt_padded, decrypt_short_link_data, derive_invitation_short_link_data_key, + derive_shared_secret, encode_ed25519_public_key_x509, encode_x25519_public_key_x509, + encrypt_padded, encrypt_short_link_data, official_sntrup761_keypair_from_seed, + official_x3dh_receiver_init, official_x3dh_receiver_init_accepting_pq, + official_x3dh_sender_init, official_x3dh_sender_init_accepting_pq, + official_x448_keypair_from_seed, random_nonce, sign_short_link_data, + verify_signed_short_link_data, }; use radroots_simplex_smp_proto::prelude::{ RADROOTS_SIMPLEX_SMP_CURRENT_CLIENT_VERSION, RADROOTS_SIMPLEX_SMP_CURRENT_TRANSPORT_VERSION, @@ -91,6 +93,36 @@ struct SimplexPreparedShortInvitationLinkData { encrypted_link_data: RadrootsSimplexSmpQueueLinkData, } +pub fn decrypt_short_invitation_link_data( + invitation: &RadrootsSimplexAgentShortInvitationLink, + encrypted_link_data: &RadrootsSimplexSmpQueueLinkData, +) -> Result<RadrootsSimplexAgentConnectionLink, RadrootsSimplexAgentRuntimeError> { + let link_data_key = derive_invitation_short_link_data_key(&invitation.link_key) + .map_err(|error| RadrootsSimplexAgentRuntimeError::Runtime(error.to_string()))?; + let signed_link_data = decrypt_short_link_data(&link_data_key, encrypted_link_data) + .map_err(|error| RadrootsSimplexAgentRuntimeError::Runtime(error.to_string()))?; + if signed_link_data.fixed_data.len() <= RADROOTS_SIMPLEX_SMP_SHORT_LINK_SIGNATURE_LENGTH { + return Err(RadrootsSimplexAgentRuntimeError::Runtime( + "SimpleX short invitation fixed data is missing its signed payload".into(), + )); + } + let fixed_payload = + &signed_link_data.fixed_data[RADROOTS_SIMPLEX_SMP_SHORT_LINK_SIGNATURE_LENGTH..]; + let fixed_data = decode_short_invitation_fixed_data(fixed_payload)?; + let verified = verify_signed_short_link_data( + &invitation.link_key, + &fixed_data.root_public_signature_key, + &signed_link_data, + ) + .map_err(|error| RadrootsSimplexAgentRuntimeError::Runtime(error.to_string()))?; + if verified.user_data != encode_short_invitation_user_data(&fixed_data.invitation) { + return Err(RadrootsSimplexAgentRuntimeError::Runtime( + "SimpleX short invitation user data does not match the fixed connection link".into(), + )); + } + Ok(fixed_data.invitation) +} + pub struct RadrootsSimplexAgentRuntimeBuilder { store: Option<RadrootsSimplexAgentStore>, queue_capacity: usize, @@ -2626,14 +2658,15 @@ mod tests { .unwrap(); assert_eq!(short_link.link_id, synthetic_link_id(b"server-dh")); let link_data_key = derive_invitation_short_link_data_key(&short_link.link_key).unwrap(); + let stored_link_data = RadrootsSimplexSmpQueueLinkData { + fixed_data: short_link.encrypted_fixed_data.clone().unwrap(), + user_data: short_link.encrypted_user_data.clone().unwrap(), + }; let verified = radroots_simplex_smp_crypto::prelude::decrypt_verify_short_link_data( &short_link.link_key, &link_data_key, &short_link.link_public_signature_key, - &RadrootsSimplexSmpQueueLinkData { - fixed_data: short_link.encrypted_fixed_data.clone().unwrap(), - user_data: short_link.encrypted_user_data.clone().unwrap(), - }, + &stored_link_data, ) .unwrap(); let decoded = radroots_simplex_agent_proto::prelude::decode_short_invitation_fixed_data( @@ -2649,6 +2682,12 @@ mod tests { created.as_bytes().to_vec() ); assert_eq!(verified.user_data, created.as_bytes().to_vec()); + let decrypted_invitation = + decrypt_short_invitation_link_data(invitation, &stored_link_data).unwrap(); + assert_eq!( + decrypted_invitation.connection_id, + created.as_bytes().to_vec() + ); assert_eq!( runtime.store.connection(&joined).unwrap().status, RadrootsSimplexAgentConnectionStatus::JoinPending diff --git a/crates/simplex_smp_crypto/src/lib.rs b/crates/simplex_smp_crypto/src/lib.rs @@ -68,6 +68,7 @@ pub mod prelude { RADROOTS_SIMPLEX_SMP_SHORT_LINK_FIXED_DATA_PADDED_LENGTH, RADROOTS_SIMPLEX_SMP_SHORT_LINK_ID_LENGTH, RADROOTS_SIMPLEX_SMP_SHORT_LINK_INVITATION_INFO, RADROOTS_SIMPLEX_SMP_SHORT_LINK_KEY_LENGTH, + RADROOTS_SIMPLEX_SMP_SHORT_LINK_SIGNATURE_LENGTH, RADROOTS_SIMPLEX_SMP_SHORT_LINK_USER_DATA_PADDED_LENGTH, RadrootsSimplexSmpContactShortLinkKeyMaterial, RadrootsSimplexSmpVerifiedShortLinkData, decrypt_short_link_data, decrypt_verify_short_link_data, diff --git a/crates/simplex_smp_crypto/src/short_link.rs b/crates/simplex_smp_crypto/src/short_link.rs @@ -19,7 +19,7 @@ pub const RADROOTS_SIMPLEX_SMP_SHORT_LINK_FIXED_DATA_PADDED_LENGTH: usize = 2008 pub const RADROOTS_SIMPLEX_SMP_SHORT_LINK_USER_DATA_PADDED_LENGTH: usize = 13784; pub const RADROOTS_SIMPLEX_SMP_SHORT_LINK_CONTACT_INFO: &[u8] = b"SimpleXContactLink"; pub const RADROOTS_SIMPLEX_SMP_SHORT_LINK_INVITATION_INFO: &[u8] = b"SimpleXInvLink"; -const RADROOTS_SIMPLEX_SMP_SHORT_LINK_SIGNATURE_LENGTH: usize = 64; +pub const RADROOTS_SIMPLEX_SMP_SHORT_LINK_SIGNATURE_LENGTH: usize = 64; #[derive(Debug, Clone, PartialEq, Eq)] pub struct RadrootsSimplexSmpContactShortLinkKeyMaterial {