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 mark_conversation_read_up_to(
1221 bridge: &mut SignalBridge,
1222 rdx_fingerprint: &str,
1223 up_to_timestamp: u64,
1224 ) -> Result<()>;
1225
1226 fn delete_message(bridge: &mut SignalBridge, message_id: i64) -> Result<()>;
1227
1228 fn delete_conversation(bridge: &mut SignalBridge, rdx_fingerprint: &str) -> Result<()>;
1229
1230 fn get_unread_count(bridge: &mut SignalBridge, rdx_fingerprint: &str) -> Result<u32>;
1231 }
1232}
1233
1234pub fn new_signal_bridge(db_path: &str) -> Result<Box<SignalBridge>, Box<dyn std::error::Error>> {
1239 let rt = tokio::runtime::Runtime::new()
1240 .map_err(|e| -> Box<dyn std::error::Error> { Box::new(e) })?;
1241 let bridge = rt
1242 .block_on(SignalBridge::new(db_path))
1243 .map_err(|e| -> Box<dyn std::error::Error> { Box::new(e) })?;
1244 Ok(Box::new(bridge))
1245}
1246
1247pub fn encrypt_message(
1254 bridge: &mut SignalBridge,
1255 peer: &str,
1256 plaintext: &[u8],
1257) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
1258 let rt = tokio::runtime::Runtime::new()
1259 .map_err(|e| -> Box<dyn std::error::Error> { Box::new(e) })?;
1260 rt.block_on(bridge.encrypt_message(peer, plaintext))
1261 .map_err(|e| -> Box<dyn std::error::Error> { Box::new(e) })
1262}
1263
1264pub fn decrypt_message(
1271 bridge: &mut SignalBridge,
1272 peer: &str,
1273 ciphertext: &[u8],
1274) -> Result<ffi::DecryptionResult, Box<dyn std::error::Error>> {
1275 let rt = tokio::runtime::Runtime::new()
1276 .map_err(|e| -> Box<dyn std::error::Error> { Box::new(e) })?;
1277 let result = rt
1278 .block_on(bridge.decrypt_message(peer, ciphertext))
1279 .map_err(|e| -> Box<dyn std::error::Error> { Box::new(e) })?;
1280 Ok(ffi::DecryptionResult {
1281 plaintext: result.plaintext,
1282 should_republish_bundle: result.should_republish_bundle,
1283 })
1284}
1285
1286pub fn establish_session(
1293 bridge: &mut SignalBridge,
1294 peer: &str,
1295 bundle: &[u8],
1296) -> Result<(), Box<dyn std::error::Error>> {
1297 let rt = tokio::runtime::Runtime::new()
1298 .map_err(|e| -> Box<dyn std::error::Error> { Box::new(e) })?;
1299 rt.block_on(bridge.establish_session(peer, bundle))
1300 .map_err(|e| -> Box<dyn std::error::Error> { Box::new(e) })
1301}
1302
1303pub fn generate_pre_key_bundle(
1311 bridge: &mut SignalBridge,
1312) -> Result<ffi::PreKeyBundleWithMetadata, Box<dyn std::error::Error>> {
1313 let rt = tokio::runtime::Runtime::new()
1314 .map_err(|e| -> Box<dyn std::error::Error> { Box::new(e) })?;
1315 let (bundle_bytes, pre_key_id, signed_pre_key_id, kyber_pre_key_id) = rt
1316 .block_on(bridge.generate_pre_key_bundle())
1317 .map_err(|e| -> Box<dyn std::error::Error> { Box::new(e) })?;
1318 Ok(ffi::PreKeyBundleWithMetadata {
1319 bundle_bytes,
1320 pre_key_id,
1321 signed_pre_key_id,
1322 kyber_pre_key_id,
1323 })
1324}
1325
1326pub fn clear_peer_session(
1332 bridge: &mut SignalBridge,
1333 peer: &str,
1334) -> Result<(), Box<dyn std::error::Error>> {
1335 let rt = tokio::runtime::Runtime::new()
1336 .map_err(|e| -> Box<dyn std::error::Error> { Box::new(e) })?;
1337 rt.block_on(bridge.clear_peer_session(peer))
1338 .map_err(|e| -> Box<dyn std::error::Error> { Box::new(e) })
1339}
1340
1341pub fn clear_all_sessions(bridge: &mut SignalBridge) -> Result<(), Box<dyn std::error::Error>> {
1346 let rt = tokio::runtime::Runtime::new()
1347 .map_err(|e| -> Box<dyn std::error::Error> { Box::new(e) })?;
1348 rt.block_on(bridge.clear_all_sessions())
1349 .map_err(|e| -> Box<dyn std::error::Error> { Box::new(e) })
1350}
1351
1352pub fn reset_identity(bridge: &mut SignalBridge) -> Result<(), Box<dyn std::error::Error>> {
1357 let rt = tokio::runtime::Runtime::new()
1358 .map_err(|e| -> Box<dyn std::error::Error> { Box::new(e) })?;
1359 rt.block_on(bridge.reset_identity())
1360 .map_err(|e| -> Box<dyn std::error::Error> { Box::new(e) })
1361}
1362
1363pub fn perform_key_maintenance(
1371 bridge: &mut SignalBridge,
1372) -> Result<ffi::KeyMaintenanceResult, Box<dyn std::error::Error>> {
1373 let rt = tokio::runtime::Runtime::new()
1374 .map_err(|e| -> Box<dyn std::error::Error> { Box::new(e) })?;
1375 let result = rt
1376 .block_on(bridge.perform_key_maintenance())
1377 .map_err(|e| -> Box<dyn std::error::Error> { Box::new(e) })?;
1378 Ok(ffi::KeyMaintenanceResult {
1379 signed_pre_key_rotated: result.signed_pre_key_rotated,
1380 kyber_pre_key_rotated: result.kyber_pre_key_rotated,
1381 pre_keys_replenished: result.pre_keys_replenished,
1382 })
1383}
1384
1385pub fn record_published_bundle(
1393 bridge: &mut SignalBridge,
1394 pre_key_id: u32,
1395 signed_pre_key_id: u32,
1396 kyber_pre_key_id: u32,
1397) -> Result<(), Box<dyn std::error::Error>> {
1398 bridge
1399 .storage
1400 .record_published_bundle(pre_key_id, signed_pre_key_id, kyber_pre_key_id)?;
1401 Ok(())
1402}
1403
1404pub fn get_conversations(
1410 bridge: &mut SignalBridge,
1411 include_archived: bool,
1412) -> Result<Vec<ffi::Conversation>, Box<dyn std::error::Error>> {
1413 let conversations = bridge
1414 .storage
1415 .message_history()
1416 .get_conversations(include_archived)?;
1417
1418 Ok(conversations
1419 .into_iter()
1420 .map(|c| ffi::Conversation {
1421 id: c.id,
1422 rdx_fingerprint: c.rdx_fingerprint,
1423 last_message_timestamp: c.last_message_timestamp,
1424 unread_count: c.unread_count,
1425 archived: c.archived,
1426 })
1427 .collect())
1428}
1429
1430pub fn get_conversation_messages(
1438 bridge: &mut SignalBridge,
1439 rdx_fingerprint: &str,
1440 limit: u32,
1441 offset: u32,
1442) -> Result<Vec<ffi::StoredMessage>, Box<dyn std::error::Error>> {
1443 let messages = bridge.storage.message_history().get_conversation_messages(
1444 rdx_fingerprint,
1445 limit,
1446 offset,
1447 )?;
1448
1449 Ok(messages
1450 .into_iter()
1451 .map(|m| ffi::StoredMessage {
1452 id: m.id,
1453 conversation_id: m.conversation_id,
1454 direction: match m.direction {
1455 MessageDirection::Incoming => ffi::MessageDirection::Incoming,
1456 MessageDirection::Outgoing => ffi::MessageDirection::Outgoing,
1457 },
1458 timestamp: m.timestamp,
1459 message_type: match m.message_type {
1460 MessageType::Text => ffi::MessageType::Text,
1461 MessageType::BundleAnnouncement => ffi::MessageType::BundleAnnouncement,
1462 MessageType::System => ffi::MessageType::System,
1463 },
1464 content: m.content,
1465 delivery_status: match m.delivery_status {
1466 DeliveryStatus::Pending => ffi::DeliveryStatus::Pending,
1467 DeliveryStatus::Sent => ffi::DeliveryStatus::Sent,
1468 DeliveryStatus::Delivered => ffi::DeliveryStatus::Delivered,
1469 DeliveryStatus::Failed => ffi::DeliveryStatus::Failed,
1470 },
1471 was_prekey_message: m.was_prekey_message,
1472 session_established: m.session_established,
1473 })
1474 .collect())
1475}
1476
1477pub fn mark_conversation_read(
1483 bridge: &mut SignalBridge,
1484 rdx_fingerprint: &str,
1485) -> Result<(), Box<dyn std::error::Error>> {
1486 bridge
1487 .storage
1488 .message_history()
1489 .mark_conversation_read(rdx_fingerprint)?;
1490 Ok(())
1491}
1492
1493pub fn mark_conversation_read_up_to(
1503 bridge: &mut SignalBridge,
1504 rdx_fingerprint: &str,
1505 up_to_timestamp: u64,
1506) -> Result<(), Box<dyn std::error::Error>> {
1507 bridge
1508 .storage
1509 .message_history()
1510 .mark_conversation_read_up_to(rdx_fingerprint, up_to_timestamp)?;
1511 Ok(())
1512}
1513
1514pub fn delete_message(
1520 bridge: &mut SignalBridge,
1521 message_id: i64,
1522) -> Result<(), Box<dyn std::error::Error>> {
1523 bridge
1524 .storage
1525 .message_history()
1526 .delete_message(message_id)?;
1527 Ok(())
1528}
1529
1530pub fn delete_conversation(
1536 bridge: &mut SignalBridge,
1537 rdx_fingerprint: &str,
1538) -> Result<(), Box<dyn std::error::Error>> {
1539 bridge
1540 .storage
1541 .message_history()
1542 .delete_conversation(rdx_fingerprint)?;
1543 Ok(())
1544}
1545
1546pub fn get_unread_count(
1552 bridge: &mut SignalBridge,
1553 rdx_fingerprint: &str,
1554) -> Result<u32, Box<dyn std::error::Error>> {
1555 let count = bridge
1556 .storage
1557 .message_history()
1558 .get_unread_count(rdx_fingerprint)?;
1559 Ok(count)
1560}
1561
1562pub fn generate_node_fingerprint(
1570 bridge: &mut SignalBridge,
1571) -> Result<String, Box<dyn std::error::Error>> {
1572 let rt = tokio::runtime::Runtime::new()
1573 .map_err(|e| -> Box<dyn std::error::Error> { Box::new(e) })?;
1574 rt.block_on(bridge.generate_node_fingerprint())
1575 .map_err(|e| -> Box<dyn std::error::Error> { Box::new(e) })
1576}
1577
1578pub fn sign_nostr_event(
1587 bridge: &mut SignalBridge,
1588 event_json: &str,
1589) -> Result<String, Box<dyn std::error::Error>> {
1590 let rt = tokio::runtime::Runtime::new()
1591 .map_err(|e| -> Box<dyn std::error::Error> { Box::new(e) })?;
1592
1593 let mut event: serde_json::Value = serde_json::from_str(event_json)
1594 .map_err(|e| -> Box<dyn std::error::Error> { Box::new(e) })?;
1595
1596 let created_at = event["created_at"].as_u64().ok_or("Missing created_at")?;
1597 let kind = event["kind"].as_u64().ok_or("Missing kind")? as u32;
1598 let content = event["content"].as_str().ok_or("Missing content")?;
1599
1600 let tags_array = event["tags"].as_array().ok_or("Missing tags")?;
1601 let tags: Vec<Vec<String>> = tags_array
1602 .iter()
1603 .map(|tag| {
1604 tag.as_array()
1605 .unwrap_or(&vec![])
1606 .iter()
1607 .map(|v| v.as_str().unwrap_or("").to_string())
1608 .collect()
1609 })
1610 .collect();
1611
1612 let (pubkey, event_id, signature) = rt
1613 .block_on(bridge.sign_nostr_event(created_at, kind, tags, content))
1614 .map_err(|e| -> Box<dyn std::error::Error> { Box::new(e) })?;
1615
1616 event["pubkey"] = serde_json::Value::String(pubkey);
1617 event["id"] = serde_json::Value::String(event_id);
1618 event["sig"] = serde_json::Value::String(signature);
1619
1620 serde_json::to_string(&event).map_err(|e| -> Box<dyn std::error::Error> { Box::new(e) })
1621}
1622
1623pub fn create_and_sign_encrypted_message(
1635 bridge: &mut SignalBridge,
1636 session_id: &str,
1637 encrypted_content: &str,
1638 timestamp: u64,
1639 project_version: &str,
1640) -> Result<String, Box<dyn std::error::Error>> {
1641 let rt = tokio::runtime::Runtime::new()
1642 .map_err(|e| -> Box<dyn std::error::Error> { Box::new(e) })?;
1643
1644 let recipient_pubkey_bytes = rt
1646 .block_on(bridge.derive_peer_nostr_key(session_id))
1647 .map_err(|e| -> Box<dyn std::error::Error> { Box::new(e) })?;
1648 let recipient_pubkey = hex::encode(&recipient_pubkey_bytes);
1649
1650 let event_json = serde_json::json!({
1652 "id": "",
1653 "pubkey": "",
1654 "created_at": timestamp,
1655 "kind": 40001,
1656 "tags": [
1657 ["p", recipient_pubkey],
1658 ["radix_version", project_version]
1659 ],
1660 "content": encrypted_content,
1661 "sig": ""
1662 });
1663
1664 let event_json_str = serde_json::to_string(&event_json)
1666 .map_err(|e| -> Box<dyn std::error::Error> { Box::new(e) })?;
1667
1668 sign_nostr_event(bridge, &event_json_str)
1670}
1671
1672pub fn create_subscription_for_self(
1682 bridge: &mut SignalBridge,
1683 subscription_id: &str,
1684 since_timestamp: u64,
1685) -> Result<String, Box<dyn std::error::Error>> {
1686 let rt = tokio::runtime::Runtime::new()
1687 .map_err(|e| -> Box<dyn std::error::Error> { Box::new(e) })?;
1688 rt.block_on(bridge.create_subscription_for_self(subscription_id, since_timestamp))
1689 .map_err(|e| -> Box<dyn std::error::Error> { Box::new(e) })
1690}
1691
1692pub fn update_last_message_timestamp(
1698 bridge: &mut SignalBridge,
1699 timestamp: u64,
1700) -> Result<(), Box<dyn std::error::Error>> {
1701 let rt = tokio::runtime::Runtime::new()
1702 .map_err(|e| -> Box<dyn std::error::Error> { Box::new(e) })?;
1703 rt.block_on(bridge.update_last_message_timestamp(timestamp))
1704 .map_err(|e| -> Box<dyn std::error::Error> { Box::new(e) })
1705}
1706
1707pub fn lookup_contact(
1716 bridge: &mut SignalBridge,
1717 identifier: &str,
1718) -> Result<ffi::ContactInfo, Box<dyn std::error::Error>> {
1719 let rt = tokio::runtime::Runtime::new()?;
1720 let contact = rt.block_on(bridge.lookup_contact(identifier))?;
1721 Ok(ffi::ContactInfo {
1722 rdx_fingerprint: contact.rdx_fingerprint,
1723 nostr_pubkey: contact.nostr_pubkey,
1724 user_alias: contact.user_alias.unwrap_or_default(),
1725 has_active_session: contact.has_active_session,
1726 })
1727}
1728
1729pub fn assign_contact_alias(
1736 bridge: &mut SignalBridge,
1737 identifier: &str,
1738 new_alias: &str,
1739) -> Result<(), Box<dyn std::error::Error>> {
1740 let rt = tokio::runtime::Runtime::new()?;
1741 rt.block_on(bridge.assign_contact_alias(identifier, new_alias))
1742 .map_err(|e| -> Box<dyn std::error::Error> { Box::new(e) })
1743}
1744
1745pub fn list_contacts(
1753 bridge: &mut SignalBridge,
1754) -> Result<Vec<ffi::ContactInfo>, Box<dyn std::error::Error>> {
1755 let rt = tokio::runtime::Runtime::new()?;
1756 let contacts = rt.block_on(bridge.list_contacts())?;
1757 Ok(contacts
1758 .into_iter()
1759 .map(|c| ffi::ContactInfo {
1760 rdx_fingerprint: c.rdx_fingerprint,
1761 nostr_pubkey: c.nostr_pubkey,
1762 user_alias: c.user_alias.unwrap_or_default(),
1763 has_active_session: c.has_active_session,
1764 })
1765 .collect())
1766}
1767
1768pub fn generate_prekey_bundle_announcement(
1777 bridge: &mut SignalBridge,
1778 project_version: &str,
1779) -> Result<ffi::BundleInfo, Box<dyn std::error::Error>> {
1780 let rt = tokio::runtime::Runtime::new()?;
1781 rt.block_on(bridge.generate_prekey_bundle_announcement(project_version))
1782 .map_err(|e| -> Box<dyn std::error::Error> { Box::new(e) })
1783}
1784
1785pub fn generate_empty_bundle_announcement(
1794 bridge: &mut SignalBridge,
1795 project_version: &str,
1796) -> Result<String, Box<dyn std::error::Error>> {
1797 let rt = tokio::runtime::Runtime::new()?;
1798 rt.block_on(bridge.generate_empty_bundle_announcement(project_version))
1799 .map_err(|e| -> Box<dyn std::error::Error> { Box::new(e) })
1800}
1801
1802pub fn add_contact_and_establish_session(
1812 bridge: &mut SignalBridge,
1813 bundle_bytes: &[u8],
1814 user_alias: &str,
1815) -> Result<String, Box<dyn std::error::Error>> {
1816 let rt = tokio::runtime::Runtime::new()?;
1817 let alias = if user_alias.is_empty() {
1818 None
1819 } else {
1820 Some(user_alias)
1821 };
1822 rt.block_on(bridge.add_contact_and_establish_session(bundle_bytes, alias))
1823 .map_err(|e| -> Box<dyn std::error::Error> { Box::new(e) })
1824}
1825
1826pub fn add_contact_and_establish_session_from_base64(
1836 bridge: &mut SignalBridge,
1837 bundle_base64: &str,
1838 user_alias: &str,
1839) -> Result<String, Box<dyn std::error::Error>> {
1840 use base64::Engine;
1841 let bundle_bytes = base64::engine::general_purpose::STANDARD
1842 .decode(bundle_base64)
1843 .map_err(|e| -> Box<dyn std::error::Error> {
1844 Box::new(SignalBridgeError::Protocol(format!(
1845 "Base64 decode error: {}",
1846 e
1847 )))
1848 })?;
1849
1850 add_contact_and_establish_session(bridge, &bundle_bytes, user_alias)
1851}
1852
1853pub fn extract_rdx_from_bundle(
1862 _bridge: &mut SignalBridge,
1863 bundle_bytes: &[u8],
1864) -> Result<String, Box<dyn std::error::Error>> {
1865 use libsignal_protocol::IdentityKey;
1866
1867 let bundle: SerializablePreKeyBundle =
1868 bincode::deserialize(bundle_bytes).map_err(|e| -> Box<dyn std::error::Error> {
1869 Box::new(SignalBridgeError::InvalidInput(format!(
1870 "Failed to deserialize bundle: {}",
1871 e
1872 )))
1873 })?;
1874
1875 let identity_key =
1876 IdentityKey::decode(&bundle.identity_key).map_err(|e| -> Box<dyn std::error::Error> {
1877 Box::new(SignalBridgeError::Protocol(e.to_string()))
1878 })?;
1879
1880 Ok(ContactManager::generate_identity_fingerprint_from_key(
1881 &identity_key,
1882 ))
1883}
1884
1885pub fn extract_rdx_from_bundle_base64(
1894 bridge: &mut SignalBridge,
1895 bundle_base64: &str,
1896) -> Result<String, Box<dyn std::error::Error>> {
1897 use base64::Engine;
1898 let bundle_bytes = base64::engine::general_purpose::STANDARD
1899 .decode(bundle_base64)
1900 .map_err(|e| -> Box<dyn std::error::Error> {
1901 Box::new(SignalBridgeError::Protocol(format!(
1902 "Base64 decode error: {}",
1903 e
1904 )))
1905 })?;
1906
1907 extract_rdx_from_bundle(bridge, &bundle_bytes)
1908}
1909
1910#[cfg(test)]
1911mod tests {
1912 use super::*;
1913 use std::time::{SystemTime, UNIX_EPOCH};
1914
1915 #[test]
1916 fn test_libsignal_basic_types() {
1917 let device_id = DeviceId::new(1).expect("Valid device ID");
1918 let protocol_address = ProtocolAddress::new("test_device".to_string(), device_id);
1919 assert_eq!(protocol_address.name(), "test_device");
1920 assert_eq!(protocol_address.device_id(), device_id);
1921 }
1922
1923 #[tokio::test]
1924 async fn test_nostr_key_derivation_deterministic() {
1925 let temp_dir = std::env::temp_dir();
1926 let db_path = temp_dir.join("test_nostr_derivation.db");
1927 let mut bridge = SignalBridge::new(db_path.to_str().unwrap()).await.unwrap();
1928
1929 let nostr_keypair1 = bridge.derive_nostr_keypair().await.unwrap();
1930 let nostr_keypair2 = bridge.derive_nostr_keypair().await.unwrap();
1931
1932 assert_eq!(nostr_keypair1.secret_key(), nostr_keypair2.secret_key());
1933 assert_eq!(nostr_keypair1.public_key(), nostr_keypair2.public_key());
1934
1935 let _ = std::fs::remove_file(&db_path);
1936 }
1937
1938 #[tokio::test]
1939 async fn test_peer_nostr_key_derivation() {
1940 let temp_dir = std::env::temp_dir();
1941 let alice_db = temp_dir.join("test_alice_nostr.db");
1942 let bob_db = temp_dir.join("test_bob_nostr.db");
1943
1944 let mut alice = SignalBridge::new(alice_db.to_str().unwrap()).await.unwrap();
1945 let mut bob = SignalBridge::new(bob_db.to_str().unwrap()).await.unwrap();
1946
1947 let (bob_bundle, _, _, _) = bob.generate_pre_key_bundle().await.unwrap();
1948 let bob_rdx = alice
1949 .add_contact_and_establish_session(&bob_bundle, Some("bob"))
1950 .await
1951 .unwrap();
1952
1953 let bob_nostr_key = alice.derive_peer_nostr_key(&bob_rdx).await.unwrap();
1954
1955 let bob_actual_nostr = bob.derive_nostr_keypair().await.unwrap();
1956 assert_eq!(
1957 bob_nostr_key,
1958 bob_actual_nostr.public_key().to_bytes().to_vec()
1959 );
1960
1961 let _ = std::fs::remove_file(&alice_db);
1962 let _ = std::fs::remove_file(&bob_db);
1963 }
1964
1965 #[test]
1966 fn test_error_types_creation() {
1967 let storage_error = SignalBridgeError::Storage("Database connection failed".to_string());
1968 assert_eq!(
1969 storage_error.to_string(),
1970 "Storage error: Database connection failed"
1971 );
1972
1973 let protocol_error = SignalBridgeError::Protocol("Invalid device ID".to_string());
1974 assert_eq!(
1975 protocol_error.to_string(),
1976 "Signal Protocol error: Invalid device ID"
1977 );
1978
1979 let serialization_error =
1980 SignalBridgeError::Serialization("Invalid data format".to_string());
1981 assert_eq!(
1982 serialization_error.to_string(),
1983 "Serialization error: Invalid data format"
1984 );
1985
1986 let invalid_input_error = SignalBridgeError::InvalidInput("Empty peer name".to_string());
1987 assert_eq!(
1988 invalid_input_error.to_string(),
1989 "Invalid input: Empty peer name"
1990 );
1991
1992 let session_not_found_error = SignalBridgeError::SessionNotFound(
1993 "Establish a session with alice before sending messages".to_string(),
1994 );
1995 assert_eq!(
1996 session_not_found_error.to_string(),
1997 "Establish a session with alice before sending messages"
1998 );
1999
2000 let schema_error = SignalBridgeError::SchemaVersionTooOld;
2001 assert_eq!(
2002 schema_error.to_string(),
2003 "Update your database to a newer schema version"
2004 );
2005 }
2006
2007 #[test]
2008 fn test_error_from_conversions() {
2009 let device_result = DeviceId::new(0);
2010 if let Err(protocol_err) = device_result {
2011 let bridge_error = SignalBridgeError::Protocol(protocol_err.to_string());
2012 assert!(matches!(bridge_error, SignalBridgeError::Protocol(_)));
2013 }
2014
2015 let invalid_data = vec![0xFF, 0xFF, 0xFF];
2016 let bincode_result: Result<String, bincode::Error> = bincode::deserialize(&invalid_data);
2017 if let Err(bincode_err) = bincode_result {
2018 let bridge_error: SignalBridgeError = bincode_err.into();
2019 assert!(matches!(bridge_error, SignalBridgeError::Serialization(_)));
2020 }
2021
2022 use std::time::{Duration, SystemTime};
2023 let bad_time = SystemTime::UNIX_EPOCH - Duration::from_secs(1);
2024 let time_result = bad_time.duration_since(SystemTime::UNIX_EPOCH);
2025 if let Err(time_err) = time_result {
2026 let bridge_error: SignalBridgeError = time_err.into();
2027 assert!(matches!(bridge_error, SignalBridgeError::Protocol(_)));
2028 }
2029 }
2030
2031 #[tokio::test]
2032 async fn test_crypto_handler_exists() -> Result<(), Box<dyn std::error::Error>> {
2033 use crate::SignalBridge;
2034 use std::env;
2035 use std::fs;
2036
2037 let temp_dir = env::temp_dir();
2038 let db_path = temp_dir.join("test_crypto_handler.db");
2039 let db_path_str = db_path.to_str().unwrap();
2040
2041 let _ = fs::remove_file(&db_path);
2042 let key_path = format!("{}.key", db_path_str);
2043 let _ = fs::remove_file(&key_path);
2044
2045 let _handler = SignalBridge::new(db_path_str).await?;
2046 Ok(())
2047 }
2048
2049 #[tokio::test]
2050 async fn test_crypto_handler_alice_bob_integration() -> Result<(), Box<dyn std::error::Error>> {
2051 use crate::SignalBridge;
2052 use std::env;
2053
2054 let temp_dir = env::temp_dir();
2055 let process_id = std::process::id();
2056 let timestamp = std::time::SystemTime::now()
2057 .duration_since(std::time::UNIX_EPOCH)
2058 .unwrap()
2059 .as_millis();
2060
2061 let alice_db_path = temp_dir.join(format!("test_alice_{}_{}.db", process_id, timestamp));
2062 let alice_db_str = alice_db_path.to_str().unwrap();
2063 let mut alice = SignalBridge::new(alice_db_str).await?;
2064
2065 let bob_db_path = temp_dir.join(format!("test_bob_{}_{}.db", process_id, timestamp));
2066 let bob_db_str = bob_db_path.to_str().unwrap();
2067 let mut bob = SignalBridge::new(bob_db_str).await?;
2068
2069 let (bob_bundle, _, _, _) = bob.generate_pre_key_bundle().await?;
2070 let (alice_bundle, _, _, _) = alice.generate_pre_key_bundle().await?;
2071
2072 let bob_rdx = alice
2073 .add_contact_and_establish_session(&bob_bundle, Some("bob"))
2074 .await?;
2075 let alice_rdx = bob
2076 .add_contact_and_establish_session(&alice_bundle, Some("alice"))
2077 .await?;
2078
2079 let plaintext = b"Hello Bob! This is Alice using SignalBridge.";
2080 let ciphertext = alice.encrypt_message(&bob_rdx, plaintext).await?;
2081 let result = bob.decrypt_message(&alice_rdx, &ciphertext).await?;
2082
2083 assert_eq!(plaintext.as_slice(), result.plaintext.as_slice());
2084
2085 Ok(())
2086 }
2087
2088 #[tokio::test]
2089 async fn test_signalbridge_persistence_across_instantiations(
2090 ) -> Result<(), Box<dyn std::error::Error>> {
2091 use crate::SignalBridge;
2092 use std::env;
2093
2094 let temp_dir = env::temp_dir();
2095 let process_id = std::process::id();
2096 let timestamp = std::time::SystemTime::now()
2097 .duration_since(std::time::UNIX_EPOCH)
2098 .unwrap()
2099 .as_millis();
2100
2101 let alice_db_path = temp_dir.join(format!(
2102 "test_persistence_alice_{}_{}.db",
2103 process_id, timestamp
2104 ));
2105 let alice_db_str = alice_db_path.to_str().unwrap();
2106 let bob_db_path = temp_dir.join(format!(
2107 "test_persistence_bob_{}_{}.db",
2108 process_id, timestamp
2109 ));
2110 let bob_db_str = bob_db_path.to_str().unwrap();
2111
2112 let original_plaintext = b"Hello Bob! This is Alice testing persistence.";
2113 let (ciphertext, bob_rdx, alice_rdx) = {
2114 let mut alice = SignalBridge::new(alice_db_str).await?;
2115 let mut bob = SignalBridge::new(bob_db_str).await?;
2116
2117 let (bob_bundle, _, _, _) = bob.generate_pre_key_bundle().await?;
2118 let (alice_bundle, _, _, _) = alice.generate_pre_key_bundle().await?;
2119
2120 let bob_rdx = alice
2121 .add_contact_and_establish_session(&bob_bundle, Some("bob"))
2122 .await?;
2123 let alice_rdx = bob
2124 .add_contact_and_establish_session(&alice_bundle, Some("alice"))
2125 .await?;
2126
2127 let ciphertext = alice.encrypt_message(&bob_rdx, original_plaintext).await?;
2128 (ciphertext, bob_rdx, alice_rdx)
2129 };
2130
2131 {
2132 let mut alice_reopened = SignalBridge::new(alice_db_str).await?;
2133
2134 let second_message = b"Second message after restart";
2135 let second_ciphertext = alice_reopened
2136 .encrypt_message(&bob_rdx, second_message)
2137 .await?;
2138
2139 assert!(!second_ciphertext.is_empty());
2140 assert_ne!(ciphertext, second_ciphertext);
2141 }
2142
2143 {
2144 let mut bob_reopened = SignalBridge::new(bob_db_str).await?;
2145
2146 let result1 = bob_reopened
2147 .decrypt_message(&alice_rdx, &ciphertext)
2148 .await?;
2149 assert_eq!(result1.plaintext, original_plaintext);
2150 }
2151
2152 Ok(())
2153 }
2154
2155 #[tokio::test]
2156 async fn test_clear_peer_session() -> Result<(), Box<dyn std::error::Error>> {
2157 use crate::SignalBridge;
2158 use std::env;
2159
2160 let temp_dir = env::temp_dir();
2161 let process_id = std::process::id();
2162 let timestamp = std::time::SystemTime::now()
2163 .duration_since(std::time::UNIX_EPOCH)
2164 .unwrap()
2165 .as_millis();
2166
2167 let alice_db_path = temp_dir.join(format!(
2168 "test_clear_peer_alice_{}_{}.db",
2169 process_id, timestamp
2170 ));
2171 let alice_db_str = alice_db_path.to_str().unwrap();
2172 let bob_db_path = temp_dir.join(format!(
2173 "test_clear_peer_bob_{}_{}.db",
2174 process_id, timestamp
2175 ));
2176 let bob_db_str = bob_db_path.to_str().unwrap();
2177
2178 let mut alice = SignalBridge::new(alice_db_str).await?;
2179 let mut bob = SignalBridge::new(bob_db_str).await?;
2180
2181 let (bob_bundle, _, _, _) = bob.generate_pre_key_bundle().await?;
2182 let bob_rdx = alice
2183 .add_contact_and_establish_session(&bob_bundle, Some("bob"))
2184 .await?;
2185
2186 let message = b"Test message";
2187 let _ciphertext = alice.encrypt_message(&bob_rdx, message).await?;
2188
2189 alice.clear_peer_session(&bob_rdx).await?;
2190
2191 let result = alice.encrypt_message(&bob_rdx, message).await;
2192 assert!(
2193 result.is_err(),
2194 "Encryption should fail after clearing peer session"
2195 );
2196
2197 Ok(())
2198 }
2199
2200 #[tokio::test]
2201 async fn test_clear_all_sessions() -> Result<(), Box<dyn std::error::Error>> {
2202 use crate::SignalBridge;
2203 use std::env;
2204
2205 let temp_dir = env::temp_dir();
2206 let process_id = std::process::id();
2207 let timestamp = std::time::SystemTime::now()
2208 .duration_since(std::time::UNIX_EPOCH)
2209 .unwrap()
2210 .as_millis();
2211
2212 let alice_db_path = temp_dir.join(format!(
2213 "test_clear_all_alice_{}_{}.db",
2214 process_id, timestamp
2215 ));
2216 let alice_db_str = alice_db_path.to_str().unwrap();
2217 let bob_db_path = temp_dir.join(format!(
2218 "test_clear_all_bob_{}_{}.db",
2219 process_id, timestamp
2220 ));
2221 let bob_db_str = bob_db_path.to_str().unwrap();
2222 let charlie_db_path = temp_dir.join(format!(
2223 "test_clear_all_charlie_{}_{}.db",
2224 process_id, timestamp
2225 ));
2226 let charlie_db_str = charlie_db_path.to_str().unwrap();
2227
2228 let mut alice = SignalBridge::new(alice_db_str).await?;
2229 let mut bob = SignalBridge::new(bob_db_str).await?;
2230 let mut charlie = SignalBridge::new(charlie_db_str).await?;
2231
2232 let (bob_bundle, _, _, _) = bob.generate_pre_key_bundle().await?;
2233 let (charlie_bundle, _, _, _) = charlie.generate_pre_key_bundle().await?;
2234
2235 let bob_rdx = alice
2236 .add_contact_and_establish_session(&bob_bundle, Some("bob"))
2237 .await?;
2238 let charlie_rdx = alice
2239 .add_contact_and_establish_session(&charlie_bundle, Some("charlie"))
2240 .await?;
2241
2242 let message = b"Test message";
2243 let _ciphertext1 = alice.encrypt_message(&bob_rdx, message).await?;
2244 let _ciphertext2 = alice.encrypt_message(&charlie_rdx, message).await?;
2245
2246 alice.clear_all_sessions().await?;
2247
2248 let result1 = alice.encrypt_message(&bob_rdx, message).await;
2249 let result2 = alice.encrypt_message(&charlie_rdx, message).await;
2250 assert!(
2251 result1.is_err(),
2252 "Encryption to Bob should fail after clearing all sessions"
2253 );
2254 assert!(
2255 result2.is_err(),
2256 "Encryption to Charlie should fail after clearing all sessions"
2257 );
2258
2259 Ok(())
2260 }
2261
2262 #[tokio::test]
2263 async fn test_reset_identity() -> Result<(), Box<dyn std::error::Error>> {
2264 use crate::SignalBridge;
2265 use std::env;
2266
2267 let temp_dir = env::temp_dir();
2268 let process_id = std::process::id();
2269 let timestamp = std::time::SystemTime::now()
2270 .duration_since(std::time::UNIX_EPOCH)
2271 .unwrap()
2272 .as_millis();
2273
2274 let alice_db_path = temp_dir.join(format!(
2275 "test_reset_identity_alice_{}_{}.db",
2276 process_id, timestamp
2277 ));
2278 let alice_db_str = alice_db_path.to_str().unwrap();
2279 let bob_db_path = temp_dir.join(format!(
2280 "test_reset_identity_bob_{}_{}.db",
2281 process_id, timestamp
2282 ));
2283 let bob_db_str = bob_db_path.to_str().unwrap();
2284
2285 let (original_alice_bundle, bob_rdx) = {
2286 let mut alice = SignalBridge::new(alice_db_str).await?;
2287 let mut bob = SignalBridge::new(bob_db_str).await?;
2288
2289 let (bob_bundle, _, _, _) = bob.generate_pre_key_bundle().await?;
2290 let bob_rdx = alice
2291 .add_contact_and_establish_session(&bob_bundle, Some("bob"))
2292 .await?;
2293
2294 let (alice_bundle, _, _, _) = alice.generate_pre_key_bundle().await?;
2295 (alice_bundle, bob_rdx)
2296 };
2297
2298 {
2299 let mut alice = SignalBridge::new(alice_db_str).await?;
2300 alice.reset_identity().await?;
2301
2302 let (new_alice_bundle, _, _, _) = alice.generate_pre_key_bundle().await?;
2303 assert_ne!(
2304 original_alice_bundle, new_alice_bundle,
2305 "Alice's identity should be different after reset"
2306 );
2307
2308 let message = b"Test message";
2309 let result = alice.encrypt_message(&bob_rdx, message).await;
2310 assert!(
2311 result.is_err(),
2312 "Encryption should fail after identity reset"
2313 );
2314 }
2315
2316 Ok(())
2317 }
2318
2319 #[tokio::test]
2320 async fn test_encrypt_message_empty_peer_name() {
2321 use std::env;
2322
2323 let temp_dir = env::temp_dir();
2324 let process_id = std::process::id();
2325 let timestamp = std::time::SystemTime::now()
2326 .duration_since(std::time::UNIX_EPOCH)
2327 .unwrap()
2328 .as_millis();
2329
2330 let db_path = temp_dir.join(format!("test_empty_peer_{}_{}.db", process_id, timestamp));
2331 let db_path_str = db_path.to_str().unwrap();
2332 let mut bridge = SignalBridge::new(db_path_str)
2333 .await
2334 .expect("Failed to create bridge");
2335
2336 let result = bridge.encrypt_message("", b"test message").await;
2337 assert!(result.is_err());
2338 if let Err(SignalBridgeError::InvalidInput(msg)) = result {
2339 assert_eq!(msg, "Specify a peer name");
2340 } else {
2341 panic!("Expected InvalidInput error for empty peer name");
2342 }
2343 }
2344
2345 #[tokio::test]
2346 async fn test_decrypt_message_empty_ciphertext() {
2347 use std::env;
2348
2349 let temp_dir = env::temp_dir();
2350 let process_id = std::process::id();
2351 let timestamp = std::time::SystemTime::now()
2352 .duration_since(std::time::UNIX_EPOCH)
2353 .unwrap()
2354 .as_millis();
2355
2356 let db_path = temp_dir.join(format!(
2357 "test_empty_ciphertext_{}_{}.db",
2358 process_id, timestamp
2359 ));
2360 let db_path_str = db_path.to_str().unwrap();
2361 let mut bridge = SignalBridge::new(db_path_str)
2362 .await
2363 .expect("Failed to create bridge");
2364
2365 let result = bridge.decrypt_message("alice", &[]).await;
2366 assert!(result.is_err());
2367 if let Err(SignalBridgeError::InvalidInput(msg)) = result {
2368 assert_eq!(msg, "Provide a message to decrypt");
2369 } else {
2370 panic!("Expected InvalidInput error for empty ciphertext");
2371 }
2372 }
2373
2374 #[tokio::test]
2375 async fn test_establish_session_empty_peer_name() {
2376 use std::env;
2377
2378 let temp_dir = env::temp_dir();
2379 let process_id = std::process::id();
2380 let timestamp = std::time::SystemTime::now()
2381 .duration_since(std::time::UNIX_EPOCH)
2382 .unwrap()
2383 .as_millis();
2384
2385 let db_path = temp_dir.join(format!(
2386 "test_empty_peer_session_{}_{}.db",
2387 process_id, timestamp
2388 ));
2389 let db_path_str = db_path.to_str().unwrap();
2390 let mut bridge = SignalBridge::new(db_path_str)
2391 .await
2392 .expect("Failed to create bridge");
2393
2394 let result = bridge.establish_session("", b"fake_bundle").await;
2395 assert!(result.is_err());
2396 if let Err(SignalBridgeError::InvalidInput(msg)) = result {
2397 assert_eq!(msg, "Specify a peer name");
2398 } else {
2399 panic!("Expected InvalidInput error for empty peer name");
2400 }
2401 }
2402
2403 #[tokio::test]
2404 async fn test_establish_session_empty_bundle() {
2405 use std::env;
2406
2407 let temp_dir = env::temp_dir();
2408 let process_id = std::process::id();
2409 let timestamp = std::time::SystemTime::now()
2410 .duration_since(std::time::UNIX_EPOCH)
2411 .unwrap()
2412 .as_millis();
2413
2414 let db_path = temp_dir.join(format!("test_empty_bundle_{}_{}.db", process_id, timestamp));
2415 let db_path_str = db_path.to_str().unwrap();
2416 let mut bridge = SignalBridge::new(db_path_str)
2417 .await
2418 .expect("Failed to create bridge");
2419
2420 let result = bridge.establish_session("alice", &[]).await;
2421 assert!(result.is_err());
2422 if let Err(SignalBridgeError::InvalidInput(msg)) = result {
2423 assert_eq!(msg, "Provide a pre-key bundle from the peer");
2424 } else {
2425 panic!("Expected InvalidInput error for empty pre-key bundle");
2426 }
2427 }
2428
2429 #[tokio::test]
2430 async fn test_clear_peer_session_empty_peer_name() {
2431 use std::env;
2432
2433 let temp_dir = env::temp_dir();
2434 let process_id = std::process::id();
2435 let timestamp = std::time::SystemTime::now()
2436 .duration_since(std::time::UNIX_EPOCH)
2437 .unwrap()
2438 .as_millis();
2439
2440 let db_path = temp_dir.join(format!(
2441 "test_clear_empty_peer_{}_{}.db",
2442 process_id, timestamp
2443 ));
2444 let db_path_str = db_path.to_str().unwrap();
2445 let mut bridge = SignalBridge::new(db_path_str)
2446 .await
2447 .expect("Failed to create bridge");
2448
2449 let result = bridge.clear_peer_session("").await;
2450 assert!(result.is_err());
2451 if let Err(SignalBridgeError::InvalidInput(msg)) = result {
2452 assert_eq!(msg, "Specify a peer name");
2453 } else {
2454 panic!("Expected InvalidInput error for empty peer name");
2455 }
2456 }
2457
2458 #[tokio::test]
2459 async fn test_encrypt_message_session_not_found() {
2460 use std::env;
2461
2462 let temp_dir = env::temp_dir();
2463 let process_id = std::process::id();
2464 let timestamp = std::time::SystemTime::now()
2465 .duration_since(std::time::UNIX_EPOCH)
2466 .unwrap()
2467 .as_millis();
2468
2469 let db_path = temp_dir.join(format!(
2470 "test_session_not_found_{}_{}.db",
2471 process_id, timestamp
2472 ));
2473 let db_path_str = db_path.to_str().unwrap();
2474 let mut bridge = SignalBridge::new(db_path_str)
2475 .await
2476 .expect("Failed to create bridge");
2477
2478 let result = bridge
2479 .encrypt_message("unknown_peer", b"test message")
2480 .await;
2481 assert!(result.is_err());
2482 if let Err(SignalBridgeError::SessionNotFound(msg)) = result {
2483 assert_eq!(
2484 msg,
2485 "Establish a session with unknown_peer before sending messages"
2486 );
2487 } else {
2488 panic!("Expected SessionNotFound error for unknown peer");
2489 }
2490 }
2491
2492 #[tokio::test]
2493 async fn test_decrypt_message_malformed_ciphertext() {
2494 use std::env;
2495
2496 let temp_dir = env::temp_dir();
2497 let process_id = std::process::id();
2498 let timestamp = std::time::SystemTime::now()
2499 .duration_since(std::time::UNIX_EPOCH)
2500 .unwrap()
2501 .as_millis();
2502
2503 let db_path = temp_dir.join(format!(
2504 "test_malformed_ciphertext_{}_{}.db",
2505 process_id, timestamp
2506 ));
2507 let db_path_str = db_path.to_str().unwrap();
2508 let mut bridge = SignalBridge::new(db_path_str)
2509 .await
2510 .expect("Failed to create bridge");
2511
2512 let malformed_data = vec![0x01, 0x02, 0x03, 0x04];
2513 let result = bridge.decrypt_message("alice", &malformed_data).await;
2514 assert!(result.is_err());
2515 if let Err(SignalBridgeError::Serialization(msg)) = result {
2516 assert_eq!(msg, "Provide a valid Signal Protocol message");
2517 } else {
2518 panic!("Expected Serialization error for malformed ciphertext");
2519 }
2520 }
2521
2522 #[tokio::test]
2523 async fn test_establish_session_malformed_bundle() {
2524 use std::env;
2525
2526 let temp_dir = env::temp_dir();
2527 let process_id = std::process::id();
2528 let timestamp = std::time::SystemTime::now()
2529 .duration_since(std::time::UNIX_EPOCH)
2530 .unwrap()
2531 .as_millis();
2532
2533 let db_path = temp_dir.join(format!(
2534 "test_malformed_bundle_{}_{}.db",
2535 process_id, timestamp
2536 ));
2537 let db_path_str = db_path.to_str().unwrap();
2538 let mut bridge = SignalBridge::new(db_path_str)
2539 .await
2540 .expect("Failed to create bridge");
2541
2542 let malformed_bundle = vec![0xFF, 0xFE, 0xFD, 0xFC];
2543 let result = bridge.establish_session("alice", &malformed_bundle).await;
2544 assert!(result.is_err());
2545 if let Err(SignalBridgeError::Serialization(msg)) = result {
2546 assert_eq!(msg, "io error: unexpected end of file");
2547 } else {
2548 panic!("Expected Serialization error for malformed bundle");
2549 }
2550 }
2551
2552 #[tokio::test]
2553 async fn test_sign_nostr_event() -> Result<(), Box<dyn std::error::Error>> {
2554 use std::env;
2555
2556 let temp_dir = env::temp_dir();
2557 let process_id = std::process::id();
2558 let timestamp = std::time::SystemTime::now()
2559 .duration_since(std::time::UNIX_EPOCH)
2560 .unwrap()
2561 .as_millis();
2562
2563 let db_path = temp_dir.join(format!("test_sign_nostr_{}_{}.db", process_id, timestamp));
2564 let db_path_str = db_path.to_str().unwrap();
2565 let mut bridge = SignalBridge::new(db_path_str).await?;
2566
2567 let _event_json = r#"{
2568 "id": "",
2569 "pubkey": "",
2570 "created_at": 1234567890,
2571 "kind": 40001,
2572 "tags": [
2573 ["p", "recipient_pubkey"]
2574 ],
2575 "content": "encrypted_content_here",
2576 "sig": ""
2577 }"#;
2578
2579 let result = bridge
2580 .sign_nostr_event(
2581 1234567890,
2582 40001,
2583 vec![vec!["p".to_string(), "recipient_pubkey".to_string()]],
2584 "encrypted_content_here",
2585 )
2586 .await;
2587 assert!(result.is_ok());
2588
2589 let (pubkey, event_id, signature) = result.unwrap();
2590 assert!(!pubkey.is_empty());
2591 assert!(!event_id.is_empty());
2592 assert!(!signature.is_empty());
2593 assert_eq!(event_id.len(), 64); assert_eq!(pubkey.len(), 64); let keys = bridge.derive_nostr_keypair().await?;
2597 assert_eq!(pubkey, keys.public_key().to_hex());
2598
2599 Ok(())
2600 }
2601
2602 #[tokio::test]
2603 async fn test_error_message_formatting() {
2604 let storage_error = SignalBridgeError::Storage("Database locked".to_string());
2605 assert_eq!(storage_error.to_string(), "Storage error: Database locked");
2606
2607 let protocol_error = SignalBridgeError::Protocol("Invalid signature".to_string());
2608 assert_eq!(
2609 protocol_error.to_string(),
2610 "Signal Protocol error: Invalid signature"
2611 );
2612
2613 let serialization_error = SignalBridgeError::Serialization("Invalid format".to_string());
2614 assert_eq!(
2615 serialization_error.to_string(),
2616 "Serialization error: Invalid format"
2617 );
2618
2619 let invalid_input_error = SignalBridgeError::InvalidInput("Name too long".to_string());
2620 assert_eq!(
2621 invalid_input_error.to_string(),
2622 "Invalid input: Name too long"
2623 );
2624
2625 let session_not_found_error = SignalBridgeError::SessionNotFound(
2626 "Establish a session with bob before sending messages".to_string(),
2627 );
2628 assert_eq!(
2629 session_not_found_error.to_string(),
2630 "Establish a session with bob before sending messages"
2631 );
2632
2633 let schema_error = SignalBridgeError::SchemaVersionTooOld;
2634 assert_eq!(
2635 schema_error.to_string(),
2636 "Update your database to a newer schema version"
2637 );
2638 }
2639
2640 #[tokio::test]
2641 async fn test_add_contact_and_establish_session_returns_rdx() {
2642 let temp_dir = std::env::temp_dir();
2643 let timestamp = SystemTime::now()
2644 .duration_since(UNIX_EPOCH)
2645 .unwrap()
2646 .as_millis();
2647
2648 let alice_db = temp_dir.join(format!("test_contact_alice_{}.db", timestamp));
2649 let bob_db = temp_dir.join(format!("test_contact_bob_{}.db", timestamp));
2650
2651 let mut alice = SignalBridge::new(alice_db.to_str().unwrap()).await.unwrap();
2652 let mut bob = SignalBridge::new(bob_db.to_str().unwrap()).await.unwrap();
2653
2654 let (bob_bundle, _, _, _) = bob.generate_pre_key_bundle().await.unwrap();
2655
2656 let bob_rdx = alice
2657 .add_contact_and_establish_session(&bob_bundle, None)
2658 .await
2659 .unwrap();
2660 assert!(bob_rdx.starts_with("RDX:"));
2661
2662 let _ = std::fs::remove_file(&alice_db);
2663 let _ = std::fs::remove_file(&bob_db);
2664 }
2665
2666 #[tokio::test]
2667 async fn test_lookup_contact_by_rdx() {
2668 let temp_dir = std::env::temp_dir();
2669 let timestamp = SystemTime::now()
2670 .duration_since(UNIX_EPOCH)
2671 .unwrap()
2672 .as_millis();
2673
2674 let alice_db = temp_dir.join(format!("test_lookup_alice_{}.db", timestamp));
2675 let bob_db = temp_dir.join(format!("test_lookup_bob_{}.db", timestamp));
2676
2677 let mut alice = SignalBridge::new(alice_db.to_str().unwrap()).await.unwrap();
2678 let mut bob = SignalBridge::new(bob_db.to_str().unwrap()).await.unwrap();
2679
2680 let (bob_bundle, _, _, _) = bob.generate_pre_key_bundle().await.unwrap();
2681 let bob_rdx = alice
2682 .add_contact_and_establish_session(&bob_bundle, None)
2683 .await
2684 .unwrap();
2685
2686 let contact = alice.lookup_contact(&bob_rdx).await.unwrap();
2687 assert_eq!(contact.rdx_fingerprint, bob_rdx);
2688 assert!(contact.user_alias.is_none());
2689 assert!(contact.has_active_session);
2690
2691 let _ = std::fs::remove_file(&alice_db);
2692 let _ = std::fs::remove_file(&bob_db);
2693 }
2694
2695 #[tokio::test]
2696 async fn test_assign_and_lookup_contact_by_alias() {
2697 let temp_dir = std::env::temp_dir();
2698 let timestamp = SystemTime::now()
2699 .duration_since(UNIX_EPOCH)
2700 .unwrap()
2701 .as_millis();
2702
2703 let alice_db = temp_dir.join(format!("test_alias_alice_{}.db", timestamp));
2704 let bob_db = temp_dir.join(format!("test_alias_bob_{}.db", timestamp));
2705
2706 let mut alice = SignalBridge::new(alice_db.to_str().unwrap()).await.unwrap();
2707 let mut bob = SignalBridge::new(bob_db.to_str().unwrap()).await.unwrap();
2708
2709 let (bob_bundle, _, _, _) = bob.generate_pre_key_bundle().await.unwrap();
2710 let bob_rdx = alice
2711 .add_contact_and_establish_session(&bob_bundle, None)
2712 .await
2713 .unwrap();
2714
2715 let contact_before = alice.lookup_contact(&bob_rdx).await.unwrap();
2716 assert!(contact_before.user_alias.is_none());
2717
2718 alice.assign_contact_alias(&bob_rdx, "bob").await.unwrap();
2719
2720 let contact_by_alias = alice.lookup_contact("bob").await.unwrap();
2721 assert_eq!(contact_by_alias.rdx_fingerprint, bob_rdx);
2722 assert_eq!(contact_by_alias.user_alias, Some("bob".to_string()));
2723
2724 let _ = std::fs::remove_file(&alice_db);
2725 let _ = std::fs::remove_file(&bob_db);
2726 }
2727
2728 #[tokio::test]
2729 async fn test_list_contacts() {
2730 let temp_dir = std::env::temp_dir();
2731 let timestamp = SystemTime::now()
2732 .duration_since(UNIX_EPOCH)
2733 .unwrap()
2734 .as_millis();
2735
2736 let alice_db = temp_dir.join(format!("test_list_alice_{}.db", timestamp));
2737 let bob_db = temp_dir.join(format!("test_list_bob_{}.db", timestamp));
2738 let charlie_db = temp_dir.join(format!("test_list_charlie_{}.db", timestamp));
2739
2740 let mut alice = SignalBridge::new(alice_db.to_str().unwrap()).await.unwrap();
2741 let mut bob = SignalBridge::new(bob_db.to_str().unwrap()).await.unwrap();
2742 let mut charlie = SignalBridge::new(charlie_db.to_str().unwrap())
2743 .await
2744 .unwrap();
2745
2746 let (bob_bundle, _, _, _) = bob.generate_pre_key_bundle().await.unwrap();
2747 let (charlie_bundle, _, _, _) = charlie.generate_pre_key_bundle().await.unwrap();
2748
2749 let bob_rdx = alice
2750 .add_contact_and_establish_session(&bob_bundle, Some("bob"))
2751 .await
2752 .unwrap();
2753 let charlie_rdx = alice
2754 .add_contact_and_establish_session(&charlie_bundle, None)
2755 .await
2756 .unwrap();
2757
2758 let contacts = alice.list_contacts().await.unwrap();
2759 assert_eq!(contacts.len(), 2);
2760
2761 let bob_contact = contacts
2762 .iter()
2763 .find(|c| c.rdx_fingerprint == bob_rdx)
2764 .unwrap();
2765 assert_eq!(bob_contact.user_alias, Some("bob".to_string()));
2766
2767 let charlie_contact = contacts
2768 .iter()
2769 .find(|c| c.rdx_fingerprint == charlie_rdx)
2770 .unwrap();
2771 assert!(charlie_contact.user_alias.is_none());
2772
2773 let _ = std::fs::remove_file(&alice_db);
2774 let _ = std::fs::remove_file(&bob_db);
2775 let _ = std::fs::remove_file(&charlie_db);
2776 }
2777
2778 #[tokio::test]
2779 async fn test_alias_with_multiple_sessions() {
2780 let temp_dir = std::env::temp_dir();
2781 let timestamp = SystemTime::now()
2782 .duration_since(UNIX_EPOCH)
2783 .unwrap()
2784 .as_millis();
2785
2786 let alice_db = temp_dir.join(format!("test_multi_alias_alice_{}.db", timestamp));
2787 let bob1_db = temp_dir.join(format!("test_multi_alias_bob1_{}.db", timestamp));
2788 let bob2_db = temp_dir.join(format!("test_multi_alias_bob2_{}.db", timestamp));
2789 let bob3_db = temp_dir.join(format!("test_multi_alias_bob3_{}.db", timestamp));
2790
2791 let mut alice = SignalBridge::new(alice_db.to_str().unwrap()).await.unwrap();
2792 let mut bob1 = SignalBridge::new(bob1_db.to_str().unwrap()).await.unwrap();
2793 let mut bob2 = SignalBridge::new(bob2_db.to_str().unwrap()).await.unwrap();
2794 let mut bob3 = SignalBridge::new(bob3_db.to_str().unwrap()).await.unwrap();
2795
2796 let (bob1_bundle, _, _, _) = bob1.generate_pre_key_bundle().await.unwrap();
2797 let (bob2_bundle, _, _, _) = bob2.generate_pre_key_bundle().await.unwrap();
2798 let (bob3_bundle, _, _, _) = bob3.generate_pre_key_bundle().await.unwrap();
2799
2800 let bob1_rdx = alice
2801 .add_contact_and_establish_session(&bob1_bundle, None)
2802 .await
2803 .unwrap();
2804 let bob2_rdx = alice
2805 .add_contact_and_establish_session(&bob2_bundle, None)
2806 .await
2807 .unwrap();
2808 let bob3_rdx = alice
2809 .add_contact_and_establish_session(&bob3_bundle, None)
2810 .await
2811 .unwrap();
2812
2813 assert_ne!(bob1_rdx, bob2_rdx);
2814 assert_ne!(bob2_rdx, bob3_rdx);
2815 assert_ne!(bob1_rdx, bob3_rdx);
2816
2817 alice.assign_contact_alias(&bob3_rdx, "bob").await.unwrap();
2818
2819 let contact_by_alias = alice.lookup_contact("bob").await.unwrap();
2820 assert_eq!(contact_by_alias.rdx_fingerprint, bob3_rdx);
2821 assert_eq!(contact_by_alias.user_alias, Some("bob".to_string()));
2822
2823 let contact1 = alice.lookup_contact(&bob1_rdx).await.unwrap();
2824 assert_eq!(contact1.rdx_fingerprint, bob1_rdx);
2825 assert!(contact1.user_alias.is_none());
2826
2827 let contact2 = alice.lookup_contact(&bob2_rdx).await.unwrap();
2828 assert_eq!(contact2.rdx_fingerprint, bob2_rdx);
2829 assert!(contact2.user_alias.is_none());
2830
2831 let _ = std::fs::remove_file(&alice_db);
2832 let _ = std::fs::remove_file(&bob1_db);
2833 let _ = std::fs::remove_file(&bob2_db);
2834 let _ = std::fs::remove_file(&bob3_db);
2835 }
2836
2837 #[tokio::test]
2838 async fn test_reassigning_same_alias_to_different_contact() {
2839 let temp_dir = std::env::temp_dir();
2840 let timestamp = SystemTime::now()
2841 .duration_since(UNIX_EPOCH)
2842 .unwrap()
2843 .as_millis();
2844
2845 let alice_db = temp_dir.join(format!("test_reassign_alias_alice_{}.db", timestamp));
2846 let bob1_db = temp_dir.join(format!("test_reassign_alias_bob1_{}.db", timestamp));
2847 let bob2_db = temp_dir.join(format!("test_reassign_alias_bob2_{}.db", timestamp));
2848
2849 let mut alice = SignalBridge::new(alice_db.to_str().unwrap()).await.unwrap();
2850 let mut bob1 = SignalBridge::new(bob1_db.to_str().unwrap()).await.unwrap();
2851 let mut bob2 = SignalBridge::new(bob2_db.to_str().unwrap()).await.unwrap();
2852
2853 let (bob1_bundle, _, _, _) = bob1.generate_pre_key_bundle().await.unwrap();
2854 let (bob2_bundle, _, _, _) = bob2.generate_pre_key_bundle().await.unwrap();
2855
2856 let bob1_rdx = alice
2857 .add_contact_and_establish_session(&bob1_bundle, None)
2858 .await
2859 .unwrap();
2860 let bob2_rdx = alice
2861 .add_contact_and_establish_session(&bob2_bundle, None)
2862 .await
2863 .unwrap();
2864
2865 alice.assign_contact_alias(&bob1_rdx, "bob").await.unwrap();
2866
2867 let contact = alice.lookup_contact("bob").await.unwrap();
2868 assert_eq!(contact.rdx_fingerprint, bob1_rdx);
2869
2870 let result = alice.assign_contact_alias(&bob2_rdx, "bob").await;
2871 assert!(result.is_err(), "Should fail when alias is already taken");
2872 assert!(
2873 result.unwrap_err().to_string().contains("already assigned"),
2874 "Error message should indicate alias is already assigned"
2875 );
2876
2877 let contact_still = alice.lookup_contact("bob").await.unwrap();
2878 assert_eq!(
2879 contact_still.rdx_fingerprint, bob1_rdx,
2880 "Alias 'bob' should still point to bob1"
2881 );
2882
2883 let _ = std::fs::remove_file(&alice_db);
2884 let _ = std::fs::remove_file(&bob1_db);
2885 let _ = std::fs::remove_file(&bob2_db);
2886 }
2887
2888 #[tokio::test]
2889 async fn test_generate_bundle_announcement_includes_rdx_tag() {
2890 let temp_dir = std::env::temp_dir();
2891 let timestamp = SystemTime::now()
2892 .duration_since(UNIX_EPOCH)
2893 .unwrap()
2894 .as_millis();
2895 let db_path = temp_dir.join(format!("test_generate_announcement_{}.db", timestamp));
2896
2897 let mut alice = SignalBridge::new(db_path.to_str().unwrap()).await.unwrap();
2898
2899 let bundle_info = alice
2900 .generate_prekey_bundle_announcement("1.0.0-test")
2901 .await
2902 .unwrap();
2903 let event: serde_json::Value =
2904 serde_json::from_str(&bundle_info.announcement_json).unwrap();
2905
2906 assert_eq!(event["kind"], 30078);
2907 assert!(event["content"].is_string());
2908
2909 let tags = event["tags"].as_array().unwrap();
2910 let rdx_tag = tags
2911 .iter()
2912 .find(|t| t[0] == "rdx")
2913 .expect("RDX tag should be present");
2914 let rdx_value = rdx_tag[1].as_str().unwrap();
2915 assert!(rdx_value.starts_with("RDX:"));
2916
2917 let version_tag = tags
2918 .iter()
2919 .find(|t| t[0] == "radix_version")
2920 .expect("Version tag should be present");
2921 assert_eq!(version_tag[1], "1.0.0-test");
2922
2923 let _ = std::fs::remove_file(&db_path);
2924 }
2925
2926 #[tokio::test]
2927 async fn test_add_contact_and_establish_session() {
2928 let temp_dir = std::env::temp_dir();
2929 let timestamp = SystemTime::now()
2930 .duration_since(UNIX_EPOCH)
2931 .unwrap()
2932 .as_millis();
2933 let alice_db = temp_dir.join(format!("test_contact_session_alice_{}.db", timestamp));
2934 let bob_db = temp_dir.join(format!("test_contact_session_bob_{}.db", timestamp));
2935
2936 let mut alice = SignalBridge::new(alice_db.to_str().unwrap()).await.unwrap();
2937 let mut bob = SignalBridge::new(bob_db.to_str().unwrap()).await.unwrap();
2938
2939 let (bob_bundle, _, _, _) = bob.generate_pre_key_bundle().await.unwrap();
2940
2941 let bob_rdx = alice
2942 .add_contact_and_establish_session(&bob_bundle, Some("bob"))
2943 .await
2944 .unwrap();
2945
2946 assert!(bob_rdx.starts_with("RDX:"));
2947
2948 let contact = alice.lookup_contact(&bob_rdx).await.unwrap();
2949 assert_eq!(contact.rdx_fingerprint, bob_rdx);
2950 assert_eq!(contact.user_alias, Some("bob".to_string()));
2951 assert!(contact.has_active_session);
2952
2953 let plaintext = b"Hello Bob!";
2954 let ciphertext = alice.encrypt_message(&bob_rdx, plaintext).await.unwrap();
2955 assert!(!ciphertext.is_empty());
2956
2957 let _ = std::fs::remove_file(&alice_db);
2958 let _ = std::fs::remove_file(&bob_db);
2959 }
2960
2961 #[tokio::test]
2962 async fn test_extract_rdx_from_bundle_without_establishing_session() {
2963 let temp_dir = std::env::temp_dir();
2964 let timestamp = SystemTime::now()
2965 .duration_since(UNIX_EPOCH)
2966 .unwrap()
2967 .as_millis();
2968
2969 let alice_db = temp_dir.join(format!("test_extract_rdx_alice_{}.db", timestamp));
2970 let bob_db = temp_dir.join(format!("test_extract_rdx_bob_{}.db", timestamp));
2971
2972 let mut alice = SignalBridge::new(alice_db.to_str().unwrap()).await.unwrap();
2973 let mut bob = SignalBridge::new(bob_db.to_str().unwrap()).await.unwrap();
2974
2975 let (bob_bundle, _, _, _) = bob.generate_pre_key_bundle().await.unwrap();
2976
2977 let extracted_rdx = extract_rdx_from_bundle(&mut alice, &bob_bundle).unwrap();
2978
2979 assert!(extracted_rdx.starts_with("RDX:"));
2980
2981 let result = alice.encrypt_message(&extracted_rdx, b"test").await;
2982 assert!(
2983 result.is_err(),
2984 "Should not be able to encrypt without establishing session"
2985 );
2986
2987 let bob_rdx = alice
2988 .add_contact_and_establish_session(&bob_bundle, Some("bob"))
2989 .await
2990 .unwrap();
2991
2992 assert_eq!(
2993 extracted_rdx, bob_rdx,
2994 "RDX extracted before session should match RDX after session establishment"
2995 );
2996
2997 let _ = std::fs::remove_file(&alice_db);
2998 let _ = std::fs::remove_file(&bob_db);
2999 }
3000
3001 #[tokio::test]
3002 async fn test_extract_rdx_from_bundle_base64() {
3003 let temp_dir = std::env::temp_dir();
3004 let timestamp = SystemTime::now()
3005 .duration_since(UNIX_EPOCH)
3006 .unwrap()
3007 .as_millis();
3008
3009 let alice_db = temp_dir.join(format!("test_extract_rdx_base64_alice_{}.db", timestamp));
3010 let bob_db = temp_dir.join(format!("test_extract_rdx_base64_bob_{}.db", timestamp));
3011
3012 let mut alice = SignalBridge::new(alice_db.to_str().unwrap()).await.unwrap();
3013 let mut bob = SignalBridge::new(bob_db.to_str().unwrap()).await.unwrap();
3014
3015 let (bob_bundle, _, _, _) = bob.generate_pre_key_bundle().await.unwrap();
3016 let bob_bundle_base64 = base64::engine::general_purpose::STANDARD.encode(&bob_bundle);
3017
3018 let extracted_rdx = extract_rdx_from_bundle_base64(&mut alice, &bob_bundle_base64).unwrap();
3019
3020 assert!(extracted_rdx.starts_with("RDX:"));
3021
3022 let bob_rdx = alice
3023 .add_contact_and_establish_session(&bob_bundle, Some("bob"))
3024 .await
3025 .unwrap();
3026
3027 assert_eq!(
3028 extracted_rdx, bob_rdx,
3029 "RDX extracted from base64 should match RDX after session establishment"
3030 );
3031
3032 let _ = std::fs::remove_file(&alice_db);
3033 let _ = std::fs::remove_file(&bob_db);
3034 }
3035
3036 #[tokio::test]
3037 async fn test_generate_empty_bundle_announcement() {
3038 let temp_dir = std::env::temp_dir();
3039 let timestamp = SystemTime::now()
3040 .duration_since(UNIX_EPOCH)
3041 .unwrap()
3042 .as_millis();
3043
3044 let db_path = temp_dir.join(format!("test_empty_bundle_{}.db", timestamp));
3045 let mut bridge = SignalBridge::new(db_path.to_str().unwrap()).await.unwrap();
3046
3047 let announcement_json = bridge
3048 .generate_empty_bundle_announcement("0.4.0")
3049 .await
3050 .unwrap();
3051
3052 let event: serde_json::Value = serde_json::from_str(&announcement_json).unwrap();
3053
3054 assert_eq!(event["kind"].as_u64().unwrap(), 30078);
3055 assert_eq!(event["content"].as_str().unwrap(), "");
3056
3057 let tags = event["tags"].as_array().unwrap();
3058 let d_tag = tags.iter().find(|t| t[0] == "d").unwrap();
3059 assert_eq!(d_tag[1].as_str().unwrap(), "radix_prekey_bundle_v1");
3060
3061 let version_tag = tags.iter().find(|t| t[0] == "radix_version").unwrap();
3062 assert_eq!(version_tag[1].as_str().unwrap(), "0.4.0");
3063
3064 assert!(
3065 !tags.iter().any(|t| t[0] == "rdx"),
3066 "Empty bundle should not contain rdx tag"
3067 );
3068
3069 let _ = std::fs::remove_file(&db_path);
3070 }
3071
3072 #[tokio::test]
3073 async fn test_perform_key_maintenance_replenishes_pre_keys(
3074 ) -> Result<(), Box<dyn std::error::Error>> {
3075 use crate::key_rotation::MIN_PRE_KEY_COUNT;
3076 use crate::SignalBridge;
3077
3078 let temp_dir = std::env::temp_dir();
3079 let timestamp = SystemTime::now()
3080 .duration_since(UNIX_EPOCH)
3081 .unwrap()
3082 .as_millis();
3083 let db_path = temp_dir.join(format!("test_maintenance_prekeys_{}.db", timestamp));
3084 let mut bridge = SignalBridge::new(db_path.to_str().unwrap()).await?;
3085
3086 let initial_count = bridge.storage.pre_key_store().pre_key_count().await;
3088
3089 bridge.perform_key_maintenance().await?;
3091
3092 let after_maintenance_count = bridge.storage.pre_key_store().pre_key_count().await;
3093 assert!(
3094 after_maintenance_count >= initial_count,
3095 "Pre-key count should not decrease after maintenance"
3096 );
3097
3098 bridge.storage.pre_key_store().clear_all_pre_keys().await?;
3100 let empty_count = bridge.storage.pre_key_store().pre_key_count().await;
3101 assert_eq!(empty_count, 0, "Pre-keys should be cleared");
3102
3103 bridge.perform_key_maintenance().await?;
3105
3106 let replenished_count = bridge.storage.pre_key_store().pre_key_count().await;
3107 assert!(
3108 replenished_count >= MIN_PRE_KEY_COUNT,
3109 "Pre-keys should be replenished to at least MIN_PRE_KEY_COUNT ({}), got {}",
3110 MIN_PRE_KEY_COUNT,
3111 replenished_count
3112 );
3113
3114 let _ = std::fs::remove_file(&db_path);
3115 Ok(())
3116 }
3117
3118 #[tokio::test]
3119 async fn test_perform_key_maintenance_no_rotation_for_fresh_keys(
3120 ) -> Result<(), Box<dyn std::error::Error>> {
3121 use crate::SignalBridge;
3122
3123 let temp_dir = std::env::temp_dir();
3124 let timestamp = SystemTime::now()
3125 .duration_since(UNIX_EPOCH)
3126 .unwrap()
3127 .as_millis();
3128 let db_path = temp_dir.join(format!("test_maintenance_fresh_{}.db", timestamp));
3129 let mut bridge = SignalBridge::new(db_path.to_str().unwrap()).await?;
3130
3131 let initial_signed_count = bridge
3133 .storage
3134 .signed_pre_key_store()
3135 .signed_pre_key_count()
3136 .await;
3137 let initial_kyber_count = bridge
3138 .storage
3139 .kyber_pre_key_store()
3140 .kyber_pre_key_count()
3141 .await;
3142
3143 bridge.perform_key_maintenance().await?;
3145
3146 let after_signed_count = bridge
3147 .storage
3148 .signed_pre_key_store()
3149 .signed_pre_key_count()
3150 .await;
3151 let after_kyber_count = bridge
3152 .storage
3153 .kyber_pre_key_store()
3154 .kyber_pre_key_count()
3155 .await;
3156
3157 assert_eq!(
3159 initial_signed_count, after_signed_count,
3160 "Fresh signed pre-keys should not be rotated"
3161 );
3162 assert_eq!(
3163 initial_kyber_count, after_kyber_count,
3164 "Fresh Kyber pre-keys should not be rotated"
3165 );
3166
3167 let _ = std::fs::remove_file(&db_path);
3168 Ok(())
3169 }
3170
3171 #[tokio::test]
3172 async fn test_perform_key_maintenance_idempotent() -> Result<(), Box<dyn std::error::Error>> {
3173 use crate::SignalBridge;
3174
3175 let temp_dir = std::env::temp_dir();
3176 let timestamp = SystemTime::now()
3177 .duration_since(UNIX_EPOCH)
3178 .unwrap()
3179 .as_millis();
3180 let db_path = temp_dir.join(format!("test_maintenance_idempotent_{}.db", timestamp));
3181 let mut bridge = SignalBridge::new(db_path.to_str().unwrap()).await?;
3182
3183 bridge.perform_key_maintenance().await?;
3185
3186 let first_pre_key_count = bridge.storage.pre_key_store().pre_key_count().await;
3187 let first_signed_count = bridge
3188 .storage
3189 .signed_pre_key_store()
3190 .signed_pre_key_count()
3191 .await;
3192
3193 bridge.perform_key_maintenance().await?;
3194
3195 let second_pre_key_count = bridge.storage.pre_key_store().pre_key_count().await;
3196 let second_signed_count = bridge
3197 .storage
3198 .signed_pre_key_store()
3199 .signed_pre_key_count()
3200 .await;
3201
3202 assert_eq!(
3204 first_pre_key_count, second_pre_key_count,
3205 "Pre-key count should be stable between maintenance calls"
3206 );
3207 assert_eq!(
3208 first_signed_count, second_signed_count,
3209 "Signed pre-key count should be stable between maintenance calls"
3210 );
3211
3212 let _ = std::fs::remove_file(&db_path);
3213 Ok(())
3214 }
3215
3216 #[tokio::test]
3217 async fn test_perform_key_maintenance_rotates_kyber_keys(
3218 ) -> Result<(), Box<dyn std::error::Error>> {
3219 use crate::key_rotation::ROTATION_INTERVAL_SECS;
3220 use crate::SignalBridge;
3221 use libsignal_protocol::{
3222 kem, GenericSignedPreKey, KyberPreKeyId, KyberPreKeyRecord, KyberPreKeyStore, Timestamp,
3223 };
3224
3225 let temp_dir = std::env::temp_dir();
3226 let timestamp = SystemTime::now()
3227 .duration_since(UNIX_EPOCH)
3228 .unwrap()
3229 .as_millis();
3230 let db_path = temp_dir.join(format!("test_maintenance_kyber_{}.db", timestamp));
3231 let mut bridge = SignalBridge::new(db_path.to_str().unwrap()).await?;
3232
3233 let initial_kyber_count = bridge
3235 .storage
3236 .kyber_pre_key_store()
3237 .kyber_pre_key_count()
3238 .await;
3239 assert!(initial_kyber_count >= 1, "Should have initial Kyber key");
3240
3241 let identity_key_pair = bridge
3243 .storage
3244 .identity_store()
3245 .get_identity_key_pair()
3246 .await?;
3247
3248 let old_timestamp =
3249 SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs() - (ROTATION_INTERVAL_SECS + 1);
3250
3251 let mut rng = rand::rng();
3252 let kyber_keypair = kem::KeyPair::generate(kem::KeyType::Kyber1024, &mut rng);
3253 let kyber_signature = identity_key_pair
3254 .private_key()
3255 .calculate_signature(&kyber_keypair.public_key.serialize(), &mut rng)?;
3256 let old_kyber_key = KyberPreKeyRecord::new(
3257 KyberPreKeyId::from(999u32),
3258 Timestamp::from_epoch_millis(old_timestamp * 1000),
3259 &kyber_keypair,
3260 &kyber_signature,
3261 );
3262
3263 bridge
3265 .storage
3266 .kyber_pre_key_store()
3267 .save_kyber_pre_key(KyberPreKeyId::from(999u32), &old_kyber_key)
3268 .await?;
3269
3270 let max_id_before = bridge
3272 .storage
3273 .kyber_pre_key_store()
3274 .get_max_kyber_pre_key_id()
3275 .await?
3276 .unwrap();
3277
3278 bridge.perform_key_maintenance().await?;
3280
3281 let max_id_after = bridge
3283 .storage
3284 .kyber_pre_key_store()
3285 .get_max_kyber_pre_key_id()
3286 .await?
3287 .unwrap();
3288
3289 assert!(
3291 max_id_after > max_id_before,
3292 "Max Kyber key ID should increase after rotation (before: {}, after: {})",
3293 max_id_before,
3294 max_id_after
3295 );
3296
3297 let _ = std::fs::remove_file(&db_path);
3298 Ok(())
3299 }
3300
3301 #[tokio::test]
3302 async fn test_perform_key_maintenance_cleans_up_expired_kyber_keys(
3303 ) -> Result<(), Box<dyn std::error::Error>> {
3304 use crate::key_rotation::GRACE_PERIOD_SECS;
3305 use crate::SignalBridge;
3306 use libsignal_protocol::{
3307 kem, GenericSignedPreKey, KyberPreKeyId, KyberPreKeyRecord, KyberPreKeyStore, Timestamp,
3308 };
3309
3310 let temp_dir = std::env::temp_dir();
3311 let timestamp = SystemTime::now()
3312 .duration_since(UNIX_EPOCH)
3313 .unwrap()
3314 .as_millis();
3315 let db_path = temp_dir.join(format!("test_maintenance_kyber_cleanup_{}.db", timestamp));
3316 let mut bridge = SignalBridge::new(db_path.to_str().unwrap()).await?;
3317
3318 let identity_key_pair = bridge
3319 .storage
3320 .identity_store()
3321 .get_identity_key_pair()
3322 .await?;
3323
3324 let expired_timestamp =
3326 SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs() - (GRACE_PERIOD_SECS + 1);
3327
3328 let mut rng = rand::rng();
3329 let kyber_keypair = kem::KeyPair::generate(kem::KeyType::Kyber1024, &mut rng);
3330 let kyber_signature = identity_key_pair
3331 .private_key()
3332 .calculate_signature(&kyber_keypair.public_key.serialize(), &mut rng)?;
3333 let expired_kyber_key = KyberPreKeyRecord::new(
3334 KyberPreKeyId::from(0u32), Timestamp::from_epoch_millis(expired_timestamp * 1000),
3336 &kyber_keypair,
3337 &kyber_signature,
3338 );
3339
3340 bridge
3341 .storage
3342 .kyber_pre_key_store()
3343 .save_kyber_pre_key(KyberPreKeyId::from(0u32), &expired_kyber_key)
3344 .await?;
3345
3346 let before_count = bridge
3347 .storage
3348 .kyber_pre_key_store()
3349 .kyber_pre_key_count()
3350 .await;
3351 assert!(
3352 before_count >= 2,
3353 "Should have at least 2 Kyber keys (fresh + expired)"
3354 );
3355
3356 bridge.perform_key_maintenance().await?;
3358
3359 let after_count = bridge
3360 .storage
3361 .kyber_pre_key_store()
3362 .kyber_pre_key_count()
3363 .await;
3364
3365 assert!(
3367 after_count < before_count,
3368 "Kyber key count should decrease after cleanup (before: {}, after: {})",
3369 before_count,
3370 after_count
3371 );
3372
3373 let _ = std::fs::remove_file(&db_path);
3374 Ok(())
3375 }
3376
3377 #[tokio::test]
3378 async fn test_bundle_uses_latest_available_keys() -> Result<(), Box<dyn std::error::Error>> {
3379 use crate::key_rotation::{rotate_kyber_pre_key, rotate_signed_pre_key};
3380 use crate::SignalBridge;
3381
3382 let temp_dir = std::env::temp_dir();
3383 let timestamp = SystemTime::now()
3384 .duration_since(UNIX_EPOCH)
3385 .unwrap()
3386 .as_millis();
3387 let db_path = temp_dir.join(format!("test_bundle_latest_keys_{}.db", timestamp));
3388 let mut bridge = SignalBridge::new(db_path.to_str().unwrap()).await?;
3389
3390 let (initial_bundle_bytes, _, _, _) = bridge.generate_pre_key_bundle().await?;
3392 let initial_bundle: SerializablePreKeyBundle = bincode::deserialize(&initial_bundle_bytes)?;
3393
3394 assert_eq!(initial_bundle.pre_key_id, Some(100));
3397 assert_eq!(initial_bundle.signed_pre_key_id, 1);
3398 assert_eq!(initial_bundle.kyber_pre_key_id, 1);
3399
3400 let identity_key_pair = bridge
3402 .storage
3403 .identity_store()
3404 .get_identity_key_pair()
3405 .await?;
3406 rotate_signed_pre_key(&mut bridge.storage, &identity_key_pair).await?;
3407 rotate_kyber_pre_key(&mut bridge.storage, &identity_key_pair).await?;
3408
3409 let (rotated_bundle_bytes, _, _, _) = bridge.generate_pre_key_bundle().await?;
3411 let rotated_bundle: SerializablePreKeyBundle = bincode::deserialize(&rotated_bundle_bytes)?;
3412
3413 assert_eq!(
3419 rotated_bundle.signed_pre_key_id, 2,
3420 "Bundle should use latest signed pre-key after rotation"
3421 );
3422 assert_eq!(
3423 rotated_bundle.kyber_pre_key_id, 2,
3424 "Bundle should use latest kyber pre-key after rotation"
3425 );
3426
3427 let _ = std::fs::remove_file(&db_path);
3428 Ok(())
3429 }
3430
3431 #[tokio::test]
3432 async fn test_extract_identity_from_prekey_message() -> Result<(), Box<dyn std::error::Error>> {
3433 use libsignal_protocol::PreKeySignalMessage;
3434
3435 let temp_dir = std::env::temp_dir();
3436 let timestamp = SystemTime::now()
3437 .duration_since(UNIX_EPOCH)
3438 .unwrap()
3439 .as_millis();
3440
3441 let alice_db_path = temp_dir.join(format!("test_extract_alice_{}.db", timestamp));
3442 let mut alice_bridge = SignalBridge::new(alice_db_path.to_str().unwrap()).await?;
3443
3444 let bob_db_path = temp_dir.join(format!("test_extract_bob_{}.db", timestamp));
3445 let mut bob_bridge = SignalBridge::new(bob_db_path.to_str().unwrap()).await?;
3446
3447 let (alice_bundle_bytes, _, _, _) = alice_bridge.generate_pre_key_bundle().await?;
3448 bob_bridge
3449 .establish_session("alice", &alice_bundle_bytes)
3450 .await?;
3451
3452 let plaintext = b"Hello Alice!";
3453 let ciphertext = bob_bridge.encrypt_message("alice", plaintext).await?;
3454
3455 let prekey_msg = PreKeySignalMessage::try_from(ciphertext.as_ref())?;
3456 let extracted_identity_key = prekey_msg.identity_key();
3457
3458 let bob_identity_key_pair = bob_bridge
3459 .storage
3460 .identity_store()
3461 .get_identity_key_pair()
3462 .await?;
3463 let bob_identity_key = bob_identity_key_pair.identity_key();
3464
3465 assert_eq!(
3466 extracted_identity_key.serialize(),
3467 bob_identity_key.serialize(),
3468 "Alice should be able to extract Bob's identity key from the PreKeySignalMessage"
3469 );
3470
3471 let _ = std::fs::remove_file(&alice_db_path);
3472 let _ = std::fs::remove_file(&bob_db_path);
3473 Ok(())
3474 }
3475
3476 #[tokio::test]
3477 async fn test_first_message_is_prekey_signal_message() -> Result<(), Box<dyn std::error::Error>>
3478 {
3479 use libsignal_protocol::PreKeySignalMessage;
3480
3481 let temp_dir = std::env::temp_dir();
3482 let timestamp = SystemTime::now()
3483 .duration_since(UNIX_EPOCH)
3484 .unwrap()
3485 .as_millis();
3486
3487 let alice_db_path = temp_dir.join(format!("test_prekey_type_alice_{}.db", timestamp));
3488 let mut alice_bridge = SignalBridge::new(alice_db_path.to_str().unwrap()).await?;
3489
3490 let bob_db_path = temp_dir.join(format!("test_prekey_type_bob_{}.db", timestamp));
3491 let mut bob_bridge = SignalBridge::new(bob_db_path.to_str().unwrap()).await?;
3492
3493 let (alice_bundle_bytes, _, _, _) = alice_bridge.generate_pre_key_bundle().await?;
3494 bob_bridge
3495 .establish_session("alice", &alice_bundle_bytes)
3496 .await?;
3497
3498 let plaintext = b"Hello Alice!";
3499 let ciphertext = bob_bridge.encrypt_message("alice", plaintext).await?;
3500
3501 assert!(
3502 PreKeySignalMessage::try_from(ciphertext.as_ref()).is_ok(),
3503 "First message to a new recipient must be a PreKeySignalMessage containing sender identity"
3504 );
3505
3506 let _ = std::fs::remove_file(&alice_db_path);
3507 let _ = std::fs::remove_file(&bob_db_path);
3508 Ok(())
3509 }
3510
3511 #[tokio::test]
3512 async fn test_decrypt_initial_message_from_unknown_sender(
3513 ) -> Result<(), Box<dyn std::error::Error>> {
3514 let temp_dir = std::env::temp_dir();
3515 let timestamp = SystemTime::now()
3516 .duration_since(UNIX_EPOCH)
3517 .unwrap()
3518 .as_millis();
3519
3520 let alice_db_path = temp_dir.join(format!("test_unknown_alice_{}.db", timestamp));
3521 let mut alice_bridge = SignalBridge::new(alice_db_path.to_str().unwrap()).await?;
3522
3523 let bob_db_path = temp_dir.join(format!("test_unknown_bob_{}.db", timestamp));
3524 let mut bob_bridge = SignalBridge::new(bob_db_path.to_str().unwrap()).await?;
3525
3526 let bob_nostr_keypair = bob_bridge.derive_nostr_keypair().await?;
3527 let bob_nostr_pubkey = bob_nostr_keypair.public_key().to_string();
3528
3529 let (alice_bundle_bytes, _, _, _) = alice_bridge.generate_pre_key_bundle().await?;
3530 let alice_rdx = bob_bridge
3531 .add_contact_and_establish_session(&alice_bundle_bytes, Some("Alice"))
3532 .await?;
3533
3534 let plaintext = b"Hello Alice!";
3535 let ciphertext = bob_bridge.encrypt_message(&alice_rdx, plaintext).await?;
3536
3537 let result = alice_bridge
3538 .decrypt_message(&bob_nostr_pubkey, &ciphertext)
3539 .await?;
3540
3541 assert_eq!(result.plaintext, plaintext);
3542 assert!(result.should_republish_bundle);
3543
3544 let bob_contact = alice_bridge.lookup_contact(&bob_nostr_pubkey).await?;
3545 assert!(bob_contact.rdx_fingerprint.starts_with("RDX:"));
3546 assert_eq!(bob_contact.nostr_pubkey, bob_nostr_pubkey);
3547 assert!(bob_contact
3548 .user_alias
3549 .as_ref()
3550 .unwrap()
3551 .starts_with("Unknown-"));
3552
3553 let response = b"Hi Bob!";
3554 let response_ciphertext = alice_bridge
3555 .encrypt_message(&bob_contact.rdx_fingerprint, response)
3556 .await?;
3557
3558 let decrypted_response = bob_bridge
3559 .decrypt_message(
3560 &alice_bridge
3561 .derive_nostr_keypair()
3562 .await?
3563 .public_key()
3564 .to_string(),
3565 &response_ciphertext,
3566 )
3567 .await?;
3568
3569 assert_eq!(decrypted_response.plaintext, response);
3570
3571 let _ = std::fs::remove_file(&alice_db_path);
3572 let _ = std::fs::remove_file(&bob_db_path);
3573 Ok(())
3574 }
3575
3576 #[tokio::test]
3577 async fn test_bidirectional_session_uses_signal_message(
3578 ) -> Result<(), Box<dyn std::error::Error>> {
3579 use libsignal_protocol::{PreKeySignalMessage, SignalMessage};
3580
3581 let temp_dir = std::env::temp_dir();
3582 let timestamp = SystemTime::now()
3583 .duration_since(UNIX_EPOCH)
3584 .unwrap()
3585 .as_millis();
3586
3587 let alice_db_path = temp_dir.join(format!("test_bidi_alice_{}.db", timestamp));
3588 let mut alice_bridge = SignalBridge::new(alice_db_path.to_str().unwrap()).await?;
3589
3590 let bob_db_path = temp_dir.join(format!("test_bidi_bob_{}.db", timestamp));
3591 let mut bob_bridge = SignalBridge::new(bob_db_path.to_str().unwrap()).await?;
3592
3593 let alice_nostr_pubkey = alice_bridge
3594 .derive_nostr_keypair()
3595 .await?
3596 .public_key()
3597 .to_string();
3598 let bob_nostr_pubkey = bob_bridge
3599 .derive_nostr_keypair()
3600 .await?
3601 .public_key()
3602 .to_string();
3603
3604 let (alice_bundle_bytes, _, _, _) = alice_bridge.generate_pre_key_bundle().await?;
3605 let alice_rdx = bob_bridge
3606 .add_contact_and_establish_session(&alice_bundle_bytes, Some("Alice"))
3607 .await?;
3608
3609 let first_msg = b"Hello Alice!";
3610 let first_ciphertext = bob_bridge.encrypt_message(&alice_rdx, first_msg).await?;
3611
3612 assert!(
3613 PreKeySignalMessage::try_from(first_ciphertext.as_ref()).is_ok(),
3614 "First message from Bob should be PreKeySignalMessage"
3615 );
3616
3617 let result = alice_bridge
3618 .decrypt_message(&bob_nostr_pubkey, &first_ciphertext)
3619 .await?;
3620 assert_eq!(result.plaintext, first_msg);
3621
3622 let bob_contact = alice_bridge.lookup_contact(&bob_nostr_pubkey).await?;
3623
3624 let alice_response = b"Hi Bob!";
3625 let alice_response_ciphertext = alice_bridge
3626 .encrypt_message(&bob_contact.rdx_fingerprint, alice_response)
3627 .await?;
3628
3629 assert!(
3630 SignalMessage::try_from(alice_response_ciphertext.as_ref()).is_ok(),
3631 "Alice's response uses SignalMessage (session established on her side during decrypt)"
3632 );
3633
3634 let bob_result = bob_bridge
3635 .decrypt_message(&alice_nostr_pubkey, &alice_response_ciphertext)
3636 .await?;
3637 assert_eq!(bob_result.plaintext, alice_response);
3638
3639 let second_msg = b"How are you?";
3640 let second_ciphertext = bob_bridge.encrypt_message(&alice_rdx, second_msg).await?;
3641
3642 assert!(
3643 SignalMessage::try_from(second_ciphertext.as_ref()).is_ok(),
3644 "After bidirectional session established, should use SignalMessage"
3645 );
3646
3647 let second_result = alice_bridge
3648 .decrypt_message(&bob_nostr_pubkey, &second_ciphertext)
3649 .await?;
3650 assert_eq!(second_result.plaintext, second_msg);
3651 assert!(
3652 !second_result.should_republish_bundle,
3653 "No pre-key consumed in SignalMessage"
3654 );
3655
3656 let _ = std::fs::remove_file(&alice_db_path);
3657 let _ = std::fs::remove_file(&bob_db_path);
3658 Ok(())
3659 }
3660
3661 #[tokio::test]
3662 async fn test_encrypt_message_stores_in_history() -> Result<(), Box<dyn std::error::Error>> {
3663 let temp_dir = std::env::temp_dir();
3664 let timestamp = SystemTime::now()
3665 .duration_since(UNIX_EPOCH)
3666 .unwrap()
3667 .as_millis();
3668
3669 let alice_db_path = temp_dir.join(format!("test_encrypt_history_alice_{}.db", timestamp));
3670 let mut alice_bridge = SignalBridge::new(alice_db_path.to_str().unwrap()).await?;
3671
3672 let bob_db_path = temp_dir.join(format!("test_encrypt_history_bob_{}.db", timestamp));
3673 let mut bob_bridge = SignalBridge::new(bob_db_path.to_str().unwrap()).await?;
3674
3675 let (alice_bundle_bytes, _, _, _) = alice_bridge.generate_pre_key_bundle().await?;
3676 let alice_rdx = bob_bridge
3677 .add_contact_and_establish_session(&alice_bundle_bytes, Some("Alice"))
3678 .await?;
3679
3680 let plaintext = b"Test outgoing message";
3681 let _ciphertext = bob_bridge.encrypt_message(&alice_rdx, plaintext).await?;
3682
3683 let messages = bob_bridge
3684 .storage
3685 .message_history()
3686 .get_conversation_messages(&alice_rdx, 10, 0)?;
3687
3688 assert_eq!(messages.len(), 1, "Should have stored 1 outgoing message");
3689 assert_eq!(
3690 messages[0].direction,
3691 crate::message_history::MessageDirection::Outgoing
3692 );
3693 assert_eq!(
3694 messages[0].content,
3695 String::from_utf8(plaintext.to_vec()).unwrap()
3696 );
3697
3698 let _ = std::fs::remove_file(&alice_db_path);
3699 let _ = std::fs::remove_file(&bob_db_path);
3700 Ok(())
3701 }
3702
3703 #[tokio::test]
3704 async fn test_decrypt_message_stores_in_history() -> Result<(), Box<dyn std::error::Error>> {
3705 let temp_dir = std::env::temp_dir();
3706 let timestamp = SystemTime::now()
3707 .duration_since(UNIX_EPOCH)
3708 .unwrap()
3709 .as_millis();
3710
3711 let alice_db_path = temp_dir.join(format!("test_decrypt_history_alice_{}.db", timestamp));
3712 let mut alice_bridge = SignalBridge::new(alice_db_path.to_str().unwrap()).await?;
3713
3714 let bob_db_path = temp_dir.join(format!("test_decrypt_history_bob_{}.db", timestamp));
3715 let mut bob_bridge = SignalBridge::new(bob_db_path.to_str().unwrap()).await?;
3716
3717 let (alice_bundle_bytes, _, _, _) = alice_bridge.generate_pre_key_bundle().await?;
3718
3719 let alice_rdx = bob_bridge
3720 .add_contact_and_establish_session(&alice_bundle_bytes, Some("Alice"))
3721 .await?;
3722
3723 let bob_keys = bob_bridge.derive_nostr_keypair().await?;
3724 let bob_nostr_pubkey = bob_keys.public_key().to_hex();
3725
3726 let plaintext = b"Test incoming message";
3727 let ciphertext = bob_bridge.encrypt_message(&alice_rdx, plaintext).await?;
3728
3729 let _result = alice_bridge.decrypt_message("", &ciphertext).await?;
3730
3731 let bob_contact = alice_bridge.lookup_contact(&bob_nostr_pubkey).await?;
3732
3733 let messages = alice_bridge
3734 .storage
3735 .message_history()
3736 .get_conversation_messages(&bob_contact.rdx_fingerprint, 10, 0)?;
3737
3738 assert_eq!(messages.len(), 1, "Should have stored 1 incoming message");
3739 assert_eq!(
3740 messages[0].direction,
3741 crate::message_history::MessageDirection::Incoming
3742 );
3743 assert_eq!(
3744 messages[0].content,
3745 String::from_utf8(plaintext.to_vec()).unwrap()
3746 );
3747 assert!(
3748 messages[0].was_prekey_message,
3749 "First message should be prekey"
3750 );
3751 assert!(
3752 messages[0].session_established,
3753 "Session should be established"
3754 );
3755
3756 let _ = std::fs::remove_file(&alice_db_path);
3757 let _ = std::fs::remove_file(&bob_db_path);
3758 Ok(())
3759 }
3760
3761 #[tokio::test]
3762 async fn test_bidirectional_message_history() -> Result<(), Box<dyn std::error::Error>> {
3763 let temp_dir = std::env::temp_dir();
3764 let timestamp = SystemTime::now()
3765 .duration_since(UNIX_EPOCH)
3766 .unwrap()
3767 .as_millis();
3768
3769 let alice_db_path =
3770 temp_dir.join(format!("test_bidirectional_history_alice_{}.db", timestamp));
3771 let mut alice_bridge = SignalBridge::new(alice_db_path.to_str().unwrap()).await?;
3772
3773 let bob_db_path = temp_dir.join(format!("test_bidirectional_history_bob_{}.db", timestamp));
3774 let mut bob_bridge = SignalBridge::new(bob_db_path.to_str().unwrap()).await?;
3775
3776 let (alice_bundle_bytes, _, _, _) = alice_bridge.generate_pre_key_bundle().await?;
3777 let alice_keys = alice_bridge.derive_nostr_keypair().await?;
3778 let alice_nostr_pubkey = alice_keys.public_key().to_hex();
3779
3780 let (bob_bundle_bytes, _, _, _) = bob_bridge.generate_pre_key_bundle().await?;
3781 let bob_keys = bob_bridge.derive_nostr_keypair().await?;
3782 let bob_nostr_pubkey = bob_keys.public_key().to_hex();
3783
3784 let bob_rdx = alice_bridge
3785 .add_contact_and_establish_session(&bob_bundle_bytes, Some("Bob"))
3786 .await?;
3787 let alice_rdx = bob_bridge
3788 .add_contact_and_establish_session(&alice_bundle_bytes, Some("Alice"))
3789 .await?;
3790
3791 let msg1 = b"Hello Bob!";
3792 let cipher1 = alice_bridge.encrypt_message(&bob_rdx, msg1).await?;
3793 let _result1 = bob_bridge
3794 .decrypt_message(&alice_nostr_pubkey, &cipher1)
3795 .await?;
3796
3797 let msg2 = b"Hi Alice!";
3798 let cipher2 = bob_bridge.encrypt_message(&alice_rdx, msg2).await?;
3799 let _result2 = alice_bridge
3800 .decrypt_message(&bob_nostr_pubkey, &cipher2)
3801 .await?;
3802
3803 let msg3 = b"How are you?";
3804 let cipher3 = alice_bridge.encrypt_message(&bob_rdx, msg3).await?;
3805 let _result3 = bob_bridge
3806 .decrypt_message(&alice_nostr_pubkey, &cipher3)
3807 .await?;
3808
3809 let alice_messages = alice_bridge
3810 .storage
3811 .message_history()
3812 .get_conversation_messages(&bob_rdx, 10, 0)?;
3813
3814 assert_eq!(
3815 alice_messages.len(),
3816 3,
3817 "Alice should have 3 messages (2 sent, 1 received)"
3818 );
3819
3820 let outgoing = alice_messages
3821 .iter()
3822 .filter(|m| m.direction == crate::message_history::MessageDirection::Outgoing)
3823 .count();
3824 let incoming = alice_messages
3825 .iter()
3826 .filter(|m| m.direction == crate::message_history::MessageDirection::Incoming)
3827 .count();
3828
3829 assert_eq!(outgoing, 2, "Alice sent 2 messages");
3830 assert_eq!(incoming, 1, "Alice received 1 message");
3831
3832 let bob_messages = bob_bridge
3833 .storage
3834 .message_history()
3835 .get_conversation_messages(&alice_rdx, 10, 0)?;
3836
3837 assert_eq!(
3838 bob_messages.len(),
3839 3,
3840 "Bob should have 3 messages (1 sent, 2 received)"
3841 );
3842
3843 let _ = std::fs::remove_file(&alice_db_path);
3844 let _ = std::fs::remove_file(&bob_db_path);
3845 Ok(())
3846 }
3847
3848 #[tokio::test]
3849 async fn test_conversation_list_ordering() -> Result<(), Box<dyn std::error::Error>> {
3850 let temp_dir = std::env::temp_dir();
3851 let timestamp = SystemTime::now()
3852 .duration_since(UNIX_EPOCH)
3853 .unwrap()
3854 .as_millis();
3855
3856 let alice_db_path =
3857 temp_dir.join(format!("test_conversation_ordering_alice_{}.db", timestamp));
3858 let mut alice_bridge = SignalBridge::new(alice_db_path.to_str().unwrap()).await?;
3859
3860 let bob_db_path = temp_dir.join(format!("test_conversation_ordering_bob_{}.db", timestamp));
3861 let mut bob_bridge = SignalBridge::new(bob_db_path.to_str().unwrap()).await?;
3862
3863 let charlie_db_path = temp_dir.join(format!(
3864 "test_conversation_ordering_charlie_{}.db",
3865 timestamp
3866 ));
3867 let mut charlie_bridge = SignalBridge::new(charlie_db_path.to_str().unwrap()).await?;
3868
3869 let (bob_bundle_bytes, _, _, _) = bob_bridge.generate_pre_key_bundle().await?;
3870 let bob_rdx = alice_bridge
3871 .add_contact_and_establish_session(&bob_bundle_bytes, Some("Bob"))
3872 .await?;
3873
3874 let (charlie_bundle_bytes, _, _, _) = charlie_bridge.generate_pre_key_bundle().await?;
3875 let charlie_rdx = alice_bridge
3876 .add_contact_and_establish_session(&charlie_bundle_bytes, Some("Charlie"))
3877 .await?;
3878
3879 let bob_msg = b"From Bob";
3880 let bob_cipher = alice_bridge.encrypt_message(&bob_rdx, bob_msg).await?;
3881
3882 let bob_keys = bob_bridge.derive_nostr_keypair().await?;
3883 let bob_nostr_pubkey = bob_keys.public_key().to_hex();
3884
3885 let _bob_result = bob_bridge
3886 .decrypt_message(&bob_nostr_pubkey, &bob_cipher)
3887 .await?;
3888
3889 tokio::time::sleep(tokio::time::Duration::from_millis(10)).await;
3890
3891 let charlie_msg = b"From Charlie";
3892 let charlie_cipher = alice_bridge
3893 .encrypt_message(&charlie_rdx, charlie_msg)
3894 .await?;
3895
3896 let charlie_keys = charlie_bridge.derive_nostr_keypair().await?;
3897 let charlie_nostr_pubkey = charlie_keys.public_key().to_hex();
3898
3899 let _charlie_result = charlie_bridge
3900 .decrypt_message(&charlie_nostr_pubkey, &charlie_cipher)
3901 .await?;
3902
3903 let conversations = alice_bridge
3904 .storage
3905 .message_history()
3906 .get_conversations(false)?;
3907
3908 assert_eq!(conversations.len(), 2, "Alice should have 2 conversations");
3909 assert!(
3910 conversations[0].last_message_timestamp > conversations[1].last_message_timestamp,
3911 "Conversations should be ordered by most recent message"
3912 );
3913
3914 let _ = std::fs::remove_file(&alice_db_path);
3915 let _ = std::fs::remove_file(&bob_db_path);
3916 let _ = std::fs::remove_file(&charlie_db_path);
3917 Ok(())
3918 }
3919}