/ core / src / protocol / messages.rs
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  }