zoe_wire_protocol/
message.rs

1use crate::{KeyPair, Signature, VerifyError, VerifyingKey};
2use blake3::Hasher;
3use serde::{Deserialize, Serialize};
4
5use crate::{
6    crypto::{ChaCha20Poly1305Content, PqxdhEncryptedContent},
7    Ed25519SelfEncryptedContent, EphemeralEcdhContent, KeyId as VerifyingKeyId, MessageId,
8    MlDsaSelfEncryptedContent,
9};
10use forward_compatible_enum::ForwardCompatibleEnum;
11
12mod store_key;
13pub use store_key::{PqxdhInboxProtocol, StoreKey};
14
15#[derive(Serialize, Deserialize, Clone, PartialEq, Eq)]
16pub enum Tag {
17    Protected, // may not be forwarded, unless the other end is authenticated as the author, may it be accepted
18    Event {
19        // referes to another event in some form
20        id: MessageId,
21        #[serde(default)]
22        relays: Vec<String>,
23    },
24    User {
25        // Refers to a user in some form
26        id: VerifyingKeyId,
27        #[serde(default)]
28        relays: Vec<String>,
29    },
30    Channel {
31        // Refers to a channel in some form, custom length but often times a hash
32        id: Vec<u8>,
33        #[serde(default)]
34        relays: Vec<String>,
35    },
36}
37
38impl From<&MessageFull> for Tag {
39    fn from(message: &MessageFull) -> Self {
40        Tag::Event {
41            id: *message.id(),
42            relays: vec![],
43        }
44    }
45}
46
47impl std::fmt::Debug for Tag {
48    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
49        match self {
50            Tag::Protected => write!(f, "Protected"),
51            Tag::Event { id, .. } => write!(f, "Event(#{})", hex::encode(id.as_bytes())),
52            Tag::User { id, .. } => write!(f, "User(#{})", hex::encode(id)),
53            Tag::Channel { id, .. } => write!(f, "Channel(#{})", hex::encode(id)),
54        }
55    }
56}
57
58#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
59pub enum Kind {
60    /// This is a regular event, that should be stored and made available to query for afterwards
61    Regular,
62    /// An ephemeral event is not kept permanently but mainly forwarded to who ever is interested
63    /// if a number larger than 0 is provided, this is the maximum seconds the event should be
64    /// stored for in case someone asks. If the timestamp + seconds is smaller than the current
65    /// server time, the event might be discarded without even forwarding it.
66    Ephemeral(u32),
67    /// This is an event that should be stored in a specific store
68    Store(StoreKey),
69    /// This is an event that should clear a specific store
70    ClearStore(StoreKey), // clear the given storekey of the user, if the events timestamp is larger than the stored one
71}
72
73/// Message content variants supporting both raw bytes and encrypted content.
74///
75/// # Content Type Design and Versioning Strategy
76///
77/// The `Content` enum represents the payload of a message and supports different encryption
78/// schemes. **Important**: The choice of available content types and their cryptographic
79/// implementations is **hard-wired at the message version level** (e.g., `MessageV0`).
80/// This means that when a new message version is introduced (like `MessageV1`), it can
81/// have different content variants or updated cryptographic schemes.
82///
83/// ## Serialization with Postcard
84///
85/// This enum uses [postcard](https://docs.rs/postcard/) for efficient binary serialization.
86/// Postcard distinguishes enum variants using a compact binary tag system:
87/// - `Raw(Vec<u8>)` → serialized as `[0, ...data bytes...]`
88/// - `ChaCha20Poly1305(content)` → serialized as `[1, ...encrypted content...]`  
89/// - `Ed25519Encrypted(content)` → serialized as `[2, ...encrypted content...]`
90///
91/// The first byte indicates which variant is being deserialized, making the format
92/// self-describing and forwards-compatible. For more details on postcard's enum
93/// handling, see: <https://docs.rs/postcard/latest/postcard/#enums>
94///
95/// ## Content Type Security Model
96///
97/// Each content type has different security properties and use cases:
98///
99/// ### `Raw` - Unencrypted Content
100/// - Used for public messages or when encryption is handled at a higher layer
101/// - Suitable for metadata, public announcements, or already-encrypted data
102/// - No confidentiality protection - readable by all message recipients
103///
104/// ### `ChaCha20Poly1305` - Context-Based Encryption
105/// - Uses ChaCha20-Poly1305 AEAD (Authenticated Encryption with Associated Data)
106/// - Encryption key derived from message context (channel tags, group keys, etc.)
107/// - Provides both confidentiality and authenticity
108/// - Minimal overhead, suitable for high-throughput scenarios
109///
110/// ### `Ed25519Encrypted` - Identity-Based Encryption
111/// - Uses Ed25519 keypairs (typically from mnemonic phrases) for key derivation
112/// - Encrypts using ChaCha20-Poly1305 with keys derived from Ed25519 operations
113/// - Suitable for direct peer-to-peer encrypted messaging
114/// - Self-contained encryption that doesn't require additional context
115///
116/// ## Version Evolution
117///
118/// When message formats evolve (e.g., `MessageV0` → `MessageV1`), the `Content` enum
119/// can be updated with:
120/// - New encryption schemes (e.g., post-quantum cryptography)
121/// - Additional metadata or structure
122/// - Different key derivation methods
123/// - Backwards-incompatible changes to existing variants
124///
125/// The versioning at the `Message` level ensures that older clients can gracefully
126/// handle unknown message versions while maintaining compatibility with supported versions.
127///
128/// ## Example Usage
129///
130/// ```rust
131/// use zoe_wire_protocol::Content;
132///
133/// // Raw content for public data
134/// let public_msg = Content::raw("Hello, world!".as_bytes().to_stdvec());
135///
136/// // Typed content (serialized with postcard)
137/// #[derive(serde::Serialize)]
138/// struct MyData { value: u32 }
139/// let typed_content = Content::raw_typed(&MyData { value: 42 })?;
140/// # Ok::<(), postcard::Error>(())
141/// ```
142#[derive(Clone, PartialEq, ForwardCompatibleEnum)]
143pub enum Content {
144    /// Raw byte content without encryption.
145    ///
146    /// Use this variant for:
147    /// - Public messages that don't require encryption
148    /// - Content that is already encrypted at a higher layer
149    /// - Metadata or routing information
150    /// - Binary data that should be transmitted as-is
151    #[discriminant(0)]
152    Raw(Vec<u8>),
153
154    /// ChaCha20-Poly1305 encrypted content with context-derived keys.
155    ///
156    /// The encryption key is determined by message context such as:
157    /// - Channel tags and group membership
158    /// - Shared secrets established through key exchange
159    /// - Derived keys from parent encryption contexts
160    ///
161    /// This variant provides minimal serialization overhead while maintaining
162    /// strong AEAD security properties.
163    #[discriminant(20)]
164    ChaCha20Poly1305(ChaCha20Poly1305Content),
165
166    /// Ed25519-derived ChaCha20-Poly1305 self-encrypted content.
167    ///
168    /// Uses sender's Ed25519 keypair to derive ChaCha20-Poly1305 encryption keys.
169    /// Only the sender can decrypt this content (encrypt-to-self pattern).
170    /// Suitable for:
171    /// - Personal data storage
172    /// - Self-encrypted notes and backups
173    /// - Content where only the author should have access
174    #[discriminant(40)]
175    Ed25519SelfEncrypted(Ed25519SelfEncryptedContent),
176
177    /// ML-DSA-derived ChaCha20-Poly1305 self-encrypted content.
178    ///
179    /// Uses sender's ML-DSA keypair to derive ChaCha20-Poly1305 encryption keys.
180    /// Only the sender can decrypt this content (encrypt-to-self pattern).
181    /// Post-quantum secure version of Ed25519SelfEncrypted.
182    /// Suitable for:
183    /// - Personal data storage (post-quantum secure)
184    /// - Self-encrypted notes and backups
185    /// - Content where only the author should have access
186    #[discriminant(41)]
187    MlDsaSelfEncrypted(MlDsaSelfEncryptedContent),
188
189    /// PQXDH encrypted content.
190    ///
191    /// Uses the PQXDH (Post-Quantum Extended Diffie-Hellman) protocol for
192    /// asynchronous secure communication establishment. Combines X25519 ECDH
193    /// with ML-KEM for hybrid classical/post-quantum security.
194    /// Suitable for:
195    /// - Asynchronous RPC initiation (tarpc-over-message)
196    /// - Secure inbox messaging
197    /// - Initial key agreement for ongoing sessions
198    /// - Post-quantum secure communication setup
199    #[discriminant(42)]
200    PqxdhEncrypted(PqxdhEncryptedContent),
201
202    /// Ephemeral ECDH encrypted content.
203    ///
204    /// Uses ephemeral X25519 key pairs for each message to encrypt for
205    /// the recipient. Only the recipient can decrypt (proper public key encryption).
206    /// Provides perfect forward secrecy. Suitable for:
207    /// - RPC calls over message infrastructure  
208    /// - One-off encrypted messages
209    /// - Public key encryption scenarios
210    #[discriminant(80)]
211    EphemeralEcdh(EphemeralEcdhContent),
212
213    /// Unknown content type.
214    ///
215    /// This variant is used when the content type is unknown or not supported.
216    /// It contains the discriminant and the raw data.
217    Unknown { discriminant: u32, data: Vec<u8> },
218}
219
220impl std::fmt::Debug for Content {
221    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
222        match self {
223            Content::Raw(data) => write!(f, "Raw([u8; {}])", data.len()),
224            Content::ChaCha20Poly1305(..) => write!(f, "ChaCha20Poly1305(#redacted#)"),
225            Content::Ed25519SelfEncrypted(..) => write!(f, "Ed25519SelfEncrypted(#redacted#)"),
226            Content::MlDsaSelfEncrypted(..) => write!(f, "MlDsaSelfEncrypted(#redacted#)"),
227            Content::PqxdhEncrypted(..) => write!(f, "PqxdhEncrypted(#redacted#)"),
228            Content::EphemeralEcdh(..) => write!(f, "EphemeralEcdh(#redacted#)"),
229            Content::Unknown { discriminant, data } => {
230                write!(f, "Unknown({:?}, {:?})", discriminant, data.len())
231            }
232        }
233    }
234}
235
236impl Content {
237    /// Create raw content from bytes
238    pub fn raw(data: Vec<u8>) -> Self {
239        Content::Raw(data)
240    }
241
242    /// Create raw content from serializable object
243    pub fn raw_typed<T: Serialize>(data: &T) -> Result<Self, postcard::Error> {
244        Ok(Content::Raw(postcard::to_stdvec(data)?))
245    }
246
247    /// Create encrypted content
248    pub fn encrypted(content: ChaCha20Poly1305Content) -> Self {
249        Content::ChaCha20Poly1305(content)
250    }
251
252    /// Create ed25519 self-encrypted content
253    pub fn ed25519_self_encrypted(content: Ed25519SelfEncryptedContent) -> Self {
254        Content::Ed25519SelfEncrypted(content)
255    }
256
257    /// Create ML-DSA self-encrypted content
258    pub fn ml_dsa_self_encrypted(content: MlDsaSelfEncryptedContent) -> Self {
259        Content::MlDsaSelfEncrypted(content)
260    }
261
262    /// Create ephemeral ECDH encrypted content
263    pub fn ephemeral_ecdh(content: EphemeralEcdhContent) -> Self {
264        Content::EphemeralEcdh(content)
265    }
266
267    /// Create PQXDH encrypted content
268    pub fn pqxdh_encrypted(content: PqxdhEncryptedContent) -> Self {
269        Content::PqxdhEncrypted(content)
270    }
271
272    /// Create PQXDH initial message content
273    pub fn pqxdh_initial(message: crate::inbox::pqxdh::PqxdhInitialMessage) -> Self {
274        Content::PqxdhEncrypted(PqxdhEncryptedContent::Initial(message))
275    }
276
277    /// Create PQXDH session message content
278    pub fn pqxdh_session(message: crate::inbox::pqxdh::PqxdhSessionMessage) -> Self {
279        Content::PqxdhEncrypted(PqxdhEncryptedContent::Session(message))
280    }
281
282    /// Get the raw bytes if this is raw content
283    pub fn as_raw(&self) -> Option<&Vec<u8>> {
284        let Content::Raw(ref content) = self else {
285            return None;
286        };
287        Some(content)
288    }
289
290    /// Get the encrypted content if this is encrypted
291    pub fn as_encrypted(&self) -> Option<&ChaCha20Poly1305Content> {
292        let Content::ChaCha20Poly1305(ref content) = self else {
293            return None;
294        };
295        Some(content)
296    }
297
298    /// Get the ed25519 self-encrypted content if this is ed25519 self-encrypted
299    pub fn as_ed25519_self_encrypted(&self) -> Option<&Ed25519SelfEncryptedContent> {
300        let Content::Ed25519SelfEncrypted(ref content) = self else {
301            return None;
302        };
303        Some(content)
304    }
305
306    /// Get the ML-DSA self-encrypted content if this is ML-DSA self-encrypted
307    pub fn as_ml_dsa_self_encrypted(&self) -> Option<&MlDsaSelfEncryptedContent> {
308        let Content::MlDsaSelfEncrypted(ref content) = self else {
309            return None;
310        };
311        Some(content)
312    }
313
314    /// Get the ephemeral ECDH encrypted content if this is ephemeral ECDH encrypted
315    pub fn as_ephemeral_ecdh(&self) -> Option<&EphemeralEcdhContent> {
316        let Content::EphemeralEcdh(ref content) = self else {
317            return None;
318        };
319        Some(content)
320    }
321
322    /// Get the PQXDH encrypted content if this is PQXDH encrypted
323    pub fn as_pqxdh_encrypted(&self) -> Option<&PqxdhEncryptedContent> {
324        let Content::PqxdhEncrypted(ref content) = self else {
325            return None;
326        };
327        Some(content)
328    }
329
330    /// Check if this content is encrypted
331    pub fn is_encrypted(&self) -> bool {
332        matches!(
333            self,
334            Content::ChaCha20Poly1305(_)
335                | Content::Ed25519SelfEncrypted(_)
336                | Content::MlDsaSelfEncrypted(_)
337                | Content::EphemeralEcdh(_)
338                | Content::PqxdhEncrypted(_)
339        )
340    }
341
342    /// Check if this content is raw
343    pub fn is_raw(&self) -> bool {
344        matches!(self, Content::Raw(_))
345    }
346}
347
348/// Top-level message format with versioning support.
349///
350/// # Message Versioning Strategy
351///
352/// The `Message` enum provides **the primary versioning mechanism** for the wire protocol.
353/// Each variant (e.g., `MessageV0`) represents a specific version of the message format
354/// with **hard-wired cryptographic choices and content types**.
355///
356/// ## Version-Specific Design Philosophy
357///
358/// Unlike other protocols where features are negotiated dynamically, this protocol
359/// **hard-wires** cryptographic algorithms and content types into each message version.
360/// This design provides several benefits:
361///
362/// - **Security**: No downgrade attacks through feature negotiation
363/// - **Simplicity**: Clear, unambiguous message format per version
364/// - **Performance**: No runtime algorithm selection overhead
365/// - **Evolution**: Clean migration path to new cryptographic standards
366///
367/// ## Content Type Binding
368///
369/// Each message version has a **fixed set** of supported [`Content`] variants:
370/// - `MessageV0` supports: `Raw`, `ChaCha20Poly1305`, `Ed25519Encrypted`
371/// - Future versions (e.g., `MessageV1`) may support different encryption schemes
372///
373/// This binding ensures that cryptographic choices are made explicitly during protocol
374/// design rather than being negotiated at runtime.
375///
376/// ## Serialization and Wire Compatibility
377///
378/// Messages are serialized using [postcard](https://docs.rs/postcard/), which handles
379/// enum versioning through compact binary tags:
380/// - `MessageV0(data)` → serialized as `[0, ...message data...]`
381/// - `MessageV1(data)` → would serialize as `[1, ...message data...]` (future)
382///
383/// This allows clients to:
384/// 1. Quickly identify the message version from the first byte
385/// 2. Skip unknown message versions gracefully
386/// 3. Maintain backwards compatibility with supported versions
387///
388/// ## Evolution Path
389///
390/// When cryptographic standards change or new features are needed, the protocol
391/// evolves by adding new message versions:
392///
393/// ```rust,ignore
394/// #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
395/// pub enum Message {
396///     MessageV0(MessageV0),        // Legacy: ChaCha20-Poly1305, Ed25519
397///     MessageV1(MessageV1),        // Future: Post-quantum crypto, new content types
398/// }
399/// ```
400///
401/// This ensures that:
402/// - Older clients continue working with `MessageV0`
403/// - Newer clients can handle both versions
404/// - Migration happens gradually and safely
405///
406/// ## Example: Version-Specific Handling
407///
408/// ```rust
409/// use zoe_wire_protocol::{Message, MessageV0, Content};
410///
411/// fn handle_message(msg: Message) {
412///     match msg {
413///         Message::MessageV0(v0_msg) => {
414///             // Handle v0-specific features
415///             match v0_msg.content {
416///                 Content::Raw(data) => { /* process raw data */ },
417///                 Content::ChaCha20Poly1305(_) => { /* decrypt with ChaCha20 */ },
418///                 Content::Ed25519SelfEncrypted(_) => { /* decrypt with Ed25519 */ },
419///             }
420///         }
421///         // Future: MessageV1 would be handled here with its own content types
422///     }
423/// }
424/// ```
425#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
426pub enum Message {
427    /// Message format version 0.
428    ///
429    /// Supports the following cryptographic primitives:
430    /// - **Signing**: Ed25519 digital signatures
431    /// - **Encryption**: ChaCha20-Poly1305 AEAD
432    /// - **Key Derivation**: Ed25519-based and context-based schemes
433    /// - **Content Types**: [`Content::Raw`], [`Content::ChaCha20Poly1305`], [`Content::Ed25519SelfEncrypted`], [`Content::EphemeralEcdh`]
434    ///
435    /// This version is designed for high-performance messaging with modern cryptographic
436    /// standards as of 2025.
437    MessageV0(MessageV0),
438}
439
440impl Message {
441    pub fn verify_sender_signature(
442        &self,
443        message_bytes: &[u8],
444        signature: &Signature,
445    ) -> Result<(), VerifyError> {
446        match self {
447            Message::MessageV0(ref inner) => inner.header.sender.verify(message_bytes, signature),
448        }
449    }
450
451    pub fn new_v0(
452        content: Content,
453        sender: VerifyingKey,
454        when: u64,
455        kind: Kind,
456        tags: Vec<Tag>,
457    ) -> Self {
458        Message::MessageV0(MessageV0 {
459            header: MessageV0Header {
460                sender,
461                when,
462                kind,
463                tags,
464            },
465            content,
466        })
467    }
468
469    pub fn new_v0_raw(
470        content: Vec<u8>,
471        sender: VerifyingKey,
472        when: u64,
473        kind: Kind,
474        tags: Vec<Tag>,
475    ) -> Self {
476        Message::MessageV0(MessageV0 {
477            header: MessageV0Header {
478                sender,
479                when,
480                kind,
481                tags,
482            },
483            content: Content::Raw(content),
484        })
485    }
486
487    pub fn new_v0_encrypted(
488        content: ChaCha20Poly1305Content,
489        sender: VerifyingKey,
490        when: u64,
491        kind: Kind,
492        tags: Vec<Tag>,
493    ) -> Self {
494        Message::MessageV0(MessageV0 {
495            header: MessageV0Header {
496                sender,
497                when,
498                kind,
499                tags,
500            },
501            content: Content::ChaCha20Poly1305(content),
502        })
503    }
504}
505
506/// Header information for MessageV0 containing metadata and routing information.
507///
508/// # MessageV0Header Structure
509///
510/// `MessageV0Header` contains all the metadata fields from a MessageV0 message
511/// except for the content payload. This allows RPC transport layers to access
512/// sender information, timing, message types, and routing tags without having
513/// to deserialize the potentially encrypted content.
514///
515/// ## Field Description
516///
517/// - **`sender`**: Ed25519 public key of the message author
518/// - **`when`**: Unix timestamp in seconds (for ordering and expiration)
519/// - **`kind`**: Message type determining storage and forwarding behavior
520/// - **`tags`**: Routing and reference tags (channels, users, events)
521///
522/// ## Usage in RPC Transport
523///
524/// This header allows RPC systems to examine message metadata before deciding
525/// how to handle the content, enabling efficient routing and access control.
526#[derive(Serialize, Deserialize, Debug, Clone)]
527pub struct MessageV0Header {
528    /// ML-DSA public key of the message sender (serialized as bytes).
529    ///
530    /// This key is used to verify the digital signature in [`MessageFull`].
531    /// The sender must possess the corresponding private key to create valid signatures.
532    /// Stored as encoded bytes for serialization compatibility.
533    pub sender: VerifyingKey,
534
535    /// Unix timestamp in seconds when the message was created.
536    ///
537    /// Used for:
538    /// - Message ordering in conversations
539    /// - Expiration of ephemeral messages  
540    /// - Preventing replay attacks (with reasonable clock skew tolerance)
541    pub when: u64,
542
543    /// Message type determining storage and forwarding behavior.
544    ///
545    /// See [`Kind`] for details on different message types:
546    /// - `Regular`: Stored permanently
547    /// - `Ephemeral`: Temporary storage with optional TTL
548    /// - `Store`: Updates a specific key-value store
549    /// - `ClearStore`: Clears a key-value store
550    pub kind: Kind,
551
552    /// Tags for routing, references, and metadata.
553    ///
554    /// Common tag types include:
555    /// - `Protected`: Message should only be forwarded to authenticated recipients
556    /// - `Event`: References another message or event by ID
557    /// - `User`: References a user identity
558    /// - `Channel`: Routes to a specific channel or group
559    ///
560    /// Default value is an empty vector when deserializing legacy messages.
561    #[serde(default)]
562    pub tags: Vec<Tag>,
563}
564
565/// Version 0 of the message format with Ed25519 signatures and ChaCha20-Poly1305 encryption.
566///
567/// # Message Structure
568///
569/// `MessageV0` represents the core message data that gets signed and transmitted.
570/// It contains metadata (header) and the actual content payload.
571/// The message is signed using Ed25519 to create a [`MessageFull`] for transmission.
572///
573/// ## Cryptographic Binding
574///
575/// This message version **hard-wires** the following cryptographic choices:
576/// - **Digital Signatures**: Ed25519 (see [`MessageFull`])
577/// - **Content Encryption**: ChaCha20-Poly1305 AEAD (see [`Content`])
578/// - **Hash Function**: Blake3 for message IDs (see [`MessageFull::new`])
579/// - **Key Derivation**: Ed25519-based and context-based schemes
580///
581/// These choices cannot be negotiated or downgraded - they are fixed for all
582/// `MessageV0` instances to prevent cryptographic downgrade attacks.
583///
584/// ## Field Description
585///
586/// - **`header`**: Message metadata including sender, timestamp, type, and tags
587/// - **`content`**: The actual message payload (see [`Content`] variants)
588///
589/// ## Serialization Format
590///
591/// Messages are serialized using [postcard](https://docs.rs/postcard/) for efficiency:
592///
593/// ```text
594/// [header: variable][content: 1+ bytes]
595/// ```
596///
597/// The compact binary format minimizes wire overhead while maintaining
598/// self-describing properties through postcard's type system.
599///
600/// ## Example Usage
601///
602/// ```rust
603/// use zoe_wire_protocol::{MessageV0, MessageV0Header, Content, Kind, Tag};
604/// use ed25519_dalek::SigningKey;
605/// use rand::rngs::OsRng;
606///
607/// let signing_key = SigningKey::generate(&mut OsRng);
608/// let message = MessageV0 {
609///     header: MessageV0Header {
610///         sender: signing_key.verifying_key(),
611///         when: 1640995200, // 2022-01-01 00:00:00 UTC
612///         kind: Kind::Regular,
613///         tags: vec![Tag::Protected],
614///     },
615///     content: Content::raw("Hello, world!".as_bytes().to_stdvec()),
616/// };
617///
618/// // Convert to signed message for transmission
619/// use zoe_wire_protocol::{Message, MessageFull};
620/// let full_message = MessageFull::new(Message::MessageV0(message), &signing_key)?;
621/// # Ok::<(), Box<dyn std::error::Error>>(())
622/// ```
623#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
624pub struct MessageV0 {
625    /// Message header containing metadata and routing information.
626    ///
627    /// Contains sender identity, timestamp, message type, and routing tags.
628    /// This information is accessible without deserializing the content.
629    pub header: MessageV0Header,
630
631    /// The message payload with optional encryption.
632    ///
633    /// See [`Content`] for available variants:
634    /// - `Raw`: Unencrypted data
635    /// - `ChaCha20Poly1305`: Context-encrypted data  
636    /// - `Ed25519Encrypted`: Identity-encrypted data
637    pub content: Content,
638}
639
640impl std::ops::Deref for MessageV0 {
641    type Target = MessageV0Header;
642
643    fn deref(&self) -> &Self::Target {
644        &self.header
645    }
646}
647
648/// Complete signed message ready for transmission and storage.
649///
650/// # Message Authentication and Integrity
651///
652/// `MessageFull` represents a complete, authenticated message that includes:
653/// 1. **Content**: The original [`Message`] (e.g., [`MessageV0`])
654/// 2. **Signature**: Ed25519 digital signature over the serialized message
655/// 3. **Identity**: Blake3 hash-based unique identifier
656///
657/// This structure ensures **non-repudiation** and **integrity** - recipients can
658/// cryptographically verify that the message came from the claimed sender and
659/// hasn't been tampered with.
660///
661/// ## Cryptographic Construction
662///
663/// The message construction follows a specific protocol:
664///
665/// 1. **Serialize**: Convert [`Message`] to bytes using [postcard](https://docs.rs/postcard/)
666/// 2. **Sign**: Create Ed25519 signature over the serialized bytes
667/// 3. **Hash**: Compute Blake3 hash of `serialized_message || signature` for the ID
668///
669/// This ensures that:
670/// - The signature covers the entire message content
671/// - The ID uniquely identifies this specific signed message
672/// - Replay attacks are prevented through unique IDs
673///
674/// ## Wire Format and Storage
675///
676/// `MessageFull` is the **canonical storage format** used throughout the system:
677/// - **Network transmission**: Serialized with postcard for efficiency
678/// - **Database storage**: Stored as binary blobs in key-value stores
679/// - **Message indexing**: ID used as primary key, sender/timestamp extracted for indexes
680///
681/// The postcard serialization format is:
682/// ```text
683/// [id: 32 bytes][message: variable][signature: 64 bytes]
684/// ```
685///
686/// ## Identity and Deduplication
687///
688/// The Blake3-based ID serves multiple purposes:
689/// - **Deduplication**: Identical signed messages have identical IDs
690/// - **Content addressing**: Messages can be retrieved by their cryptographic hash
691/// - **Integrity verification**: ID changes if any part of the message is modified
692/// - **Ordering**: IDs provide deterministic message ordering (with timestamp ties)
693///
694/// ## Security Properties
695///
696/// `MessageFull` provides the following security guarantees:
697/// - **Authentication**: Ed25519 signature proves sender identity
698/// - **Integrity**: Any modification invalidates the signature
699/// - **Non-repudiation**: Sender cannot deny creating a valid signature
700/// - **Uniqueness**: Blake3 ID prevents message duplication
701///
702/// Note: **Confidentiality** is provided by the [`Content`] encryption, not at this layer.
703///
704/// ## Example Usage
705///
706/// ```rust
707/// use zoe_wire_protocol::{MessageFull, Message, MessageV0, Content, Kind};
708/// use ed25519_dalek::SigningKey;
709/// use rand::rngs::OsRng;
710///
711/// // Create a signed message
712/// let signing_key = SigningKey::generate(&mut OsRng);
713/// let message = Message::MessageV0(MessageV0 {
714///     sender: signing_key.verifying_key(),
715///     when: 1640995200,
716///     kind: Kind::Regular,
717///     tags: vec![],
718///     content: Content::raw("Hello!".as_bytes().to_stdvec()),
719/// });
720///
721/// let full_message = MessageFull::new(message, &signing_key)?;
722///
723/// // Verify the signature and ID
724/// assert!(full_message.verify()?);
725///
726/// // The ID is deterministic for the same signed content
727/// let id = full_message.id;
728/// # Ok::<(), Box<dyn std::error::Error>>(())
729/// ```
730///
731/// ## Storage Integration
732///
733/// This structure is optimized for the key-value storage architecture:
734/// - **Primary key**: `id` field for O(1) message retrieval  
735/// - **Indexed fields**: `sender` and `when` extracted from embedded message for queries
736/// - **Tag tables**: Separate tables for efficient tag-based filtering
737/// - **Blob storage**: Entire `MessageFull` serialized as atomic unit
738///
739#[derive(Debug, Clone, Serialize, Deserialize)]
740#[serde(try_from = "MessageFullWire")]
741pub struct MessageFull {
742    /// Blake3 hash serving as the unique message identifier.
743    ///
744    /// Computed as: `Blake3(messsage.as_bytes())`
745    /// Notice that the ID does not include the signature as
746    /// that may contain random ness (ML-DSA does) and thus would
747    /// create different hash IDs for the same content message.
748    ///
749    /// That means that if there are two messages with the same contentn
750    /// but valid signatures (verified against the sender) they are considered
751    /// the same in terms of storage and retrieval and there is no guarantee you
752    /// received the one with the same signature - just with _a_ valid signature.
753    ///
754    /// This ID is:
755    /// - **Unique**: Cryptographically improbable to collide
756    /// - **Deterministic**: Same content  always produces same ID
757    /// - **Tamper-evident**: Changes to messagd change the ID
758    /// - **Content-addressed**: Can be used to retrieve the message
759    #[serde(skip_serializing)]
760    id: MessageId, // FIXNE we could and should compute this on the fly and caceh it
761
762    /// Cryptographic signature over the serialized message.
763    ///
764    /// Created by signing `postcard::serialize(message)` with the sender's private key.
765    /// Recipients verify this signature using the public key in `message.sender`.
766    ///
767    /// **Security note**: The signature covers the *entire* serialized message,
768    /// including all metadata, tags, and content. This prevents partial modification attacks.
769    signature: Signature,
770
771    /// The original message content and metadata.
772    ///
773    /// Boxed to minimize stack usage since messages can be large.
774    /// Contains version-specific message data (e.g., [`MessageV0`]).
775    message: Box<Message>,
776}
777
778/// The Wire protocol variant just signature and message as bytes
779/// the signature is redundent and the message is raw bytes
780#[derive(Debug, Clone, Serialize, Deserialize)]
781pub struct MessageFullWire {
782    signature: Signature,
783    message: Box<Message>,
784}
785
786impl From<MessageFull> for MessageFullWire {
787    fn from(val: MessageFull) -> Self {
788        MessageFullWire {
789            signature: val.signature,
790            message: val.message,
791        }
792    }
793}
794
795#[derive(Debug, thiserror::Error)]
796pub enum MessageFullError {
797    #[error("Signature does not match sender message")]
798    VerificationFailed(#[from] VerifyError),
799    #[error("Postcard Serialization error")]
800    SerializationError(#[from] postcard::Error),
801    #[error("Invalid content")]
802    InvalidContent,
803}
804
805impl TryFrom<MessageFullWire> for MessageFull {
806    type Error = MessageFullError;
807    fn try_from(value: MessageFullWire) -> Result<Self, Self::Error> {
808        let message_bytes = postcard::to_stdvec(&value.message)?;
809        Self::with_signature(value.signature, value.message, &message_bytes)
810    }
811}
812
813impl MessageFull {
814    /// Create a new MessageFull with proper signature and ID
815    pub fn new(message: Message, signer: &KeyPair) -> Result<Self, MessageFullError> {
816        let message_bytes = postcard::to_stdvec(&message)?;
817        let signature = signer.sign(&message_bytes);
818        Self::with_signature(signature, Box::new(message), &message_bytes)
819    }
820
821    fn with_signature(
822        signature: Signature,
823        message: Box<Message>,
824        message_bytes: &[u8],
825    ) -> Result<Self, MessageFullError> {
826        message.verify_sender_signature(message_bytes, &signature)?;
827        let mut hasher = Hasher::new();
828        hasher.update(message_bytes);
829        Ok(Self {
830            id: MessageId::new(hasher.finalize()),
831            message,
832            signature,
833        })
834    }
835
836    pub fn id(&self) -> &MessageId {
837        &self.id
838    }
839
840    pub fn message(&self) -> &Message {
841        &self.message
842    }
843
844    pub fn signature(&self) -> &Signature {
845        &self.signature
846    }
847
848    pub fn ref_tag(&self) -> Tag {
849        Tag::Event {
850            id: *self.id(),
851            relays: vec![],
852        }
853    }
854
855    /// The value this message is stored under in the storage
856    pub fn storage_value(&self) -> Result<Vec<u8>, MessageFullError> {
857        Ok(postcard::to_stdvec(&self)?)
858    }
859
860    /// The timeout for this message in the storage
861    pub fn storage_timeout(&self) -> Option<u64> {
862        match &*self.message {
863            Message::MessageV0(msg) => match msg.header.kind {
864                Kind::Ephemeral(timeout) if timeout > 0 => Some(timeout as u64),
865                _ => None,
866            },
867        }
868    }
869
870    pub fn store_key(&self) -> Option<StoreKey> {
871        match &*self.message {
872            Message::MessageV0(msg) => match &msg.header.kind {
873                Kind::Store(key) => Some(key.clone()),
874                _ => None,
875            },
876        }
877    }
878
879    /// this is meant to clear a storage key
880    pub fn clear_key(&self) -> Option<StoreKey> {
881        None
882    }
883
884    /// Deserialize a message from its storage value
885    pub fn from_storage_value(value: &[u8]) -> Result<Self, MessageFullError> {
886        let message: Self = postcard::from_bytes(value)?;
887        Ok(message)
888    }
889
890    pub fn author(&self) -> &VerifyingKey {
891        match &*self.message {
892            Message::MessageV0(message) => &message.header.sender,
893        }
894    }
895
896    pub fn when(&self) -> &u64 {
897        match &*self.message {
898            Message::MessageV0(message) => &message.header.when,
899        }
900    }
901
902    pub fn kind(&self) -> &Kind {
903        match &*self.message {
904            Message::MessageV0(message) => &message.header.kind,
905        }
906    }
907
908    pub fn tags(&self) -> &Vec<Tag> {
909        match &*self.message {
910            Message::MessageV0(message) => &message.header.tags,
911        }
912    }
913
914    pub fn content(&self) -> &Content {
915        match &*self.message {
916            Message::MessageV0(message) => &message.content,
917        }
918    }
919
920    /// Get raw content bytes if this message contains raw content
921    pub fn raw_content(&self) -> Option<&Vec<u8>> {
922        self.content().as_raw()
923    }
924
925    /// Get encrypted content if this message contains encrypted content
926    pub fn encrypted_content(&self) -> Option<&ChaCha20Poly1305Content> {
927        self.content().as_encrypted()
928    }
929
930    /// Check if this message is expired based on its timeout and current time
931    pub fn is_expired(&self, current_time: u64) -> bool {
932        if let Some(timeout) = self.storage_timeout() {
933            let expiration_time = self.when().saturating_add(timeout);
934            return expiration_time < current_time;
935        }
936        false
937    }
938}
939
940impl MessageFull {
941    /// Try to deserialize content if it's raw (unencrypted)
942    pub fn try_deserialize_content<C>(&self) -> Result<C, MessageFullError>
943    where
944        C: for<'a> Deserialize<'a>,
945    {
946        let Message::MessageV0(MessageV0 {
947            content: Content::Raw(bytes),
948            ..
949        }) = &*self.message
950        else {
951            return Err(MessageFullError::InvalidContent);
952        };
953        Ok(postcard::from_bytes(bytes)?)
954    }
955}
956
957/// Manual PartialEq implementation for MessageV0Header
958impl PartialEq for MessageV0Header {
959    fn eq(&self, other: &Self) -> bool {
960        // Compare ML-DSA keys by their encoded bytes
961        let self_encoded = self.sender.encode();
962        let other_encoded = other.sender.encode();
963
964        self_encoded == other_encoded
965            && self.when == other.when
966            && self.kind == other.kind
967            && self.tags == other.tags
968    }
969}
970
971/// Manual Eq implementation for MessageV0Header
972impl Eq for MessageV0Header {}
973
974/// Manual PartialEq implementation for MessageFull
975impl PartialEq for MessageFull {
976    fn eq(&self, other: &Self) -> bool {
977        // Compare signatures by their encoded bytes
978        let self_sig_encoded = self.signature.encode();
979        let other_sig_encoded = other.signature.encode();
980
981        self.id == other.id
982            && self.message == other.message
983            && self_sig_encoded == other_sig_encoded
984    }
985}
986
987/// Manual Eq implementation for MessageFull
988impl Eq for MessageFull {}
989
990#[cfg(test)]
991mod tests {
992    use super::*;
993    use crate::{
994        keys::{KeyPair, VerifyingKey},
995        Hash, KeyId,
996    };
997    use rand::rngs::OsRng;
998    // use signature::Signer; // Not needed since we use KeyPair.sign() method
999
1000    fn make_hash() -> Hash {
1001        let mut hasher = Hasher::new();
1002        hasher.update(b"1234567890abcdef");
1003        hasher.finalize()
1004    }
1005
1006    #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1007    struct DummyContent {
1008        value: u32,
1009    }
1010
1011    #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1012    struct ComplexContent {
1013        text: String,
1014        numbers: Vec<i32>,
1015        flag: bool,
1016    }
1017
1018    #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1019    struct TestContent {
1020        text: String,
1021        timestamp: u64,
1022        value: u32,
1023    }
1024
1025    fn make_keys() -> (KeyPair, VerifyingKey) {
1026        let mut csprng = OsRng;
1027        let keypair = KeyPair::generate(&mut csprng);
1028        let public_key = keypair.public_key();
1029        (keypair, public_key)
1030    }
1031
1032    #[test]
1033    fn test_message_sign_and_verify() {
1034        let (sk, pk) = make_keys();
1035        let content = DummyContent { value: 42 };
1036        let core = Message::new_v0(
1037            Content::Raw(postcard::to_stdvec(&content).unwrap()),
1038            pk,
1039            1714857600,
1040            Kind::Regular,
1041            vec![Tag::Protected],
1042        );
1043        let msg_full = MessageFull::new(core.clone(), &sk).unwrap();
1044        let mut tampered: MessageFullWire = msg_full.clone().into();
1045        // Tamper with the message by modifying the content
1046        match &mut *tampered.message {
1047            Message::MessageV0(ref mut msg_v0) => {
1048                msg_v0.header.when += 1; // Change timestamp to invalidate signature
1049            }
1050        }
1051        assert!(MessageFull::try_from(tampered).is_err());
1052    }
1053
1054    #[test]
1055    fn test_signature_fails_with_wrong_key() {
1056        let (sk1, pk1) = make_keys();
1057        let (sk2, _pk2) = make_keys();
1058        let content = DummyContent { value: 7 };
1059        let core = Message::new_v0(
1060            Content::Raw(postcard::to_stdvec(&content).unwrap()),
1061            pk1,
1062            1714857600,
1063            Kind::Regular,
1064            vec![Tag::Protected],
1065        );
1066        let msg_full = MessageFull::new(core, &sk1).unwrap();
1067        let mut tampered: MessageFullWire = msg_full.clone().into();
1068        // Replace signature with one from a different key
1069        let message_bytes = postcard::to_stdvec(&tampered.message).unwrap();
1070        let fake_sig = sk2.sign(&message_bytes);
1071        tampered.signature = fake_sig;
1072        assert!(MessageFull::try_from(tampered).is_err());
1073    }
1074
1075    #[test]
1076    fn test_empty_content() {
1077        let (sk, pk) = make_keys();
1078        let core = Message::new_v0(
1079            Content::Raw(postcard::to_stdvec(&DummyContent { value: 0 }).unwrap()),
1080            pk,
1081            1714857600,
1082            Kind::Regular,
1083            vec![Tag::Protected],
1084        );
1085        let _msg_full = MessageFull::new(core, &sk).unwrap();
1086        // MessageFull only exists in verified state, so creation success implies verification
1087    }
1088
1089    #[test]
1090    fn test_multiple_content_items() {
1091        let (sk, pk) = make_keys();
1092        let contents = [
1093            DummyContent { value: 1 },
1094            DummyContent { value: 2 },
1095            DummyContent { value: 3 },
1096        ];
1097        let core = Message::new_v0(
1098            Content::Raw(postcard::to_stdvec(&contents[0]).unwrap()),
1099            pk,
1100            1714857600,
1101            Kind::Regular,
1102            vec![
1103                Tag::Protected,
1104                Tag::Event {
1105                    id: MessageId::new(make_hash()),
1106                    relays: vec!["relay1".to_string()],
1107                },
1108            ],
1109        );
1110
1111        let _msg_full = MessageFull::new(core, &sk).unwrap();
1112        // MessageFull only exists in verified state, so creation success implies verification
1113    }
1114
1115    #[test]
1116    fn test_complex_content_serialization() {
1117        let (sk, pk) = make_keys();
1118        let complex_content = ComplexContent {
1119            text: "Hello, World!".to_string(),
1120            numbers: vec![1, 2, 3, 4, 5],
1121            flag: true,
1122        };
1123        let core = Message::new_v0(
1124            Content::Raw(postcard::to_stdvec(&complex_content).unwrap()),
1125            pk,
1126            1714857600,
1127            Kind::Regular,
1128            vec![Tag::User {
1129                id: KeyId::from_bytes([1u8; 32]),
1130                relays: vec!["relay1".to_string()],
1131            }],
1132        );
1133        let msg_full = MessageFull::new(core, &sk).unwrap();
1134        // MessageFull only exists in verified state, so creation success implies verification
1135        // Serialize and deserialize
1136        let serialized = msg_full.storage_value().unwrap();
1137        let deserialized = MessageFull::from_storage_value(&serialized).unwrap();
1138
1139        let de_content: ComplexContent = deserialized.try_deserialize_content().unwrap();
1140
1141        assert_eq!(msg_full, deserialized);
1142        assert_eq!(complex_content, de_content);
1143        // Deserialized MessageFull is also verified by construction
1144    }
1145
1146    #[test]
1147    #[ignore = "this won't work, and that's the proof"]
1148    fn test_complex_content_serialization_is_not_vec_u8() {
1149        let (sk, pk) = make_keys();
1150        let complex_content = ComplexContent {
1151            text: "Hello, World!".to_string(),
1152            numbers: vec![1, 2, 3, 4, 5],
1153            flag: true,
1154        };
1155        let core = Message::new_v0(
1156            Content::Raw(postcard::to_stdvec(&complex_content).unwrap()),
1157            pk,
1158            1714857600,
1159            Kind::Regular,
1160            vec![Tag::User {
1161                id: KeyId::from_bytes([1u8; 32]),
1162                relays: vec!["relay1".to_string()],
1163            }],
1164        );
1165
1166        let msg_full = MessageFull::new(core, &sk).unwrap();
1167        // Serialize and deserialize
1168        let serialized = msg_full.storage_value().unwrap();
1169        let deserialized = MessageFull::from_storage_value(&serialized).unwrap();
1170        // Deserialized MessageFull is also verified by construction
1171
1172        let deserialize_u8 = MessageFull::from_storage_value(&serialized).unwrap();
1173        let de_content: ComplexContent = deserialize_u8.try_deserialize_content().unwrap();
1174
1175        assert_eq!(msg_full, deserialized);
1176        assert_eq!(complex_content, de_content);
1177    }
1178
1179    #[test]
1180    fn test_complex_content_no_tags_serialization() {
1181        let (sk, pk) = make_keys();
1182        let complex_content = ComplexContent {
1183            text: "Hello, World!".to_string(),
1184            numbers: vec![1, 2, 3, 4, 5],
1185            flag: true,
1186        };
1187        let core = Message::new_v0(
1188            Content::Raw(postcard::to_stdvec(&complex_content).unwrap()),
1189            pk,
1190            1714857600,
1191            Kind::Regular,
1192            vec![],
1193        );
1194
1195        let msg_full = MessageFull::new(core, &sk).unwrap();
1196        // MessageFull only exists in verified state, so creation success implies verification
1197        // Serialize and deserialize
1198        let serialized = msg_full.storage_value().unwrap();
1199        let deserialized = MessageFull::from_storage_value(&serialized).unwrap();
1200
1201        let de_content: ComplexContent = deserialized.try_deserialize_content().unwrap();
1202
1203        assert_eq!(msg_full, deserialized);
1204        assert_eq!(complex_content, de_content);
1205        // Deserialized MessageFull is also verified by construction
1206    }
1207
1208    #[test]
1209    fn test_all_tag_types_serialization() {
1210        let (sk, pk) = make_keys();
1211        let content = DummyContent { value: 100 };
1212
1213        let tags = [
1214            Tag::Protected,
1215            Tag::Event {
1216                id: MessageId::new(make_hash()),
1217                relays: vec!["relay1".to_string()],
1218            },
1219            Tag::User {
1220                id: KeyId::from_bytes([2u8; 32]),
1221                relays: vec!["relay2".to_string()],
1222            },
1223            Tag::Channel {
1224                id: vec![3],
1225                relays: vec!["relay3".to_string()],
1226            },
1227        ];
1228
1229        for tag in tags {
1230            let core = Message::new_v0(
1231                Content::Raw(postcard::to_stdvec(&content).unwrap()),
1232                pk.clone(),
1233                1714857600,
1234                Kind::Regular,
1235                vec![tag.clone()],
1236            );
1237
1238            let msg_full = MessageFull::new(core, &sk).unwrap();
1239            // MessageFull only exists in verified state, so creation success implies verification
1240            // Serialize and deserialize
1241            let serialized = msg_full.storage_value().unwrap();
1242            let deserialized = MessageFull::from_storage_value(&serialized).unwrap();
1243            let de_content: DummyContent = deserialized.try_deserialize_content().unwrap();
1244
1245            assert_eq!(msg_full, deserialized);
1246            assert_eq!(content, de_content);
1247            // Deserialized MessageFull is also verified by construction
1248        }
1249    }
1250    #[test]
1251    fn test_complex_content_empheral_kind_serialization() {
1252        let (sk, pk) = make_keys();
1253        let complex_content = ComplexContent {
1254            text: "Hello, World!".to_string(),
1255            numbers: vec![1, 2, 3, 4, 5],
1256            flag: true,
1257        };
1258        let core = Message::new_v0(
1259            Content::Raw(postcard::to_stdvec(&complex_content).unwrap()),
1260            pk,
1261            1714857600,
1262            Kind::Ephemeral(10),
1263            vec![],
1264        );
1265
1266        let msg_full = MessageFull::new(core, &sk).unwrap();
1267        // MessageFull only exists in verified state, so creation success implies verification
1268        // Serialize and deserialize
1269        let serialized = msg_full.storage_value().unwrap();
1270        let deserialized = MessageFull::from_storage_value(&serialized).unwrap();
1271        let de_content: ComplexContent = deserialized.try_deserialize_content().unwrap();
1272
1273        assert_eq!(msg_full, deserialized);
1274        assert_eq!(complex_content, de_content);
1275
1276        assert_eq!(msg_full, deserialized);
1277        // Deserialized MessageFull is also verified by construction
1278    }
1279
1280    #[test]
1281    fn test_complex_content_clear_store_kind_serialization() {
1282        // Create a signing key for testing
1283        let (signing_key, verifying_key) = make_keys();
1284
1285        for i in 0..10 {
1286            let content = TestContent {
1287                text: format!("Test message {}", i + 1),
1288                timestamp: std::time::SystemTime::now()
1289                    .duration_since(std::time::UNIX_EPOCH)
1290                    .unwrap()
1291                    .as_secs(),
1292                value: i as u32,
1293            };
1294
1295            let mut tags = Vec::new();
1296            // Create a fake event ID (32 bytes)
1297            let mut event_id_bytes = [0u8; 32];
1298            event_id_bytes[0] = i as u8;
1299            let event_id = blake3::Hash::from(event_id_bytes);
1300            tags.push(Tag::Event {
1301                id: MessageId::new(event_id),
1302                relays: Vec::new(),
1303            });
1304
1305            let message = Message::new_v0(
1306                Content::Raw(postcard::to_stdvec(&content).unwrap()),
1307                verifying_key.clone(),
1308                std::time::SystemTime::now()
1309                    .duration_since(std::time::UNIX_EPOCH)
1310                    .unwrap()
1311                    .as_secs(),
1312                Kind::Regular,
1313                tags,
1314            );
1315
1316            let msg_full = MessageFull::new(message, &signing_key).unwrap();
1317            // MessageFull only exists in verified state, so creation success implies verification
1318            // Serialize and deserialize
1319            let serialized = msg_full.storage_value().unwrap();
1320            let deserialized = MessageFull::from_storage_value(&serialized).unwrap();
1321            let de_content: TestContent = deserialized.try_deserialize_content().unwrap();
1322
1323            assert_eq!(msg_full, deserialized);
1324            assert_eq!(content, de_content);
1325            // Deserialized MessageFull is also verified by construction
1326        }
1327    }
1328
1329    #[test]
1330    fn test_complex_content_store_kind_serialization() {
1331        let (sk, pk) = make_keys();
1332        let complex_content = ComplexContent {
1333            text: "Hello, World!".to_string(),
1334            numbers: vec![1, 2, 3, 4, 5],
1335            flag: true,
1336        };
1337        let core = Message::new_v0(
1338            Content::Raw(postcard::to_stdvec(&complex_content).unwrap()),
1339            pk,
1340            1714857600,
1341            Kind::Store(StoreKey::CustomKey(10)),
1342            vec![],
1343        );
1344
1345        let msg_full = MessageFull::new(core, &sk).unwrap();
1346        // MessageFull only exists in verified state, so creation success implies verification
1347        // Serialize and deserialize
1348        let serialized = msg_full.storage_value().unwrap();
1349        let deserialized = MessageFull::from_storage_value(&serialized).unwrap();
1350        let de_content: ComplexContent = deserialized.try_deserialize_content().unwrap();
1351
1352        assert_eq!(msg_full, deserialized);
1353        assert_eq!(complex_content, de_content);
1354        // Deserialized MessageFull is also verified by construction
1355    }
1356
1357    #[test]
1358    fn test_wire_format_roundtrip() {
1359        let (sk, pk) = make_keys();
1360        let content = DummyContent { value: 42 };
1361        let core = Message::new_v0(
1362            Content::Raw(postcard::to_stdvec(&content).unwrap()),
1363            pk,
1364            1714857600,
1365            Kind::Regular,
1366            vec![Tag::Protected],
1367        );
1368        let msg_full = MessageFull::new(core, &sk).unwrap();
1369        let original_id = *msg_full.id();
1370
1371        // Convert to wire format and back
1372        let wire: MessageFullWire = msg_full.clone().into();
1373        let reconstructed = MessageFull::try_from(wire).unwrap();
1374
1375        // Should be identical after roundtrip
1376        assert_eq!(msg_full, reconstructed);
1377        assert_eq!(original_id, *reconstructed.id());
1378    }
1379
1380    #[test]
1381    fn test_invalid_signature_wire() {
1382        let (sk, pk) = make_keys();
1383        let (wrong_sk, _) = make_keys();
1384        let content = DummyContent { value: 42 };
1385        let core = Message::new_v0(
1386            Content::Raw(postcard::to_stdvec(&content).unwrap()),
1387            pk,
1388            1714857600,
1389            Kind::Regular,
1390            vec![Tag::Protected],
1391        );
1392        let msg_full = MessageFull::new(core.clone(), &sk).unwrap();
1393        let mut tampered_wire: MessageFullWire = msg_full.into();
1394
1395        // Replace signature with one from a different key
1396        let message_bytes = postcard::to_stdvec(&tampered_wire.message).unwrap();
1397        let wrong_sig = wrong_sk.sign(&message_bytes);
1398        tampered_wire.signature = wrong_sig;
1399
1400        // Attempting to convert back to MessageFull should fail
1401        assert!(MessageFull::try_from(tampered_wire).is_err());
1402    }
1403
1404    #[test]
1405    fn test_message_tampering_wire() {
1406        let (sk, pk) = make_keys();
1407        let content = DummyContent { value: 42 };
1408        let core = Message::new_v0(
1409            Content::Raw(postcard::to_stdvec(&content).unwrap()),
1410            pk,
1411            1714857600,
1412            Kind::Regular,
1413            vec![Tag::Protected],
1414        );
1415        let msg_full = MessageFull::new(core.clone(), &sk).unwrap();
1416        let mut tampered_wire: MessageFullWire = msg_full.into();
1417
1418        // Tamper with the message content by modifying the sender
1419        let (_, different_pk) = make_keys();
1420        match &mut *tampered_wire.message {
1421            Message::MessageV0(ref mut msg_v0) => {
1422                msg_v0.header.sender = different_pk;
1423            }
1424        }
1425
1426        // Attempting to convert back to MessageFull should fail due to signature mismatch
1427        assert!(MessageFull::try_from(tampered_wire).is_err());
1428    }
1429
1430    #[test]
1431    fn test_signature_tampering_wire() {
1432        let (sk, pk) = make_keys();
1433        let content = DummyContent { value: 42 };
1434        let core = Message::new_v0(
1435            Content::Raw(postcard::to_stdvec(&content).unwrap()),
1436            pk,
1437            1714857600,
1438            Kind::Regular,
1439            vec![Tag::Protected],
1440        );
1441        let msg_full = MessageFull::new(core.clone(), &sk).unwrap();
1442        let mut tampered_wire: MessageFullWire = msg_full.into();
1443
1444        // Create a completely different signature using a different key
1445        let (wrong_sk, _) = make_keys();
1446        let message_bytes = postcard::to_stdvec(&tampered_wire.message).unwrap();
1447        let wrong_signature = wrong_sk.sign(&message_bytes);
1448        tampered_wire.signature = wrong_signature;
1449
1450        // Attempting to convert back to MessageFull should fail due to invalid signature
1451        assert!(MessageFull::try_from(tampered_wire).is_err());
1452    }
1453
1454    #[test]
1455    fn test_serialization_roundtrip() {
1456        let (sk, pk) = make_keys();
1457        let content = ComplexContent {
1458            text: "Hello, World!".to_string(),
1459            numbers: vec![1, 2, 3, 4, 5],
1460            flag: true,
1461        };
1462        let core = Message::new_v0(
1463            Content::Raw(postcard::to_stdvec(&content).unwrap()),
1464            pk,
1465            1714857600,
1466            Kind::Regular,
1467            vec![
1468                Tag::Protected,
1469                Tag::Event {
1470                    id: MessageId::new(make_hash()),
1471                    relays: vec!["relay1".to_string()],
1472                },
1473            ],
1474        );
1475
1476        let msg_full = MessageFull::new(core, &sk).unwrap();
1477
1478        // Serialize and deserialize
1479        let serialized = msg_full.storage_value().unwrap();
1480        let deserialized = MessageFull::from_storage_value(&serialized).unwrap();
1481        let de_content: ComplexContent = deserialized.try_deserialize_content().unwrap();
1482
1483        assert_eq!(msg_full, deserialized);
1484        assert_eq!(content, de_content);
1485        // Deserialized MessageFull is also verified by construction
1486    }
1487
1488    #[test]
1489    fn test_multiple_tags() {
1490        let (sk, pk) = make_keys();
1491        let content = DummyContent { value: 42 };
1492        let core = Message::new_v0(
1493            Content::Raw(postcard::to_stdvec(&content).unwrap()),
1494            pk,
1495            1714857600,
1496            Kind::Regular,
1497            vec![
1498                Tag::Protected,
1499                Tag::Event {
1500                    id: MessageId::new(make_hash()),
1501                    relays: vec!["relay1".to_string()],
1502                },
1503                Tag::User {
1504                    id: KeyId::from_bytes([2u8; 32]),
1505                    relays: vec!["relay2".to_string()],
1506                },
1507            ],
1508        );
1509        let _msg_full = MessageFull::new(core, &sk).unwrap();
1510        // MessageFull only exists in verified state, so creation success implies verification
1511    }
1512
1513    #[test]
1514    fn test_large_content() {
1515        let (sk, pk) = make_keys();
1516        let large_content = ComplexContent {
1517            text: "A".repeat(1000),       // Large string
1518            numbers: (0..1000).collect(), // Large vector
1519            flag: false,
1520        };
1521        let core = Message::new_v0(
1522            Content::Raw(postcard::to_stdvec(&large_content).unwrap()),
1523            pk,
1524            1714857600,
1525            Kind::Regular,
1526            vec![Tag::Channel {
1527                id: vec![1],
1528                relays: vec!["relay1".to_string()],
1529            }],
1530        );
1531        let _msg_full = MessageFull::new(core, &sk).unwrap();
1532        // MessageFull only exists in verified state, so creation success implies verification
1533    }
1534
1535    #[test]
1536    fn test_id_uniqueness() {
1537        let (sk, pk) = make_keys();
1538        let content1 = DummyContent { value: 1 };
1539        let content2 = DummyContent { value: 2 };
1540
1541        let core1 = Message::new_v0(
1542            Content::Raw(postcard::to_stdvec(&content1).unwrap()),
1543            pk.clone(),
1544            1714857600,
1545            Kind::Regular,
1546            vec![Tag::Protected],
1547        );
1548        let core2 = Message::new_v0(
1549            Content::Raw(postcard::to_stdvec(&content2).unwrap()),
1550            pk.clone(),
1551            1714857600,
1552            Kind::Regular,
1553            vec![Tag::Protected],
1554        );
1555
1556        let msg_full1 = MessageFull::new(core1, &sk).unwrap();
1557        let msg_full2 = MessageFull::new(core2, &sk).unwrap();
1558
1559        // Different content should produce different IDs
1560        assert_ne!(msg_full1.id, msg_full2.id);
1561    }
1562
1563    #[test]
1564    fn test_same_content_different_signatures_but_same_id() {
1565        let (sk, pk) = make_keys();
1566        let content = DummyContent { value: 42 };
1567
1568        let core1 = Message::new_v0(
1569            Content::Raw(postcard::to_stdvec(&content).unwrap()),
1570            pk.clone(),
1571            1714857600,
1572            Kind::Regular,
1573            vec![Tag::Protected],
1574        );
1575        let core2 = Message::new_v0(
1576            Content::Raw(postcard::to_stdvec(&content).unwrap()),
1577            pk.clone(),
1578            1714857600,
1579            Kind::Regular,
1580            vec![Tag::Protected],
1581        );
1582
1583        let msg_full1 = MessageFull::new(core1, &sk).unwrap();
1584        let msg_full2 = MessageFull::new(core2, &sk).unwrap();
1585
1586        // Same content but different signatures (ML-DSA is non-deterministic)
1587        assert_ne!(msg_full1.signature, msg_full2.signature);
1588
1589        // but the id and message should be the same
1590        assert_eq!(msg_full1.id, msg_full2.id);
1591        assert_eq!(msg_full1.message, msg_full2.message);
1592    }
1593
1594    #[test]
1595    fn test_pqxdh_encrypted_content() {
1596        use crate::{
1597            inbox::pqxdh::{PqxdhInitialMessage, PqxdhSessionMessage},
1598            PqxdhEncryptedContent,
1599        };
1600
1601        // Create a mock PQXDH initial message
1602        let pqxdh_initial = PqxdhInitialMessage {
1603            initiator_identity: VerifyingKey::Ed25519(Box::new(
1604                ed25519_dalek::VerifyingKey::from_bytes(&[0u8; 32]).unwrap(),
1605            )),
1606            ephemeral_key: x25519_dalek::PublicKey::from([1u8; 32]),
1607            kem_ciphertext: vec![2u8; 100],
1608            signed_prekey_id: "spk_001".to_string(),
1609            one_time_prekey_id: Some("otk_001".to_string()),
1610            pq_signed_prekey_id: "pqspk_001".to_string(),
1611            pq_one_time_key_id: Some("pqotk_001".to_string()),
1612            encrypted_payload: vec![3u8; 50],
1613        };
1614
1615        // Create a mock PQXDH session message
1616        let pqxdh_session = PqxdhSessionMessage {
1617            sequence_number: 42,
1618            encrypted_payload: vec![5u8; 30],
1619            auth_tag: [6u8; 16],
1620        };
1621
1622        // Test initial message content
1623        let initial_content = Content::pqxdh_initial(pqxdh_initial.clone());
1624
1625        // Test session message content
1626        let session_content = Content::pqxdh_session(pqxdh_session.clone());
1627
1628        // Test getters
1629        let retrieved_initial = initial_content.as_pqxdh_encrypted().unwrap();
1630        if let PqxdhEncryptedContent::Initial(msg) = retrieved_initial {
1631            assert_eq!(*msg, pqxdh_initial);
1632        } else {
1633            panic!("Expected Initial variant");
1634        }
1635
1636        let retrieved_session = session_content.as_pqxdh_encrypted().unwrap();
1637        if let PqxdhEncryptedContent::Session(msg) = retrieved_session {
1638            assert_eq!(*msg, pqxdh_session);
1639        } else {
1640            panic!("Expected Session variant");
1641        }
1642
1643        // Test is_encrypted
1644        assert!(initial_content.is_encrypted());
1645        assert!(session_content.is_encrypted());
1646        assert!(!initial_content.is_raw());
1647        assert!(!session_content.is_raw());
1648
1649        // Test serialization
1650        let initial_serialized = postcard::to_stdvec(&initial_content).unwrap();
1651        let initial_deserialized: Content = postcard::from_bytes(&initial_serialized).unwrap();
1652        assert_eq!(initial_content, initial_deserialized);
1653
1654        let session_serialized = postcard::to_stdvec(&session_content).unwrap();
1655        let session_deserialized: Content = postcard::from_bytes(&session_serialized).unwrap();
1656        assert_eq!(session_content, session_deserialized);
1657    }
1658
1659    #[test]
1660    fn test_content_type_discriminants() {
1661        // Test that our new PQXDH discriminant doesn't conflict
1662        let raw_content = Content::raw(vec![1, 2, 3]);
1663        let pqxdh_content = Content::pqxdh_initial(crate::inbox::pqxdh::PqxdhInitialMessage {
1664            initiator_identity: VerifyingKey::Ed25519(Box::new(
1665                ed25519_dalek::VerifyingKey::from_bytes(&[0u8; 32]).unwrap(),
1666            )),
1667            ephemeral_key: x25519_dalek::PublicKey::from([0u8; 32]),
1668            kem_ciphertext: vec![],
1669            signed_prekey_id: "test".to_string(),
1670            one_time_prekey_id: None,
1671            pq_signed_prekey_id: "test".to_string(),
1672            pq_one_time_key_id: None,
1673            encrypted_payload: vec![],
1674        });
1675
1676        // Serialize both and ensure they have different discriminants
1677        let raw_serialized = postcard::to_stdvec(&raw_content).unwrap();
1678        let pqxdh_serialized = postcard::to_stdvec(&pqxdh_content).unwrap();
1679
1680        // First byte should be the discriminant
1681        assert_ne!(raw_serialized[0], pqxdh_serialized[0]);
1682        assert_eq!(raw_serialized[0], 0); // Raw should be discriminant 0
1683        assert_eq!(pqxdh_serialized[0], 42); // PQXDH should be discriminant 42
1684    }
1685}
1686
1687#[cfg(test)]
1688mod size_tests {
1689    use super::*;
1690    use crate::keys::KeyPair;
1691    use rand::rngs::OsRng;
1692    /// Test helper to create keypairs of different types for size testing
1693    fn create_keypairs_by_type() -> Vec<(String, KeyPair)> {
1694        let mut rng = OsRng;
1695        vec![
1696            ("Ed25519".to_string(), KeyPair::generate_ed25519(&mut rng)),
1697            ("MlDsa44".to_string(), KeyPair::generate_ml_dsa44(&mut rng)),
1698            ("MlDsa65".to_string(), KeyPair::generate_ml_dsa65(&mut rng)),
1699            ("MlDsa87".to_string(), KeyPair::generate_ml_dsa87(&mut rng)),
1700        ]
1701    }
1702
1703    /// Create a minimal message with empty content and no tags
1704    fn create_minimal_message(keypair: &KeyPair) -> MessageFull {
1705        let public_key = keypair.public_key();
1706
1707        // Create message with empty raw content and no tags
1708        let message = MessageV0 {
1709            header: MessageV0Header {
1710                sender: public_key,
1711                when: 1640995200, // Fixed timestamp for consistency
1712                kind: Kind::Regular,
1713                tags: vec![], // Empty tags for minimum size
1714            },
1715            content: Content::Raw(vec![]), // Empty raw content for minimum size
1716        };
1717
1718        MessageFull::new(Message::MessageV0(message), keypair).unwrap()
1719    }
1720
1721    #[test]
1722    fn test_minimum_message_sizes_by_key_variant() {
1723        let keypairs = create_keypairs_by_type();
1724
1725        for (key_type, keypair) in keypairs {
1726            let msg_full = create_minimal_message(&keypair);
1727            let serialized = postcard::to_stdvec(&MessageFullWire::from(msg_full)).unwrap();
1728            let size = serialized.len();
1729
1730            println!("Minimum serialized size for {key_type}: {size} bytes");
1731
1732            // Assert expected minimum sizes based on our analysis
1733            match key_type.as_str() {
1734                "Ed25519" => {
1735                    // Ed25519: ~120 bytes (allow some variance for postcard overhead)
1736                    assert!(
1737                        (100..=150).contains(&size),
1738                        "Ed25519 message size {size} should be ~120 bytes (100-150 range)"
1739                    );
1740                }
1741                "MlDsa44" => {
1742                    // ML-DSA-44: ~3,832 bytes (allow reasonable variance)
1743                    assert!(
1744                        (3700..=4000).contains(&size),
1745                        "ML-DSA-44 message size {size} should be ~3,832 bytes (3700-4000 range)"
1746                    );
1747                }
1748                "MlDsa65" => {
1749                    // ML-DSA-65: ~5,362 bytes (allow reasonable variance)
1750                    assert!(
1751                        (5200..=5600).contains(&size),
1752                        "ML-DSA-65 message size {size} should be ~5,362 bytes (5200-5600 range)"
1753                    );
1754                }
1755                "MlDsa87" => {
1756                    // ML-DSA-87: ~7,322 bytes (allow reasonable variance)
1757                    assert!(
1758                        (7100..=7600).contains(&size),
1759                        "ML-DSA-87 message size {size} should be ~7,322 bytes (7100-7600 range)"
1760                    );
1761                }
1762                _ => panic!("Unknown key type: {key_type}"),
1763            }
1764        }
1765    }
1766
1767    #[test]
1768    fn test_ed25519_minimum_size_precise() {
1769        let mut rng = OsRng;
1770        let keypair = KeyPair::generate_ed25519(&mut rng);
1771        let msg_full = create_minimal_message(&keypair);
1772        let serialized = postcard::to_stdvec(&MessageFullWire::from(msg_full)).unwrap();
1773        let size = serialized.len();
1774
1775        // Ed25519 should be the smallest - around 120 bytes
1776        assert!(
1777            (100..=150).contains(&size),
1778            "Ed25519 minimum message size {size} should be approximately 120 bytes"
1779        );
1780
1781        println!("Ed25519 precise minimum size: {size} bytes");
1782    }
1783
1784    #[test]
1785    fn test_mldsa44_minimum_size_precise() {
1786        let mut rng = OsRng;
1787        let keypair = KeyPair::generate_ml_dsa44(&mut rng);
1788        let msg_full = create_minimal_message(&keypair);
1789        let serialized = postcard::to_stdvec(&MessageFullWire::from(msg_full)).unwrap();
1790        let size = serialized.len();
1791
1792        // ML-DSA-44 should be around 3,832 bytes
1793        assert!(
1794            (3700..=4000).contains(&size),
1795            "ML-DSA-44 minimum message size {size} should be approximately 3,832 bytes"
1796        );
1797
1798        println!("ML-DSA-44 precise minimum size: {size} bytes");
1799    }
1800
1801    #[test]
1802    fn test_mldsa65_minimum_size_precise() {
1803        let mut rng = OsRng;
1804        let keypair = KeyPair::generate_ml_dsa65(&mut rng);
1805        let msg_full = create_minimal_message(&keypair);
1806        let serialized = postcard::to_stdvec(&MessageFullWire::from(msg_full)).unwrap();
1807        let size = serialized.len();
1808
1809        // ML-DSA-65 should be around 5,362 bytes
1810        assert!(
1811            (5200..=5600).contains(&size),
1812            "ML-DSA-65 minimum message size {size} should be approximately 5,362 bytes"
1813        );
1814
1815        println!("ML-DSA-65 precise minimum size: {size} bytes");
1816    }
1817
1818    #[test]
1819    fn test_mldsa87_minimum_size_precise() {
1820        let mut rng = OsRng;
1821        let keypair = KeyPair::generate_ml_dsa87(&mut rng);
1822        let msg_full = create_minimal_message(&keypair);
1823        let serialized = postcard::to_stdvec(&MessageFullWire::from(msg_full)).unwrap();
1824        let size = serialized.len();
1825
1826        // ML-DSA-87 should be around 7,322 bytes
1827        assert!(
1828            (7100..=7600).contains(&size),
1829            "ML-DSA-87 minimum message size {size} should be approximately 7,322 bytes"
1830        );
1831
1832        println!("ML-DSA-87 precise minimum size: {size} bytes");
1833    }
1834
1835    #[test]
1836    fn test_size_comparison_ratios() {
1837        let keypairs = create_keypairs_by_type();
1838        let mut sizes = std::collections::BTreeMap::new();
1839
1840        for (key_type, keypair) in keypairs {
1841            let msg_full = create_minimal_message(&keypair);
1842            let serialized = postcard::to_stdvec(&MessageFullWire::from(msg_full)).unwrap();
1843            sizes.insert(key_type, serialized.len());
1844        }
1845
1846        let ed25519_size = sizes["Ed25519"] as f64;
1847
1848        // Test size ratios relative to Ed25519
1849        let mldsa44_ratio = sizes["MlDsa44"] as f64 / ed25519_size;
1850        let mldsa65_ratio = sizes["MlDsa65"] as f64 / ed25519_size;
1851        let mldsa87_ratio = sizes["MlDsa87"] as f64 / ed25519_size;
1852
1853        println!("Size ratios relative to Ed25519:");
1854        println!("  Ed25519: {} bytes (1.0x)", sizes["Ed25519"]);
1855        println!(
1856            "  ML-DSA-44: {} bytes ({:.1}x)",
1857            sizes["MlDsa44"], mldsa44_ratio
1858        );
1859        println!(
1860            "  ML-DSA-65: {} bytes ({:.1}x)",
1861            sizes["MlDsa65"], mldsa65_ratio
1862        );
1863        println!(
1864            "  ML-DSA-87: {} bytes ({:.1}x)",
1865            sizes["MlDsa87"], mldsa87_ratio
1866        );
1867
1868        // Assert expected ratios (ML-DSA should be significantly larger)
1869        assert!(
1870            (25.0..=40.0).contains(&mldsa44_ratio),
1871            "ML-DSA-44 should be 25-40x larger than Ed25519, got {mldsa44_ratio:.1}x"
1872        );
1873        assert!(
1874            (35.0..=55.0).contains(&mldsa65_ratio),
1875            "ML-DSA-65 should be 35-55x larger than Ed25519, got {mldsa65_ratio:.1}x"
1876        );
1877        assert!(
1878            (50.0..=75.0).contains(&mldsa87_ratio),
1879            "ML-DSA-87 should be 50-75x larger than Ed25519, got {mldsa87_ratio:.1}x"
1880        );
1881    }
1882
1883    #[test]
1884    fn test_component_sizes_breakdown() {
1885        let mut rng = OsRng;
1886
1887        // Test each key type and break down the component sizes
1888        let test_cases = vec![
1889            ("Ed25519", KeyPair::generate_ed25519(&mut rng)),
1890            ("MlDsa44", KeyPair::generate_ml_dsa44(&mut rng)),
1891            ("MlDsa65", KeyPair::generate_ml_dsa65(&mut rng)),
1892            ("MlDsa87", KeyPair::generate_ml_dsa87(&mut rng)),
1893        ];
1894
1895        for (key_type, keypair) in test_cases {
1896            let public_key = keypair.public_key();
1897
1898            // Serialize individual components
1899            let verifying_key_size = postcard::to_stdvec(&public_key).unwrap().len();
1900
1901            // Create a minimal signature
1902            let test_data = b"test message";
1903            let signature = keypair.sign(test_data);
1904            let signature_size = postcard::to_stdvec(&signature).unwrap().len();
1905
1906            // Create full message and measure
1907            let msg_full = create_minimal_message(&keypair);
1908            let total_size = postcard::to_stdvec(&MessageFullWire::from(msg_full))
1909                .unwrap()
1910                .len();
1911
1912            println!("{key_type} component breakdown:");
1913            println!("  VerifyingKey: {verifying_key_size} bytes");
1914            println!("  Signature: {signature_size} bytes");
1915            println!("  Total message: {total_size} bytes");
1916            println!(
1917                "  Overhead: {} bytes",
1918                total_size - verifying_key_size - signature_size
1919            );
1920            println!();
1921
1922            // Verify that the key and signature are the dominant components
1923            let key_and_sig_size = verifying_key_size + signature_size;
1924            let overhead = total_size - key_and_sig_size;
1925
1926            // Overhead should be reasonable (< 100 bytes for fixed fields + postcard encoding)
1927            assert!(
1928                overhead < 100,
1929                "{key_type} overhead {overhead} bytes should be less than 100 bytes"
1930            );
1931
1932            // Key and signature should account for most of the message size
1933            let key_sig_ratio = key_and_sig_size as f64 / total_size as f64;
1934            assert!(
1935                key_sig_ratio >= 0.85,
1936                "{} key+signature should be at least 85% of total size, got {:.1}%",
1937                key_type,
1938                key_sig_ratio * 100.0
1939            );
1940        }
1941    }
1942}