Skip to main content

Wire Protocol Crate

The wire-protocol crate defines the message formats and serialization for network communication in Zoe Relay.

Overview

This crate provides:

  • Message type definitions
  • Serialization/deserialization
  • Protocol versioning
  • Network message routing

Key Components

Message Types

Core message definitions for network communication:

Example Message Structure
use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Message {
pub id: String,
pub content: Vec<u8>,
pub timestamp: u64,
}

Invitation Protocol

Group invitation message types:

crates/wire-protocol/src/invitation.rs
//! Group invitation message types for PQXDH-based secure invitations
//!
//! This module defines the message types used in the group invitation flow,
//! providing type-safe structures for the multi-step verification process.

use crate::{Tag, VerifyingKey};
use serde::{Deserialize, Serialize};

/// Protocol version for invitation messages
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
pub enum InboxHandshakeProtocolVersion {
V1,
}

/// Purpose of the PQXDH handshake session
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
pub enum HandshakePurpose {
GroupInvitation,
DirectMessage,
FileTransfer,
// Future purposes can be added here
}

/// Initial handshake request sent in PqxdhInitialMessage payload
///
/// This message establishes the PQXDH session and requests verification.
/// It contains NO sensitive group information for security.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub struct VerificationHandshakeRequest {
pub protocol_version: InboxHandshakeProtocolVersion,
pub purpose: HandshakePurpose,
pub timestamp: u64,
}

/// Response sent after emoji verification by the invitee
///
/// This message indicates whether the user accepted or rejected the invitation
/// after verifying the emoji sequence derived from the shared PQXDH secret.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub struct HandshakeResponse {
pub accepted: bool,
pub timestamp: u64,
}

/// User profile information
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub struct UserProfile {
pub display_name: String,
pub avatar: Option<Vec<u8>>, // Optional avatar data
pub public_key: VerifyingKey,
}

/// Group metadata shared during invitation
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub struct GroupMetadata {
pub name: String,
pub description: Option<String>,
pub member_count: u32,
pub created_at: u64,
}

/// Sensitive group data sent only after successful verification
///
/// This message contains all the information needed for the invitee to join
/// the group. It is only sent after the handshake has been confirmed.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub struct GroupInvitationData {
pub group_tag: Tag,
pub shared_aes_key: [u8; 32],
pub inviter_profile: UserProfile,
pub group_metadata: GroupMetadata,
pub timestamp: u64,
}

/// Event sent to the group to announce a new member
///
/// This is sent outside the PQXDH session to the group's regular channel
/// to notify existing members of the new joiner.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub struct ProfileSetEvent {
pub event_type: String, // "member_joined"
pub user_profile: UserProfile,
pub timestamp: u64,
}

/// Generate a random ephemeral group invite protocol ID
///
/// Returns a random value in the range 0-999 for use with
/// PqxdhInboxProtocol::EphemeralGroupInvite(id).
/// This provides unlinkability between different invitation sessions.
pub fn generate_ephemeral_group_invite_id() -> u32 {
use rand::Rng;
let mut rng = rand::thread_rng();
rng.gen_range(0..1000)
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_ephemeral_id_range() {
for _ in 0..100 {
let id = generate_ephemeral_group_invite_id();
assert!(id < 1000, "Generated ID {id} should be less than 1000");
}
}

#[test]
fn test_message_serialization() {
let request = VerificationHandshakeRequest {
protocol_version: InboxHandshakeProtocolVersion::V1,
purpose: HandshakePurpose::GroupInvitation,
timestamp: 1234567890,
};

let serialized = postcard::to_allocvec(&request).unwrap();
let deserialized: VerificationHandshakeRequest = postcard::from_bytes(&serialized).unwrap();
assert_eq!(request, deserialized);
}

#[test]
fn test_handshake_response() {
let response = HandshakeResponse {
accepted: true,
timestamp: 1234567890,
};

let serialized = postcard::to_allocvec(&response).unwrap();
let deserialized: HandshakeResponse = postcard::from_bytes(&serialized).unwrap();
assert_eq!(response, deserialized);
}
}

PQXDH Protocol

Post-quantum key exchange messages:

Example PQXDH Message Structure
use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PqxdhInitialMessage {
pub sender_identity_key: Vec<u8>,
pub sender_ephemeral_key: Vec<u8>,
pub kyber_ciphertext: Vec<u8>,
pub encrypted_payload: Vec<u8>,
}

Usage

Add this to your Cargo.toml:

[dependencies]
zoe-wire-protocol = { path = "../wire-protocol" }

Examples

Creating an Invitation Message

use zoe_wire_protocol::invitation::{VerificationHandshakeRequest, HandshakePurpose};

let request = VerificationHandshakeRequest {
protocol_version: ProtocolVersion::V1,
purpose: HandshakePurpose::GroupInvitation,
timestamp: SystemTime::now(),
};

For complete API documentation, see the Rust API docs.