Skip to content
Documentation (14 pages)

Wire Protocol

The Tallow Transfer Protocol enables end-to-end encrypted file transfer between two parties via an untrusted relay.

Protocol Overview

Sender Relay Receiver
| | |
|--- Join Room (hash) --->|<--- Join Room (hash) ---|
| | |
|<-------- KEM Handshake (ML-KEM-1024 + X25519) -->|
| | |
|--- FileOffer (encrypted metadata) ------------->|
| | |
|<-- Accept/Reject -------------------------------|
| | |
|--- Encrypted Chunks (AES-256-GCM) ------------>|
|--- Encrypted Chunks ---------------------------->|
|--- TransferComplete (Merkle root) -------------->|
| | |
|<-- Verification --------------------------------|

Key Exchange

Room Creation

  1. Sender generates a six-character passcode (32-symbol confusable-free alphabet)
  2. Room ID = Argon2id memory-hard derivation of the passcode (each guess costs ~64 MiB × 3 passes); the passcode itself is never sent
  3. Both parties connect to the relay using the room ID
  4. The relay matches participants but never sees the passcode

Hybrid KEM Handshake

  1. Both parties exchange ML-KEM-1024 public keys and X25519 ephemeral keys
  2. ML-KEM-1024 encapsulation produces a post-quantum shared secret
  3. X25519 Diffie-Hellman produces a classical shared secret
  4. The KEM and PAKE secrets are combined via HKDF-SHA256 with domain separation, binding the transmitted KEM material into the derivation:
session_key = HKDF-SHA256(
salt = BLAKE3(handshake transcript),
ikm = kem_shared_secret || cpace_secret
|| BLAKE3(kem_ciphertext) || x25519_ephemeral_pub || transcript_hash,
info = "tallow.session_key.kem_pake.v3"
)

The hybrid approach ensures security even if one algorithm is broken.

Data Transfer

Chunking

Encryption

Each chunk is encrypted with AES-256-GCM:

Integrity

Wire Format

Serialization

Messages are serialized with postcard (Serde-compatible, compact binary format) and framed with a 4-byte big-endian length prefix:

[4 bytes: length][N bytes: postcard-serialized message]

Message Types

Message Direction Purpose
RoomJoin Both to Relay Join a room by code hash
RoomJoined Relay to Both Confirm room membership
KemPublicKey Both Exchange KEM public keys
KemCiphertext Responder to Initiator KEM encapsulation result
FileOffer Sender to Receiver Encrypted file manifest
FileAccept Receiver to Sender Accept the transfer
FileReject Receiver to Sender Reject the transfer
Chunk Sender to Receiver Encrypted file chunk
ChunkAck Receiver to Sender Acknowledge received chunk
TransferComplete Sender to Receiver Final Merkle root
ResumeInfo Both Resume state for interrupted transfers
ChatMessage Both Encrypted chat message
ClipboardData Both Encrypted clipboard content
Ping / Pong Both Keep-alive

Version Negotiation

Protocol version is exchanged on connection. The current version is v1. A TLV (Type-Length-Value) extension mechanism is reserved for future features without breaking backward compatibility.

Transfer Flow

Sliding Window

Chunks are sent using a sliding window of size 64:

  1. Sender sends up to 64 chunks without waiting for acknowledgement
  2. Receiver acknowledges each chunk as it is verified
  3. Sender advances the window as acknowledgements arrive
  4. This maximizes throughput on high-latency connections

Resume

If a transfer is interrupted:

  1. Both sides exchange ResumeInfo with the manifest hash and list of verified chunks
  2. The sender skips already-verified chunks
  3. The transfer continues from where it left off
  4. The final Merkle tree verification covers all chunks (original + resumed)

Transport

QUIC (Primary)

WebSocket (Browser)