A new protected group mode is coming: groups that ride ordinary Telegram delivery and blob storage while Telegram itself never sees plaintext or key material. The decisions that are already fixed — trust on first use with safety codes, a password-unlocked account vault, fail-closed freshness, a replaceable MLS engine — are the kind that silently erode when they live only in chat logs, so they go into the tree first, as a README and a docs set: architecture, threat model, protocol and group-state models, identity, storage, history access, files, transport, a decision record, and the open questions. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2.8 KiB
Account Identity and Safety Codes
Version-one credential
The stable account identity is separate from per-group MLS sender keys. Its version-one public credential is the fixed binary tuple:
byte[8] magic = "TDE2EACT"
uint16 credential_version = 1
uint16 initial_mls_cipher_suite = 0x0001
byte[32] Ed25519 account_signing_public_key
byte[32] X25519 archive_HPKE_public_key
The account identifier is:
SHA-256("TDE2E/account-id/v1" || 0x00 || encoded_public_credential)
The account signing key authorizes independent client credentials and identity gossip. It is not reused as the MLS leaf signature key. Every installation and protected group receives independent MLS signing and sender state authorized by the account credential.
Version one starts with the mandatory MLS 1.0 cipher suite 0x0001,
MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519. A later post-quantum or hybrid
suite requires a new credential version, interoperability review, and an
authenticated group migration; it is not silently negotiated by Telegram.
Pinning
The first accepted credential pins a Telegram user identifier to an account identifier inside the local account vault and protected group state. A different credential for the same Telegram user is an identity-change event, not an automatic update. The old account must be removed and the new account admitted through an authorized protected transition before it receives keys or history.
This is TOFU. It cannot authenticate a perfectly isolated first delivery from an active Telegram server.
Pairwise safety code
The pairwise digest sorts the two account identifiers lexicographically and hashes:
SHA-256("TDE2E/pair-safety/v1" || 0x00 || lower_id || higher_id)
Both users therefore see the same result. Comparing it out of band detects a first-contact substitution affecting either credential.
Group safety code
The group digest hashes the random conversation identifier, E2E owner, and sorted unique active account roster:
SHA-256(
"TDE2E/group-safety/v1" || 0x00 ||
conversation_id || owner_account_id || uint32(member_count) ||
sorted_member_account_ids)
It changes when the protected owner or account roster changes, but not when an account adds another installation. The interface displays the first 240 bits as 48 Crockford Base32 characters in twelve four-character groups. QR comparison uses the full 256-bit digest and versioned context.
Gossip
Signed gossip carries pinned (telegram_user_id, account_id) observations and
the observer's protected generation. Conflicting observations create a visible
security event and block automatic identity replacement. Gossip can reveal a
split view after partitions reconnect, but Telegram can censor it and maintain
permanent isolation.