messages.rs
1 //! Message structures and encryption for Dead Drop. 2 //! 3 //! This module defines the message types used in the Dead Drop protocol, 4 //! including plaintext message construction, encryption for transmission, 5 //! and lifecycle state tracking. 6 //! 7 //! # Message Flow 8 //! 9 //! ```text 10 //! ┌──────────────────┐ encrypt_message() ┌───────────────────┐ 11 //! │ PlaintextMessage │ ─────────────────────────> │ EncryptedMessage │ 12 //! │ (content, type) │ │ (ciphertext, │ 13 //! └──────────────────┘ │ signature, etc) │ 14 //! └───────────────────┘ 15 //! │ 16 //! │ transmit 17 //! ▼ 18 //! ┌───────────────────┐ 19 //! │ Wire (MsgData) │ 20 //! └───────────────────┘ 21 //! │ 22 //! │ receive 23 //! ▼ 24 //! ┌───────────────────┐ 25 //! │ EncryptedMessage │ 26 //! └───────────────────┘ 27 //! │ 28 //! decrypt_message() │ 29 //! ┌──────────────────┐ <───────────────────────────────────┘ 30 //! │ PlaintextMessage │ 31 //! └──────────────────┘ 32 //! ``` 33 //! 34 //! # Encryption Scheme 35 //! 36 //! Messages use a hybrid encryption scheme for forward secrecy: 37 //! 38 //! 1. Generate ephemeral X25519 key pair 39 //! 2. Perform ECDH with recipient's static exchange key 40 //! 3. Derive message key using HKDF 41 //! 4. Encrypt content with ChaCha20-Poly1305 42 //! 5. Sign the ciphertext with sender's identity key 43 //! 44 //! # Message Lifecycle 45 //! 46 //! Outbound messages progress through states: 47 //! `Draft` → `Queued` → `Transmitting` → `Delivered` → `Acknowledged` 48 //! 49 //! Inbound messages progress through states: 50 //! `Receiving` → `Received` → `Read` → (auto-deleted after TTL) 51 //! 52 //! # Example 53 //! 54 //! ``` 55 //! use dead_drop_core::crypto::keys::{IdentityKeyPair, ExchangeKeyPair}; 56 //! use dead_drop_core::protocol::messages::{ 57 //! PlaintextMessage, ContentType, encrypt_message, decrypt_message, 58 //! }; 59 //! 60 //! // Setup sender and recipient keys 61 //! let sender_identity = IdentityKeyPair::generate(); 62 //! let recipient_exchange = ExchangeKeyPair::generate(); 63 //! 64 //! // Create and encrypt a message 65 //! let plaintext = PlaintextMessage::text("Hello, World!"); 66 //! let encrypted = encrypt_message( 67 //! &plaintext, 68 //! &sender_identity, 69 //! &recipient_exchange.public_bytes(), 70 //! ).unwrap(); 71 //! 72 //! // Decrypt on the receiving end 73 //! let decrypted = decrypt_message( 74 //! &encrypted, 75 //! &recipient_exchange, 76 //! &sender_identity.public_bytes(), 77 //! ).unwrap(); 78 //! 79 //! assert_eq!(decrypted.content, plaintext.content); 80 //! ``` 81 82 use chrono::{DateTime, Duration, Utc}; 83 use serde::{Deserialize, Serialize}; 84 85 // ============================================================================= 86 // SERDE HELPERS FOR LARGE ARRAYS 87 // ============================================================================= 88 89 /// Helper module for serializing [u8; 64] arrays. 90 /// 91 /// Serde doesn't natively support arrays larger than 32 elements, 92 /// so we use a custom serializer that treats them as byte slices. 93 mod signature_serde { 94 use serde::de::{Error, SeqAccess, Visitor}; 95 use serde::{Deserializer, Serializer}; 96 97 pub fn serialize<S>(data: &[u8; 64], serializer: S) -> std::result::Result<S::Ok, S::Error> 98 where 99 S: Serializer, 100 { 101 serializer.serialize_bytes(data) 102 } 103 104 pub fn deserialize<'de, D>(deserializer: D) -> std::result::Result<[u8; 64], D::Error> 105 where 106 D: Deserializer<'de>, 107 { 108 struct ByteArrayVisitor; 109 110 impl<'de> Visitor<'de> for ByteArrayVisitor { 111 type Value = [u8; 64]; 112 113 fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { 114 formatter.write_str("a byte array of length 64") 115 } 116 117 fn visit_bytes<E>(self, v: &[u8]) -> std::result::Result<Self::Value, E> 118 where 119 E: Error, 120 { 121 v.try_into() 122 .map_err(|_| E::invalid_length(v.len(), &"64 bytes")) 123 } 124 125 fn visit_seq<A>(self, mut seq: A) -> std::result::Result<Self::Value, A::Error> 126 where 127 A: SeqAccess<'de>, 128 { 129 let mut arr = [0u8; 64]; 130 for (i, byte) in arr.iter_mut().enumerate() { 131 *byte = seq 132 .next_element()? 133 .ok_or_else(|| Error::invalid_length(i, &"64 bytes"))?; 134 } 135 Ok(arr) 136 } 137 } 138 139 deserializer.deserialize_bytes(ByteArrayVisitor) 140 } 141 } 142 143 use crate::crypto::{ 144 aead, ecdh, 145 hkdf::derive_message_key, 146 keys::{generate_random_id, ExchangeKeyPair, IdentityKeyPair}, 147 signing::{sign, verify}, 148 }; 149 use crate::error::{DeadDropError, Result}; 150 151 // ============================================================================= 152 // CONSTANTS 153 // ============================================================================= 154 155 /// Maximum plaintext content size in bytes. 156 /// 157 /// Set to 32KB to allow for text messages and small documents while 158 /// keeping transmission times reasonable over BLE. 159 pub const MAX_PLAINTEXT_SIZE: usize = 32768; 160 161 /// Default message expiry in days. 162 /// 163 /// Messages older than this are considered expired and will not be 164 /// delivered. Set to 30 days by default. 165 pub const DEFAULT_MESSAGE_EXPIRY_DAYS: i64 = 30; 166 167 /// Default auto-delete time after reading in days. 168 /// 169 /// Messages are automatically deleted this many days after being read. 170 pub const DEFAULT_AUTO_DELETE_DAYS: i64 = 7; 171 172 /// Size of the message ID in bytes. 173 pub const MESSAGE_ID_SIZE: usize = 16; 174 175 /// Size of the contact ID in bytes. 176 pub const CONTACT_ID_SIZE: usize = 16; 177 178 // ============================================================================= 179 // TYPE ALIASES 180 // ============================================================================= 181 182 /// Unique identifier for a message. 183 /// 184 /// A random 128-bit value generated for each message. Used for: 185 /// - Replay attack prevention 186 /// - Message tracking and acknowledgement 187 /// - Key derivation (as part of HKDF info) 188 pub type MessageId = [u8; MESSAGE_ID_SIZE]; 189 190 /// Unique identifier for a contact. 191 /// 192 /// A random 128-bit value assigned when a contact is added. This is 193 /// an internal identifier, not the contact's public key. 194 pub type ContactId = [u8; CONTACT_ID_SIZE]; 195 196 // ============================================================================= 197 // CONTENT TYPES 198 // ============================================================================= 199 200 /// Type of content in a message. 201 /// 202 /// Currently supports text and document (file) content types. 203 /// Additional types may be added in future protocol versions. 204 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] 205 #[repr(u8)] 206 pub enum ContentType { 207 /// Plain text content (UTF-8 encoded). 208 Text = 0, 209 /// Binary document/file content (small files, <= 30KB). 210 Document = 1, 211 /// Reaction to a message (JSON payload with target_message_id, emoji, action). 212 Reaction = 2, 213 /// Chunk of a large document (> 30KB files split into multiple messages). 214 DocumentChunk = 3, 215 /// Delivery receipt confirming message was received by the recipient. 216 DeliveryReceipt = 4, 217 /// Group management action (invite, add, remove, rename, leave). 218 GroupManagement = 5, 219 } 220 221 impl ContentType { 222 /// Convert a byte to ContentType. 223 pub fn from_byte(byte: u8) -> Option<Self> { 224 match byte { 225 0 => Some(ContentType::Text), 226 1 => Some(ContentType::Document), 227 2 => Some(ContentType::Reaction), 228 3 => Some(ContentType::DocumentChunk), 229 4 => Some(ContentType::DeliveryReceipt), 230 5 => Some(ContentType::GroupManagement), 231 _ => None, 232 } 233 } 234 235 /// Convert ContentType to byte. 236 pub fn to_byte(self) -> u8 { 237 self as u8 238 } 239 } 240 241 // ============================================================================= 242 // DOCUMENT CHUNK PAYLOAD 243 // ============================================================================= 244 245 /// Maximum size for a single chunk (30KB to leave room for metadata in 32KB limit). 246 pub const MAX_CHUNK_SIZE: usize = 30 * 1024; 247 248 /// Payload for chunked document messages. 249 /// 250 /// Large documents (> 30KB) are split into multiple chunks, each sent as a 251 /// separate message with `ContentType::DocumentChunk`. The receiver collects 252 /// all chunks and reassembles the original document. 253 #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 254 pub struct ChunkPayload { 255 /// Unique identifier grouping all chunks of the same document. 256 pub document_id: [u8; 16], 257 /// Zero-based index of this chunk. 258 pub chunk_index: u16, 259 /// Total number of chunks in the document. 260 pub total_chunks: u16, 261 /// Filename (included in all chunks for redundancy). 262 pub filename: String, 263 /// Total size of the complete document in bytes. 264 pub total_size: u64, 265 /// The chunk data (max 30KB). 266 pub data: Vec<u8>, 267 } 268 269 impl ChunkPayload { 270 /// Create a new chunk payload. 271 pub fn new( 272 document_id: [u8; 16], 273 chunk_index: u16, 274 total_chunks: u16, 275 filename: String, 276 total_size: u64, 277 data: Vec<u8>, 278 ) -> Self { 279 Self { 280 document_id, 281 chunk_index, 282 total_chunks, 283 filename, 284 total_size, 285 data, 286 } 287 } 288 289 /// Serialize to bytes. 290 pub fn to_bytes(&self) -> Vec<u8> { 291 bincode::serialize(self).unwrap_or_default() 292 } 293 294 /// Deserialize from bytes. 295 pub fn from_bytes(bytes: &[u8]) -> Result<Self> { 296 bincode::deserialize(bytes).map_err(|e| DeadDropError::Serialization(e.to_string())) 297 } 298 } 299 300 // ============================================================================= 301 // PLAINTEXT MESSAGE 302 // ============================================================================= 303 304 /// A message before encryption. 305 /// 306 /// Contains the raw content and metadata. This structure is serialized 307 /// and then encrypted to produce an [`EncryptedMessage`]. 308 /// 309 /// # Fields 310 /// 311 /// - `content_type`: Type of content (text or document) 312 /// - `content`: The actual content bytes 313 /// - `filename`: Optional filename for documents 314 /// 315 /// # Example 316 /// 317 /// ``` 318 /// use dead_drop_core::protocol::messages::{PlaintextMessage, ContentType}; 319 /// 320 /// // Create a text message 321 /// let text_msg = PlaintextMessage::text("Hello!"); 322 /// assert_eq!(text_msg.content_type, ContentType::Text); 323 /// 324 /// // Create a document message 325 /// let doc_msg = PlaintextMessage::document( 326 /// vec![0x50, 0x4B, 0x03, 0x04], // ZIP magic bytes 327 /// "archive.zip".to_string(), 328 /// ); 329 /// assert_eq!(doc_msg.content_type, ContentType::Document); 330 /// ``` 331 #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 332 pub struct PlaintextMessage { 333 /// The type of content. 334 pub content_type: ContentType, 335 /// The content bytes. 336 pub content: Vec<u8>, 337 /// Optional filename (for documents). 338 pub filename: Option<String>, 339 /// Disappearing message duration in seconds (None = standard retention). 340 #[serde(default)] 341 pub disappearing_duration: Option<u64>, 342 /// Unix timestamp (seconds UTC) when the message was composed. 343 #[serde(default)] 344 pub sent_at: Option<u64>, 345 /// Group ID if this is a group message (None = 1-to-1). 346 #[serde(default)] 347 pub group_id: Option<[u8; 16]>, 348 } 349 350 impl PlaintextMessage { 351 /// Create a text message. 352 /// 353 /// # Arguments 354 /// 355 /// * `text` - The message text (will be UTF-8 encoded) 356 /// 357 /// # Example 358 /// 359 /// ``` 360 /// use dead_drop_core::protocol::messages::PlaintextMessage; 361 /// 362 /// let msg = PlaintextMessage::text("Hello, World!"); 363 /// assert_eq!(msg.as_text().unwrap(), "Hello, World!"); 364 /// ``` 365 pub fn text(text: &str) -> Self { 366 Self { 367 content_type: ContentType::Text, 368 content: text.as_bytes().to_vec(), 369 filename: None, 370 disappearing_duration: None, 371 sent_at: None, 372 group_id: None, 373 } 374 } 375 376 /// Create a document message. 377 /// 378 /// # Arguments 379 /// 380 /// * `content` - The document bytes 381 /// * `filename` - The filename to display 382 pub fn document(content: Vec<u8>, filename: String) -> Self { 383 Self { 384 content_type: ContentType::Document, 385 content, 386 filename: Some(filename), 387 disappearing_duration: None, 388 sent_at: None, 389 group_id: None, 390 } 391 } 392 393 /// Create a message from raw components. 394 pub fn new(content_type: ContentType, content: Vec<u8>, filename: Option<String>) -> Self { 395 Self { 396 content_type, 397 content, 398 filename, 399 disappearing_duration: None, 400 sent_at: None, 401 group_id: None, 402 } 403 } 404 405 /// Get the content as a UTF-8 string (for text messages). 406 /// 407 /// # Returns 408 /// 409 /// `Some(String)` if the content is valid UTF-8, `None` otherwise. 410 pub fn as_text(&self) -> Option<String> { 411 if self.content_type == ContentType::Text { 412 String::from_utf8(self.content.clone()).ok() 413 } else { 414 None 415 } 416 } 417 418 /// Validate the message. 419 /// 420 /// # Errors 421 /// 422 /// - `MessageTooLarge` if content exceeds [`MAX_PLAINTEXT_SIZE`] 423 /// - `InvalidInput` if text content is not valid UTF-8 424 pub fn validate(&self) -> Result<()> { 425 if self.content.len() > MAX_PLAINTEXT_SIZE { 426 return Err(DeadDropError::MessageTooLarge { 427 size: self.content.len(), 428 max: MAX_PLAINTEXT_SIZE, 429 }); 430 } 431 432 if self.content_type == ContentType::Text { 433 std::str::from_utf8(&self.content).map_err(|_| { 434 DeadDropError::InvalidInput("Text content must be valid UTF-8".to_string()) 435 })?; 436 } 437 438 Ok(()) 439 } 440 441 /// Serialize to bytes for encryption. 442 pub fn to_bytes(&self) -> Result<Vec<u8>> { 443 bincode::serialize(self).map_err(|e| DeadDropError::Serialization(e.to_string())) 444 } 445 446 /// Deserialize from bytes after decryption. 447 pub fn from_bytes(bytes: &[u8]) -> Result<Self> { 448 bincode::deserialize(bytes) 449 .map_err(|e| DeadDropError::Deserialization(format!("Invalid plaintext: {}", e))) 450 } 451 452 /// Create a reaction message. 453 /// 454 /// # Arguments 455 /// 456 /// * `payload` - The reaction payload (serialized ReactionPayload) 457 pub fn reaction(payload: ReactionPayload) -> Self { 458 Self { 459 content_type: ContentType::Reaction, 460 content: payload.to_bytes(), 461 filename: None, 462 disappearing_duration: None, 463 sent_at: None, 464 group_id: None, 465 } 466 } 467 468 /// Create a delivery receipt message. 469 /// 470 /// # Arguments 471 /// 472 /// * `message_id` - The ID of the message being acknowledged 473 pub fn delivery_receipt(message_id: &[u8; 16]) -> Self { 474 Self { 475 content_type: ContentType::DeliveryReceipt, 476 content: message_id.to_vec(), 477 filename: None, 478 disappearing_duration: None, 479 sent_at: None, 480 group_id: None, 481 } 482 } 483 484 /// Parse reaction payload from content (for Reaction content type). 485 pub fn as_reaction(&self) -> Option<ReactionPayload> { 486 if self.content_type != ContentType::Reaction { 487 return None; 488 } 489 ReactionPayload::from_bytes(&self.content).ok() 490 } 491 } 492 493 // ============================================================================= 494 // REACTION PAYLOAD 495 // ============================================================================= 496 497 /// Payload for reaction messages. 498 /// 499 /// Reactions are sent as messages with ContentType::Reaction and this 500 /// structure serialized as the content. 501 #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 502 pub struct ReactionPayload { 503 /// The message ID being reacted to (16 bytes). 504 pub target_message_id: [u8; 16], 505 /// The emoji reaction. 506 pub emoji: String, 507 /// True to add the reaction, false to remove it. 508 pub add: bool, 509 } 510 511 impl ReactionPayload { 512 /// Create a new reaction payload. 513 pub fn new(target_message_id: [u8; 16], emoji: String, add: bool) -> Self { 514 Self { 515 target_message_id, 516 emoji, 517 add, 518 } 519 } 520 521 /// Serialize to bytes for transmission. 522 pub fn to_bytes(&self) -> Vec<u8> { 523 // Simple format: JSON for flexibility 524 serde_json::to_vec(self).unwrap_or_default() 525 } 526 527 /// Deserialize from bytes. 528 pub fn from_bytes(bytes: &[u8]) -> Result<Self> { 529 serde_json::from_slice(bytes) 530 .map_err(|e| DeadDropError::Deserialization(format!("Invalid reaction payload: {}", e))) 531 } 532 } 533 534 // ============================================================================= 535 // GROUP MANAGEMENT PAYLOAD 536 // ============================================================================= 537 538 /// Info about a group member (included in invites). 539 #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 540 pub struct GroupMemberInfo { 541 /// Ed25519 identity public key. 542 pub identity_public: [u8; 32], 543 /// X25519 exchange public key. 544 pub exchange_public: [u8; 32], 545 /// Display name / nickname. 546 pub nickname: Option<String>, 547 } 548 549 /// Group management actions sent as GroupManagement content type. 550 #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 551 pub enum GroupAction { 552 /// Invite to join a group (sent to each member on creation or to new member). 553 Invite { 554 group_id: [u8; 16], 555 group_name: String, 556 members: Vec<GroupMemberInfo>, 557 }, 558 /// A member was added (sent to existing members). 559 MemberAdded { 560 group_id: [u8; 16], 561 identity_public: [u8; 32], 562 exchange_public: [u8; 32], 563 nickname: Option<String>, 564 }, 565 /// A member was removed (sent to remaining members). 566 MemberRemoved { 567 group_id: [u8; 16], 568 identity_public: [u8; 32], 569 }, 570 /// Group was renamed. 571 Rename { 572 group_id: [u8; 16], 573 new_name: String, 574 }, 575 /// Member is leaving the group. 576 Leave { 577 group_id: [u8; 16], 578 }, 579 } 580 581 impl GroupAction { 582 /// Serialize to bytes for transmission. 583 pub fn to_bytes(&self) -> Vec<u8> { 584 serde_json::to_vec(self).unwrap_or_default() 585 } 586 587 /// Deserialize from bytes. 588 pub fn from_bytes(bytes: &[u8]) -> Result<Self> { 589 serde_json::from_slice(bytes) 590 .map_err(|e| DeadDropError::Deserialization(format!("Invalid group action: {}", e))) 591 } 592 } 593 594 // ============================================================================= 595 // ENCRYPTED MESSAGE 596 // ============================================================================= 597 598 /// An encrypted message ready for transmission. 599 /// 600 /// Contains all the data needed to transmit and later decrypt a message: 601 /// the ciphertext, ephemeral public key for key agreement, nonce, 602 /// timestamp, and sender's signature. 603 /// 604 /// # Wire Format 605 /// 606 /// When serialized, this structure is placed in the `payload` field 607 /// of a [`MsgData`](super::wire::MsgData) wire message. 608 /// 609 /// # Security Properties 610 /// 611 /// - **Confidentiality**: ChaCha20-Poly1305 encryption 612 /// - **Integrity**: Poly1305 authentication tag 613 /// - **Authenticity**: Ed25519 signature over ciphertext 614 /// - **Forward Secrecy**: Ephemeral key agreement 615 /// - **Replay Protection**: Unique message ID 616 #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] 617 pub struct EncryptedMessage { 618 /// Unique message identifier (random 128 bits). 619 pub message_id: MessageId, 620 /// Ephemeral X25519 public key for key agreement. 621 pub ephemeral_public: [u8; 32], 622 /// Nonce for ChaCha20-Poly1305 encryption. 623 pub nonce: [u8; 12], 624 /// Encrypted content (ciphertext + auth tag). 625 pub ciphertext: Vec<u8>, 626 /// Unix timestamp when the message was created. 627 pub timestamp: i64, 628 /// Ed25519 signature over the message_id + ciphertext. 629 #[serde(with = "signature_serde")] 630 pub signature: [u8; 64], 631 } 632 633 impl EncryptedMessage { 634 /// Check if the message has expired. 635 /// 636 /// # Arguments 637 /// 638 /// * `max_age_days` - Maximum message age in days 639 /// 640 /// # Returns 641 /// 642 /// `true` if the message timestamp is older than `max_age_days`. 643 pub fn is_expired(&self, max_age_days: i64) -> bool { 644 let created = DateTime::from_timestamp(self.timestamp, 0); 645 match created { 646 Some(created_time) => { 647 let expiry = created_time + Duration::days(max_age_days); 648 Utc::now() > expiry 649 } 650 None => true, // Invalid timestamp = expired 651 } 652 } 653 654 /// Get the data that was signed. 655 /// 656 /// The signature covers the message_id concatenated with the ciphertext. 657 fn signed_data(&self) -> Vec<u8> { 658 let mut data = Vec::with_capacity(self.message_id.len() + self.ciphertext.len()); 659 data.extend_from_slice(&self.message_id); 660 data.extend_from_slice(&self.ciphertext); 661 data 662 } 663 664 /// Serialize to bytes for wire transmission. 665 pub fn to_bytes(&self) -> Result<Vec<u8>> { 666 bincode::serialize(self).map_err(|e| DeadDropError::Serialization(e.to_string())) 667 } 668 669 /// Deserialize from wire bytes. 670 pub fn from_bytes(bytes: &[u8]) -> Result<Self> { 671 bincode::deserialize(bytes) 672 .map_err(|e| DeadDropError::Deserialization(format!("Invalid encrypted message: {}", e))) 673 } 674 } 675 676 // ============================================================================= 677 // MESSAGE STATES 678 // ============================================================================= 679 680 /// State of an outbound message in the sending lifecycle. 681 /// 682 /// Messages progress through these states as they are composed, 683 /// queued, transmitted, and acknowledged. 684 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] 685 #[repr(u8)] 686 pub enum OutboundState { 687 /// Message is being composed (not yet queued). 688 Draft = 0, 689 /// Message is queued for delivery. 690 Queued = 1, 691 /// Message is currently being transmitted. 692 Transmitting = 2, 693 /// Message was delivered (received by recipient). 694 Delivered = 3, 695 /// Message delivery was acknowledged by recipient. 696 Acknowledged = 4, 697 /// Message expired before delivery. 698 Expired = 5, 699 /// Message delivery failed (will retry). 700 Failed = 6, 701 /// Message is waiting for BLE gossip mesh relay delivery. 702 PendingRelay = 7, 703 } 704 705 impl OutboundState { 706 /// Check if this is a terminal state. 707 /// 708 /// Terminal states indicate the message lifecycle is complete. 709 pub fn is_terminal(&self) -> bool { 710 matches!(self, OutboundState::Acknowledged | OutboundState::Expired) 711 } 712 713 /// Check if the message is pending delivery. 714 pub fn is_pending(&self) -> bool { 715 matches!( 716 self, 717 OutboundState::Draft | OutboundState::Queued | OutboundState::Transmitting | OutboundState::PendingRelay 718 ) 719 } 720 721 /// Check if the message failed and can be retried. 722 pub fn is_failed(&self) -> bool { 723 matches!(self, OutboundState::Failed) 724 } 725 726 /// Convert from byte. 727 pub fn from_byte(byte: u8) -> Option<Self> { 728 match byte { 729 0 => Some(OutboundState::Draft), 730 1 => Some(OutboundState::Queued), 731 2 => Some(OutboundState::Transmitting), 732 3 => Some(OutboundState::Delivered), 733 4 => Some(OutboundState::Acknowledged), 734 5 => Some(OutboundState::Expired), 735 6 => Some(OutboundState::Failed), 736 7 => Some(OutboundState::PendingRelay), 737 _ => None, 738 } 739 } 740 } 741 742 /// State of an inbound message in the receiving lifecycle. 743 /// 744 /// Messages progress through these states as they are received, 745 /// read, and eventually deleted. 746 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] 747 #[repr(u8)] 748 pub enum InboundState { 749 /// Message is being received (partial transfer). 750 Receiving = 0, 751 /// Message was fully received but not yet read. 752 Received = 1, 753 /// Message has been read by the user. 754 Read = 2, 755 /// Message was deleted by the user. 756 Deleted = 3, 757 } 758 759 impl InboundState { 760 /// Check if this is a terminal state. 761 pub fn is_terminal(&self) -> bool { 762 matches!(self, InboundState::Deleted) 763 } 764 765 /// Convert from byte. 766 pub fn from_byte(byte: u8) -> Option<Self> { 767 match byte { 768 0 => Some(InboundState::Receiving), 769 1 => Some(InboundState::Received), 770 2 => Some(InboundState::Read), 771 3 => Some(InboundState::Deleted), 772 _ => None, 773 } 774 } 775 } 776 777 // ============================================================================= 778 // FULL MESSAGE RECORDS 779 // ============================================================================= 780 781 /// Complete outbound message record for storage. 782 /// 783 /// Contains both the plaintext (for display) and encrypted form 784 /// (for transmission), along with metadata and state. 785 #[derive(Debug, Clone)] 786 pub struct OutboundMessage { 787 /// Unique message ID. 788 pub message_id: MessageId, 789 /// ID of the recipient contact. 790 pub recipient_id: ContactId, 791 /// Content type (text or document). 792 pub content_type: ContentType, 793 /// Original plaintext content. 794 pub plaintext: Vec<u8>, 795 /// Optional filename for document messages. 796 pub filename: Option<String>, 797 /// Encrypted message for transmission. 798 pub encrypted: EncryptedMessage, 799 /// Current lifecycle state. 800 pub state: OutboundState, 801 /// When the message was created. 802 pub created_at: DateTime<Utc>, 803 /// When the message expires. 804 pub expires_at: DateTime<Utc>, 805 /// Number of delivery attempts. 806 pub attempts: u32, 807 /// Document ID for chunk messages (links chunks to parent document). 808 pub document_id: Option<MessageId>, 809 /// Group ID if this is a group message. 810 pub group_id: Option<[u8; 16]>, 811 } 812 813 impl OutboundMessage { 814 /// Check if the message has expired. 815 pub fn is_expired(&self) -> bool { 816 Utc::now() > self.expires_at 817 } 818 819 /// Increment the attempt counter and return the new count. 820 pub fn increment_attempts(&mut self) -> u32 { 821 self.attempts += 1; 822 self.attempts 823 } 824 } 825 826 /// Complete inbound message record for storage. 827 /// 828 /// Contains the decrypted plaintext along with metadata about 829 /// receipt and display state. 830 #[derive(Debug, Clone)] 831 pub struct InboundMessage { 832 /// Unique message ID. 833 pub message_id: MessageId, 834 /// ID of the sender contact. 835 pub sender_id: ContactId, 836 /// Content type (text or document). 837 pub content_type: ContentType, 838 /// Decrypted plaintext content. 839 pub plaintext: Vec<u8>, 840 /// Optional filename (for documents). 841 pub filename: Option<String>, 842 /// Current lifecycle state. 843 pub state: InboundState, 844 /// When the message was received. 845 pub received_at: DateTime<Utc>, 846 /// When the message was read (if read). 847 pub read_at: Option<DateTime<Utc>>, 848 /// When the message should be auto-deleted. 849 pub delete_at: DateTime<Utc>, 850 /// Transport used to receive this message (BLE, DHT, Relay). 851 pub receive_transport: Option<u8>, 852 /// Disappearing message duration in seconds (0 = standard retention). 853 pub disappearing_duration: u64, 854 /// When the sender composed the message (if available). 855 pub sent_at: Option<DateTime<Utc>>, 856 /// Group ID if this is a group message. 857 pub group_id: Option<[u8; 16]>, 858 } 859 860 impl InboundMessage { 861 /// Mark the message as read. 862 /// 863 /// Updates the state and sets the read timestamp. 864 pub fn mark_read(&mut self) { 865 if self.state == InboundState::Received { 866 self.state = InboundState::Read; 867 self.read_at = Some(Utc::now()); 868 } 869 } 870 871 /// Check if the message should be auto-deleted. 872 pub fn should_auto_delete(&self) -> bool { 873 Utc::now() > self.delete_at 874 } 875 876 /// Get the content as a string (for text messages). 877 pub fn as_text(&self) -> Option<String> { 878 if self.content_type == ContentType::Text { 879 String::from_utf8(self.plaintext.clone()).ok() 880 } else { 881 None 882 } 883 } 884 } 885 886 // ============================================================================= 887 // ENCRYPTION / DECRYPTION 888 // ============================================================================= 889 890 /// Encrypt a plaintext message for a recipient. 891 /// 892 /// Performs the full encryption flow: 893 /// 1. Validates the plaintext 894 /// 2. Generates ephemeral key pair 895 /// 3. Performs ECDH with recipient's exchange key 896 /// 4. Derives message key using HKDF 897 /// 5. Encrypts with ChaCha20-Poly1305 898 /// 6. Signs the ciphertext with sender's identity key 899 /// 900 /// # Arguments 901 /// 902 /// * `plaintext` - The message to encrypt 903 /// * `sender_identity` - Sender's identity key pair (for signing) 904 /// * `recipient_exchange_public` - Recipient's X25519 public key 905 /// 906 /// # Returns 907 /// 908 /// An encrypted message ready for transmission. 909 /// 910 /// # Errors 911 /// 912 /// - `MessageTooLarge` if plaintext exceeds size limit 913 /// - `Encryption` if encryption fails 914 /// 915 /// # Example 916 /// 917 /// ``` 918 /// use dead_drop_core::crypto::keys::{IdentityKeyPair, ExchangeKeyPair}; 919 /// use dead_drop_core::protocol::messages::{PlaintextMessage, encrypt_message}; 920 /// 921 /// let sender = IdentityKeyPair::generate(); 922 /// let recipient = ExchangeKeyPair::generate(); 923 /// 924 /// let msg = PlaintextMessage::text("Secret message"); 925 /// let encrypted = encrypt_message(&msg, &sender, &recipient.public_bytes()).unwrap(); 926 /// ``` 927 pub fn encrypt_message( 928 plaintext: &PlaintextMessage, 929 sender_identity: &IdentityKeyPair, 930 recipient_exchange_public: &[u8; 32], 931 ) -> Result<EncryptedMessage> { 932 // Validate plaintext 933 plaintext.validate()?; 934 935 // Generate message ID 936 let message_id: MessageId = generate_random_id(); 937 938 // Perform key agreement 939 let key_agreement = ecdh::perform_key_agreement(recipient_exchange_public, &message_id); 940 941 // Derive message encryption key 942 let encryption_key = derive_message_key(&key_agreement.key, &message_id); 943 944 // Serialize plaintext 945 let plaintext_bytes = plaintext.to_bytes()?; 946 947 // Encrypt with AEAD 948 let nonce = aead::generate_nonce(); 949 let ciphertext = aead::encrypt(&encryption_key, &nonce, &plaintext_bytes, &message_id)?; 950 951 // Get current timestamp 952 let timestamp = Utc::now().timestamp(); 953 954 // Sign the message_id + ciphertext 955 let mut signed_data = Vec::with_capacity(message_id.len() + ciphertext.len()); 956 signed_data.extend_from_slice(&message_id); 957 signed_data.extend_from_slice(&ciphertext); 958 let signature = sign(sender_identity, &signed_data); 959 960 Ok(EncryptedMessage { 961 message_id, 962 ephemeral_public: key_agreement.ephemeral_public, 963 nonce, 964 ciphertext, 965 timestamp, 966 signature, 967 }) 968 } 969 970 /// Decrypt an encrypted message from a sender. 971 /// 972 /// Performs the full decryption flow: 973 /// 1. Verifies the sender's signature 974 /// 2. Performs ECDH to derive shared secret 975 /// 3. Derives message key using HKDF 976 /// 4. Decrypts with ChaCha20-Poly1305 977 /// 5. Deserializes the plaintext 978 /// 979 /// # Arguments 980 /// 981 /// * `encrypted` - The encrypted message 982 /// * `recipient_exchange` - Recipient's exchange key pair 983 /// * `sender_identity_public` - Sender's Ed25519 public key (for verification) 984 /// 985 /// # Returns 986 /// 987 /// The decrypted plaintext message. 988 /// 989 /// # Errors 990 /// 991 /// - `InvalidSignature` if signature verification fails 992 /// - `Decryption` if decryption fails 993 /// - `Deserialization` if plaintext cannot be parsed 994 /// 995 /// # Example 996 /// 997 /// ``` 998 /// use dead_drop_core::crypto::keys::{IdentityKeyPair, ExchangeKeyPair}; 999 /// use dead_drop_core::protocol::messages::{ 1000 /// PlaintextMessage, encrypt_message, decrypt_message, 1001 /// }; 1002 /// 1003 /// let sender = IdentityKeyPair::generate(); 1004 /// let recipient = ExchangeKeyPair::generate(); 1005 /// 1006 /// let original = PlaintextMessage::text("Hello"); 1007 /// let encrypted = encrypt_message(&original, &sender, &recipient.public_bytes()).unwrap(); 1008 /// let decrypted = decrypt_message(&encrypted, &recipient, &sender.public_bytes()).unwrap(); 1009 /// 1010 /// assert_eq!(original.content, decrypted.content); 1011 /// ``` 1012 pub fn decrypt_message( 1013 encrypted: &EncryptedMessage, 1014 recipient_exchange: &ExchangeKeyPair, 1015 sender_identity_public: &[u8; 32], 1016 ) -> Result<PlaintextMessage> { 1017 // Verify signature first 1018 let signed_data = encrypted.signed_data(); 1019 verify(sender_identity_public, &signed_data, &encrypted.signature)?; 1020 1021 // Derive decryption key 1022 let decryption_key = ecdh::derive_decryption_key( 1023 recipient_exchange, 1024 &encrypted.ephemeral_public, 1025 &encrypted.message_id, 1026 ); 1027 1028 // Derive message key 1029 let message_key = derive_message_key(&decryption_key, &encrypted.message_id); 1030 1031 // Decrypt 1032 let plaintext_bytes = aead::decrypt( 1033 &message_key, 1034 &encrypted.nonce, 1035 &encrypted.ciphertext, 1036 &encrypted.message_id, 1037 )?; 1038 1039 // Deserialize 1040 PlaintextMessage::from_bytes(&plaintext_bytes) 1041 } 1042 1043 /// Verify an encrypted message's signature without decrypting. 1044 /// 1045 /// Useful for checking message authenticity before full processing. 1046 /// 1047 /// # Arguments 1048 /// 1049 /// * `encrypted` - The encrypted message 1050 /// * `sender_identity_public` - Sender's Ed25519 public key 1051 /// 1052 /// # Returns 1053 /// 1054 /// `Ok(())` if signature is valid, error otherwise. 1055 pub fn verify_message_signature( 1056 encrypted: &EncryptedMessage, 1057 sender_identity_public: &[u8; 32], 1058 ) -> Result<()> { 1059 let signed_data = encrypted.signed_data(); 1060 verify(sender_identity_public, &signed_data, &encrypted.signature) 1061 } 1062 1063 /// Generate a new random message ID. 1064 /// 1065 /// Convenience function that wraps [`generate_random_id`]. 1066 pub fn generate_message_id() -> MessageId { 1067 generate_random_id() 1068 } 1069 1070 /// Generate a new random contact ID. 1071 /// 1072 /// Convenience function that wraps [`generate_random_id`]. 1073 pub fn generate_contact_id() -> ContactId { 1074 generate_random_id() 1075 } 1076 1077 #[cfg(test)] 1078 mod tests { 1079 use super::*; 1080 1081 // ==================== ContentType Tests ==================== 1082 1083 #[test] 1084 fn test_content_type_from_byte() { 1085 assert_eq!(ContentType::from_byte(0), Some(ContentType::Text)); 1086 assert_eq!(ContentType::from_byte(1), Some(ContentType::Document)); 1087 assert_eq!(ContentType::from_byte(2), Some(ContentType::Reaction)); 1088 assert_eq!(ContentType::from_byte(3), Some(ContentType::DocumentChunk)); 1089 assert_eq!(ContentType::from_byte(4), Some(ContentType::DeliveryReceipt)); 1090 assert_eq!(ContentType::from_byte(5), Some(ContentType::GroupManagement)); 1091 assert_eq!(ContentType::from_byte(6), None); 1092 } 1093 1094 #[test] 1095 fn test_content_type_round_trip() { 1096 for ct in [ContentType::Text, ContentType::Document, ContentType::Reaction, ContentType::DocumentChunk, ContentType::DeliveryReceipt, ContentType::GroupManagement] { 1097 let byte = ct.to_byte(); 1098 let recovered = ContentType::from_byte(byte).unwrap(); 1099 assert_eq!(recovered, ct); 1100 } 1101 } 1102 1103 // ==================== PlaintextMessage Tests ==================== 1104 1105 #[test] 1106 fn test_plaintext_message_text() { 1107 let msg = PlaintextMessage::text("Hello, World!"); 1108 1109 assert_eq!(msg.content_type, ContentType::Text); 1110 assert_eq!(msg.content, b"Hello, World!"); 1111 assert_eq!(msg.filename, None); 1112 assert_eq!(msg.as_text().unwrap(), "Hello, World!"); 1113 } 1114 1115 #[test] 1116 fn test_plaintext_message_document() { 1117 let content = vec![0x50, 0x4B, 0x03, 0x04]; // ZIP magic 1118 let msg = PlaintextMessage::document(content.clone(), "test.zip".to_string()); 1119 1120 assert_eq!(msg.content_type, ContentType::Document); 1121 assert_eq!(msg.content, content); 1122 assert_eq!(msg.filename, Some("test.zip".to_string())); 1123 assert_eq!(msg.as_text(), None); // Not a text message 1124 } 1125 1126 #[test] 1127 fn test_plaintext_message_validate_success() { 1128 let msg = PlaintextMessage::text("Valid message"); 1129 assert!(msg.validate().is_ok()); 1130 } 1131 1132 #[test] 1133 fn test_plaintext_message_validate_too_large() { 1134 let large_content = vec![0u8; MAX_PLAINTEXT_SIZE + 1]; 1135 let msg = PlaintextMessage::new(ContentType::Document, large_content, None); 1136 1137 let result = msg.validate(); 1138 assert!(result.is_err()); 1139 assert!(matches!(result.unwrap_err(), DeadDropError::MessageTooLarge { .. })); 1140 } 1141 1142 #[test] 1143 fn test_plaintext_message_validate_invalid_utf8() { 1144 // Invalid UTF-8 sequence 1145 let invalid_utf8 = vec![0xFF, 0xFE]; 1146 let msg = PlaintextMessage::new(ContentType::Text, invalid_utf8, None); 1147 1148 let result = msg.validate(); 1149 assert!(result.is_err()); 1150 } 1151 1152 #[test] 1153 fn test_plaintext_message_serialization() { 1154 let original = PlaintextMessage::text("Test message"); 1155 let bytes = original.to_bytes().unwrap(); 1156 let recovered = PlaintextMessage::from_bytes(&bytes).unwrap(); 1157 1158 assert_eq!(original, recovered); 1159 } 1160 1161 // ==================== EncryptedMessage Tests ==================== 1162 1163 #[test] 1164 fn test_encrypted_message_is_expired() { 1165 let sender = IdentityKeyPair::generate(); 1166 let recipient = ExchangeKeyPair::generate(); 1167 let plaintext = PlaintextMessage::text("Test"); 1168 1169 let encrypted = encrypt_message(&plaintext, &sender, &recipient.public_bytes()).unwrap(); 1170 1171 // Just created, should not be expired 1172 assert!(!encrypted.is_expired(30)); 1173 1174 // With 0 day expiry, should be expired 1175 assert!(encrypted.is_expired(0)); 1176 } 1177 1178 #[test] 1179 fn test_encrypted_message_serialization() { 1180 let sender = IdentityKeyPair::generate(); 1181 let recipient = ExchangeKeyPair::generate(); 1182 let plaintext = PlaintextMessage::text("Test serialization"); 1183 1184 let original = encrypt_message(&plaintext, &sender, &recipient.public_bytes()).unwrap(); 1185 let bytes = original.to_bytes().unwrap(); 1186 let recovered = EncryptedMessage::from_bytes(&bytes).unwrap(); 1187 1188 assert_eq!(original, recovered); 1189 } 1190 1191 // ==================== State Tests ==================== 1192 1193 #[test] 1194 fn test_outbound_state_is_terminal() { 1195 assert!(!OutboundState::Draft.is_terminal()); 1196 assert!(!OutboundState::Queued.is_terminal()); 1197 assert!(!OutboundState::Transmitting.is_terminal()); 1198 assert!(!OutboundState::Delivered.is_terminal()); 1199 assert!(OutboundState::Acknowledged.is_terminal()); 1200 assert!(OutboundState::Expired.is_terminal()); 1201 } 1202 1203 #[test] 1204 fn test_outbound_state_is_pending() { 1205 assert!(OutboundState::Draft.is_pending()); 1206 assert!(OutboundState::Queued.is_pending()); 1207 assert!(OutboundState::Transmitting.is_pending()); 1208 assert!(!OutboundState::Delivered.is_pending()); 1209 assert!(!OutboundState::Acknowledged.is_pending()); 1210 assert!(!OutboundState::Expired.is_pending()); 1211 } 1212 1213 #[test] 1214 fn test_inbound_state_is_terminal() { 1215 assert!(!InboundState::Receiving.is_terminal()); 1216 assert!(!InboundState::Received.is_terminal()); 1217 assert!(!InboundState::Read.is_terminal()); 1218 assert!(InboundState::Deleted.is_terminal()); 1219 } 1220 1221 // ==================== Encryption/Decryption Tests ==================== 1222 1223 #[test] 1224 fn test_encrypt_decrypt_text() { 1225 let sender = IdentityKeyPair::generate(); 1226 let recipient = ExchangeKeyPair::generate(); 1227 1228 let original = PlaintextMessage::text("Hello, secure world!"); 1229 let encrypted = encrypt_message(&original, &sender, &recipient.public_bytes()).unwrap(); 1230 let decrypted = 1231 decrypt_message(&encrypted, &recipient, &sender.public_bytes()).unwrap(); 1232 1233 assert_eq!(original.content_type, decrypted.content_type); 1234 assert_eq!(original.content, decrypted.content); 1235 assert_eq!(original.filename, decrypted.filename); 1236 } 1237 1238 #[test] 1239 fn test_encrypt_decrypt_document() { 1240 let sender = IdentityKeyPair::generate(); 1241 let recipient = ExchangeKeyPair::generate(); 1242 1243 let content = vec![0xDE, 0xAD, 0xBE, 0xEF, 0xCA, 0xFE]; 1244 let original = PlaintextMessage::document(content, "test.bin".to_string()); 1245 1246 let encrypted = encrypt_message(&original, &sender, &recipient.public_bytes()).unwrap(); 1247 let decrypted = 1248 decrypt_message(&encrypted, &recipient, &sender.public_bytes()).unwrap(); 1249 1250 assert_eq!(original.content_type, decrypted.content_type); 1251 assert_eq!(original.content, decrypted.content); 1252 assert_eq!(original.filename, decrypted.filename); 1253 } 1254 1255 #[test] 1256 fn test_encrypt_decrypt_empty_text() { 1257 let sender = IdentityKeyPair::generate(); 1258 let recipient = ExchangeKeyPair::generate(); 1259 1260 let original = PlaintextMessage::text(""); 1261 let encrypted = encrypt_message(&original, &sender, &recipient.public_bytes()).unwrap(); 1262 let decrypted = 1263 decrypt_message(&encrypted, &recipient, &sender.public_bytes()).unwrap(); 1264 1265 assert_eq!(original.content, decrypted.content); 1266 } 1267 1268 #[test] 1269 fn test_decrypt_wrong_recipient_key() { 1270 let sender = IdentityKeyPair::generate(); 1271 let recipient = ExchangeKeyPair::generate(); 1272 let wrong_recipient = ExchangeKeyPair::generate(); 1273 1274 let original = PlaintextMessage::text("Secret"); 1275 let encrypted = encrypt_message(&original, &sender, &recipient.public_bytes()).unwrap(); 1276 1277 // Try to decrypt with wrong key 1278 let result = decrypt_message(&encrypted, &wrong_recipient, &sender.public_bytes()); 1279 assert!(result.is_err()); 1280 } 1281 1282 #[test] 1283 fn test_decrypt_wrong_sender_identity() { 1284 let sender = IdentityKeyPair::generate(); 1285 let wrong_sender = IdentityKeyPair::generate(); 1286 let recipient = ExchangeKeyPair::generate(); 1287 1288 let original = PlaintextMessage::text("Secret"); 1289 let encrypted = encrypt_message(&original, &sender, &recipient.public_bytes()).unwrap(); 1290 1291 // Try to verify with wrong sender 1292 let result = decrypt_message(&encrypted, &recipient, &wrong_sender.public_bytes()); 1293 assert!(result.is_err()); 1294 assert!(matches!(result.unwrap_err(), DeadDropError::InvalidSignature)); 1295 } 1296 1297 #[test] 1298 fn test_decrypt_tampered_ciphertext() { 1299 let sender = IdentityKeyPair::generate(); 1300 let recipient = ExchangeKeyPair::generate(); 1301 1302 let original = PlaintextMessage::text("Original message"); 1303 let mut encrypted = encrypt_message(&original, &sender, &recipient.public_bytes()).unwrap(); 1304 1305 // Tamper with ciphertext 1306 if !encrypted.ciphertext.is_empty() { 1307 encrypted.ciphertext[0] ^= 0xFF; 1308 } 1309 1310 // Signature verification should fail 1311 let result = decrypt_message(&encrypted, &recipient, &sender.public_bytes()); 1312 assert!(result.is_err()); 1313 } 1314 1315 #[test] 1316 fn test_verify_message_signature() { 1317 let sender = IdentityKeyPair::generate(); 1318 let recipient = ExchangeKeyPair::generate(); 1319 1320 let plaintext = PlaintextMessage::text("Test"); 1321 let encrypted = encrypt_message(&plaintext, &sender, &recipient.public_bytes()).unwrap(); 1322 1323 // Valid signature 1324 assert!(verify_message_signature(&encrypted, &sender.public_bytes()).is_ok()); 1325 1326 // Invalid signature (wrong sender) 1327 let wrong_sender = IdentityKeyPair::generate(); 1328 assert!(verify_message_signature(&encrypted, &wrong_sender.public_bytes()).is_err()); 1329 } 1330 1331 #[test] 1332 fn test_message_id_uniqueness() { 1333 let id1 = generate_message_id(); 1334 let id2 = generate_message_id(); 1335 assert_ne!(id1, id2); 1336 } 1337 1338 #[test] 1339 fn test_contact_id_uniqueness() { 1340 let id1 = generate_contact_id(); 1341 let id2 = generate_contact_id(); 1342 assert_ne!(id1, id2); 1343 } 1344 1345 // ==================== Integration Tests ==================== 1346 1347 #[test] 1348 fn test_full_message_lifecycle() { 1349 let sender_identity = IdentityKeyPair::generate(); 1350 let recipient_exchange = ExchangeKeyPair::generate(); 1351 1352 // 1. Create message 1353 let plaintext = PlaintextMessage::text("Important message"); 1354 assert!(plaintext.validate().is_ok()); 1355 1356 // 2. Encrypt 1357 let encrypted = 1358 encrypt_message(&plaintext, &sender_identity, &recipient_exchange.public_bytes()) 1359 .unwrap(); 1360 1361 // 3. Verify signature (without decrypting) 1362 assert!( 1363 verify_message_signature(&encrypted, &sender_identity.public_bytes()).is_ok() 1364 ); 1365 1366 // 4. Serialize for transmission 1367 let wire_bytes = encrypted.to_bytes().unwrap(); 1368 1369 // 5. Deserialize on receipt 1370 let received = EncryptedMessage::from_bytes(&wire_bytes).unwrap(); 1371 1372 // 6. Decrypt 1373 let decrypted = 1374 decrypt_message(&received, &recipient_exchange, &sender_identity.public_bytes()) 1375 .unwrap(); 1376 1377 assert_eq!(plaintext.as_text(), decrypted.as_text()); 1378 } 1379 1380 #[test] 1381 fn test_multiple_messages_different_ids() { 1382 let sender = IdentityKeyPair::generate(); 1383 let recipient = ExchangeKeyPair::generate(); 1384 let plaintext = PlaintextMessage::text("Same content"); 1385 1386 let encrypted1 = encrypt_message(&plaintext, &sender, &recipient.public_bytes()).unwrap(); 1387 let encrypted2 = encrypt_message(&plaintext, &sender, &recipient.public_bytes()).unwrap(); 1388 1389 // Each encryption should have unique message ID 1390 assert_ne!(encrypted1.message_id, encrypted2.message_id); 1391 1392 // And different ephemeral keys 1393 assert_ne!(encrypted1.ephemeral_public, encrypted2.ephemeral_public); 1394 1395 // And different nonces 1396 assert_ne!(encrypted1.nonce, encrypted2.nonce); 1397 1398 // But both should decrypt to the same content 1399 let decrypted1 = 1400 decrypt_message(&encrypted1, &recipient, &sender.public_bytes()).unwrap(); 1401 let decrypted2 = 1402 decrypt_message(&encrypted2, &recipient, &sender.public_bytes()).unwrap(); 1403 1404 assert_eq!(decrypted1.content, decrypted2.content); 1405 } 1406 }