Files
wz-phone/vault/PRDs/PRD-delegated-trust.md
Siavash Sameni ed8a7ae5aa docs: protocol audit 2026-05-25, update architecture + Obsidian vault
Audit:
- docs/AUDIT-2026-05-25.md: full protocol audit covering 8 findings
  (4 critical, 2 high, 5 medium, 4 low) with code references and fix
  effort estimates
- vault/Audit/Tasks.md: Obsidian Tasks plugin file tracking all audit
  items with priorities, due dates, and per-step checklists

Architecture docs updated for Wire format v2 and Wave 5/6 features:
- ARCHITECTURE.md: adds wzp-video to dependency graph and project
  structure; wire format updated to v2 (16B header, 5B MiniHeader);
  relay concurrency section corrected (DashMap+RwLock is current, not
  a future optimization); test count 571→702; Android note
- PROGRESS.md: Wave 5 and Wave 6 sections appended; test count 372→702;
  current status and open blockers as of 2026-05-25
- ROAD-TO-VIDEO.md: implementation status table inserted (/🟡/🔴/🔲
  per phase); 6-step critical path to first video call
- WZP-SPEC.md: MediaHeader updated to v2 (16B byte-aligned); MiniHeader
  updated to 5B with seq_delta; codec IDs 9-12 added (H.264/H.265/AV1);
  version negotiation section added

Obsidian vault (vault/):
- 114 files across Architecture/, PRDs/, Reports/, Android/,
  Reference/, Audit/ with YAML frontmatter
- 00 - Home.md index note with wiki links
- .obsidian/app.json config

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 06:00:17 +04:00

6.1 KiB

tags, type
tags type
prd
wzp
prd

PRD: Delegated Trust for Relay Federation

Problem

In the current federation model, when Relay 1 trusts Relay 2, and Relay 2 forwards media from Relay 3, Relay 1 has no way to know or control that Relay 3's traffic is reaching it. This is a trust gap — any relay in the chain can introduce untrusted traffic.

Example: Relay 1 (trusted zone) ←→ Relay 2 (hub) ←→ Relay 3 (unknown)

Relay 1 explicitly trusts Relay 2. But Relay 2 forwards Relay 3's media to Relay 1 without Relay 1's consent. Relay 1 receives media that originated from an entity it never approved.

Solution

Add a delegate flag to [[trusted]] entries. When delegate = true, the relay accepts media forwarded through the trusted peer from relays that the trusted peer vouches for. When delegate = false (default), only media originating from explicitly trusted/peered relays is accepted.

Trust Levels

Config Meaning
[[peers]] "I connect to you and trust your identity"
[[trusted]] "I accept connections from you"
[[trusted]] delegate = true "I accept connections from you AND from relays you vouch for"
No entry "I reject your connections and drop your forwarded media"

Configuration

# Relay 1: trusts Relay 2 and delegates trust
[[trusted]]
fingerprint = "relay-2-tls-fingerprint"
label = "Relay 2 (Hub)"
delegate = true    # Accept relays that Relay 2 forwards from

# Without delegate (default = false):
[[trusted]]
fingerprint = "relay-4-tls-fingerprint"
label = "Relay 4"
# delegate = false  (implicit default)
# Only direct media from Relay 4 is accepted

Protocol Changes

Relay-to-Relay Media Authorization

When Relay 2 forwards media from Relay 3 to Relay 1, the datagram needs to carry origin information so Relay 1 can decide whether to accept it.

Option A: Origin tag in datagram (recommended)

Extend the federation datagram format:

[room_hash: 8 bytes][origin_relay_fp: 8 bytes][media_packet]

The 8-byte origin fingerprint identifies which relay originally produced the media. The forwarding relay (Relay 2) sets this to the source relay's fingerprint. Relay 1 checks:

  1. Is the origin relay directly trusted? → accept
  2. Is the forwarding relay trusted with delegate = true? → accept
  3. Otherwise → drop

Option B: Trust announcement signal

When Relay 2 connects to Relay 1, it sends a FederationTrustChain signal listing which relays it will forward from:

FederationTrustChain {
    /// Fingerprints of relays this peer may forward media from
    vouched_relays: Vec<String>,
}

Relay 1 checks each fingerprint against its policy:

  • If Relay 2 has delegate = true in Relay 1's config → accept all listed relays
  • If Relay 2 has delegate = false → reject, only accept direct media from Relay 2

Option B is simpler to implement (no datagram format change) but less granular.

Option B is simpler — the trust chain is established at connection time, not per-datagram. The forwarding relay announces what it will forward, and the receiving relay approves or rejects upfront.

Implementation

Config Changes

#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct TrustedConfig {
    pub fingerprint: String,
    #[serde(default)]
    pub label: Option<String>,
    /// When true, also accept media forwarded through this relay from
    /// relays it vouches for. Default: false.
    #[serde(default)]
    pub delegate: bool,
}

Federation Signal

/// Sent after FederationHello — lists relays this peer will forward from.
FederationTrustChain {
    /// TLS fingerprints of relays whose media may be forwarded through us.
    vouched_relays: Vec<String>,
}

Forwarding Authorization

In handle_datagram, before forwarding media to local participants:

// Check if we should accept this forwarded media
let is_authorized = if source_is_direct_peer {
    true  // Direct peer, always accepted
} else {
    // Check if the forwarding peer has delegate=true
    let forwarding_peer = fm.find_trusted_by_fingerprint(forwarding_peer_fp);
    forwarding_peer.map(|t| t.delegate).unwrap_or(false)
};

if !is_authorized {
    warn!("dropping forwarded media from unauthorized relay chain");
    return;
}

Relay 2 (Hub) Behavior

When Relay 2 receives FederationTrustChain queries from peers:

  1. Collect all directly connected peer fingerprints
  2. Send FederationTrustChain { vouched_relays } to each peer
  3. When a new relay connects, update all peers' trust chains

Anti-Spam Properties

Attack Mitigation
Unknown relay connects to hub Hub rejects (not in [[trusted]])
Hub forwards spam relay's media Receiving relay checks delegate flag, drops if false
Relay spoofs origin fingerprint Origin tag is set by the forwarding relay, not the source. The forwarding relay is trusted, so if it lies about origin, the trust is misplaced at the config level.
Chain amplification (A→B→C→D→...) TTL on forwarded datagrams (decrement at each hop, drop at 0). Default TTL=2 (one intermediate relay).

TTL for Chain Length

Add a TTL byte to the federation datagram to limit chain depth:

[room_hash: 8 bytes][ttl: 1 byte][media_packet]
  • Default TTL = 2 (allows one intermediate relay: A→B→C)
  • Each forwarding relay decrements TTL
  • When TTL = 0, don't forward further (only deliver to local participants)
  • Configurable per-relay: max_federation_hops = 2

Milestones

Phase Scope Effort
1 Add delegate field to TrustedConfig 0.5 day
2 FederationTrustChain signal + announcement 1 day
3 Authorization check in handle_datagram 0.5 day
4 TTL in federation datagrams 0.5 day
5 Testing: authorized vs unauthorized forwarding 0.5 day

Non-Goals (v1)

  • Per-room trust policies (trust Relay X only for room "android")
  • Dynamic trust negotiation (relays negotiate trust level at runtime)
  • Revocation (removing a relay from trust chain requires config edit + restart)
  • Cryptographic proof of origin (signed datagrams from source relay)