AUDITABLE ARCHITECTURE

Security Policy

Cohrtz is designed with a defense-in-depth security model that ensures data privacy, integrity, and authenticity through end-to-end encryption (E2EE) and decentralized trust. The server never has access to your plaintext data or encryption keys—only encrypted blobs transit the network.

Last updated: February 19, 2026

Security Architecture Overview

The security model is built on five primary pillars:

Pillar Description
Local Identity & Hardware-Backed Keys Cryptographic seeds stored in OS-level secure enclaves (Keychain/Keystore)
Peer-to-Peer Trust Ed25519 digital signatures for packet authentication
Pairwise Key Exchange X25519 Diffie-Hellman for deriving shared secrets
Group Key Management TreeKEM (RFC 9420-inspired) for forward secrecy and efficient group rekeying
Secure Synchronization Vector clock-based CRDT sync with encrypted payloads and Merkle root consistency checks

1. Local Security & Identity

Upon first launch, the app creates a unique user identity and generates cryptographic key material that never leaves the device in plaintext form.

Key Generation

Algorithm Purpose Key Size
Ed25519 Digital signatures 256-bit
X25519 Key exchange 256-bit
AES-GCM-256 Symmetric encryption 256-bit
Argon2id KDF (native only) 256-bit

Hardware-Backed Storage

Platform Storage
iOS Keychain (SEP)
macOS Keychain (T2/M-series)
Android Keystore (TEE/StrongBox)
Web SharedPreferences (fallback, development only)

Local Security Initialization Flow

flowchart TD Start((App Startup)) --> ID_Init[IdentityService.initialize] ID_Init --> Load_Profile{Profile in
SharedPreferences?} Load_Profile -- No --> Create_ID[Create UUID v7 User ID] Load_Profile -- Yes --> Parse_ID[Parse UserProfile JSON] Create_ID --> Save_ID[Save Profile to SharedPreferences] Parse_ID --> Sec_Init[SecurityService.initialize] Save_ID --> Sec_Init Sec_Init --> Read_Seeds[Read Seeds from SecureStorageService] Read_Seeds --> Seeds_Exist{Seeds in
Keychain/Keystore?} Seeds_Exist -- No --> Gen_Ed[Generate Ed25519 KeyPair] Gen_Ed --> Gen_X[Generate X25519 KeyPair] Gen_X --> Save_Seeds[Save Private Seeds to Secure Storage] Seeds_Exist -- Yes --> Derive_Ed[Derive Ed25519 KeyPair from Seed] Derive_Ed --> Derive_X[Derive X25519 KeyPair from Seed] Save_Seeds --> Ready((Service Ready)) Derive_X --> Ready

2. Peer-to-Peer Trust & Handshake

Before any data is synchronized, peers must exchange public keys to establish cryptographic trust. This handshake forms the foundation of the E2EE protocol.

Handshake Mechanism

  1. 1. Key Broadcast: Upon connecting to a room, each peer broadcasts a HANDSHAKE packet containing Ed25519 signing public key (32 bytes), X25519 encryption public key (32 bytes), and optional TreeKEM public key.
  2. 2. Key Storage: HandshakeHandler maintains maps for signing keys and encryption keys indexed by sender ID.
  3. 3. Packet Buffering: Packets from unknown peers are queued until their handshake is received and validated.

Packet Signature Verification

Every packet includes an Ed25519 signature over:

  • type - Packet type enum
  • request_id - UUID for correlation
  • sender_id - Participant identity
  • payload - Encrypted content
  • chunk_index - For large payloads
  • is_last_chunk - Stream flag
  • target_id - Unicast destination
  • encrypted - Encryption flag

Peer Trust Sequence

sequenceDiagram participant A as Local User (A) participant Room as LiveKit Room participant B as Remote User (B) A->>Room: Connect to Room A->>Room: Broadcast HANDSHAKE
[Ed25519 PubKey, X25519 PubKey] B->>Room: Receive Handshake Note over B: HandshakeHandler stores
User A's public keys B->>A: Reply HANDSHAKE
[Ed25519 PubKey, X25519 PubKey] Note over A: HandshakeHandler stores
User B's public keys rect rgb(200, 230, 200) Note over A,B: Trust Established
All subsequent packets verified via Ed25519 signature end A->>B: SYNC_REQ [Signed + GSK-encrypted when available] Note over B: SecurityService.verifyPacket()
validates signature against stored key

3. Pairwise Encryption

For unicast communication (e.g., GSK sharing, secure sync responses), Cohrtz uses X25519 Diffie-Hellman to derive shared secrets.

Shared Secret Derivation

  1. 1. Perform X25519 key agreement between local private key and remote public key
  2. 2. Derive symmetric key using SHA-256: Hash(sharedSecret || salt)
  3. 3. Use derived 256-bit key for AES-GCM-256 encryption

AES-GCM-256 Payload Format

+--------+------------+-----+
| Nonce  | Ciphertext | MAC |
| 12 B   | Variable   | 16 B|
+--------+------------+-----+

The 12-byte nonce ensures uniqueness, while the 16-byte MAC provides authenticity verification.

4. Group Key Management (TreeKEM)

For efficient and secure group communication, Cohrtz implements TreeKEM based on RFC 9420 (Messaging Layer Security).

GSK

256-bit AES-GCM key shared by all group members

Ratchet Tree

Binary tree structure where each node holds key material

Forward Secrecy

Keys rotate on member join/leave

O(log n)

Logarithmic complexity for member updates

Group Key Lifecycle

flowchart TD subgraph Initialization Create[Group Created] --> GenGSK[Generate 256-bit GSK] GenGSK --> StoreGSK[Store GSK in SecureStorage] StoreGSK --> InitTree[Initialize RatchetTree
with single leaf] end subgraph "Member Join" Join[New Member Joins] --> Welcome[TreekemService.welcomeNewMember] Welcome --> |HPKE-sealed path secret| NewMember[New member derives GSK] Welcome --> Rotate1[Host calls createUpdate] Rotate1 --> FwdSec1[Rotate GSK
Forward Secrecy] end subgraph "Member Leave" Leave[Member Leaves] --> Blank[Blank leaf + direct path] Blank --> Rotate2[Remaining member calls createUpdate] Rotate2 --> FwdSec2[Rotate GSK
Post-Compromise Security] end FwdSec1 --> SaveState[Save TreeKEM state
Public → CRDT
Private → SecureStorage] FwdSec2 --> SaveState

5. Secure Data Synchronization

The synchronization protocol ensures CRDT data remains consistent across all nodes without exposing plaintext to the network.

SYNC_REQ / SYNC_CLAIM Transport

  • Primary path: SYNC_REQ and SYNC_CLAIM are GSK-encrypted broadcasts when the Group Secret Key is available.
  • Fallback: If the GSK is not yet available (for example first join), the protocol falls back to pairwise unicast to known peers.
  • Data response: The actual changeset is always pairwise encrypted and sent directly to the requester.

Vector Clock Synchronization

Each CRDT database maintains a Vector Clock tracking the latest Hybrid Logical Clock (HLC) timestamp from each peer:

{
  "user:01234...": "2024-01-15T10:30:00.000Z-0001-abcd1234",
  "user:56789...": "2024-01-15T10:30:01.500Z-0002-efgh5678"
}

Consistency Verification

Peers periodically broadcast CONSISTENCY_CHECK containing:

  • record_count - Total records across all tables
  • merkle_root - SHA-256 hash of sorted record HLCs
  • table_counts - Per-table record counts

Synchronization Protocol Sequence

sequenceDiagram participant A as Requester (A) participant B as Responder (B) participant C as Responder (C) A->>B: SYNC_REQ + Vector Clock A->>C: SYNC_REQ + Vector Clock rect rgb(62, 78, 110) Note over B,C: Election Phase
Both start jitter timers end B-->>A: [Timer: 120ms] C-->>A: [Timer: 145ms] Note over B: Timer expires first B->>A: SYNC_CLAIM B->>C: SYNC_CLAIM Note over C: Cancel timer
(lost election) Note over B: CrdtService.getChangesetFromVector
Generate delta changeset B->>A: DATA_CHUNK [AES-GCM Encrypted, Ed25519 Signed] Note over A: 1. Verify Ed25519 signature
2. Derive shared secret (X25519)
3. Decrypt with AES-GCM-256
4. Merge into local SQLite CRDT A->>B: CONSISTENCY_CHECK [Merkle Root + Diagnostics] A->>C: CONSISTENCY_CHECK [Merkle Root + Diagnostics]

6. Invite System Security

Group invites use a secure code-based system with optional single-use tokens.

Invite Flow

  1. 1.Host generates invite code (alphanumeric, configurable expiration)
  2. 2.Invite metadata stored in CRDT (group_settings table)
  3. 3.Invitee connects to public invite room using group name
  4. 4.Invitee broadcasts INVITE_REQ with code
  5. 5.Host validates code against stored invites
  6. 6.On success: INVITE_ACK with private data room UUID
  7. 7.On failure: INVITE_NACK with rejection reason

Invite Security Properties

Property Implementation
Rate Limiting Host controls invite generation
Expiration Configurable expiresAt timestamp
Single-Use Optional isSingleUse flag, consumed after successful join
Room Separation Invite negotiation on public room; data sync on private UUID room

Packet Types Reference

Type Value Description Encryption
SYNC_REQ0Request missing CRDT data with vector clockSigned
SYNC_CLAIM1Election winner acknowledgmentSigned
DATA_CHUNK2Encrypted CRDT changeset payloadGSK or Pairwise
HANDSHAKE3Public key exchangeSigned
CONSISTENCY_CHECK4Merkle root + diagnosticsSigned
INVITE_REQ5Request to join via invite codeSigned
INVITE_ACK6Invite accepted, includes data room UUIDSigned
INVITE_NACK7Invite rejectedSigned
UNICAST_REQ8Initiate direct communicationPairwise
UNICAST_ACK9Confirm unicast channelPairwise

Cryptographic Standards Summary

Category Algorithm Standard Notes
Symmetric Encryption AES-GCM-256 NIST SP 800-38D 12-byte nonce, 16-byte MAC
Digital Signatures Ed25519 RFC 8032 256-bit keys, deterministic
Key Exchange X25519 RFC 7748 Curve25519 ECDH
Key Derivation Argon2id RFC 9106 Memory-hard, side-channel resistant
Hash Function SHA-256 FIPS 180-4 Used for HKDF, Merkle roots
HKDF HKDF-SHA256 RFC 5869 Extract-and-Expand
Group Key Management TreeKEM RFC 9420 (MLS) Ratchet tree structure
Timestamps Hybrid Logical Clock Lamport + Physical Causality-preserving

Threat Model & Mitigations

Threat Mitigation
Server Compromise Server only sees encrypted blobs; cannot decrypt without GSK
Man-in-the-Middle Ed25519 signatures authenticate all packets; X25519 provides key agreement
Key Compromise TreeKEM forward secrecy limits exposure; key rotation on member changes
Replay Attacks Request IDs (UUID v4) + HLC timestamps prevent replays
Malicious Member Post-compromise security via TreeKEM rotation on leave
Brute Force (Keys) 256-bit keys provide 128-bit security level
Brute Force (KDF) Argon2id memory-hard parameters resist GPU/ASIC attacks
Side Channels Ed25519/X25519 have constant-time implementations

Reporting Security Issues

If you discover a security vulnerability, please email security@cohrtz.com with:

Description of the vulnerability

Detailed explanation of the issue

Steps to reproduce

Clear reproduction instructions

Potential impact assessment

Severity and scope of the issue

Any suggested mitigations

Potential fixes or workarounds

We aim to respond within 48 hours and will coordinate disclosure timelines with you.

Questions about our security?

We're committed to transparency and would be happy to discuss our approach.

Back to Home