MessageFull

Struct MessageFull 

Source
pub struct MessageFull {
    id: MessageId,
    signature: Signature,
    message: Box<Message>,
}
Expand description

Complete signed message ready for transmission and storage.

§Message Authentication and Integrity

MessageFull represents a complete, authenticated message that includes:

  1. Content: The original Message (e.g., MessageV0)
  2. Signature: Ed25519 digital signature over the serialized message
  3. Identity: Blake3 hash-based unique identifier

This structure ensures non-repudiation and integrity - recipients can cryptographically verify that the message came from the claimed sender and hasn’t been tampered with.

§Cryptographic Construction

The message construction follows a specific protocol:

  1. Serialize: Convert Message to bytes using postcard
  2. Sign: Create Ed25519 signature over the serialized bytes
  3. Hash: Compute Blake3 hash of serialized_message || signature for the ID

This ensures that:

  • The signature covers the entire message content
  • The ID uniquely identifies this specific signed message
  • Replay attacks are prevented through unique IDs

§Wire Format and Storage

MessageFull is the canonical storage format used throughout the system:

  • Network transmission: Serialized with postcard for efficiency
  • Database storage: Stored as binary blobs in key-value stores
  • Message indexing: ID used as primary key, sender/timestamp extracted for indexes

The postcard serialization format is:

[id: 32 bytes][message: variable][signature: 64 bytes]

§Identity and Deduplication

The Blake3-based ID serves multiple purposes:

  • Deduplication: Identical signed messages have identical IDs
  • Content addressing: Messages can be retrieved by their cryptographic hash
  • Integrity verification: ID changes if any part of the message is modified
  • Ordering: IDs provide deterministic message ordering (with timestamp ties)

§Security Properties

MessageFull provides the following security guarantees:

  • Authentication: Ed25519 signature proves sender identity
  • Integrity: Any modification invalidates the signature
  • Non-repudiation: Sender cannot deny creating a valid signature
  • Uniqueness: Blake3 ID prevents message duplication

Note: Confidentiality is provided by the Content encryption, not at this layer.

§Example Usage

use zoe_wire_protocol::{MessageFull, Message, MessageV0, Content, Kind};
use ed25519_dalek::SigningKey;
use rand::rngs::OsRng;

// Create a signed message
let signing_key = SigningKey::generate(&mut OsRng);
let message = Message::MessageV0(MessageV0 {
    sender: signing_key.verifying_key(),
    when: 1640995200,
    kind: Kind::Regular,
    tags: vec![],
    content: Content::raw("Hello!".as_bytes().to_stdvec()),
});

let full_message = MessageFull::new(message, &signing_key)?;

// Verify the signature and ID
assert!(full_message.verify()?);

// The ID is deterministic for the same signed content
let id = full_message.id;

§Storage Integration

This structure is optimized for the key-value storage architecture:

  • Primary key: id field for O(1) message retrieval
  • Indexed fields: sender and when extracted from embedded message for queries
  • Tag tables: Separate tables for efficient tag-based filtering
  • Blob storage: Entire MessageFull serialized as atomic unit

Fields§

§id: MessageId

Blake3 hash serving as the unique message identifier.

Computed as: Blake3(messsage.as_bytes()) Notice that the ID does not include the signature as that may contain random ness (ML-DSA does) and thus would create different hash IDs for the same content message.

That means that if there are two messages with the same contentn but valid signatures (verified against the sender) they are considered the same in terms of storage and retrieval and there is no guarantee you received the one with the same signature - just with a valid signature.

This ID is:

  • Unique: Cryptographically improbable to collide
  • Deterministic: Same content always produces same ID
  • Tamper-evident: Changes to messagd change the ID
  • Content-addressed: Can be used to retrieve the message
§signature: Signature

Cryptographic signature over the serialized message.

Created by signing postcard::serialize(message) with the sender’s private key. Recipients verify this signature using the public key in message.sender.

Security note: The signature covers the entire serialized message, including all metadata, tags, and content. This prevents partial modification attacks.

§message: Box<Message>

The original message content and metadata.

Boxed to minimize stack usage since messages can be large. Contains version-specific message data (e.g., MessageV0).

Implementations§

Source§

impl MessageFull

Source

pub fn new(message: Message, signer: &KeyPair) -> Result<Self, MessageFullError>

Create a new MessageFull with proper signature and ID

Source

fn with_signature( signature: Signature, message: Box<Message>, message_bytes: &[u8], ) -> Result<Self, MessageFullError>

Source

pub fn id(&self) -> &MessageId

Source

pub fn message(&self) -> &Message

Source

pub fn signature(&self) -> &Signature

Source

pub fn ref_tag(&self) -> Tag

Source

pub fn storage_value(&self) -> Result<Vec<u8>, MessageFullError>

The value this message is stored under in the storage

Source

pub fn storage_timeout(&self) -> Option<u64>

The timeout for this message in the storage

Source

pub fn store_key(&self) -> Option<StoreKey>

Source

pub fn clear_key(&self) -> Option<StoreKey>

this is meant to clear a storage key

Source

pub fn from_storage_value(value: &[u8]) -> Result<Self, MessageFullError>

Deserialize a message from its storage value

Source

pub fn author(&self) -> &VerifyingKey

Source

pub fn when(&self) -> &u64

Source

pub fn kind(&self) -> &Kind

Source

pub fn tags(&self) -> &Vec<Tag>

Source

pub fn content(&self) -> &Content

Source

pub fn raw_content(&self) -> Option<&Vec<u8>>

Get raw content bytes if this message contains raw content

Source

pub fn encrypted_content(&self) -> Option<&ChaCha20Poly1305Content>

Get encrypted content if this message contains encrypted content

Source

pub fn is_expired(&self, current_time: u64) -> bool

Check if this message is expired based on its timeout and current time

Source§

impl MessageFull

Source

pub fn try_deserialize_content<C>(&self) -> Result<C, MessageFullError>
where C: for<'a> Deserialize<'a>,

Try to deserialize content if it’s raw (unencrypted)

Trait Implementations§

Source§

impl Clone for MessageFull

Source§

fn clone(&self) -> MessageFull

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for MessageFull

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl<'de> Deserialize<'de> for MessageFull

Source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>
where __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
Source§

impl From<&MessageFull> for Tag

Source§

fn from(message: &MessageFull) -> Self

Converts to this type from the input type.
Source§

impl From<MessageFull> for MessageFullWire

Source§

fn from(val: MessageFull) -> Self

Converts to this type from the input type.
Source§

impl PartialEq for MessageFull

Manual PartialEq implementation for MessageFull

Source§

fn eq(&self, other: &Self) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, and should not be overridden without very good reason.
Source§

impl Serialize for MessageFull

Source§

fn serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>
where __S: Serializer,

Serialize this value into the given Serde serializer. Read more
Source§

impl TryFrom<MessageFullWire> for MessageFull

Source§

type Error = MessageFullError

The type returned in the event of a conversion error.
Source§

fn try_from(value: MessageFullWire) -> Result<Self, Self::Error>

Performs the conversion.
Source§

impl Eq for MessageFull

Manual Eq implementation for MessageFull

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
§

impl<'a, T, E> AsTaggedExplicit<'a, E> for T
where T: 'a,

§

fn explicit(self, class: Class, tag: u32) -> TaggedParser<'a, Explicit, Self, E>

§

impl<'a, T, E> AsTaggedImplicit<'a, E> for T
where T: 'a,

§

fn implicit( self, class: Class, constructed: bool, tag: u32, ) -> TaggedParser<'a, Implicit, Self, E>

Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
§

impl<T> Classify for T

§

type Classified = T

§

fn classify(self) -> T

§

impl<T> Classify for T

§

type Classified = T

§

fn classify(self) -> T

Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
§

impl<T> Declassify for T

§

type Declassified = T

§

fn declassify(self) -> T

§

impl<T> Declassify for T

§

type Declassified = T

§

fn declassify(self) -> T

§

impl<Q, K> Equivalent<K> for Q
where Q: Eq + ?Sized, K: Borrow<Q> + ?Sized,

§

fn equivalent(&self, key: &K) -> bool

Compare self to key and return true if they are equal.
§

impl<Q, K> Equivalent<K> for Q
where Q: Eq + ?Sized, K: Borrow<Q> + ?Sized,

§

fn equivalent(&self, key: &K) -> bool

Checks if this value is equivalent to the given key. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

§

impl<T> FutureExt for T

§

fn with_context(self, otel_cx: Context) -> WithContext<Self>

Attaches the provided Context to this type, returning a WithContext wrapper. Read more
§

fn with_current_context(self) -> WithContext<Self>

Attaches the current Context to this type, returning a WithContext wrapper. Read more
§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a [WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a [WithDispatch] wrapper. Read more
§

impl<T> DartSafe for T

Source§

impl<T> DeserializeOwned for T
where T: for<'de> Deserialize<'de>,

§

impl<T> TaskRetFutTrait for T
where T: Send,