1mod contact_manager;
7mod db_encryption;
8mod encryption_trait;
9pub mod key_rotation;
10mod keys;
11pub mod memory_storage;
12pub mod message_history;
13mod nostr_identity;
14mod session_trait;
15pub mod sqlite_storage;
16pub mod storage_trait;
17
18#[cfg(test)]
19mod session;
20
21#[cfg(test)]
22mod encryption;
23
24#[cfg(test)]
25mod sqlcipher_tests;
26
27#[cfg(test)]
28mod message_history_tests;
29
30pub use contact_manager::{ContactInfo, ContactManager};
31pub use key_rotation::{
32 cleanup_expired_kyber_pre_keys, cleanup_expired_signed_pre_keys, consume_pre_key,
33 kyber_pre_key_needs_rotation, replenish_pre_keys, rotate_kyber_pre_key, rotate_signed_pre_key,
34 signed_pre_key_needs_rotation, GRACE_PERIOD_SECS, MIN_PRE_KEY_COUNT, REPLENISH_COUNT,
35 ROTATION_INTERVAL_SECS,
36};
37pub use message_history::{
38 Conversation, DeliveryStatus, MessageDirection, MessageHistory, MessageType, StoredMessage,
39};
40
41#[derive(Debug, Clone, Default)]
43pub struct KeyMaintenanceResult {
44 pub signed_pre_key_rotated: bool,
46 pub kyber_pre_key_rotated: bool,
48 pub pre_keys_replenished: bool,
50}
51pub use memory_storage::MemoryStorage;
52pub use nostr_identity::NostrIdentity;
53pub use sqlite_storage::SqliteStorage;
54pub use storage_trait::{
55 ExtendedIdentityStore, ExtendedKyberPreKeyStore, ExtendedPreKeyStore, ExtendedSessionStore,
56 ExtendedSignedPreKeyStore, ExtendedStorageOps, SignalStorageContainer,
57};
58
59use base64::Engine;
60use libsignal_protocol::{
61 CiphertextMessage, DeviceId, IdentityKeyPair, IdentityKeyStore, ProtocolAddress, SessionStore,
62};
63use nostr::{EventBuilder, Keys, Kind, Tag};
64use serde::{Deserialize, Serialize};
65use std::time::{SystemTime, UNIX_EPOCH};
66
67#[derive(Serialize, Deserialize, Clone)]
68struct SerializablePreKeyBundle {
69 pub registration_id: u32,
70 pub device_id: u32,
71 pub pre_key_id: Option<u32>,
72 pub pre_key_public: Option<Vec<u8>>,
73 pub signed_pre_key_id: u32,
74 pub signed_pre_key_public: Vec<u8>,
75 pub signed_pre_key_signature: Vec<u8>,
76 pub identity_key: Vec<u8>,
77 pub kyber_pre_key_id: u32,
78 pub kyber_pre_key_public: Vec<u8>,
79 pub kyber_pre_key_signature: Vec<u8>,
80}
81
82pub struct DecryptionResult {
84 pub plaintext: Vec<u8>,
86 pub should_republish_bundle: bool,
88}
89
90pub struct SignalBridge {
92 pub(crate) storage: SqliteStorage,
94 contact_manager: ContactManager,
96}
97
98impl SignalBridge {
99 async fn ensure_keys_exist(
100 storage: &mut SqliteStorage,
101 ) -> Result<IdentityKeyPair, SignalBridgeError> {
102 use crate::keys::{generate_identity_key_pair, generate_pre_keys, generate_signed_pre_key};
103 use crate::storage_trait::{
104 ExtendedIdentityStore, ExtendedKyberPreKeyStore, ExtendedPreKeyStore,
105 ExtendedSignedPreKeyStore,
106 };
107 use libsignal_protocol::*;
108
109 let identity_key_pair = match storage.identity_store().get_identity_key_pair().await {
110 Ok(existing_identity) => existing_identity,
111 Err(_) => {
112 let new_identity = generate_identity_key_pair().await?;
113 let new_registration_id = rand::random::<u32>();
114
115 storage
116 .identity_store()
117 .set_local_identity_key_pair(&new_identity)
118 .await?;
119 storage
120 .identity_store()
121 .set_local_registration_id(new_registration_id)
122 .await?;
123
124 new_identity
125 }
126 };
127
128 let current_pre_key_count = storage.pre_key_store().pre_key_count().await;
129 if current_pre_key_count < 5 {
130 let pre_keys = generate_pre_keys(1, 100).await?;
132 for (key_id, key_pair) in &pre_keys {
133 let record = PreKeyRecord::new((*key_id).into(), key_pair);
134 storage
135 .pre_key_store()
136 .save_pre_key((*key_id).into(), &record)
137 .await?;
138 }
139 }
140
141 let current_signed_pre_key_count =
142 storage.signed_pre_key_store().signed_pre_key_count().await;
143 if current_signed_pre_key_count == 0 {
144 let signed_pre_key = generate_signed_pre_key(&identity_key_pair, 1).await?;
145 storage
146 .signed_pre_key_store()
147 .save_signed_pre_key(signed_pre_key.id()?, &signed_pre_key)
148 .await?;
149 }
150
151 let current_kyber_pre_key_count = storage.kyber_pre_key_store().kyber_pre_key_count().await;
152 if current_kyber_pre_key_count == 0 {
153 let mut rng = rand::rng();
154 let kyber_keypair = kem::KeyPair::generate(kem::KeyType::Kyber1024, &mut rng);
155 let kyber_signature = identity_key_pair
156 .private_key()
157 .calculate_signature(&kyber_keypair.public_key.serialize(), &mut rng)
158 .map_err(|e| SignalBridgeError::Protocol(e.to_string()))?;
159
160 let now = std::time::SystemTime::now();
161 let kyber_record = KyberPreKeyRecord::new(
162 KyberPreKeyId::from(1u32),
163 Timestamp::from_epoch_millis(
164 now.duration_since(std::time::UNIX_EPOCH)?.as_millis() as u64,
165 ),
166 &kyber_keypair,
167 &kyber_signature,
168 );
169 storage
170 .kyber_pre_key_store()
171 .save_kyber_pre_key(KyberPreKeyId::from(1u32), &kyber_record)
172 .await?;
173 }
174
175 Ok(identity_key_pair)
176 }
177
178 pub async fn new(db_path: &str) -> Result<Self, SignalBridgeError> {
179 if let Some(parent) = std::path::Path::new(db_path).parent() {
180 std::fs::create_dir_all(parent)?;
181 }
182
183 let mut storage = SqliteStorage::new(db_path).await?;
184 storage.initialize()?;
185
186 let schema_version = storage.get_schema_version()?;
187 if schema_version < 1 {
188 return Err(SignalBridgeError::SchemaVersionTooOld);
189 }
190
191 Self::ensure_keys_exist(&mut storage).await?;
192
193 println!("SignalBridge initialized with {} storage, {} existing sessions, {} peer identities, {} pre-keys, {} signed pre-keys, {} kyber pre-keys",
194 storage.storage_type(),
195 storage.session_store().session_count().await,
196 storage.identity_store().identity_count().await,
197 storage.pre_key_store().pre_key_count().await,
198 storage.signed_pre_key_store().signed_pre_key_count().await,
199 storage.kyber_pre_key_store().kyber_pre_key_count().await);
200
201 let contact_manager = ContactManager::new(storage.connection());
202
203 Ok(Self {
204 storage,
205 contact_manager,
206 })
207 }
208
209 pub async fn encrypt_message(
210 &mut self,
211 peer: &str,
212 plaintext: &[u8],
213 ) -> Result<Vec<u8>, SignalBridgeError> {
214 if peer.is_empty() {
215 return Err(SignalBridgeError::InvalidInput(
216 "Specify a peer name".to_string(),
217 ));
218 }
219
220 let session_address = match self
221 .contact_manager
222 .lookup_contact(peer, self.storage.session_store())
223 .await
224 {
225 Ok(contact) => contact.rdx_fingerprint,
226 Err(_) => peer.to_string(),
227 };
228
229 let address = ProtocolAddress::new(
230 session_address.clone(),
231 DeviceId::new(1).map_err(|e| SignalBridgeError::Protocol(e.to_string()))?,
232 );
233
234 let session = self.storage.session_store().load_session(&address).await?;
235 if session.is_none() {
236 return Err(SignalBridgeError::SessionNotFound(format!(
237 "Establish a session with {} before sending messages",
238 peer
239 )));
240 }
241
242 let ciphertext = self.storage.encrypt_message(&address, plaintext).await?;
243
244 let timestamp = std::time::SystemTime::now()
245 .duration_since(std::time::UNIX_EPOCH)
246 .map_err(|e| SignalBridgeError::Protocol(e.to_string()))?
247 .as_millis() as u64;
248
249 if let Err(e) = self.storage.message_history().store_outgoing_message(
250 &session_address,
251 timestamp,
252 plaintext,
253 ) {
254 eprintln!("Warning: Failed to store outgoing message: {}", e);
255 }
256
257 Ok(ciphertext.serialize().to_vec())
258 }
259
260 pub async fn decrypt_message(
261 &mut self,
262 peer_hint: &str,
263 ciphertext_bytes: &[u8],
264 ) -> Result<DecryptionResult, SignalBridgeError> {
265 use crate::contact_manager::ContactManager;
266 use libsignal_protocol::{PreKeySignalMessage, SignalMessage};
267
268 if ciphertext_bytes.is_empty() {
269 return Err(SignalBridgeError::InvalidInput(
270 "Provide a message to decrypt".to_string(),
271 ));
272 }
273
274 let (session_address, pre_key_consumed, ciphertext) =
275 if let Ok(prekey_msg) = PreKeySignalMessage::try_from(ciphertext_bytes) {
276 let sender_identity = prekey_msg.identity_key();
277 let rdx_fingerprint =
278 ContactManager::generate_identity_fingerprint_from_key(sender_identity);
279
280 let contact_exists = self
281 .contact_manager
282 .lookup_contact(&rdx_fingerprint, self.storage.session_store())
283 .await
284 .is_ok();
285
286 if !contact_exists {
287 self.contact_manager
288 .add_contact_from_identity_key(sender_identity)
289 .await?;
290 }
291
292 let pre_key_consumed = prekey_msg.pre_key_id().is_some();
293 (
294 rdx_fingerprint,
295 pre_key_consumed,
296 CiphertextMessage::PreKeySignalMessage(prekey_msg),
297 )
298 } else if let Ok(signal_msg) = SignalMessage::try_from(ciphertext_bytes) {
299 if peer_hint.is_empty() {
300 return Err(SignalBridgeError::InvalidInput(
301 "SignalMessage requires peer_hint (Nostr pubkey from event wrapper)"
302 .to_string(),
303 ));
304 }
305
306 let contact = self
307 .contact_manager
308 .lookup_contact(peer_hint, self.storage.session_store())
309 .await?;
310
311 (
312 contact.rdx_fingerprint,
313 false,
314 CiphertextMessage::SignalMessage(signal_msg),
315 )
316 } else {
317 return Err(SignalBridgeError::Serialization(
318 "Provide a valid Signal Protocol message".to_string(),
319 ));
320 };
321
322 let address = ProtocolAddress::new(
323 session_address,
324 DeviceId::new(1).map_err(|e| SignalBridgeError::Protocol(e.to_string()))?,
325 );
326
327 let plaintext = self.storage.decrypt_message(&address, &ciphertext).await?;
328
329 let timestamp = std::time::SystemTime::now()
330 .duration_since(std::time::UNIX_EPOCH)
331 .map_err(|e| SignalBridgeError::Protocol(e.to_string()))?
332 .as_millis() as u64;
333
334 let session_established = pre_key_consumed;
335
336 if let Err(e) = self.storage.message_history().store_incoming_message(
337 address.name(),
338 timestamp,
339 &plaintext,
340 pre_key_consumed,
341 session_established,
342 ) {
343 eprintln!("Warning: Failed to store incoming message: {}", e);
344 }
345
346 Ok(DecryptionResult {
347 plaintext,
348 should_republish_bundle: pre_key_consumed,
349 })
350 }
351
352 pub async fn establish_session(
353 &mut self,
354 peer: &str,
355 pre_key_bundle_bytes: &[u8],
356 ) -> Result<(), SignalBridgeError> {
357 if peer.is_empty() {
358 return Err(SignalBridgeError::InvalidInput(
359 "Specify a peer name".to_string(),
360 ));
361 }
362
363 if pre_key_bundle_bytes.is_empty() {
364 return Err(SignalBridgeError::InvalidInput(
365 "Provide a pre-key bundle from the peer".to_string(),
366 ));
367 }
368 use crate::storage_trait::ExtendedStorageOps;
369 use libsignal_protocol::*;
370
371 let serializable: SerializablePreKeyBundle = bincode::deserialize(pre_key_bundle_bytes)?;
372
373 let bundle = PreKeyBundle::new(
374 serializable.registration_id,
375 DeviceId::new(serializable.device_id.try_into().map_err(|e| {
376 SignalBridgeError::InvalidInput(format!("Invalid device ID: {}", e))
377 })?)
378 .map_err(|e| SignalBridgeError::Protocol(e.to_string()))?,
379 serializable
380 .pre_key_id
381 .map(|id| {
382 let public_key =
383 PublicKey::deserialize(serializable.pre_key_public.as_ref().unwrap())
384 .map_err(|e| SignalBridgeError::Serialization(e.to_string()))?;
385 Ok::<_, SignalBridgeError>((PreKeyId::from(id), public_key))
386 })
387 .transpose()?,
388 SignedPreKeyId::from(serializable.signed_pre_key_id),
389 PublicKey::deserialize(&serializable.signed_pre_key_public)
390 .map_err(|e| SignalBridgeError::Serialization(e.to_string()))?,
391 serializable.signed_pre_key_signature,
392 KyberPreKeyId::from(serializable.kyber_pre_key_id),
393 kem::PublicKey::deserialize(&serializable.kyber_pre_key_public)
394 .map_err(|e| SignalBridgeError::Serialization(e.to_string()))?,
395 serializable.kyber_pre_key_signature,
396 IdentityKey::decode(&serializable.identity_key)
397 .map_err(|e| SignalBridgeError::Serialization(e.to_string()))?,
398 )?;
399
400 let address = ProtocolAddress::new(
401 peer.to_string(),
402 DeviceId::new(1).map_err(|e| SignalBridgeError::Protocol(e.to_string()))?,
403 );
404
405 self.storage
406 .establish_session_from_bundle(&address, &bundle)
407 .await?;
408
409 Ok(())
410 }
411
412 pub async fn generate_pre_key_bundle(
413 &mut self,
414 ) -> Result<(Vec<u8>, u32, u32, u32), SignalBridgeError> {
415 use crate::storage_trait::{
416 ExtendedKyberPreKeyStore, ExtendedPreKeyStore, ExtendedSignedPreKeyStore,
417 };
418 use libsignal_protocol::*;
419
420 let identity_key = *self
421 .storage
422 .identity_store()
423 .get_identity_key_pair()
424 .await?
425 .identity_key();
426 let registration_id = self
427 .storage
428 .identity_store()
429 .get_local_registration_id()
430 .await?;
431
432 let pre_key_id = self
435 .storage
436 .pre_key_store()
437 .get_max_pre_key_id()
438 .await?
439 .ok_or_else(|| SignalBridgeError::Protocol("No pre-keys available".to_string()))?;
440 let signed_pre_key_id = self
441 .storage
442 .signed_pre_key_store()
443 .get_max_signed_pre_key_id()
444 .await?
445 .ok_or_else(|| {
446 SignalBridgeError::Protocol("No signed pre-keys available".to_string())
447 })?;
448 let kyber_pre_key_id = self
449 .storage
450 .kyber_pre_key_store()
451 .get_max_kyber_pre_key_id()
452 .await?
453 .ok_or_else(|| {
454 SignalBridgeError::Protocol("No kyber pre-keys available".to_string())
455 })?;
456
457 let pre_key_record = self
458 .storage
459 .pre_key_store()
460 .get_pre_key(PreKeyId::from(pre_key_id))
461 .await?;
462 let signed_pre_key_record = self
463 .storage
464 .signed_pre_key_store()
465 .get_signed_pre_key(SignedPreKeyId::from(signed_pre_key_id))
466 .await?;
467 let kyber_pre_key_record = self
468 .storage
469 .kyber_pre_key_store()
470 .get_kyber_pre_key(KyberPreKeyId::from(kyber_pre_key_id))
471 .await?;
472
473 let bundle = PreKeyBundle::new(
474 registration_id,
475 DeviceId::new(1).map_err(|e| SignalBridgeError::Protocol(e.to_string()))?,
476 Some((
477 PreKeyId::from(pre_key_id),
478 pre_key_record.key_pair()?.public_key,
479 )),
480 signed_pre_key_record.id()?,
481 signed_pre_key_record.public_key()?,
482 signed_pre_key_record.signature()?.to_vec(),
483 KyberPreKeyId::from(kyber_pre_key_id),
484 kyber_pre_key_record.key_pair()?.public_key,
485 kyber_pre_key_record.signature()?.to_vec(),
486 identity_key,
487 )?;
488
489 let serializable = SerializablePreKeyBundle {
490 registration_id: bundle.registration_id()?,
491 device_id: bundle.device_id()?.into(),
492 pre_key_id: bundle.pre_key_id()?.map(|id| id.into()),
493 pre_key_public: bundle.pre_key_public()?.map(|key| key.serialize().to_vec()),
494 signed_pre_key_id: bundle.signed_pre_key_id()?.into(),
495 signed_pre_key_public: bundle.signed_pre_key_public()?.serialize().to_vec(),
496 signed_pre_key_signature: bundle.signed_pre_key_signature()?.to_vec(),
497 identity_key: bundle.identity_key()?.serialize().to_vec(),
498 kyber_pre_key_id: bundle.kyber_pre_key_id()?.into(),
499 kyber_pre_key_public: bundle.kyber_pre_key_public()?.serialize().to_vec(),
500 kyber_pre_key_signature: bundle.kyber_pre_key_signature()?.to_vec(),
501 };
502
503 let bundle_bytes = bincode::serialize(&serializable)?;
504 Ok((
505 bundle_bytes,
506 pre_key_id,
507 signed_pre_key_id,
508 kyber_pre_key_id,
509 ))
510 }
511
512 pub async fn cleanup_all_sessions(&mut self) -> Result<(), SignalBridgeError> {
513 let session_count = self.storage.session_store().session_count().await;
514 if session_count > 0 {
515 println!("Cleaning up {} sessions", session_count);
516 self.storage.session_store().clear_all_sessions().await?;
517 }
518 Ok(())
519 }
520
521 pub async fn clear_peer_session(&mut self, peer: &str) -> Result<(), SignalBridgeError> {
522 if peer.is_empty() {
523 return Err(SignalBridgeError::InvalidInput(
524 "Specify a peer name".to_string(),
525 ));
526 }
527 let address = ProtocolAddress::new(
528 peer.to_string(),
529 DeviceId::new(1).map_err(|e| SignalBridgeError::Protocol(e.to_string()))?,
530 );
531
532 self.storage
533 .session_store()
534 .delete_session(&address)
535 .await?;
536
537 if (self
538 .storage
539 .identity_store()
540 .get_peer_identity(&address)
541 .await)
542 .is_ok()
543 {
544 self.storage
545 .identity_store()
546 .delete_identity(&address)
547 .await?;
548 }
549
550 println!("Cleared session and identity for peer: {}", peer);
551 Ok(())
552 }
553
554 pub async fn clear_all_sessions(&mut self) -> Result<(), SignalBridgeError> {
555 let session_count = self.storage.session_store().session_count().await;
556 let identity_count = self.storage.identity_store().identity_count().await;
557
558 if session_count > 0 || identity_count > 0 {
559 self.storage.session_store().clear_all_sessions().await?;
560 self.storage.identity_store().clear_all_identities().await?;
561
562 println!(
563 "Cleared all {} sessions and {} peer identities",
564 session_count, identity_count
565 );
566 }
567 Ok(())
568 }
569
570 pub async fn reset_identity(&mut self) -> Result<(), SignalBridgeError> {
571 use crate::storage_trait::{
572 ExtendedIdentityStore, ExtendedKyberPreKeyStore, ExtendedPreKeyStore,
573 ExtendedSignedPreKeyStore,
574 };
575
576 println!("WARNING: Resetting identity - all existing sessions will be invalidated");
577
578 self.clear_all_sessions().await?;
579
580 self.storage.pre_key_store().clear_all_pre_keys().await?;
581 self.storage
582 .signed_pre_key_store()
583 .clear_all_signed_pre_keys()
584 .await?;
585 self.storage
586 .kyber_pre_key_store()
587 .clear_all_kyber_pre_keys()
588 .await?;
589 self.storage.identity_store().clear_local_identity().await?;
590
591 Self::ensure_keys_exist(&mut self.storage).await?;
592
593 println!("Identity reset complete - new identity generated with fresh keys");
594 Ok(())
595 }
596
597 pub async fn derive_nostr_keypair(&mut self) -> Result<Keys, SignalBridgeError> {
598 let identity_key_pair = self
599 .storage
600 .identity_store()
601 .get_identity_key_pair()
602 .await?;
603 NostrIdentity::derive_from_signal_identity(&identity_key_pair)
604 }
605
606 pub async fn derive_peer_nostr_key(
607 &mut self,
608 peer: &str,
609 ) -> Result<Vec<u8>, SignalBridgeError> {
610 let session_address = match self
611 .contact_manager
612 .lookup_contact(peer, self.storage.session_store())
613 .await
614 {
615 Ok(contact) => contact.rdx_fingerprint,
616 Err(_) => peer.to_string(),
617 };
618
619 let peer_identity = self
620 .storage
621 .identity_store()
622 .get_identity(&ProtocolAddress::new(
623 session_address,
624 DeviceId::new(1).unwrap(),
625 ))
626 .await?
627 .ok_or_else(|| {
628 SignalBridgeError::SessionNotFound(format!("No identity found for peer: {}", peer))
629 })?;
630
631 let peer_nostr_public_key =
632 NostrIdentity::derive_public_key_from_peer_identity(&peer_identity)?;
633 Ok(peer_nostr_public_key.to_bytes().to_vec())
634 }
635
636 pub async fn create_subscription_for_self(
637 &mut self,
638 subscription_id: &str,
639 since_timestamp: u64,
640 ) -> Result<String, SignalBridgeError> {
641 let keys = self.derive_nostr_keypair().await?;
642 let our_pubkey = keys.public_key().to_hex();
643
644 let since = if since_timestamp > 0 {
645 since_timestamp
646 } else {
647 self.storage.get_last_message_timestamp().map_err(|e| {
648 SignalBridgeError::Protocol(format!("Failed to get last message timestamp: {}", e))
649 })?
650 };
651
652 let mut filter = serde_json::json!({
653 "kinds": [40001],
654 "#p": [our_pubkey]
655 });
656
657 if since > 0 {
658 filter
659 .as_object_mut()
660 .unwrap()
661 .insert("since".to_string(), serde_json::json!(since));
662 }
663
664 let subscription = serde_json::json!(["REQ", subscription_id, filter]);
665
666 Ok(subscription.to_string())
667 }
668
669 pub async fn update_last_message_timestamp(
670 &mut self,
671 timestamp: u64,
672 ) -> Result<(), SignalBridgeError> {
673 self.storage
674 .set_last_message_timestamp(timestamp)
675 .map_err(|e| {
676 SignalBridgeError::Protocol(format!(
677 "Failed to update last message timestamp: {}",
678 e
679 ))
680 })
681 }
682
683 pub async fn generate_prekey_bundle_announcement(
684 &mut self,
685 project_version: &str,
686 ) -> Result<ffi::BundleInfo, SignalBridgeError> {
687 let (bundle_bytes, pre_key_id, signed_pre_key_id, kyber_pre_key_id) =
688 self.generate_pre_key_bundle().await?;
689
690 let rdx_fingerprint = self
691 .add_contact_from_bundle(&bundle_bytes, Some("self"))
692 .await?;
693
694 let bundle_base64 = base64::engine::general_purpose::STANDARD.encode(&bundle_bytes);
695 let timestamp = SystemTime::now()
696 .duration_since(UNIX_EPOCH)
697 .map_err(|e| SignalBridgeError::Protocol(e.to_string()))?
698 .as_secs();
699
700 let tags = vec![
701 vec!["d".to_string(), "radix_prekey_bundle_v1".to_string()],
702 vec!["rdx".to_string(), rdx_fingerprint.clone()],
703 vec!["radix_version".to_string(), project_version.to_string()],
704 vec!["bundle_timestamp".to_string(), timestamp.to_string()],
705 ];
706
707 let (pubkey, event_id, signature) = self
708 .sign_nostr_event(timestamp, 30078, tags.clone(), &bundle_base64)
709 .await?;
710
711 let event = serde_json::json!({
712 "id": event_id,
713 "pubkey": pubkey,
714 "created_at": timestamp,
715 "kind": 30078,
716 "tags": [
717 ["d", "radix_prekey_bundle_v1"],
718 ["rdx", rdx_fingerprint],
719 ["radix_version", project_version],
720 ["bundle_timestamp", timestamp.to_string()],
721 ],
722 "content": bundle_base64,
723 "sig": signature,
724 });
725
726 let announcement_json = serde_json::to_string(&event)
727 .map_err(|e| SignalBridgeError::Serialization(e.to_string()))?;
728
729 Ok(ffi::BundleInfo {
730 announcement_json,
731 pre_key_id,
732 signed_pre_key_id,
733 kyber_pre_key_id,
734 })
735 }
736
737 pub async fn generate_empty_bundle_announcement(
738 &mut self,
739 project_version: &str,
740 ) -> Result<String, SignalBridgeError> {
741 let timestamp = SystemTime::now()
742 .duration_since(UNIX_EPOCH)
743 .map_err(|e| SignalBridgeError::Protocol(e.to_string()))?
744 .as_secs();
745
746 let tags = vec![
747 vec!["d".to_string(), "radix_prekey_bundle_v1".to_string()],
748 vec!["radix_version".to_string(), project_version.to_string()],
749 ];
750
751 let (pubkey, event_id, signature) = self
752 .sign_nostr_event(timestamp, 30078, tags.clone(), "")
753 .await?;
754
755 let event = serde_json::json!({
756 "id": event_id,
757 "pubkey": pubkey,
758 "created_at": timestamp,
759 "kind": 30078,
760 "tags": [
761 ["d", "radix_prekey_bundle_v1"],
762 ["radix_version", project_version],
763 ],
764 "content": "",
765 "sig": signature,
766 });
767
768 serde_json::to_string(&event).map_err(|e| SignalBridgeError::Serialization(e.to_string()))
769 }
770
771 pub async fn add_contact_and_establish_session(
772 &mut self,
773 bundle_bytes: &[u8],
774 user_alias: Option<&str>,
775 ) -> Result<String, SignalBridgeError> {
776 use crate::SerializablePreKeyBundle;
777 use libsignal_protocol::IdentityKey;
778
779 let bundle: SerializablePreKeyBundle = bincode::deserialize(bundle_bytes).map_err(|e| {
780 SignalBridgeError::InvalidInput(format!("Failed to deserialize bundle: {}", e))
781 })?;
782
783 let bundle_identity_key = IdentityKey::decode(&bundle.identity_key)
784 .map_err(|e| SignalBridgeError::Protocol(e.to_string()))?;
785
786 let our_identity_pair = self
787 .storage
788 .identity_store()
789 .get_identity_key_pair()
790 .await
791 .map_err(|e| SignalBridgeError::Storage(e.to_string()))?;
792 let our_identity_key = our_identity_pair.identity_key();
793
794 if bundle_identity_key.public_key() == our_identity_key.public_key() {
795 return Err(SignalBridgeError::InvalidInput(
796 "Ignoring bundle from self".to_string(),
797 ));
798 }
799
800 let rdx = self
801 .add_contact_from_bundle(bundle_bytes, user_alias)
802 .await?;
803
804 self.establish_session(&rdx, bundle_bytes).await?;
805
806 Ok(rdx)
807 }
808
809 pub async fn sign_nostr_event(
810 &mut self,
811 created_at: u64,
812 kind: u32,
813 tags: Vec<Vec<String>>,
814 content: &str,
815 ) -> Result<(String, String, String), SignalBridgeError> {
816 let keys = self.derive_nostr_keypair().await?;
817
818 let nostr_tags: Vec<Tag> = tags
819 .into_iter()
820 .map(|tag_vec| {
821 if tag_vec.is_empty() {
822 Tag::parse(&[""]).unwrap_or_else(|_| Tag::parse(&["unknown"]).unwrap())
823 } else {
824 Tag::parse(&tag_vec).unwrap_or_else(|_| Tag::parse(&["unknown"]).unwrap())
825 }
826 })
827 .collect();
828
829 let event = EventBuilder::new(Kind::Custom(kind as u16), content, nostr_tags)
830 .custom_created_at(nostr::Timestamp::from(created_at))
831 .to_event(&keys)
832 .map_err(|e| SignalBridgeError::Protocol(format!("Failed to create event: {}", e)))?;
833
834 Ok((
835 keys.public_key().to_hex(),
836 event.id.to_hex(),
837 event.sig.to_string(),
838 ))
839 }
840
841 pub async fn generate_node_fingerprint(&mut self) -> Result<String, SignalBridgeError> {
842 let identity_key_pair = self
843 .storage
844 .identity_store()
845 .get_identity_key_pair()
846 .await?;
847
848 Ok(ContactManager::generate_identity_fingerprint_from_key(
849 identity_key_pair.identity_key(),
850 ))
851 }
852
853 pub async fn add_contact_from_bundle(
854 &mut self,
855 bundle_bytes: &[u8],
856 user_alias: Option<&str>,
857 ) -> Result<String, SignalBridgeError> {
858 self.contact_manager
859 .add_contact_from_bundle(bundle_bytes, user_alias, self.storage.session_store())
860 .await
861 }
862
863 pub async fn lookup_contact(
864 &mut self,
865 identifier: &str,
866 ) -> Result<ContactInfo, SignalBridgeError> {
867 self.contact_manager
868 .lookup_contact(identifier, self.storage.session_store())
869 .await
870 }
871
872 pub async fn assign_contact_alias(
873 &mut self,
874 identifier: &str,
875 new_alias: &str,
876 ) -> Result<(), SignalBridgeError> {
877 self.contact_manager
878 .assign_contact_alias(identifier, new_alias, self.storage.session_store())
879 .await
880 }
881
882 pub async fn list_contacts(&mut self) -> Result<Vec<ContactInfo>, SignalBridgeError> {
883 self.contact_manager
884 .list_contacts(self.storage.session_store())
885 .await
886 }
887
888 pub async fn perform_key_maintenance(
899 &mut self,
900 ) -> Result<KeyMaintenanceResult, SignalBridgeError> {
901 use crate::key_rotation::{
902 cleanup_expired_kyber_pre_keys, cleanup_expired_signed_pre_keys,
903 kyber_pre_key_needs_rotation, replenish_pre_keys, rotate_kyber_pre_key,
904 rotate_signed_pre_key, signed_pre_key_needs_rotation, MIN_PRE_KEY_COUNT,
905 };
906
907 let mut result = KeyMaintenanceResult::default();
908
909 let identity_key_pair = self
910 .storage
911 .identity_store()
912 .get_identity_key_pair()
913 .await?;
914
915 if signed_pre_key_needs_rotation(&mut self.storage).await? {
917 rotate_signed_pre_key(&mut self.storage, &identity_key_pair).await?;
918 result.signed_pre_key_rotated = true;
919 }
920
921 if kyber_pre_key_needs_rotation(&mut self.storage).await? {
923 rotate_kyber_pre_key(&mut self.storage, &identity_key_pair).await?;
924 result.kyber_pre_key_rotated = true;
925 }
926
927 cleanup_expired_signed_pre_keys(&mut self.storage).await?;
929 cleanup_expired_kyber_pre_keys(&mut self.storage).await?;
930
931 let pre_key_count = self.storage.pre_key_store().pre_key_count().await;
933 if pre_key_count < MIN_PRE_KEY_COUNT {
934 replenish_pre_keys(&mut self.storage).await?;
935 result.pre_keys_replenished = true;
936 }
937
938 Ok(result)
939 }
940}
941
942impl Drop for SignalBridge {
943 fn drop(&mut self) {
944 if !self.storage.is_closed() {
945 if let Err(e) = self.storage.close() {
946 eprintln!("Warning: Failed to close storage properly: {}", e);
947 }
948 }
949 }
950}
951
952#[derive(Debug, thiserror::Error)]
954pub enum SignalBridgeError {
955 #[error("Storage error: {0}")]
957 Storage(String),
958 #[error("Signal Protocol error: {0}")]
960 Protocol(String),
961 #[error("Serialization error: {0}")]
963 Serialization(String),
964 #[error("Invalid input: {0}")]
966 InvalidInput(String),
967 #[error("{0}")]
969 SessionNotFound(String),
970 #[error("Key derivation error: {0}")]
972 KeyDerivation(String),
973 #[error("Update your database to a newer schema version")]
975 SchemaVersionTooOld,
976}
977
978impl From<libsignal_protocol::SignalProtocolError> for SignalBridgeError {
979 fn from(err: libsignal_protocol::SignalProtocolError) -> Self {
980 SignalBridgeError::Protocol(err.to_string())
981 }
982}
983
984impl From<bincode::Error> for SignalBridgeError {
985 fn from(err: bincode::Error) -> Self {
986 SignalBridgeError::Serialization(err.to_string())
987 }
988}
989
990impl From<std::time::SystemTimeError> for SignalBridgeError {
991 fn from(err: std::time::SystemTimeError) -> Self {
992 SignalBridgeError::Protocol(err.to_string())
993 }
994}
995
996impl From<rusqlite::Error> for SignalBridgeError {
997 fn from(err: rusqlite::Error) -> Self {
998 SignalBridgeError::Storage(err.to_string())
999 }
1000}
1001
1002impl From<Box<dyn std::error::Error>> for SignalBridgeError {
1003 fn from(err: Box<dyn std::error::Error>) -> Self {
1004 SignalBridgeError::Storage(err.to_string())
1005 }
1006}
1007
1008impl From<std::num::TryFromIntError> for SignalBridgeError {
1009 fn from(err: std::num::TryFromIntError) -> Self {
1010 SignalBridgeError::InvalidInput(format!("Integer conversion error: {}", err))
1011 }
1012}
1013
1014impl From<std::io::Error> for SignalBridgeError {
1015 fn from(err: std::io::Error) -> Self {
1016 SignalBridgeError::Storage(format!("Filesystem error: {}", err))
1017 }
1018}
1019
1020#[cxx::bridge(namespace = "radix_relay")]
1021mod ffi {
1022 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
1024 #[repr(u8)]
1025 pub enum MessageDirection {
1026 Incoming = 0,
1027 Outgoing = 1,
1028 }
1029
1030 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
1032 #[repr(u8)]
1033 pub enum MessageType {
1034 Text = 0,
1035 BundleAnnouncement = 1,
1036 System = 2,
1037 }
1038
1039 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
1041 #[repr(u8)]
1042 pub enum DeliveryStatus {
1043 Pending = 0,
1044 Sent = 1,
1045 Delivered = 2,
1046 Failed = 3,
1047 }
1048
1049 #[derive(Clone, Debug)]
1050 pub struct ContactInfo {
1051 pub rdx_fingerprint: String,
1052 pub nostr_pubkey: String,
1053 pub user_alias: String,
1054 pub has_active_session: bool,
1055 }
1056
1057 #[derive(Clone, Debug, Default)]
1058 pub struct KeyMaintenanceResult {
1059 pub signed_pre_key_rotated: bool,
1060 pub kyber_pre_key_rotated: bool,
1061 pub pre_keys_replenished: bool,
1062 }
1063
1064 #[derive(Clone, Debug)]
1065 pub struct DecryptionResult {
1066 pub plaintext: Vec<u8>,
1067 pub should_republish_bundle: bool,
1068 }
1069
1070 #[derive(Clone, Debug)]
1071 pub struct BundleInfo {
1072 pub announcement_json: String,
1073 pub pre_key_id: u32,
1074 pub signed_pre_key_id: u32,
1075 pub kyber_pre_key_id: u32,
1076 }
1077
1078 #[derive(Clone, Debug)]
1079 pub struct PreKeyBundleWithMetadata {
1080 pub bundle_bytes: Vec<u8>,
1081 pub pre_key_id: u32,
1082 pub signed_pre_key_id: u32,
1083 pub kyber_pre_key_id: u32,
1084 }
1085
1086 #[derive(Clone, Debug)]
1087 pub struct StoredMessage {
1088 pub id: i64,
1089 pub conversation_id: i64,
1090 pub direction: MessageDirection,
1091 pub timestamp: u64,
1092 pub message_type: MessageType,
1093 pub content: String,
1094 pub delivery_status: DeliveryStatus,
1095 pub was_prekey_message: bool,
1096 pub session_established: bool,
1097 }
1098
1099 #[derive(Clone, Debug)]
1100 pub struct Conversation {
1101 pub id: i64,
1102 pub rdx_fingerprint: String,
1103 pub last_message_timestamp: u64,
1104 pub unread_count: u32,
1105 pub archived: bool,
1106 }
1107
1108 extern "Rust" {
1109 type SignalBridge;
1110
1111 fn new_signal_bridge(db_path: &str) -> Result<Box<SignalBridge>>;
1112
1113 fn encrypt_message(
1114 bridge: &mut SignalBridge,
1115 peer: &str,
1116 plaintext: &[u8],
1117 ) -> Result<Vec<u8>>;
1118
1119 fn decrypt_message(
1120 bridge: &mut SignalBridge,
1121 peer: &str,
1122 ciphertext: &[u8],
1123 ) -> Result<DecryptionResult>;
1124
1125 fn establish_session(bridge: &mut SignalBridge, peer: &str, bundle: &[u8]) -> Result<()>;
1126
1127 fn generate_pre_key_bundle(bridge: &mut SignalBridge) -> Result<PreKeyBundleWithMetadata>;
1128
1129 fn clear_peer_session(bridge: &mut SignalBridge, peer: &str) -> Result<()>;
1130
1131 fn clear_all_sessions(bridge: &mut SignalBridge) -> Result<()>;
1132
1133 fn reset_identity(bridge: &mut SignalBridge) -> Result<()>;
1134
1135 fn generate_node_fingerprint(bridge: &mut SignalBridge) -> Result<String>;
1136
1137 fn sign_nostr_event(bridge: &mut SignalBridge, event_json: &str) -> Result<String>;
1138
1139 fn create_and_sign_encrypted_message(
1140 bridge: &mut SignalBridge,
1141 session_id: &str,
1142 encrypted_content: &str,
1143 timestamp: u64,
1144 project_version: &str,
1145 ) -> Result<String>;
1146
1147 fn create_subscription_for_self(
1148 bridge: &mut SignalBridge,
1149 subscription_id: &str,
1150 since_timestamp: u64,
1151 ) -> Result<String>;
1152
1153 fn update_last_message_timestamp(bridge: &mut SignalBridge, timestamp: u64) -> Result<()>;
1154
1155 fn lookup_contact(bridge: &mut SignalBridge, identifier: &str) -> Result<ContactInfo>;
1156
1157 fn assign_contact_alias(
1158 bridge: &mut SignalBridge,
1159 identifier: &str,
1160 new_alias: &str,
1161 ) -> Result<()>;
1162
1163 fn list_contacts(bridge: &mut SignalBridge) -> Result<Vec<ContactInfo>>;
1164
1165 fn generate_prekey_bundle_announcement(
1166 bridge: &mut SignalBridge,
1167 project_version: &str,
1168 ) -> Result<BundleInfo>;
1169
1170 fn generate_empty_bundle_announcement(
1171 bridge: &mut SignalBridge,
1172 project_version: &str,
1173 ) -> Result<String>;
1174
1175 fn add_contact_and_establish_session(
1176 bridge: &mut SignalBridge,
1177 bundle_bytes: &[u8],
1178 user_alias: &str,
1179 ) -> Result<String>;
1180
1181 fn add_contact_and_establish_session_from_base64(
1182 bridge: &mut SignalBridge,
1183 bundle_base64: &str,
1184 user_alias: &str,
1185 ) -> Result<String>;
1186
1187 fn extract_rdx_from_bundle(
1188 bridge: &mut SignalBridge,
1189 bundle_bytes: &[u8],
1190 ) -> Result<String>;
1191
1192 fn extract_rdx_from_bundle_base64(
1193 bridge: &mut SignalBridge,
1194 bundle_base64: &str,
1195 ) -> Result<String>;
1196
1197 fn perform_key_maintenance(bridge: &mut SignalBridge) -> Result<KeyMaintenanceResult>;
1198
1199 fn record_published_bundle(
1200 bridge: &mut SignalBridge,
1201 pre_key_id: u32,
1202 signed_pre_key_id: u32,
1203 kyber_pre_key_id: u32,
1204 ) -> Result<()>;
1205
1206 fn get_conversations(
1207 bridge: &mut SignalBridge,
1208 include_archived: bool,
1209 ) -> Result<Vec<Conversation>>;
1210
1211 fn get_conversation_messages(
1212 bridge: &mut SignalBridge,
1213 rdx_fingerprint: &str,
1214 limit: u32,
1215 offset: u32,
1216 ) -> Result<Vec<StoredMessage>>;
1217
1218 fn mark_conversation_read(bridge: &mut SignalBridge, rdx_fingerprint: &str) -> Result<()>;
1219
1220 fn delete_message(bridge: &mut SignalBridge, message_id: i64) -> Result<()>;
1221
1222 fn delete_conversation(bridge: &mut SignalBridge, rdx_fingerprint: &str) -> Result<()>;
1223
1224 fn get_unread_count(bridge: &mut SignalBridge, rdx_fingerprint: &str) -> Result<u32>;
1225 }
1226}
1227
1228pub fn new_signal_bridge(db_path: &str) -> Result<Box<SignalBridge>, Box<dyn std::error::Error>> {
1233 let rt = tokio::runtime::Runtime::new()
1234 .map_err(|e| -> Box<dyn std::error::Error> { Box::new(e) })?;
1235 let bridge = rt
1236 .block_on(SignalBridge::new(db_path))
1237 .map_err(|e| -> Box<dyn std::error::Error> { Box::new(e) })?;
1238 Ok(Box::new(bridge))
1239}
1240
1241pub fn encrypt_message(
1248 bridge: &mut SignalBridge,
1249 peer: &str,
1250 plaintext: &[u8],
1251) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
1252 let rt = tokio::runtime::Runtime::new()
1253 .map_err(|e| -> Box<dyn std::error::Error> { Box::new(e) })?;
1254 rt.block_on(bridge.encrypt_message(peer, plaintext))
1255 .map_err(|e| -> Box<dyn std::error::Error> { Box::new(e) })
1256}
1257
1258pub fn decrypt_message(
1265 bridge: &mut SignalBridge,
1266 peer: &str,
1267 ciphertext: &[u8],
1268) -> Result<ffi::DecryptionResult, Box<dyn std::error::Error>> {
1269 let rt = tokio::runtime::Runtime::new()
1270 .map_err(|e| -> Box<dyn std::error::Error> { Box::new(e) })?;
1271 let result = rt
1272 .block_on(bridge.decrypt_message(peer, ciphertext))
1273 .map_err(|e| -> Box<dyn std::error::Error> { Box::new(e) })?;
1274 Ok(ffi::DecryptionResult {
1275 plaintext: result.plaintext,
1276 should_republish_bundle: result.should_republish_bundle,
1277 })
1278}
1279
1280pub fn establish_session(
1287 bridge: &mut SignalBridge,
1288 peer: &str,
1289 bundle: &[u8],
1290) -> Result<(), Box<dyn std::error::Error>> {
1291 let rt = tokio::runtime::Runtime::new()
1292 .map_err(|e| -> Box<dyn std::error::Error> { Box::new(e) })?;
1293 rt.block_on(bridge.establish_session(peer, bundle))
1294 .map_err(|e| -> Box<dyn std::error::Error> { Box::new(e) })
1295}
1296
1297pub fn generate_pre_key_bundle(
1305 bridge: &mut SignalBridge,
1306) -> Result<ffi::PreKeyBundleWithMetadata, Box<dyn std::error::Error>> {
1307 let rt = tokio::runtime::Runtime::new()
1308 .map_err(|e| -> Box<dyn std::error::Error> { Box::new(e) })?;
1309 let (bundle_bytes, pre_key_id, signed_pre_key_id, kyber_pre_key_id) = rt
1310 .block_on(bridge.generate_pre_key_bundle())
1311 .map_err(|e| -> Box<dyn std::error::Error> { Box::new(e) })?;
1312 Ok(ffi::PreKeyBundleWithMetadata {
1313 bundle_bytes,
1314 pre_key_id,
1315 signed_pre_key_id,
1316 kyber_pre_key_id,
1317 })
1318}
1319
1320pub fn clear_peer_session(
1326 bridge: &mut SignalBridge,
1327 peer: &str,
1328) -> Result<(), Box<dyn std::error::Error>> {
1329 let rt = tokio::runtime::Runtime::new()
1330 .map_err(|e| -> Box<dyn std::error::Error> { Box::new(e) })?;
1331 rt.block_on(bridge.clear_peer_session(peer))
1332 .map_err(|e| -> Box<dyn std::error::Error> { Box::new(e) })
1333}
1334
1335pub fn clear_all_sessions(bridge: &mut SignalBridge) -> Result<(), Box<dyn std::error::Error>> {
1340 let rt = tokio::runtime::Runtime::new()
1341 .map_err(|e| -> Box<dyn std::error::Error> { Box::new(e) })?;
1342 rt.block_on(bridge.clear_all_sessions())
1343 .map_err(|e| -> Box<dyn std::error::Error> { Box::new(e) })
1344}
1345
1346pub fn reset_identity(bridge: &mut SignalBridge) -> Result<(), Box<dyn std::error::Error>> {
1351 let rt = tokio::runtime::Runtime::new()
1352 .map_err(|e| -> Box<dyn std::error::Error> { Box::new(e) })?;
1353 rt.block_on(bridge.reset_identity())
1354 .map_err(|e| -> Box<dyn std::error::Error> { Box::new(e) })
1355}
1356
1357pub fn perform_key_maintenance(
1365 bridge: &mut SignalBridge,
1366) -> Result<ffi::KeyMaintenanceResult, Box<dyn std::error::Error>> {
1367 let rt = tokio::runtime::Runtime::new()
1368 .map_err(|e| -> Box<dyn std::error::Error> { Box::new(e) })?;
1369 let result = rt
1370 .block_on(bridge.perform_key_maintenance())
1371 .map_err(|e| -> Box<dyn std::error::Error> { Box::new(e) })?;
1372 Ok(ffi::KeyMaintenanceResult {
1373 signed_pre_key_rotated: result.signed_pre_key_rotated,
1374 kyber_pre_key_rotated: result.kyber_pre_key_rotated,
1375 pre_keys_replenished: result.pre_keys_replenished,
1376 })
1377}
1378
1379pub fn record_published_bundle(
1387 bridge: &mut SignalBridge,
1388 pre_key_id: u32,
1389 signed_pre_key_id: u32,
1390 kyber_pre_key_id: u32,
1391) -> Result<(), Box<dyn std::error::Error>> {
1392 bridge
1393 .storage
1394 .record_published_bundle(pre_key_id, signed_pre_key_id, kyber_pre_key_id)?;
1395 Ok(())
1396}
1397
1398pub fn get_conversations(
1404 bridge: &mut SignalBridge,
1405 include_archived: bool,
1406) -> Result<Vec<ffi::Conversation>, Box<dyn std::error::Error>> {
1407 let conversations = bridge
1408 .storage
1409 .message_history()
1410 .get_conversations(include_archived)?;
1411
1412 Ok(conversations
1413 .into_iter()
1414 .map(|c| ffi::Conversation {
1415 id: c.id,
1416 rdx_fingerprint: c.rdx_fingerprint,
1417 last_message_timestamp: c.last_message_timestamp,
1418 unread_count: c.unread_count,
1419 archived: c.archived,
1420 })
1421 .collect())
1422}
1423
1424pub fn get_conversation_messages(
1432 bridge: &mut SignalBridge,
1433 rdx_fingerprint: &str,
1434 limit: u32,
1435 offset: u32,
1436) -> Result<Vec<ffi::StoredMessage>, Box<dyn std::error::Error>> {
1437 let messages = bridge.storage.message_history().get_conversation_messages(
1438 rdx_fingerprint,
1439 limit,
1440 offset,
1441 )?;
1442
1443 Ok(messages
1444 .into_iter()
1445 .map(|m| ffi::StoredMessage {
1446 id: m.id,
1447 conversation_id: m.conversation_id,
1448 direction: match m.direction {
1449 MessageDirection::Incoming => ffi::MessageDirection::Incoming,
1450 MessageDirection::Outgoing => ffi::MessageDirection::Outgoing,
1451 },
1452 timestamp: m.timestamp,
1453 message_type: match m.message_type {
1454 MessageType::Text => ffi::MessageType::Text,
1455 MessageType::BundleAnnouncement => ffi::MessageType::BundleAnnouncement,
1456 MessageType::System => ffi::MessageType::System,
1457 },
1458 content: m.content,
1459 delivery_status: match m.delivery_status {
1460 DeliveryStatus::Pending => ffi::DeliveryStatus::Pending,
1461 DeliveryStatus::Sent => ffi::DeliveryStatus::Sent,
1462 DeliveryStatus::Delivered => ffi::DeliveryStatus::Delivered,
1463 DeliveryStatus::Failed => ffi::DeliveryStatus::Failed,
1464 },
1465 was_prekey_message: m.was_prekey_message,
1466 session_established: m.session_established,
1467 })
1468 .collect())
1469}
1470
1471pub fn mark_conversation_read(
1477 bridge: &mut SignalBridge,
1478 rdx_fingerprint: &str,
1479) -> Result<(), Box<dyn std::error::Error>> {
1480 bridge
1481 .storage
1482 .message_history()
1483 .mark_conversation_read(rdx_fingerprint)?;
1484 Ok(())
1485}
1486
1487pub fn delete_message(
1493 bridge: &mut SignalBridge,
1494 message_id: i64,
1495) -> Result<(), Box<dyn std::error::Error>> {
1496 bridge
1497 .storage
1498 .message_history()
1499 .delete_message(message_id)?;
1500 Ok(())
1501}
1502
1503pub fn delete_conversation(
1509 bridge: &mut SignalBridge,
1510 rdx_fingerprint: &str,
1511) -> Result<(), Box<dyn std::error::Error>> {
1512 bridge
1513 .storage
1514 .message_history()
1515 .delete_conversation(rdx_fingerprint)?;
1516 Ok(())
1517}
1518
1519pub fn get_unread_count(
1525 bridge: &mut SignalBridge,
1526 rdx_fingerprint: &str,
1527) -> Result<u32, Box<dyn std::error::Error>> {
1528 let count = bridge
1529 .storage
1530 .message_history()
1531 .get_unread_count(rdx_fingerprint)?;
1532 Ok(count)
1533}
1534
1535pub fn generate_node_fingerprint(
1543 bridge: &mut SignalBridge,
1544) -> Result<String, Box<dyn std::error::Error>> {
1545 let rt = tokio::runtime::Runtime::new()
1546 .map_err(|e| -> Box<dyn std::error::Error> { Box::new(e) })?;
1547 rt.block_on(bridge.generate_node_fingerprint())
1548 .map_err(|e| -> Box<dyn std::error::Error> { Box::new(e) })
1549}
1550
1551pub fn sign_nostr_event(
1560 bridge: &mut SignalBridge,
1561 event_json: &str,
1562) -> Result<String, Box<dyn std::error::Error>> {
1563 let rt = tokio::runtime::Runtime::new()
1564 .map_err(|e| -> Box<dyn std::error::Error> { Box::new(e) })?;
1565
1566 let mut event: serde_json::Value = serde_json::from_str(event_json)
1567 .map_err(|e| -> Box<dyn std::error::Error> { Box::new(e) })?;
1568
1569 let created_at = event["created_at"].as_u64().ok_or("Missing created_at")?;
1570 let kind = event["kind"].as_u64().ok_or("Missing kind")? as u32;
1571 let content = event["content"].as_str().ok_or("Missing content")?;
1572
1573 let tags_array = event["tags"].as_array().ok_or("Missing tags")?;
1574 let tags: Vec<Vec<String>> = tags_array
1575 .iter()
1576 .map(|tag| {
1577 tag.as_array()
1578 .unwrap_or(&vec![])
1579 .iter()
1580 .map(|v| v.as_str().unwrap_or("").to_string())
1581 .collect()
1582 })
1583 .collect();
1584
1585 let (pubkey, event_id, signature) = rt
1586 .block_on(bridge.sign_nostr_event(created_at, kind, tags, content))
1587 .map_err(|e| -> Box<dyn std::error::Error> { Box::new(e) })?;
1588
1589 event["pubkey"] = serde_json::Value::String(pubkey);
1590 event["id"] = serde_json::Value::String(event_id);
1591 event["sig"] = serde_json::Value::String(signature);
1592
1593 serde_json::to_string(&event).map_err(|e| -> Box<dyn std::error::Error> { Box::new(e) })
1594}
1595
1596pub fn create_and_sign_encrypted_message(
1608 bridge: &mut SignalBridge,
1609 session_id: &str,
1610 encrypted_content: &str,
1611 timestamp: u64,
1612 project_version: &str,
1613) -> Result<String, Box<dyn std::error::Error>> {
1614 let rt = tokio::runtime::Runtime::new()
1615 .map_err(|e| -> Box<dyn std::error::Error> { Box::new(e) })?;
1616
1617 let recipient_pubkey_bytes = rt
1619 .block_on(bridge.derive_peer_nostr_key(session_id))
1620 .map_err(|e| -> Box<dyn std::error::Error> { Box::new(e) })?;
1621 let recipient_pubkey = hex::encode(&recipient_pubkey_bytes);
1622
1623 let event_json = serde_json::json!({
1625 "id": "",
1626 "pubkey": "",
1627 "created_at": timestamp,
1628 "kind": 40001,
1629 "tags": [
1630 ["p", recipient_pubkey],
1631 ["radix_version", project_version]
1632 ],
1633 "content": encrypted_content,
1634 "sig": ""
1635 });
1636
1637 let event_json_str = serde_json::to_string(&event_json)
1639 .map_err(|e| -> Box<dyn std::error::Error> { Box::new(e) })?;
1640
1641 sign_nostr_event(bridge, &event_json_str)
1643}
1644
1645pub fn create_subscription_for_self(
1655 bridge: &mut SignalBridge,
1656 subscription_id: &str,
1657 since_timestamp: u64,
1658) -> Result<String, Box<dyn std::error::Error>> {
1659 let rt = tokio::runtime::Runtime::new()
1660 .map_err(|e| -> Box<dyn std::error::Error> { Box::new(e) })?;
1661 rt.block_on(bridge.create_subscription_for_self(subscription_id, since_timestamp))
1662 .map_err(|e| -> Box<dyn std::error::Error> { Box::new(e) })
1663}
1664
1665pub fn update_last_message_timestamp(
1671 bridge: &mut SignalBridge,
1672 timestamp: u64,
1673) -> Result<(), Box<dyn std::error::Error>> {
1674 let rt = tokio::runtime::Runtime::new()
1675 .map_err(|e| -> Box<dyn std::error::Error> { Box::new(e) })?;
1676 rt.block_on(bridge.update_last_message_timestamp(timestamp))
1677 .map_err(|e| -> Box<dyn std::error::Error> { Box::new(e) })
1678}
1679
1680pub fn lookup_contact(
1689 bridge: &mut SignalBridge,
1690 identifier: &str,
1691) -> Result<ffi::ContactInfo, Box<dyn std::error::Error>> {
1692 let rt = tokio::runtime::Runtime::new()?;
1693 let contact = rt.block_on(bridge.lookup_contact(identifier))?;
1694 Ok(ffi::ContactInfo {
1695 rdx_fingerprint: contact.rdx_fingerprint,
1696 nostr_pubkey: contact.nostr_pubkey,
1697 user_alias: contact.user_alias.unwrap_or_default(),
1698 has_active_session: contact.has_active_session,
1699 })
1700}
1701
1702pub fn assign_contact_alias(
1709 bridge: &mut SignalBridge,
1710 identifier: &str,
1711 new_alias: &str,
1712) -> Result<(), Box<dyn std::error::Error>> {
1713 let rt = tokio::runtime::Runtime::new()?;
1714 rt.block_on(bridge.assign_contact_alias(identifier, new_alias))
1715 .map_err(|e| -> Box<dyn std::error::Error> { Box::new(e) })
1716}
1717
1718pub fn list_contacts(
1726 bridge: &mut SignalBridge,
1727) -> Result<Vec<ffi::ContactInfo>, Box<dyn std::error::Error>> {
1728 let rt = tokio::runtime::Runtime::new()?;
1729 let contacts = rt.block_on(bridge.list_contacts())?;
1730 Ok(contacts
1731 .into_iter()
1732 .map(|c| ffi::ContactInfo {
1733 rdx_fingerprint: c.rdx_fingerprint,
1734 nostr_pubkey: c.nostr_pubkey,
1735 user_alias: c.user_alias.unwrap_or_default(),
1736 has_active_session: c.has_active_session,
1737 })
1738 .collect())
1739}
1740
1741pub fn generate_prekey_bundle_announcement(
1750 bridge: &mut SignalBridge,
1751 project_version: &str,
1752) -> Result<ffi::BundleInfo, Box<dyn std::error::Error>> {
1753 let rt = tokio::runtime::Runtime::new()?;
1754 rt.block_on(bridge.generate_prekey_bundle_announcement(project_version))
1755 .map_err(|e| -> Box<dyn std::error::Error> { Box::new(e) })
1756}
1757
1758pub fn generate_empty_bundle_announcement(
1767 bridge: &mut SignalBridge,
1768 project_version: &str,
1769) -> Result<String, Box<dyn std::error::Error>> {
1770 let rt = tokio::runtime::Runtime::new()?;
1771 rt.block_on(bridge.generate_empty_bundle_announcement(project_version))
1772 .map_err(|e| -> Box<dyn std::error::Error> { Box::new(e) })
1773}
1774
1775pub fn add_contact_and_establish_session(
1785 bridge: &mut SignalBridge,
1786 bundle_bytes: &[u8],
1787 user_alias: &str,
1788) -> Result<String, Box<dyn std::error::Error>> {
1789 let rt = tokio::runtime::Runtime::new()?;
1790 let alias = if user_alias.is_empty() {
1791 None
1792 } else {
1793 Some(user_alias)
1794 };
1795 rt.block_on(bridge.add_contact_and_establish_session(bundle_bytes, alias))
1796 .map_err(|e| -> Box<dyn std::error::Error> { Box::new(e) })
1797}
1798
1799pub fn add_contact_and_establish_session_from_base64(
1809 bridge: &mut SignalBridge,
1810 bundle_base64: &str,
1811 user_alias: &str,
1812) -> Result<String, Box<dyn std::error::Error>> {
1813 use base64::Engine;
1814 let bundle_bytes = base64::engine::general_purpose::STANDARD
1815 .decode(bundle_base64)
1816 .map_err(|e| -> Box<dyn std::error::Error> {
1817 Box::new(SignalBridgeError::Protocol(format!(
1818 "Base64 decode error: {}",
1819 e
1820 )))
1821 })?;
1822
1823 add_contact_and_establish_session(bridge, &bundle_bytes, user_alias)
1824}
1825
1826pub fn extract_rdx_from_bundle(
1835 _bridge: &mut SignalBridge,
1836 bundle_bytes: &[u8],
1837) -> Result<String, Box<dyn std::error::Error>> {
1838 use libsignal_protocol::IdentityKey;
1839
1840 let bundle: SerializablePreKeyBundle =
1841 bincode::deserialize(bundle_bytes).map_err(|e| -> Box<dyn std::error::Error> {
1842 Box::new(SignalBridgeError::InvalidInput(format!(
1843 "Failed to deserialize bundle: {}",
1844 e
1845 )))
1846 })?;
1847
1848 let identity_key =
1849 IdentityKey::decode(&bundle.identity_key).map_err(|e| -> Box<dyn std::error::Error> {
1850 Box::new(SignalBridgeError::Protocol(e.to_string()))
1851 })?;
1852
1853 Ok(ContactManager::generate_identity_fingerprint_from_key(
1854 &identity_key,
1855 ))
1856}
1857
1858pub fn extract_rdx_from_bundle_base64(
1867 bridge: &mut SignalBridge,
1868 bundle_base64: &str,
1869) -> Result<String, Box<dyn std::error::Error>> {
1870 use base64::Engine;
1871 let bundle_bytes = base64::engine::general_purpose::STANDARD
1872 .decode(bundle_base64)
1873 .map_err(|e| -> Box<dyn std::error::Error> {
1874 Box::new(SignalBridgeError::Protocol(format!(
1875 "Base64 decode error: {}",
1876 e
1877 )))
1878 })?;
1879
1880 extract_rdx_from_bundle(bridge, &bundle_bytes)
1881}
1882
1883#[cfg(test)]
1884mod tests {
1885 use super::*;
1886 use std::time::{SystemTime, UNIX_EPOCH};
1887
1888 #[test]
1889 fn test_libsignal_basic_types() {
1890 let device_id = DeviceId::new(1).expect("Valid device ID");
1891 let protocol_address = ProtocolAddress::new("test_device".to_string(), device_id);
1892 assert_eq!(protocol_address.name(), "test_device");
1893 assert_eq!(protocol_address.device_id(), device_id);
1894 }
1895
1896 #[tokio::test]
1897 async fn test_nostr_key_derivation_deterministic() {
1898 let temp_dir = std::env::temp_dir();
1899 let db_path = temp_dir.join("test_nostr_derivation.db");
1900 let mut bridge = SignalBridge::new(db_path.to_str().unwrap()).await.unwrap();
1901
1902 let nostr_keypair1 = bridge.derive_nostr_keypair().await.unwrap();
1903 let nostr_keypair2 = bridge.derive_nostr_keypair().await.unwrap();
1904
1905 assert_eq!(nostr_keypair1.secret_key(), nostr_keypair2.secret_key());
1906 assert_eq!(nostr_keypair1.public_key(), nostr_keypair2.public_key());
1907
1908 let _ = std::fs::remove_file(&db_path);
1909 }
1910
1911 #[tokio::test]
1912 async fn test_peer_nostr_key_derivation() {
1913 let temp_dir = std::env::temp_dir();
1914 let alice_db = temp_dir.join("test_alice_nostr.db");
1915 let bob_db = temp_dir.join("test_bob_nostr.db");
1916
1917 let mut alice = SignalBridge::new(alice_db.to_str().unwrap()).await.unwrap();
1918 let mut bob = SignalBridge::new(bob_db.to_str().unwrap()).await.unwrap();
1919
1920 let (bob_bundle, _, _, _) = bob.generate_pre_key_bundle().await.unwrap();
1921 let bob_rdx = alice
1922 .add_contact_and_establish_session(&bob_bundle, Some("bob"))
1923 .await
1924 .unwrap();
1925
1926 let bob_nostr_key = alice.derive_peer_nostr_key(&bob_rdx).await.unwrap();
1927
1928 let bob_actual_nostr = bob.derive_nostr_keypair().await.unwrap();
1929 assert_eq!(
1930 bob_nostr_key,
1931 bob_actual_nostr.public_key().to_bytes().to_vec()
1932 );
1933
1934 let _ = std::fs::remove_file(&alice_db);
1935 let _ = std::fs::remove_file(&bob_db);
1936 }
1937
1938 #[test]
1939 fn test_error_types_creation() {
1940 let storage_error = SignalBridgeError::Storage("Database connection failed".to_string());
1941 assert_eq!(
1942 storage_error.to_string(),
1943 "Storage error: Database connection failed"
1944 );
1945
1946 let protocol_error = SignalBridgeError::Protocol("Invalid device ID".to_string());
1947 assert_eq!(
1948 protocol_error.to_string(),
1949 "Signal Protocol error: Invalid device ID"
1950 );
1951
1952 let serialization_error =
1953 SignalBridgeError::Serialization("Invalid data format".to_string());
1954 assert_eq!(
1955 serialization_error.to_string(),
1956 "Serialization error: Invalid data format"
1957 );
1958
1959 let invalid_input_error = SignalBridgeError::InvalidInput("Empty peer name".to_string());
1960 assert_eq!(
1961 invalid_input_error.to_string(),
1962 "Invalid input: Empty peer name"
1963 );
1964
1965 let session_not_found_error = SignalBridgeError::SessionNotFound(
1966 "Establish a session with alice before sending messages".to_string(),
1967 );
1968 assert_eq!(
1969 session_not_found_error.to_string(),
1970 "Establish a session with alice before sending messages"
1971 );
1972
1973 let schema_error = SignalBridgeError::SchemaVersionTooOld;
1974 assert_eq!(
1975 schema_error.to_string(),
1976 "Update your database to a newer schema version"
1977 );
1978 }
1979
1980 #[test]
1981 fn test_error_from_conversions() {
1982 let device_result = DeviceId::new(0);
1983 if let Err(protocol_err) = device_result {
1984 let bridge_error = SignalBridgeError::Protocol(protocol_err.to_string());
1985 assert!(matches!(bridge_error, SignalBridgeError::Protocol(_)));
1986 }
1987
1988 let invalid_data = vec![0xFF, 0xFF, 0xFF];
1989 let bincode_result: Result<String, bincode::Error> = bincode::deserialize(&invalid_data);
1990 if let Err(bincode_err) = bincode_result {
1991 let bridge_error: SignalBridgeError = bincode_err.into();
1992 assert!(matches!(bridge_error, SignalBridgeError::Serialization(_)));
1993 }
1994
1995 use std::time::{Duration, SystemTime};
1996 let bad_time = SystemTime::UNIX_EPOCH - Duration::from_secs(1);
1997 let time_result = bad_time.duration_since(SystemTime::UNIX_EPOCH);
1998 if let Err(time_err) = time_result {
1999 let bridge_error: SignalBridgeError = time_err.into();
2000 assert!(matches!(bridge_error, SignalBridgeError::Protocol(_)));
2001 }
2002 }
2003
2004 #[tokio::test]
2005 async fn test_crypto_handler_exists() -> Result<(), Box<dyn std::error::Error>> {
2006 use crate::SignalBridge;
2007 use std::env;
2008 use std::fs;
2009
2010 let temp_dir = env::temp_dir();
2011 let db_path = temp_dir.join("test_crypto_handler.db");
2012 let db_path_str = db_path.to_str().unwrap();
2013
2014 let _ = fs::remove_file(&db_path);
2015 let key_path = format!("{}.key", db_path_str);
2016 let _ = fs::remove_file(&key_path);
2017
2018 let _handler = SignalBridge::new(db_path_str).await?;
2019 Ok(())
2020 }
2021
2022 #[tokio::test]
2023 async fn test_crypto_handler_alice_bob_integration() -> Result<(), Box<dyn std::error::Error>> {
2024 use crate::SignalBridge;
2025 use std::env;
2026
2027 let temp_dir = env::temp_dir();
2028 let process_id = std::process::id();
2029 let timestamp = std::time::SystemTime::now()
2030 .duration_since(std::time::UNIX_EPOCH)
2031 .unwrap()
2032 .as_millis();
2033
2034 let alice_db_path = temp_dir.join(format!("test_alice_{}_{}.db", process_id, timestamp));
2035 let alice_db_str = alice_db_path.to_str().unwrap();
2036 let mut alice = SignalBridge::new(alice_db_str).await?;
2037
2038 let bob_db_path = temp_dir.join(format!("test_bob_{}_{}.db", process_id, timestamp));
2039 let bob_db_str = bob_db_path.to_str().unwrap();
2040 let mut bob = SignalBridge::new(bob_db_str).await?;
2041
2042 let (bob_bundle, _, _, _) = bob.generate_pre_key_bundle().await?;
2043 let (alice_bundle, _, _, _) = alice.generate_pre_key_bundle().await?;
2044
2045 let bob_rdx = alice
2046 .add_contact_and_establish_session(&bob_bundle, Some("bob"))
2047 .await?;
2048 let alice_rdx = bob
2049 .add_contact_and_establish_session(&alice_bundle, Some("alice"))
2050 .await?;
2051
2052 let plaintext = b"Hello Bob! This is Alice using SignalBridge.";
2053 let ciphertext = alice.encrypt_message(&bob_rdx, plaintext).await?;
2054 let result = bob.decrypt_message(&alice_rdx, &ciphertext).await?;
2055
2056 assert_eq!(plaintext.as_slice(), result.plaintext.as_slice());
2057
2058 Ok(())
2059 }
2060
2061 #[tokio::test]
2062 async fn test_signalbridge_persistence_across_instantiations(
2063 ) -> Result<(), Box<dyn std::error::Error>> {
2064 use crate::SignalBridge;
2065 use std::env;
2066
2067 let temp_dir = env::temp_dir();
2068 let process_id = std::process::id();
2069 let timestamp = std::time::SystemTime::now()
2070 .duration_since(std::time::UNIX_EPOCH)
2071 .unwrap()
2072 .as_millis();
2073
2074 let alice_db_path = temp_dir.join(format!(
2075 "test_persistence_alice_{}_{}.db",
2076 process_id, timestamp
2077 ));
2078 let alice_db_str = alice_db_path.to_str().unwrap();
2079 let bob_db_path = temp_dir.join(format!(
2080 "test_persistence_bob_{}_{}.db",
2081 process_id, timestamp
2082 ));
2083 let bob_db_str = bob_db_path.to_str().unwrap();
2084
2085 let original_plaintext = b"Hello Bob! This is Alice testing persistence.";
2086 let (ciphertext, bob_rdx, alice_rdx) = {
2087 let mut alice = SignalBridge::new(alice_db_str).await?;
2088 let mut bob = SignalBridge::new(bob_db_str).await?;
2089
2090 let (bob_bundle, _, _, _) = bob.generate_pre_key_bundle().await?;
2091 let (alice_bundle, _, _, _) = alice.generate_pre_key_bundle().await?;
2092
2093 let bob_rdx = alice
2094 .add_contact_and_establish_session(&bob_bundle, Some("bob"))
2095 .await?;
2096 let alice_rdx = bob
2097 .add_contact_and_establish_session(&alice_bundle, Some("alice"))
2098 .await?;
2099
2100 let ciphertext = alice.encrypt_message(&bob_rdx, original_plaintext).await?;
2101 (ciphertext, bob_rdx, alice_rdx)
2102 };
2103
2104 {
2105 let mut alice_reopened = SignalBridge::new(alice_db_str).await?;
2106
2107 let second_message = b"Second message after restart";
2108 let second_ciphertext = alice_reopened
2109 .encrypt_message(&bob_rdx, second_message)
2110 .await?;
2111
2112 assert!(!second_ciphertext.is_empty());
2113 assert_ne!(ciphertext, second_ciphertext);
2114 }
2115
2116 {
2117 let mut bob_reopened = SignalBridge::new(bob_db_str).await?;
2118
2119 let result1 = bob_reopened
2120 .decrypt_message(&alice_rdx, &ciphertext)
2121 .await?;
2122 assert_eq!(result1.plaintext, original_plaintext);
2123 }
2124
2125 Ok(())
2126 }
2127
2128 #[tokio::test]
2129 async fn test_clear_peer_session() -> Result<(), Box<dyn std::error::Error>> {
2130 use crate::SignalBridge;
2131 use std::env;
2132
2133 let temp_dir = env::temp_dir();
2134 let process_id = std::process::id();
2135 let timestamp = std::time::SystemTime::now()
2136 .duration_since(std::time::UNIX_EPOCH)
2137 .unwrap()
2138 .as_millis();
2139
2140 let alice_db_path = temp_dir.join(format!(
2141 "test_clear_peer_alice_{}_{}.db",
2142 process_id, timestamp
2143 ));
2144 let alice_db_str = alice_db_path.to_str().unwrap();
2145 let bob_db_path = temp_dir.join(format!(
2146 "test_clear_peer_bob_{}_{}.db",
2147 process_id, timestamp
2148 ));
2149 let bob_db_str = bob_db_path.to_str().unwrap();
2150
2151 let mut alice = SignalBridge::new(alice_db_str).await?;
2152 let mut bob = SignalBridge::new(bob_db_str).await?;
2153
2154 let (bob_bundle, _, _, _) = bob.generate_pre_key_bundle().await?;
2155 let bob_rdx = alice
2156 .add_contact_and_establish_session(&bob_bundle, Some("bob"))
2157 .await?;
2158
2159 let message = b"Test message";
2160 let _ciphertext = alice.encrypt_message(&bob_rdx, message).await?;
2161
2162 alice.clear_peer_session(&bob_rdx).await?;
2163
2164 let result = alice.encrypt_message(&bob_rdx, message).await;
2165 assert!(
2166 result.is_err(),
2167 "Encryption should fail after clearing peer session"
2168 );
2169
2170 Ok(())
2171 }
2172
2173 #[tokio::test]
2174 async fn test_clear_all_sessions() -> Result<(), Box<dyn std::error::Error>> {
2175 use crate::SignalBridge;
2176 use std::env;
2177
2178 let temp_dir = env::temp_dir();
2179 let process_id = std::process::id();
2180 let timestamp = std::time::SystemTime::now()
2181 .duration_since(std::time::UNIX_EPOCH)
2182 .unwrap()
2183 .as_millis();
2184
2185 let alice_db_path = temp_dir.join(format!(
2186 "test_clear_all_alice_{}_{}.db",
2187 process_id, timestamp
2188 ));
2189 let alice_db_str = alice_db_path.to_str().unwrap();
2190 let bob_db_path = temp_dir.join(format!(
2191 "test_clear_all_bob_{}_{}.db",
2192 process_id, timestamp
2193 ));
2194 let bob_db_str = bob_db_path.to_str().unwrap();
2195 let charlie_db_path = temp_dir.join(format!(
2196 "test_clear_all_charlie_{}_{}.db",
2197 process_id, timestamp
2198 ));
2199 let charlie_db_str = charlie_db_path.to_str().unwrap();
2200
2201 let mut alice = SignalBridge::new(alice_db_str).await?;
2202 let mut bob = SignalBridge::new(bob_db_str).await?;
2203 let mut charlie = SignalBridge::new(charlie_db_str).await?;
2204
2205 let (bob_bundle, _, _, _) = bob.generate_pre_key_bundle().await?;
2206 let (charlie_bundle, _, _, _) = charlie.generate_pre_key_bundle().await?;
2207
2208 let bob_rdx = alice
2209 .add_contact_and_establish_session(&bob_bundle, Some("bob"))
2210 .await?;
2211 let charlie_rdx = alice
2212 .add_contact_and_establish_session(&charlie_bundle, Some("charlie"))
2213 .await?;
2214
2215 let message = b"Test message";
2216 let _ciphertext1 = alice.encrypt_message(&bob_rdx, message).await?;
2217 let _ciphertext2 = alice.encrypt_message(&charlie_rdx, message).await?;
2218
2219 alice.clear_all_sessions().await?;
2220
2221 let result1 = alice.encrypt_message(&bob_rdx, message).await;
2222 let result2 = alice.encrypt_message(&charlie_rdx, message).await;
2223 assert!(
2224 result1.is_err(),
2225 "Encryption to Bob should fail after clearing all sessions"
2226 );
2227 assert!(
2228 result2.is_err(),
2229 "Encryption to Charlie should fail after clearing all sessions"
2230 );
2231
2232 Ok(())
2233 }
2234
2235 #[tokio::test]
2236 async fn test_reset_identity() -> Result<(), Box<dyn std::error::Error>> {
2237 use crate::SignalBridge;
2238 use std::env;
2239
2240 let temp_dir = env::temp_dir();
2241 let process_id = std::process::id();
2242 let timestamp = std::time::SystemTime::now()
2243 .duration_since(std::time::UNIX_EPOCH)
2244 .unwrap()
2245 .as_millis();
2246
2247 let alice_db_path = temp_dir.join(format!(
2248 "test_reset_identity_alice_{}_{}.db",
2249 process_id, timestamp
2250 ));
2251 let alice_db_str = alice_db_path.to_str().unwrap();
2252 let bob_db_path = temp_dir.join(format!(
2253 "test_reset_identity_bob_{}_{}.db",
2254 process_id, timestamp
2255 ));
2256 let bob_db_str = bob_db_path.to_str().unwrap();
2257
2258 let (original_alice_bundle, bob_rdx) = {
2259 let mut alice = SignalBridge::new(alice_db_str).await?;
2260 let mut bob = SignalBridge::new(bob_db_str).await?;
2261
2262 let (bob_bundle, _, _, _) = bob.generate_pre_key_bundle().await?;
2263 let bob_rdx = alice
2264 .add_contact_and_establish_session(&bob_bundle, Some("bob"))
2265 .await?;
2266
2267 let (alice_bundle, _, _, _) = alice.generate_pre_key_bundle().await?;
2268 (alice_bundle, bob_rdx)
2269 };
2270
2271 {
2272 let mut alice = SignalBridge::new(alice_db_str).await?;
2273 alice.reset_identity().await?;
2274
2275 let (new_alice_bundle, _, _, _) = alice.generate_pre_key_bundle().await?;
2276 assert_ne!(
2277 original_alice_bundle, new_alice_bundle,
2278 "Alice's identity should be different after reset"
2279 );
2280
2281 let message = b"Test message";
2282 let result = alice.encrypt_message(&bob_rdx, message).await;
2283 assert!(
2284 result.is_err(),
2285 "Encryption should fail after identity reset"
2286 );
2287 }
2288
2289 Ok(())
2290 }
2291
2292 #[tokio::test]
2293 async fn test_encrypt_message_empty_peer_name() {
2294 use std::env;
2295
2296 let temp_dir = env::temp_dir();
2297 let process_id = std::process::id();
2298 let timestamp = std::time::SystemTime::now()
2299 .duration_since(std::time::UNIX_EPOCH)
2300 .unwrap()
2301 .as_millis();
2302
2303 let db_path = temp_dir.join(format!("test_empty_peer_{}_{}.db", process_id, timestamp));
2304 let db_path_str = db_path.to_str().unwrap();
2305 let mut bridge = SignalBridge::new(db_path_str)
2306 .await
2307 .expect("Failed to create bridge");
2308
2309 let result = bridge.encrypt_message("", b"test message").await;
2310 assert!(result.is_err());
2311 if let Err(SignalBridgeError::InvalidInput(msg)) = result {
2312 assert_eq!(msg, "Specify a peer name");
2313 } else {
2314 panic!("Expected InvalidInput error for empty peer name");
2315 }
2316 }
2317
2318 #[tokio::test]
2319 async fn test_decrypt_message_empty_ciphertext() {
2320 use std::env;
2321
2322 let temp_dir = env::temp_dir();
2323 let process_id = std::process::id();
2324 let timestamp = std::time::SystemTime::now()
2325 .duration_since(std::time::UNIX_EPOCH)
2326 .unwrap()
2327 .as_millis();
2328
2329 let db_path = temp_dir.join(format!(
2330 "test_empty_ciphertext_{}_{}.db",
2331 process_id, timestamp
2332 ));
2333 let db_path_str = db_path.to_str().unwrap();
2334 let mut bridge = SignalBridge::new(db_path_str)
2335 .await
2336 .expect("Failed to create bridge");
2337
2338 let result = bridge.decrypt_message("alice", &[]).await;
2339 assert!(result.is_err());
2340 if let Err(SignalBridgeError::InvalidInput(msg)) = result {
2341 assert_eq!(msg, "Provide a message to decrypt");
2342 } else {
2343 panic!("Expected InvalidInput error for empty ciphertext");
2344 }
2345 }
2346
2347 #[tokio::test]
2348 async fn test_establish_session_empty_peer_name() {
2349 use std::env;
2350
2351 let temp_dir = env::temp_dir();
2352 let process_id = std::process::id();
2353 let timestamp = std::time::SystemTime::now()
2354 .duration_since(std::time::UNIX_EPOCH)
2355 .unwrap()
2356 .as_millis();
2357
2358 let db_path = temp_dir.join(format!(
2359 "test_empty_peer_session_{}_{}.db",
2360 process_id, timestamp
2361 ));
2362 let db_path_str = db_path.to_str().unwrap();
2363 let mut bridge = SignalBridge::new(db_path_str)
2364 .await
2365 .expect("Failed to create bridge");
2366
2367 let result = bridge.establish_session("", b"fake_bundle").await;
2368 assert!(result.is_err());
2369 if let Err(SignalBridgeError::InvalidInput(msg)) = result {
2370 assert_eq!(msg, "Specify a peer name");
2371 } else {
2372 panic!("Expected InvalidInput error for empty peer name");
2373 }
2374 }
2375
2376 #[tokio::test]
2377 async fn test_establish_session_empty_bundle() {
2378 use std::env;
2379
2380 let temp_dir = env::temp_dir();
2381 let process_id = std::process::id();
2382 let timestamp = std::time::SystemTime::now()
2383 .duration_since(std::time::UNIX_EPOCH)
2384 .unwrap()
2385 .as_millis();
2386
2387 let db_path = temp_dir.join(format!("test_empty_bundle_{}_{}.db", process_id, timestamp));
2388 let db_path_str = db_path.to_str().unwrap();
2389 let mut bridge = SignalBridge::new(db_path_str)
2390 .await
2391 .expect("Failed to create bridge");
2392
2393 let result = bridge.establish_session("alice", &[]).await;
2394 assert!(result.is_err());
2395 if let Err(SignalBridgeError::InvalidInput(msg)) = result {
2396 assert_eq!(msg, "Provide a pre-key bundle from the peer");
2397 } else {
2398 panic!("Expected InvalidInput error for empty pre-key bundle");
2399 }
2400 }
2401
2402 #[tokio::test]
2403 async fn test_clear_peer_session_empty_peer_name() {
2404 use std::env;
2405
2406 let temp_dir = env::temp_dir();
2407 let process_id = std::process::id();
2408 let timestamp = std::time::SystemTime::now()
2409 .duration_since(std::time::UNIX_EPOCH)
2410 .unwrap()
2411 .as_millis();
2412
2413 let db_path = temp_dir.join(format!(
2414 "test_clear_empty_peer_{}_{}.db",
2415 process_id, timestamp
2416 ));
2417 let db_path_str = db_path.to_str().unwrap();
2418 let mut bridge = SignalBridge::new(db_path_str)
2419 .await
2420 .expect("Failed to create bridge");
2421
2422 let result = bridge.clear_peer_session("").await;
2423 assert!(result.is_err());
2424 if let Err(SignalBridgeError::InvalidInput(msg)) = result {
2425 assert_eq!(msg, "Specify a peer name");
2426 } else {
2427 panic!("Expected InvalidInput error for empty peer name");
2428 }
2429 }
2430
2431 #[tokio::test]
2432 async fn test_encrypt_message_session_not_found() {
2433 use std::env;
2434
2435 let temp_dir = env::temp_dir();
2436 let process_id = std::process::id();
2437 let timestamp = std::time::SystemTime::now()
2438 .duration_since(std::time::UNIX_EPOCH)
2439 .unwrap()
2440 .as_millis();
2441
2442 let db_path = temp_dir.join(format!(
2443 "test_session_not_found_{}_{}.db",
2444 process_id, timestamp
2445 ));
2446 let db_path_str = db_path.to_str().unwrap();
2447 let mut bridge = SignalBridge::new(db_path_str)
2448 .await
2449 .expect("Failed to create bridge");
2450
2451 let result = bridge
2452 .encrypt_message("unknown_peer", b"test message")
2453 .await;
2454 assert!(result.is_err());
2455 if let Err(SignalBridgeError::SessionNotFound(msg)) = result {
2456 assert_eq!(
2457 msg,
2458 "Establish a session with unknown_peer before sending messages"
2459 );
2460 } else {
2461 panic!("Expected SessionNotFound error for unknown peer");
2462 }
2463 }
2464
2465 #[tokio::test]
2466 async fn test_decrypt_message_malformed_ciphertext() {
2467 use std::env;
2468
2469 let temp_dir = env::temp_dir();
2470 let process_id = std::process::id();
2471 let timestamp = std::time::SystemTime::now()
2472 .duration_since(std::time::UNIX_EPOCH)
2473 .unwrap()
2474 .as_millis();
2475
2476 let db_path = temp_dir.join(format!(
2477 "test_malformed_ciphertext_{}_{}.db",
2478 process_id, timestamp
2479 ));
2480 let db_path_str = db_path.to_str().unwrap();
2481 let mut bridge = SignalBridge::new(db_path_str)
2482 .await
2483 .expect("Failed to create bridge");
2484
2485 let malformed_data = vec![0x01, 0x02, 0x03, 0x04];
2486 let result = bridge.decrypt_message("alice", &malformed_data).await;
2487 assert!(result.is_err());
2488 if let Err(SignalBridgeError::Serialization(msg)) = result {
2489 assert_eq!(msg, "Provide a valid Signal Protocol message");
2490 } else {
2491 panic!("Expected Serialization error for malformed ciphertext");
2492 }
2493 }
2494
2495 #[tokio::test]
2496 async fn test_establish_session_malformed_bundle() {
2497 use std::env;
2498
2499 let temp_dir = env::temp_dir();
2500 let process_id = std::process::id();
2501 let timestamp = std::time::SystemTime::now()
2502 .duration_since(std::time::UNIX_EPOCH)
2503 .unwrap()
2504 .as_millis();
2505
2506 let db_path = temp_dir.join(format!(
2507 "test_malformed_bundle_{}_{}.db",
2508 process_id, timestamp
2509 ));
2510 let db_path_str = db_path.to_str().unwrap();
2511 let mut bridge = SignalBridge::new(db_path_str)
2512 .await
2513 .expect("Failed to create bridge");
2514
2515 let malformed_bundle = vec![0xFF, 0xFE, 0xFD, 0xFC];
2516 let result = bridge.establish_session("alice", &malformed_bundle).await;
2517 assert!(result.is_err());
2518 if let Err(SignalBridgeError::Serialization(msg)) = result {
2519 assert_eq!(msg, "io error: unexpected end of file");
2520 } else {
2521 panic!("Expected Serialization error for malformed bundle");
2522 }
2523 }
2524
2525 #[tokio::test]
2526 async fn test_sign_nostr_event() -> Result<(), Box<dyn std::error::Error>> {
2527 use std::env;
2528
2529 let temp_dir = env::temp_dir();
2530 let process_id = std::process::id();
2531 let timestamp = std::time::SystemTime::now()
2532 .duration_since(std::time::UNIX_EPOCH)
2533 .unwrap()
2534 .as_millis();
2535
2536 let db_path = temp_dir.join(format!("test_sign_nostr_{}_{}.db", process_id, timestamp));
2537 let db_path_str = db_path.to_str().unwrap();
2538 let mut bridge = SignalBridge::new(db_path_str).await?;
2539
2540 let _event_json = r#"{
2541 "id": "",
2542 "pubkey": "",
2543 "created_at": 1234567890,
2544 "kind": 40001,
2545 "tags": [
2546 ["p", "recipient_pubkey"]
2547 ],
2548 "content": "encrypted_content_here",
2549 "sig": ""
2550 }"#;
2551
2552 let result = bridge
2553 .sign_nostr_event(
2554 1234567890,
2555 40001,
2556 vec![vec!["p".to_string(), "recipient_pubkey".to_string()]],
2557 "encrypted_content_here",
2558 )
2559 .await;
2560 assert!(result.is_ok());
2561
2562 let (pubkey, event_id, signature) = result.unwrap();
2563 assert!(!pubkey.is_empty());
2564 assert!(!event_id.is_empty());
2565 assert!(!signature.is_empty());
2566 assert_eq!(event_id.len(), 64); assert_eq!(pubkey.len(), 64); let keys = bridge.derive_nostr_keypair().await?;
2570 assert_eq!(pubkey, keys.public_key().to_hex());
2571
2572 Ok(())
2573 }
2574
2575 #[tokio::test]
2576 async fn test_error_message_formatting() {
2577 let storage_error = SignalBridgeError::Storage("Database locked".to_string());
2578 assert_eq!(storage_error.to_string(), "Storage error: Database locked");
2579
2580 let protocol_error = SignalBridgeError::Protocol("Invalid signature".to_string());
2581 assert_eq!(
2582 protocol_error.to_string(),
2583 "Signal Protocol error: Invalid signature"
2584 );
2585
2586 let serialization_error = SignalBridgeError::Serialization("Invalid format".to_string());
2587 assert_eq!(
2588 serialization_error.to_string(),
2589 "Serialization error: Invalid format"
2590 );
2591
2592 let invalid_input_error = SignalBridgeError::InvalidInput("Name too long".to_string());
2593 assert_eq!(
2594 invalid_input_error.to_string(),
2595 "Invalid input: Name too long"
2596 );
2597
2598 let session_not_found_error = SignalBridgeError::SessionNotFound(
2599 "Establish a session with bob before sending messages".to_string(),
2600 );
2601 assert_eq!(
2602 session_not_found_error.to_string(),
2603 "Establish a session with bob before sending messages"
2604 );
2605
2606 let schema_error = SignalBridgeError::SchemaVersionTooOld;
2607 assert_eq!(
2608 schema_error.to_string(),
2609 "Update your database to a newer schema version"
2610 );
2611 }
2612
2613 #[tokio::test]
2614 async fn test_add_contact_and_establish_session_returns_rdx() {
2615 let temp_dir = std::env::temp_dir();
2616 let timestamp = SystemTime::now()
2617 .duration_since(UNIX_EPOCH)
2618 .unwrap()
2619 .as_millis();
2620
2621 let alice_db = temp_dir.join(format!("test_contact_alice_{}.db", timestamp));
2622 let bob_db = temp_dir.join(format!("test_contact_bob_{}.db", timestamp));
2623
2624 let mut alice = SignalBridge::new(alice_db.to_str().unwrap()).await.unwrap();
2625 let mut bob = SignalBridge::new(bob_db.to_str().unwrap()).await.unwrap();
2626
2627 let (bob_bundle, _, _, _) = bob.generate_pre_key_bundle().await.unwrap();
2628
2629 let bob_rdx = alice
2630 .add_contact_and_establish_session(&bob_bundle, None)
2631 .await
2632 .unwrap();
2633 assert!(bob_rdx.starts_with("RDX:"));
2634
2635 let _ = std::fs::remove_file(&alice_db);
2636 let _ = std::fs::remove_file(&bob_db);
2637 }
2638
2639 #[tokio::test]
2640 async fn test_lookup_contact_by_rdx() {
2641 let temp_dir = std::env::temp_dir();
2642 let timestamp = SystemTime::now()
2643 .duration_since(UNIX_EPOCH)
2644 .unwrap()
2645 .as_millis();
2646
2647 let alice_db = temp_dir.join(format!("test_lookup_alice_{}.db", timestamp));
2648 let bob_db = temp_dir.join(format!("test_lookup_bob_{}.db", timestamp));
2649
2650 let mut alice = SignalBridge::new(alice_db.to_str().unwrap()).await.unwrap();
2651 let mut bob = SignalBridge::new(bob_db.to_str().unwrap()).await.unwrap();
2652
2653 let (bob_bundle, _, _, _) = bob.generate_pre_key_bundle().await.unwrap();
2654 let bob_rdx = alice
2655 .add_contact_and_establish_session(&bob_bundle, None)
2656 .await
2657 .unwrap();
2658
2659 let contact = alice.lookup_contact(&bob_rdx).await.unwrap();
2660 assert_eq!(contact.rdx_fingerprint, bob_rdx);
2661 assert!(contact.user_alias.is_none());
2662 assert!(contact.has_active_session);
2663
2664 let _ = std::fs::remove_file(&alice_db);
2665 let _ = std::fs::remove_file(&bob_db);
2666 }
2667
2668 #[tokio::test]
2669 async fn test_assign_and_lookup_contact_by_alias() {
2670 let temp_dir = std::env::temp_dir();
2671 let timestamp = SystemTime::now()
2672 .duration_since(UNIX_EPOCH)
2673 .unwrap()
2674 .as_millis();
2675
2676 let alice_db = temp_dir.join(format!("test_alias_alice_{}.db", timestamp));
2677 let bob_db = temp_dir.join(format!("test_alias_bob_{}.db", timestamp));
2678
2679 let mut alice = SignalBridge::new(alice_db.to_str().unwrap()).await.unwrap();
2680 let mut bob = SignalBridge::new(bob_db.to_str().unwrap()).await.unwrap();
2681
2682 let (bob_bundle, _, _, _) = bob.generate_pre_key_bundle().await.unwrap();
2683 let bob_rdx = alice
2684 .add_contact_and_establish_session(&bob_bundle, None)
2685 .await
2686 .unwrap();
2687
2688 let contact_before = alice.lookup_contact(&bob_rdx).await.unwrap();
2689 assert!(contact_before.user_alias.is_none());
2690
2691 alice.assign_contact_alias(&bob_rdx, "bob").await.unwrap();
2692
2693 let contact_by_alias = alice.lookup_contact("bob").await.unwrap();
2694 assert_eq!(contact_by_alias.rdx_fingerprint, bob_rdx);
2695 assert_eq!(contact_by_alias.user_alias, Some("bob".to_string()));
2696
2697 let _ = std::fs::remove_file(&alice_db);
2698 let _ = std::fs::remove_file(&bob_db);
2699 }
2700
2701 #[tokio::test]
2702 async fn test_list_contacts() {
2703 let temp_dir = std::env::temp_dir();
2704 let timestamp = SystemTime::now()
2705 .duration_since(UNIX_EPOCH)
2706 .unwrap()
2707 .as_millis();
2708
2709 let alice_db = temp_dir.join(format!("test_list_alice_{}.db", timestamp));
2710 let bob_db = temp_dir.join(format!("test_list_bob_{}.db", timestamp));
2711 let charlie_db = temp_dir.join(format!("test_list_charlie_{}.db", timestamp));
2712
2713 let mut alice = SignalBridge::new(alice_db.to_str().unwrap()).await.unwrap();
2714 let mut bob = SignalBridge::new(bob_db.to_str().unwrap()).await.unwrap();
2715 let mut charlie = SignalBridge::new(charlie_db.to_str().unwrap())
2716 .await
2717 .unwrap();
2718
2719 let (bob_bundle, _, _, _) = bob.generate_pre_key_bundle().await.unwrap();
2720 let (charlie_bundle, _, _, _) = charlie.generate_pre_key_bundle().await.unwrap();
2721
2722 let bob_rdx = alice
2723 .add_contact_and_establish_session(&bob_bundle, Some("bob"))
2724 .await
2725 .unwrap();
2726 let charlie_rdx = alice
2727 .add_contact_and_establish_session(&charlie_bundle, None)
2728 .await
2729 .unwrap();
2730
2731 let contacts = alice.list_contacts().await.unwrap();
2732 assert_eq!(contacts.len(), 2);
2733
2734 let bob_contact = contacts
2735 .iter()
2736 .find(|c| c.rdx_fingerprint == bob_rdx)
2737 .unwrap();
2738 assert_eq!(bob_contact.user_alias, Some("bob".to_string()));
2739
2740 let charlie_contact = contacts
2741 .iter()
2742 .find(|c| c.rdx_fingerprint == charlie_rdx)
2743 .unwrap();
2744 assert!(charlie_contact.user_alias.is_none());
2745
2746 let _ = std::fs::remove_file(&alice_db);
2747 let _ = std::fs::remove_file(&bob_db);
2748 let _ = std::fs::remove_file(&charlie_db);
2749 }
2750
2751 #[tokio::test]
2752 async fn test_alias_with_multiple_sessions() {
2753 let temp_dir = std::env::temp_dir();
2754 let timestamp = SystemTime::now()
2755 .duration_since(UNIX_EPOCH)
2756 .unwrap()
2757 .as_millis();
2758
2759 let alice_db = temp_dir.join(format!("test_multi_alias_alice_{}.db", timestamp));
2760 let bob1_db = temp_dir.join(format!("test_multi_alias_bob1_{}.db", timestamp));
2761 let bob2_db = temp_dir.join(format!("test_multi_alias_bob2_{}.db", timestamp));
2762 let bob3_db = temp_dir.join(format!("test_multi_alias_bob3_{}.db", timestamp));
2763
2764 let mut alice = SignalBridge::new(alice_db.to_str().unwrap()).await.unwrap();
2765 let mut bob1 = SignalBridge::new(bob1_db.to_str().unwrap()).await.unwrap();
2766 let mut bob2 = SignalBridge::new(bob2_db.to_str().unwrap()).await.unwrap();
2767 let mut bob3 = SignalBridge::new(bob3_db.to_str().unwrap()).await.unwrap();
2768
2769 let (bob1_bundle, _, _, _) = bob1.generate_pre_key_bundle().await.unwrap();
2770 let (bob2_bundle, _, _, _) = bob2.generate_pre_key_bundle().await.unwrap();
2771 let (bob3_bundle, _, _, _) = bob3.generate_pre_key_bundle().await.unwrap();
2772
2773 let bob1_rdx = alice
2774 .add_contact_and_establish_session(&bob1_bundle, None)
2775 .await
2776 .unwrap();
2777 let bob2_rdx = alice
2778 .add_contact_and_establish_session(&bob2_bundle, None)
2779 .await
2780 .unwrap();
2781 let bob3_rdx = alice
2782 .add_contact_and_establish_session(&bob3_bundle, None)
2783 .await
2784 .unwrap();
2785
2786 assert_ne!(bob1_rdx, bob2_rdx);
2787 assert_ne!(bob2_rdx, bob3_rdx);
2788 assert_ne!(bob1_rdx, bob3_rdx);
2789
2790 alice.assign_contact_alias(&bob3_rdx, "bob").await.unwrap();
2791
2792 let contact_by_alias = alice.lookup_contact("bob").await.unwrap();
2793 assert_eq!(contact_by_alias.rdx_fingerprint, bob3_rdx);
2794 assert_eq!(contact_by_alias.user_alias, Some("bob".to_string()));
2795
2796 let contact1 = alice.lookup_contact(&bob1_rdx).await.unwrap();
2797 assert_eq!(contact1.rdx_fingerprint, bob1_rdx);
2798 assert!(contact1.user_alias.is_none());
2799
2800 let contact2 = alice.lookup_contact(&bob2_rdx).await.unwrap();
2801 assert_eq!(contact2.rdx_fingerprint, bob2_rdx);
2802 assert!(contact2.user_alias.is_none());
2803
2804 let _ = std::fs::remove_file(&alice_db);
2805 let _ = std::fs::remove_file(&bob1_db);
2806 let _ = std::fs::remove_file(&bob2_db);
2807 let _ = std::fs::remove_file(&bob3_db);
2808 }
2809
2810 #[tokio::test]
2811 async fn test_reassigning_same_alias_to_different_contact() {
2812 let temp_dir = std::env::temp_dir();
2813 let timestamp = SystemTime::now()
2814 .duration_since(UNIX_EPOCH)
2815 .unwrap()
2816 .as_millis();
2817
2818 let alice_db = temp_dir.join(format!("test_reassign_alias_alice_{}.db", timestamp));
2819 let bob1_db = temp_dir.join(format!("test_reassign_alias_bob1_{}.db", timestamp));
2820 let bob2_db = temp_dir.join(format!("test_reassign_alias_bob2_{}.db", timestamp));
2821
2822 let mut alice = SignalBridge::new(alice_db.to_str().unwrap()).await.unwrap();
2823 let mut bob1 = SignalBridge::new(bob1_db.to_str().unwrap()).await.unwrap();
2824 let mut bob2 = SignalBridge::new(bob2_db.to_str().unwrap()).await.unwrap();
2825
2826 let (bob1_bundle, _, _, _) = bob1.generate_pre_key_bundle().await.unwrap();
2827 let (bob2_bundle, _, _, _) = bob2.generate_pre_key_bundle().await.unwrap();
2828
2829 let bob1_rdx = alice
2830 .add_contact_and_establish_session(&bob1_bundle, None)
2831 .await
2832 .unwrap();
2833 let bob2_rdx = alice
2834 .add_contact_and_establish_session(&bob2_bundle, None)
2835 .await
2836 .unwrap();
2837
2838 alice.assign_contact_alias(&bob1_rdx, "bob").await.unwrap();
2839
2840 let contact = alice.lookup_contact("bob").await.unwrap();
2841 assert_eq!(contact.rdx_fingerprint, bob1_rdx);
2842
2843 let result = alice.assign_contact_alias(&bob2_rdx, "bob").await;
2844 assert!(result.is_err(), "Should fail when alias is already taken");
2845 assert!(
2846 result.unwrap_err().to_string().contains("already assigned"),
2847 "Error message should indicate alias is already assigned"
2848 );
2849
2850 let contact_still = alice.lookup_contact("bob").await.unwrap();
2851 assert_eq!(
2852 contact_still.rdx_fingerprint, bob1_rdx,
2853 "Alias 'bob' should still point to bob1"
2854 );
2855
2856 let _ = std::fs::remove_file(&alice_db);
2857 let _ = std::fs::remove_file(&bob1_db);
2858 let _ = std::fs::remove_file(&bob2_db);
2859 }
2860
2861 #[tokio::test]
2862 async fn test_generate_bundle_announcement_includes_rdx_tag() {
2863 let temp_dir = std::env::temp_dir();
2864 let timestamp = SystemTime::now()
2865 .duration_since(UNIX_EPOCH)
2866 .unwrap()
2867 .as_millis();
2868 let db_path = temp_dir.join(format!("test_generate_announcement_{}.db", timestamp));
2869
2870 let mut alice = SignalBridge::new(db_path.to_str().unwrap()).await.unwrap();
2871
2872 let bundle_info = alice
2873 .generate_prekey_bundle_announcement("1.0.0-test")
2874 .await
2875 .unwrap();
2876 let event: serde_json::Value =
2877 serde_json::from_str(&bundle_info.announcement_json).unwrap();
2878
2879 assert_eq!(event["kind"], 30078);
2880 assert!(event["content"].is_string());
2881
2882 let tags = event["tags"].as_array().unwrap();
2883 let rdx_tag = tags
2884 .iter()
2885 .find(|t| t[0] == "rdx")
2886 .expect("RDX tag should be present");
2887 let rdx_value = rdx_tag[1].as_str().unwrap();
2888 assert!(rdx_value.starts_with("RDX:"));
2889
2890 let version_tag = tags
2891 .iter()
2892 .find(|t| t[0] == "radix_version")
2893 .expect("Version tag should be present");
2894 assert_eq!(version_tag[1], "1.0.0-test");
2895
2896 let _ = std::fs::remove_file(&db_path);
2897 }
2898
2899 #[tokio::test]
2900 async fn test_add_contact_and_establish_session() {
2901 let temp_dir = std::env::temp_dir();
2902 let timestamp = SystemTime::now()
2903 .duration_since(UNIX_EPOCH)
2904 .unwrap()
2905 .as_millis();
2906 let alice_db = temp_dir.join(format!("test_contact_session_alice_{}.db", timestamp));
2907 let bob_db = temp_dir.join(format!("test_contact_session_bob_{}.db", timestamp));
2908
2909 let mut alice = SignalBridge::new(alice_db.to_str().unwrap()).await.unwrap();
2910 let mut bob = SignalBridge::new(bob_db.to_str().unwrap()).await.unwrap();
2911
2912 let (bob_bundle, _, _, _) = bob.generate_pre_key_bundle().await.unwrap();
2913
2914 let bob_rdx = alice
2915 .add_contact_and_establish_session(&bob_bundle, Some("bob"))
2916 .await
2917 .unwrap();
2918
2919 assert!(bob_rdx.starts_with("RDX:"));
2920
2921 let contact = alice.lookup_contact(&bob_rdx).await.unwrap();
2922 assert_eq!(contact.rdx_fingerprint, bob_rdx);
2923 assert_eq!(contact.user_alias, Some("bob".to_string()));
2924 assert!(contact.has_active_session);
2925
2926 let plaintext = b"Hello Bob!";
2927 let ciphertext = alice.encrypt_message(&bob_rdx, plaintext).await.unwrap();
2928 assert!(!ciphertext.is_empty());
2929
2930 let _ = std::fs::remove_file(&alice_db);
2931 let _ = std::fs::remove_file(&bob_db);
2932 }
2933
2934 #[tokio::test]
2935 async fn test_extract_rdx_from_bundle_without_establishing_session() {
2936 let temp_dir = std::env::temp_dir();
2937 let timestamp = SystemTime::now()
2938 .duration_since(UNIX_EPOCH)
2939 .unwrap()
2940 .as_millis();
2941
2942 let alice_db = temp_dir.join(format!("test_extract_rdx_alice_{}.db", timestamp));
2943 let bob_db = temp_dir.join(format!("test_extract_rdx_bob_{}.db", timestamp));
2944
2945 let mut alice = SignalBridge::new(alice_db.to_str().unwrap()).await.unwrap();
2946 let mut bob = SignalBridge::new(bob_db.to_str().unwrap()).await.unwrap();
2947
2948 let (bob_bundle, _, _, _) = bob.generate_pre_key_bundle().await.unwrap();
2949
2950 let extracted_rdx = extract_rdx_from_bundle(&mut alice, &bob_bundle).unwrap();
2951
2952 assert!(extracted_rdx.starts_with("RDX:"));
2953
2954 let result = alice.encrypt_message(&extracted_rdx, b"test").await;
2955 assert!(
2956 result.is_err(),
2957 "Should not be able to encrypt without establishing session"
2958 );
2959
2960 let bob_rdx = alice
2961 .add_contact_and_establish_session(&bob_bundle, Some("bob"))
2962 .await
2963 .unwrap();
2964
2965 assert_eq!(
2966 extracted_rdx, bob_rdx,
2967 "RDX extracted before session should match RDX after session establishment"
2968 );
2969
2970 let _ = std::fs::remove_file(&alice_db);
2971 let _ = std::fs::remove_file(&bob_db);
2972 }
2973
2974 #[tokio::test]
2975 async fn test_extract_rdx_from_bundle_base64() {
2976 let temp_dir = std::env::temp_dir();
2977 let timestamp = SystemTime::now()
2978 .duration_since(UNIX_EPOCH)
2979 .unwrap()
2980 .as_millis();
2981
2982 let alice_db = temp_dir.join(format!("test_extract_rdx_base64_alice_{}.db", timestamp));
2983 let bob_db = temp_dir.join(format!("test_extract_rdx_base64_bob_{}.db", timestamp));
2984
2985 let mut alice = SignalBridge::new(alice_db.to_str().unwrap()).await.unwrap();
2986 let mut bob = SignalBridge::new(bob_db.to_str().unwrap()).await.unwrap();
2987
2988 let (bob_bundle, _, _, _) = bob.generate_pre_key_bundle().await.unwrap();
2989 let bob_bundle_base64 = base64::engine::general_purpose::STANDARD.encode(&bob_bundle);
2990
2991 let extracted_rdx = extract_rdx_from_bundle_base64(&mut alice, &bob_bundle_base64).unwrap();
2992
2993 assert!(extracted_rdx.starts_with("RDX:"));
2994
2995 let bob_rdx = alice
2996 .add_contact_and_establish_session(&bob_bundle, Some("bob"))
2997 .await
2998 .unwrap();
2999
3000 assert_eq!(
3001 extracted_rdx, bob_rdx,
3002 "RDX extracted from base64 should match RDX after session establishment"
3003 );
3004
3005 let _ = std::fs::remove_file(&alice_db);
3006 let _ = std::fs::remove_file(&bob_db);
3007 }
3008
3009 #[tokio::test]
3010 async fn test_generate_empty_bundle_announcement() {
3011 let temp_dir = std::env::temp_dir();
3012 let timestamp = SystemTime::now()
3013 .duration_since(UNIX_EPOCH)
3014 .unwrap()
3015 .as_millis();
3016
3017 let db_path = temp_dir.join(format!("test_empty_bundle_{}.db", timestamp));
3018 let mut bridge = SignalBridge::new(db_path.to_str().unwrap()).await.unwrap();
3019
3020 let announcement_json = bridge
3021 .generate_empty_bundle_announcement("0.4.0")
3022 .await
3023 .unwrap();
3024
3025 let event: serde_json::Value = serde_json::from_str(&announcement_json).unwrap();
3026
3027 assert_eq!(event["kind"].as_u64().unwrap(), 30078);
3028 assert_eq!(event["content"].as_str().unwrap(), "");
3029
3030 let tags = event["tags"].as_array().unwrap();
3031 let d_tag = tags.iter().find(|t| t[0] == "d").unwrap();
3032 assert_eq!(d_tag[1].as_str().unwrap(), "radix_prekey_bundle_v1");
3033
3034 let version_tag = tags.iter().find(|t| t[0] == "radix_version").unwrap();
3035 assert_eq!(version_tag[1].as_str().unwrap(), "0.4.0");
3036
3037 assert!(
3038 !tags.iter().any(|t| t[0] == "rdx"),
3039 "Empty bundle should not contain rdx tag"
3040 );
3041
3042 let _ = std::fs::remove_file(&db_path);
3043 }
3044
3045 #[tokio::test]
3046 async fn test_perform_key_maintenance_replenishes_pre_keys(
3047 ) -> Result<(), Box<dyn std::error::Error>> {
3048 use crate::key_rotation::MIN_PRE_KEY_COUNT;
3049 use crate::SignalBridge;
3050
3051 let temp_dir = std::env::temp_dir();
3052 let timestamp = SystemTime::now()
3053 .duration_since(UNIX_EPOCH)
3054 .unwrap()
3055 .as_millis();
3056 let db_path = temp_dir.join(format!("test_maintenance_prekeys_{}.db", timestamp));
3057 let mut bridge = SignalBridge::new(db_path.to_str().unwrap()).await?;
3058
3059 let initial_count = bridge.storage.pre_key_store().pre_key_count().await;
3061
3062 bridge.perform_key_maintenance().await?;
3064
3065 let after_maintenance_count = bridge.storage.pre_key_store().pre_key_count().await;
3066 assert!(
3067 after_maintenance_count >= initial_count,
3068 "Pre-key count should not decrease after maintenance"
3069 );
3070
3071 bridge.storage.pre_key_store().clear_all_pre_keys().await?;
3073 let empty_count = bridge.storage.pre_key_store().pre_key_count().await;
3074 assert_eq!(empty_count, 0, "Pre-keys should be cleared");
3075
3076 bridge.perform_key_maintenance().await?;
3078
3079 let replenished_count = bridge.storage.pre_key_store().pre_key_count().await;
3080 assert!(
3081 replenished_count >= MIN_PRE_KEY_COUNT,
3082 "Pre-keys should be replenished to at least MIN_PRE_KEY_COUNT ({}), got {}",
3083 MIN_PRE_KEY_COUNT,
3084 replenished_count
3085 );
3086
3087 let _ = std::fs::remove_file(&db_path);
3088 Ok(())
3089 }
3090
3091 #[tokio::test]
3092 async fn test_perform_key_maintenance_no_rotation_for_fresh_keys(
3093 ) -> Result<(), Box<dyn std::error::Error>> {
3094 use crate::SignalBridge;
3095
3096 let temp_dir = std::env::temp_dir();
3097 let timestamp = SystemTime::now()
3098 .duration_since(UNIX_EPOCH)
3099 .unwrap()
3100 .as_millis();
3101 let db_path = temp_dir.join(format!("test_maintenance_fresh_{}.db", timestamp));
3102 let mut bridge = SignalBridge::new(db_path.to_str().unwrap()).await?;
3103
3104 let initial_signed_count = bridge
3106 .storage
3107 .signed_pre_key_store()
3108 .signed_pre_key_count()
3109 .await;
3110 let initial_kyber_count = bridge
3111 .storage
3112 .kyber_pre_key_store()
3113 .kyber_pre_key_count()
3114 .await;
3115
3116 bridge.perform_key_maintenance().await?;
3118
3119 let after_signed_count = bridge
3120 .storage
3121 .signed_pre_key_store()
3122 .signed_pre_key_count()
3123 .await;
3124 let after_kyber_count = bridge
3125 .storage
3126 .kyber_pre_key_store()
3127 .kyber_pre_key_count()
3128 .await;
3129
3130 assert_eq!(
3132 initial_signed_count, after_signed_count,
3133 "Fresh signed pre-keys should not be rotated"
3134 );
3135 assert_eq!(
3136 initial_kyber_count, after_kyber_count,
3137 "Fresh Kyber pre-keys should not be rotated"
3138 );
3139
3140 let _ = std::fs::remove_file(&db_path);
3141 Ok(())
3142 }
3143
3144 #[tokio::test]
3145 async fn test_perform_key_maintenance_idempotent() -> Result<(), Box<dyn std::error::Error>> {
3146 use crate::SignalBridge;
3147
3148 let temp_dir = std::env::temp_dir();
3149 let timestamp = SystemTime::now()
3150 .duration_since(UNIX_EPOCH)
3151 .unwrap()
3152 .as_millis();
3153 let db_path = temp_dir.join(format!("test_maintenance_idempotent_{}.db", timestamp));
3154 let mut bridge = SignalBridge::new(db_path.to_str().unwrap()).await?;
3155
3156 bridge.perform_key_maintenance().await?;
3158
3159 let first_pre_key_count = bridge.storage.pre_key_store().pre_key_count().await;
3160 let first_signed_count = bridge
3161 .storage
3162 .signed_pre_key_store()
3163 .signed_pre_key_count()
3164 .await;
3165
3166 bridge.perform_key_maintenance().await?;
3167
3168 let second_pre_key_count = bridge.storage.pre_key_store().pre_key_count().await;
3169 let second_signed_count = bridge
3170 .storage
3171 .signed_pre_key_store()
3172 .signed_pre_key_count()
3173 .await;
3174
3175 assert_eq!(
3177 first_pre_key_count, second_pre_key_count,
3178 "Pre-key count should be stable between maintenance calls"
3179 );
3180 assert_eq!(
3181 first_signed_count, second_signed_count,
3182 "Signed pre-key count should be stable between maintenance calls"
3183 );
3184
3185 let _ = std::fs::remove_file(&db_path);
3186 Ok(())
3187 }
3188
3189 #[tokio::test]
3190 async fn test_perform_key_maintenance_rotates_kyber_keys(
3191 ) -> Result<(), Box<dyn std::error::Error>> {
3192 use crate::key_rotation::ROTATION_INTERVAL_SECS;
3193 use crate::SignalBridge;
3194 use libsignal_protocol::{
3195 kem, GenericSignedPreKey, KyberPreKeyId, KyberPreKeyRecord, KyberPreKeyStore, Timestamp,
3196 };
3197
3198 let temp_dir = std::env::temp_dir();
3199 let timestamp = SystemTime::now()
3200 .duration_since(UNIX_EPOCH)
3201 .unwrap()
3202 .as_millis();
3203 let db_path = temp_dir.join(format!("test_maintenance_kyber_{}.db", timestamp));
3204 let mut bridge = SignalBridge::new(db_path.to_str().unwrap()).await?;
3205
3206 let initial_kyber_count = bridge
3208 .storage
3209 .kyber_pre_key_store()
3210 .kyber_pre_key_count()
3211 .await;
3212 assert!(initial_kyber_count >= 1, "Should have initial Kyber key");
3213
3214 let identity_key_pair = bridge
3216 .storage
3217 .identity_store()
3218 .get_identity_key_pair()
3219 .await?;
3220
3221 let old_timestamp =
3222 SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs() - (ROTATION_INTERVAL_SECS + 1);
3223
3224 let mut rng = rand::rng();
3225 let kyber_keypair = kem::KeyPair::generate(kem::KeyType::Kyber1024, &mut rng);
3226 let kyber_signature = identity_key_pair
3227 .private_key()
3228 .calculate_signature(&kyber_keypair.public_key.serialize(), &mut rng)?;
3229 let old_kyber_key = KyberPreKeyRecord::new(
3230 KyberPreKeyId::from(999u32),
3231 Timestamp::from_epoch_millis(old_timestamp * 1000),
3232 &kyber_keypair,
3233 &kyber_signature,
3234 );
3235
3236 bridge
3238 .storage
3239 .kyber_pre_key_store()
3240 .save_kyber_pre_key(KyberPreKeyId::from(999u32), &old_kyber_key)
3241 .await?;
3242
3243 let max_id_before = bridge
3245 .storage
3246 .kyber_pre_key_store()
3247 .get_max_kyber_pre_key_id()
3248 .await?
3249 .unwrap();
3250
3251 bridge.perform_key_maintenance().await?;
3253
3254 let max_id_after = bridge
3256 .storage
3257 .kyber_pre_key_store()
3258 .get_max_kyber_pre_key_id()
3259 .await?
3260 .unwrap();
3261
3262 assert!(
3264 max_id_after > max_id_before,
3265 "Max Kyber key ID should increase after rotation (before: {}, after: {})",
3266 max_id_before,
3267 max_id_after
3268 );
3269
3270 let _ = std::fs::remove_file(&db_path);
3271 Ok(())
3272 }
3273
3274 #[tokio::test]
3275 async fn test_perform_key_maintenance_cleans_up_expired_kyber_keys(
3276 ) -> Result<(), Box<dyn std::error::Error>> {
3277 use crate::key_rotation::GRACE_PERIOD_SECS;
3278 use crate::SignalBridge;
3279 use libsignal_protocol::{
3280 kem, GenericSignedPreKey, KyberPreKeyId, KyberPreKeyRecord, KyberPreKeyStore, Timestamp,
3281 };
3282
3283 let temp_dir = std::env::temp_dir();
3284 let timestamp = SystemTime::now()
3285 .duration_since(UNIX_EPOCH)
3286 .unwrap()
3287 .as_millis();
3288 let db_path = temp_dir.join(format!("test_maintenance_kyber_cleanup_{}.db", timestamp));
3289 let mut bridge = SignalBridge::new(db_path.to_str().unwrap()).await?;
3290
3291 let identity_key_pair = bridge
3292 .storage
3293 .identity_store()
3294 .get_identity_key_pair()
3295 .await?;
3296
3297 let expired_timestamp =
3299 SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs() - (GRACE_PERIOD_SECS + 1);
3300
3301 let mut rng = rand::rng();
3302 let kyber_keypair = kem::KeyPair::generate(kem::KeyType::Kyber1024, &mut rng);
3303 let kyber_signature = identity_key_pair
3304 .private_key()
3305 .calculate_signature(&kyber_keypair.public_key.serialize(), &mut rng)?;
3306 let expired_kyber_key = KyberPreKeyRecord::new(
3307 KyberPreKeyId::from(0u32), Timestamp::from_epoch_millis(expired_timestamp * 1000),
3309 &kyber_keypair,
3310 &kyber_signature,
3311 );
3312
3313 bridge
3314 .storage
3315 .kyber_pre_key_store()
3316 .save_kyber_pre_key(KyberPreKeyId::from(0u32), &expired_kyber_key)
3317 .await?;
3318
3319 let before_count = bridge
3320 .storage
3321 .kyber_pre_key_store()
3322 .kyber_pre_key_count()
3323 .await;
3324 assert!(
3325 before_count >= 2,
3326 "Should have at least 2 Kyber keys (fresh + expired)"
3327 );
3328
3329 bridge.perform_key_maintenance().await?;
3331
3332 let after_count = bridge
3333 .storage
3334 .kyber_pre_key_store()
3335 .kyber_pre_key_count()
3336 .await;
3337
3338 assert!(
3340 after_count < before_count,
3341 "Kyber key count should decrease after cleanup (before: {}, after: {})",
3342 before_count,
3343 after_count
3344 );
3345
3346 let _ = std::fs::remove_file(&db_path);
3347 Ok(())
3348 }
3349
3350 #[tokio::test]
3351 async fn test_bundle_uses_latest_available_keys() -> Result<(), Box<dyn std::error::Error>> {
3352 use crate::key_rotation::{rotate_kyber_pre_key, rotate_signed_pre_key};
3353 use crate::SignalBridge;
3354
3355 let temp_dir = std::env::temp_dir();
3356 let timestamp = SystemTime::now()
3357 .duration_since(UNIX_EPOCH)
3358 .unwrap()
3359 .as_millis();
3360 let db_path = temp_dir.join(format!("test_bundle_latest_keys_{}.db", timestamp));
3361 let mut bridge = SignalBridge::new(db_path.to_str().unwrap()).await?;
3362
3363 let (initial_bundle_bytes, _, _, _) = bridge.generate_pre_key_bundle().await?;
3365 let initial_bundle: SerializablePreKeyBundle = bincode::deserialize(&initial_bundle_bytes)?;
3366
3367 assert_eq!(initial_bundle.pre_key_id, Some(100));
3370 assert_eq!(initial_bundle.signed_pre_key_id, 1);
3371 assert_eq!(initial_bundle.kyber_pre_key_id, 1);
3372
3373 let identity_key_pair = bridge
3375 .storage
3376 .identity_store()
3377 .get_identity_key_pair()
3378 .await?;
3379 rotate_signed_pre_key(&mut bridge.storage, &identity_key_pair).await?;
3380 rotate_kyber_pre_key(&mut bridge.storage, &identity_key_pair).await?;
3381
3382 let (rotated_bundle_bytes, _, _, _) = bridge.generate_pre_key_bundle().await?;
3384 let rotated_bundle: SerializablePreKeyBundle = bincode::deserialize(&rotated_bundle_bytes)?;
3385
3386 assert_eq!(
3392 rotated_bundle.signed_pre_key_id, 2,
3393 "Bundle should use latest signed pre-key after rotation"
3394 );
3395 assert_eq!(
3396 rotated_bundle.kyber_pre_key_id, 2,
3397 "Bundle should use latest kyber pre-key after rotation"
3398 );
3399
3400 let _ = std::fs::remove_file(&db_path);
3401 Ok(())
3402 }
3403
3404 #[tokio::test]
3405 async fn test_extract_identity_from_prekey_message() -> Result<(), Box<dyn std::error::Error>> {
3406 use libsignal_protocol::PreKeySignalMessage;
3407
3408 let temp_dir = std::env::temp_dir();
3409 let timestamp = SystemTime::now()
3410 .duration_since(UNIX_EPOCH)
3411 .unwrap()
3412 .as_millis();
3413
3414 let alice_db_path = temp_dir.join(format!("test_extract_alice_{}.db", timestamp));
3415 let mut alice_bridge = SignalBridge::new(alice_db_path.to_str().unwrap()).await?;
3416
3417 let bob_db_path = temp_dir.join(format!("test_extract_bob_{}.db", timestamp));
3418 let mut bob_bridge = SignalBridge::new(bob_db_path.to_str().unwrap()).await?;
3419
3420 let (alice_bundle_bytes, _, _, _) = alice_bridge.generate_pre_key_bundle().await?;
3421 bob_bridge
3422 .establish_session("alice", &alice_bundle_bytes)
3423 .await?;
3424
3425 let plaintext = b"Hello Alice!";
3426 let ciphertext = bob_bridge.encrypt_message("alice", plaintext).await?;
3427
3428 let prekey_msg = PreKeySignalMessage::try_from(ciphertext.as_ref())?;
3429 let extracted_identity_key = prekey_msg.identity_key();
3430
3431 let bob_identity_key_pair = bob_bridge
3432 .storage
3433 .identity_store()
3434 .get_identity_key_pair()
3435 .await?;
3436 let bob_identity_key = bob_identity_key_pair.identity_key();
3437
3438 assert_eq!(
3439 extracted_identity_key.serialize(),
3440 bob_identity_key.serialize(),
3441 "Alice should be able to extract Bob's identity key from the PreKeySignalMessage"
3442 );
3443
3444 let _ = std::fs::remove_file(&alice_db_path);
3445 let _ = std::fs::remove_file(&bob_db_path);
3446 Ok(())
3447 }
3448
3449 #[tokio::test]
3450 async fn test_first_message_is_prekey_signal_message() -> Result<(), Box<dyn std::error::Error>>
3451 {
3452 use libsignal_protocol::PreKeySignalMessage;
3453
3454 let temp_dir = std::env::temp_dir();
3455 let timestamp = SystemTime::now()
3456 .duration_since(UNIX_EPOCH)
3457 .unwrap()
3458 .as_millis();
3459
3460 let alice_db_path = temp_dir.join(format!("test_prekey_type_alice_{}.db", timestamp));
3461 let mut alice_bridge = SignalBridge::new(alice_db_path.to_str().unwrap()).await?;
3462
3463 let bob_db_path = temp_dir.join(format!("test_prekey_type_bob_{}.db", timestamp));
3464 let mut bob_bridge = SignalBridge::new(bob_db_path.to_str().unwrap()).await?;
3465
3466 let (alice_bundle_bytes, _, _, _) = alice_bridge.generate_pre_key_bundle().await?;
3467 bob_bridge
3468 .establish_session("alice", &alice_bundle_bytes)
3469 .await?;
3470
3471 let plaintext = b"Hello Alice!";
3472 let ciphertext = bob_bridge.encrypt_message("alice", plaintext).await?;
3473
3474 assert!(
3475 PreKeySignalMessage::try_from(ciphertext.as_ref()).is_ok(),
3476 "First message to a new recipient must be a PreKeySignalMessage containing sender identity"
3477 );
3478
3479 let _ = std::fs::remove_file(&alice_db_path);
3480 let _ = std::fs::remove_file(&bob_db_path);
3481 Ok(())
3482 }
3483
3484 #[tokio::test]
3485 async fn test_decrypt_initial_message_from_unknown_sender(
3486 ) -> Result<(), Box<dyn std::error::Error>> {
3487 let temp_dir = std::env::temp_dir();
3488 let timestamp = SystemTime::now()
3489 .duration_since(UNIX_EPOCH)
3490 .unwrap()
3491 .as_millis();
3492
3493 let alice_db_path = temp_dir.join(format!("test_unknown_alice_{}.db", timestamp));
3494 let mut alice_bridge = SignalBridge::new(alice_db_path.to_str().unwrap()).await?;
3495
3496 let bob_db_path = temp_dir.join(format!("test_unknown_bob_{}.db", timestamp));
3497 let mut bob_bridge = SignalBridge::new(bob_db_path.to_str().unwrap()).await?;
3498
3499 let bob_nostr_keypair = bob_bridge.derive_nostr_keypair().await?;
3500 let bob_nostr_pubkey = bob_nostr_keypair.public_key().to_string();
3501
3502 let (alice_bundle_bytes, _, _, _) = alice_bridge.generate_pre_key_bundle().await?;
3503 let alice_rdx = bob_bridge
3504 .add_contact_and_establish_session(&alice_bundle_bytes, Some("Alice"))
3505 .await?;
3506
3507 let plaintext = b"Hello Alice!";
3508 let ciphertext = bob_bridge.encrypt_message(&alice_rdx, plaintext).await?;
3509
3510 let result = alice_bridge
3511 .decrypt_message(&bob_nostr_pubkey, &ciphertext)
3512 .await?;
3513
3514 assert_eq!(result.plaintext, plaintext);
3515 assert!(result.should_republish_bundle);
3516
3517 let bob_contact = alice_bridge.lookup_contact(&bob_nostr_pubkey).await?;
3518 assert!(bob_contact.rdx_fingerprint.starts_with("RDX:"));
3519 assert_eq!(bob_contact.nostr_pubkey, bob_nostr_pubkey);
3520 assert!(bob_contact
3521 .user_alias
3522 .as_ref()
3523 .unwrap()
3524 .starts_with("Unknown-"));
3525
3526 let response = b"Hi Bob!";
3527 let response_ciphertext = alice_bridge
3528 .encrypt_message(&bob_contact.rdx_fingerprint, response)
3529 .await?;
3530
3531 let decrypted_response = bob_bridge
3532 .decrypt_message(
3533 &alice_bridge
3534 .derive_nostr_keypair()
3535 .await?
3536 .public_key()
3537 .to_string(),
3538 &response_ciphertext,
3539 )
3540 .await?;
3541
3542 assert_eq!(decrypted_response.plaintext, response);
3543
3544 let _ = std::fs::remove_file(&alice_db_path);
3545 let _ = std::fs::remove_file(&bob_db_path);
3546 Ok(())
3547 }
3548
3549 #[tokio::test]
3550 async fn test_bidirectional_session_uses_signal_message(
3551 ) -> Result<(), Box<dyn std::error::Error>> {
3552 use libsignal_protocol::{PreKeySignalMessage, SignalMessage};
3553
3554 let temp_dir = std::env::temp_dir();
3555 let timestamp = SystemTime::now()
3556 .duration_since(UNIX_EPOCH)
3557 .unwrap()
3558 .as_millis();
3559
3560 let alice_db_path = temp_dir.join(format!("test_bidi_alice_{}.db", timestamp));
3561 let mut alice_bridge = SignalBridge::new(alice_db_path.to_str().unwrap()).await?;
3562
3563 let bob_db_path = temp_dir.join(format!("test_bidi_bob_{}.db", timestamp));
3564 let mut bob_bridge = SignalBridge::new(bob_db_path.to_str().unwrap()).await?;
3565
3566 let alice_nostr_pubkey = alice_bridge
3567 .derive_nostr_keypair()
3568 .await?
3569 .public_key()
3570 .to_string();
3571 let bob_nostr_pubkey = bob_bridge
3572 .derive_nostr_keypair()
3573 .await?
3574 .public_key()
3575 .to_string();
3576
3577 let (alice_bundle_bytes, _, _, _) = alice_bridge.generate_pre_key_bundle().await?;
3578 let alice_rdx = bob_bridge
3579 .add_contact_and_establish_session(&alice_bundle_bytes, Some("Alice"))
3580 .await?;
3581
3582 let first_msg = b"Hello Alice!";
3583 let first_ciphertext = bob_bridge.encrypt_message(&alice_rdx, first_msg).await?;
3584
3585 assert!(
3586 PreKeySignalMessage::try_from(first_ciphertext.as_ref()).is_ok(),
3587 "First message from Bob should be PreKeySignalMessage"
3588 );
3589
3590 let result = alice_bridge
3591 .decrypt_message(&bob_nostr_pubkey, &first_ciphertext)
3592 .await?;
3593 assert_eq!(result.plaintext, first_msg);
3594
3595 let bob_contact = alice_bridge.lookup_contact(&bob_nostr_pubkey).await?;
3596
3597 let alice_response = b"Hi Bob!";
3598 let alice_response_ciphertext = alice_bridge
3599 .encrypt_message(&bob_contact.rdx_fingerprint, alice_response)
3600 .await?;
3601
3602 assert!(
3603 SignalMessage::try_from(alice_response_ciphertext.as_ref()).is_ok(),
3604 "Alice's response uses SignalMessage (session established on her side during decrypt)"
3605 );
3606
3607 let bob_result = bob_bridge
3608 .decrypt_message(&alice_nostr_pubkey, &alice_response_ciphertext)
3609 .await?;
3610 assert_eq!(bob_result.plaintext, alice_response);
3611
3612 let second_msg = b"How are you?";
3613 let second_ciphertext = bob_bridge.encrypt_message(&alice_rdx, second_msg).await?;
3614
3615 assert!(
3616 SignalMessage::try_from(second_ciphertext.as_ref()).is_ok(),
3617 "After bidirectional session established, should use SignalMessage"
3618 );
3619
3620 let second_result = alice_bridge
3621 .decrypt_message(&bob_nostr_pubkey, &second_ciphertext)
3622 .await?;
3623 assert_eq!(second_result.plaintext, second_msg);
3624 assert!(
3625 !second_result.should_republish_bundle,
3626 "No pre-key consumed in SignalMessage"
3627 );
3628
3629 let _ = std::fs::remove_file(&alice_db_path);
3630 let _ = std::fs::remove_file(&bob_db_path);
3631 Ok(())
3632 }
3633
3634 #[tokio::test]
3635 async fn test_encrypt_message_stores_in_history() -> Result<(), Box<dyn std::error::Error>> {
3636 let temp_dir = std::env::temp_dir();
3637 let timestamp = SystemTime::now()
3638 .duration_since(UNIX_EPOCH)
3639 .unwrap()
3640 .as_millis();
3641
3642 let alice_db_path = temp_dir.join(format!("test_encrypt_history_alice_{}.db", timestamp));
3643 let mut alice_bridge = SignalBridge::new(alice_db_path.to_str().unwrap()).await?;
3644
3645 let bob_db_path = temp_dir.join(format!("test_encrypt_history_bob_{}.db", timestamp));
3646 let mut bob_bridge = SignalBridge::new(bob_db_path.to_str().unwrap()).await?;
3647
3648 let (alice_bundle_bytes, _, _, _) = alice_bridge.generate_pre_key_bundle().await?;
3649 let alice_rdx = bob_bridge
3650 .add_contact_and_establish_session(&alice_bundle_bytes, Some("Alice"))
3651 .await?;
3652
3653 let plaintext = b"Test outgoing message";
3654 let _ciphertext = bob_bridge.encrypt_message(&alice_rdx, plaintext).await?;
3655
3656 let messages = bob_bridge
3657 .storage
3658 .message_history()
3659 .get_conversation_messages(&alice_rdx, 10, 0)?;
3660
3661 assert_eq!(messages.len(), 1, "Should have stored 1 outgoing message");
3662 assert_eq!(
3663 messages[0].direction,
3664 crate::message_history::MessageDirection::Outgoing
3665 );
3666 assert_eq!(
3667 messages[0].content,
3668 String::from_utf8(plaintext.to_vec()).unwrap()
3669 );
3670
3671 let _ = std::fs::remove_file(&alice_db_path);
3672 let _ = std::fs::remove_file(&bob_db_path);
3673 Ok(())
3674 }
3675
3676 #[tokio::test]
3677 async fn test_decrypt_message_stores_in_history() -> Result<(), Box<dyn std::error::Error>> {
3678 let temp_dir = std::env::temp_dir();
3679 let timestamp = SystemTime::now()
3680 .duration_since(UNIX_EPOCH)
3681 .unwrap()
3682 .as_millis();
3683
3684 let alice_db_path = temp_dir.join(format!("test_decrypt_history_alice_{}.db", timestamp));
3685 let mut alice_bridge = SignalBridge::new(alice_db_path.to_str().unwrap()).await?;
3686
3687 let bob_db_path = temp_dir.join(format!("test_decrypt_history_bob_{}.db", timestamp));
3688 let mut bob_bridge = SignalBridge::new(bob_db_path.to_str().unwrap()).await?;
3689
3690 let (alice_bundle_bytes, _, _, _) = alice_bridge.generate_pre_key_bundle().await?;
3691
3692 let alice_rdx = bob_bridge
3693 .add_contact_and_establish_session(&alice_bundle_bytes, Some("Alice"))
3694 .await?;
3695
3696 let bob_keys = bob_bridge.derive_nostr_keypair().await?;
3697 let bob_nostr_pubkey = bob_keys.public_key().to_hex();
3698
3699 let plaintext = b"Test incoming message";
3700 let ciphertext = bob_bridge.encrypt_message(&alice_rdx, plaintext).await?;
3701
3702 let _result = alice_bridge.decrypt_message("", &ciphertext).await?;
3703
3704 let bob_contact = alice_bridge.lookup_contact(&bob_nostr_pubkey).await?;
3705
3706 let messages = alice_bridge
3707 .storage
3708 .message_history()
3709 .get_conversation_messages(&bob_contact.rdx_fingerprint, 10, 0)?;
3710
3711 assert_eq!(messages.len(), 1, "Should have stored 1 incoming message");
3712 assert_eq!(
3713 messages[0].direction,
3714 crate::message_history::MessageDirection::Incoming
3715 );
3716 assert_eq!(
3717 messages[0].content,
3718 String::from_utf8(plaintext.to_vec()).unwrap()
3719 );
3720 assert!(
3721 messages[0].was_prekey_message,
3722 "First message should be prekey"
3723 );
3724 assert!(
3725 messages[0].session_established,
3726 "Session should be established"
3727 );
3728
3729 let _ = std::fs::remove_file(&alice_db_path);
3730 let _ = std::fs::remove_file(&bob_db_path);
3731 Ok(())
3732 }
3733
3734 #[tokio::test]
3735 async fn test_bidirectional_message_history() -> Result<(), Box<dyn std::error::Error>> {
3736 let temp_dir = std::env::temp_dir();
3737 let timestamp = SystemTime::now()
3738 .duration_since(UNIX_EPOCH)
3739 .unwrap()
3740 .as_millis();
3741
3742 let alice_db_path =
3743 temp_dir.join(format!("test_bidirectional_history_alice_{}.db", timestamp));
3744 let mut alice_bridge = SignalBridge::new(alice_db_path.to_str().unwrap()).await?;
3745
3746 let bob_db_path = temp_dir.join(format!("test_bidirectional_history_bob_{}.db", timestamp));
3747 let mut bob_bridge = SignalBridge::new(bob_db_path.to_str().unwrap()).await?;
3748
3749 let (alice_bundle_bytes, _, _, _) = alice_bridge.generate_pre_key_bundle().await?;
3750 let alice_keys = alice_bridge.derive_nostr_keypair().await?;
3751 let alice_nostr_pubkey = alice_keys.public_key().to_hex();
3752
3753 let (bob_bundle_bytes, _, _, _) = bob_bridge.generate_pre_key_bundle().await?;
3754 let bob_keys = bob_bridge.derive_nostr_keypair().await?;
3755 let bob_nostr_pubkey = bob_keys.public_key().to_hex();
3756
3757 let bob_rdx = alice_bridge
3758 .add_contact_and_establish_session(&bob_bundle_bytes, Some("Bob"))
3759 .await?;
3760 let alice_rdx = bob_bridge
3761 .add_contact_and_establish_session(&alice_bundle_bytes, Some("Alice"))
3762 .await?;
3763
3764 let msg1 = b"Hello Bob!";
3765 let cipher1 = alice_bridge.encrypt_message(&bob_rdx, msg1).await?;
3766 let _result1 = bob_bridge
3767 .decrypt_message(&alice_nostr_pubkey, &cipher1)
3768 .await?;
3769
3770 let msg2 = b"Hi Alice!";
3771 let cipher2 = bob_bridge.encrypt_message(&alice_rdx, msg2).await?;
3772 let _result2 = alice_bridge
3773 .decrypt_message(&bob_nostr_pubkey, &cipher2)
3774 .await?;
3775
3776 let msg3 = b"How are you?";
3777 let cipher3 = alice_bridge.encrypt_message(&bob_rdx, msg3).await?;
3778 let _result3 = bob_bridge
3779 .decrypt_message(&alice_nostr_pubkey, &cipher3)
3780 .await?;
3781
3782 let alice_messages = alice_bridge
3783 .storage
3784 .message_history()
3785 .get_conversation_messages(&bob_rdx, 10, 0)?;
3786
3787 assert_eq!(
3788 alice_messages.len(),
3789 3,
3790 "Alice should have 3 messages (2 sent, 1 received)"
3791 );
3792
3793 let outgoing = alice_messages
3794 .iter()
3795 .filter(|m| m.direction == crate::message_history::MessageDirection::Outgoing)
3796 .count();
3797 let incoming = alice_messages
3798 .iter()
3799 .filter(|m| m.direction == crate::message_history::MessageDirection::Incoming)
3800 .count();
3801
3802 assert_eq!(outgoing, 2, "Alice sent 2 messages");
3803 assert_eq!(incoming, 1, "Alice received 1 message");
3804
3805 let bob_messages = bob_bridge
3806 .storage
3807 .message_history()
3808 .get_conversation_messages(&alice_rdx, 10, 0)?;
3809
3810 assert_eq!(
3811 bob_messages.len(),
3812 3,
3813 "Bob should have 3 messages (1 sent, 2 received)"
3814 );
3815
3816 let _ = std::fs::remove_file(&alice_db_path);
3817 let _ = std::fs::remove_file(&bob_db_path);
3818 Ok(())
3819 }
3820
3821 #[tokio::test]
3822 async fn test_conversation_list_ordering() -> Result<(), Box<dyn std::error::Error>> {
3823 let temp_dir = std::env::temp_dir();
3824 let timestamp = SystemTime::now()
3825 .duration_since(UNIX_EPOCH)
3826 .unwrap()
3827 .as_millis();
3828
3829 let alice_db_path =
3830 temp_dir.join(format!("test_conversation_ordering_alice_{}.db", timestamp));
3831 let mut alice_bridge = SignalBridge::new(alice_db_path.to_str().unwrap()).await?;
3832
3833 let bob_db_path = temp_dir.join(format!("test_conversation_ordering_bob_{}.db", timestamp));
3834 let mut bob_bridge = SignalBridge::new(bob_db_path.to_str().unwrap()).await?;
3835
3836 let charlie_db_path = temp_dir.join(format!(
3837 "test_conversation_ordering_charlie_{}.db",
3838 timestamp
3839 ));
3840 let mut charlie_bridge = SignalBridge::new(charlie_db_path.to_str().unwrap()).await?;
3841
3842 let (bob_bundle_bytes, _, _, _) = bob_bridge.generate_pre_key_bundle().await?;
3843 let bob_rdx = alice_bridge
3844 .add_contact_and_establish_session(&bob_bundle_bytes, Some("Bob"))
3845 .await?;
3846
3847 let (charlie_bundle_bytes, _, _, _) = charlie_bridge.generate_pre_key_bundle().await?;
3848 let charlie_rdx = alice_bridge
3849 .add_contact_and_establish_session(&charlie_bundle_bytes, Some("Charlie"))
3850 .await?;
3851
3852 let bob_msg = b"From Bob";
3853 let bob_cipher = alice_bridge.encrypt_message(&bob_rdx, bob_msg).await?;
3854
3855 let bob_keys = bob_bridge.derive_nostr_keypair().await?;
3856 let bob_nostr_pubkey = bob_keys.public_key().to_hex();
3857
3858 let _bob_result = bob_bridge
3859 .decrypt_message(&bob_nostr_pubkey, &bob_cipher)
3860 .await?;
3861
3862 tokio::time::sleep(tokio::time::Duration::from_millis(10)).await;
3863
3864 let charlie_msg = b"From Charlie";
3865 let charlie_cipher = alice_bridge
3866 .encrypt_message(&charlie_rdx, charlie_msg)
3867 .await?;
3868
3869 let charlie_keys = charlie_bridge.derive_nostr_keypair().await?;
3870 let charlie_nostr_pubkey = charlie_keys.public_key().to_hex();
3871
3872 let _charlie_result = charlie_bridge
3873 .decrypt_message(&charlie_nostr_pubkey, &charlie_cipher)
3874 .await?;
3875
3876 let conversations = alice_bridge
3877 .storage
3878 .message_history()
3879 .get_conversations(false)?;
3880
3881 assert_eq!(conversations.len(), 2, "Alice should have 2 conversations");
3882 assert!(
3883 conversations[0].last_message_timestamp > conversations[1].last_message_timestamp,
3884 "Conversations should be ordered by most recent message"
3885 );
3886
3887 let _ = std::fs::remove_file(&alice_db_path);
3888 let _ = std::fs::remove_file(&bob_db_path);
3889 let _ = std::fs::remove_file(&charlie_db_path);
3890 Ok(())
3891 }
3892}